This document is meant to be read after reading the explanation of 1.5 piston mechanics.
TNT duplication was originally discovered in 1.9 by Myren Eario (see his video explaining 1.9 TNT duplication). The same bug is present in older versions of the game including 1.5. It is just a bit different.
In this document I will explain both the extension TNT duplication very close to the one discovered by Myren Eario as well as the less practical retraction TNT duplication discovered by myself.
When reading through the piston extension code one may notice that while the game is creating moving piston blocks, there is always a brief moment where both the original block and its moving exist. This is the period of time separating the creation of this moving piston block and the next one, since the original block is always destroyed by the next moving piston block.
until cursor is at piston base pos
offset cursor in opposite of piston base facing direction
get block at cursor
get block metadata at cursor
if block id at the cursor is this block id AND cursor pos is piston base pos
set block at last cursor pos to moving piston (do not update neighbours)
set block entity at last cursor pos to new piston block entity of piston head
else
set block at last cursor pos to moving piston (do not update neighbours)
set block entity at last cursor pos to new piston block entity of block under cursor
add current block ID to the list
In fact, the window is a bit larger than that. It starts when the game obtains the data for the moving block (not after it creates it) and ends when the original block gets destroyed.
TNT duplication takes advantage of this by igniting a TNT block in this unfinished state of the extension.
Ideally we would want to get an update from the set-block action of the moving piston block for the moving block of our TNT. When a set-block action is performed it has an option to update adjacent blocks. This option is used in most cases. However, it is not used in the code creating moving blocks. Adjacent blocks are only updated after out TNT is no longer there.
There are some other opportunities for block updates though. Blocks can have certain behaviour to execute after they have been added or removed (or even before they have been removed; no blocks use this though). For example, containers like chests will have on-break behaviour to eject items from their inventories and remove their block entities. Something like a redstone torch will need to emit additional block updates because it impacts more than just directly adjacent blocks (it can power things indirectly, through blocks too).
The behaviour that is interesting to us is the on-break behaviour. This is because the only on-added behaviour available to us in this context is the one of the moving piston block and the moving piston block's on-added behaviour is simply a no-op.
I have already mentioned that redstone torches update blocks as part of their on-break behaviour. The problem is that not only can we not place a lit redstone torch next to the TNT without igniting it, but the redstone torch will get destroyed in the previous stage of the extension, during the check for blocks to push.
Therefore, only movable blocks can be used for our purposes. There is only one movable block that fulfills all of our requirements is a rail.
When an ascending rail gets broken, the game will update the direct neighbours of the block above the rail. If the rail is either a powered rail or a detector rail, the game will update its direct neighbours as well as the direct neighbors of the block below the rail.
This means that we can simply push a powered rail or a detector rail in front of a budded TNT to update it.
There is actually a place in the code where the game reaches the desired state too and the moving piston block placement actually updates adjacent blocks. This is when a piston pulls a block.
set block at pos 1 block in front of piston base to moving piston
set block entity at pos 1 block in front of piston base pos to piston block entity
set block at pos 2 blocks in front of piston base to air
This might make you think that you can simply pull a budded TNT block to duplicate TNT. However, this is not the case. The TNT will actually get updated earlier than the placement of the moving piston block.
Remember that at the very beggining of the retraction the game will place a moving block of the retracting piston breaking the piston head and updating the TNT.
One way to get around that is to only power the TNT after the first update. This can be achieved, for example, by having a floating detector rail or lever redirecting redstone dust. The first update makes lever pop off, redirecting the redstone and budding the TNT. The second update will update the now budded TNT and duplicate it.
The other way to get around this is by simply eliminating the head altogether and using a headless sticky piston to pull the budded TNT.