On this page
  1. How it works
  2. Implementations
  3. History
  4. Related
  5. Games
  6. References
  7. Sources

A demo is a game playing itself, on the title screen or from a file. Most of the games read here feed stored inputs back through their own input path, from Super Mario Bros.’ script of 21 button bytes1 to Doom’s 4 bytes per player per tic2. Quake records the server’s messages instead3.

  • Input scriptSuper Mario Bros. (1985). The title screen's demo is a recording of button presses, played through the ordinary game.1,4,5,6,7
  • Recorded inputTetris (NES) (1989). 256 (buttons, frames − 1) pairs and a fixed 256-byte piece list; a recorder for the format is left in the code, switched off.8,9,10,11Doom (1993). A 13-byte header, then 4 bytes per player per tic fed into each tic command; random indices reset at the start.2,12,13,14,15,16
  • Recorded stateQuake (1996). The server's messages with the client's view angles, read in place of the network; no server runs.17,3,18

#How it works

An input demo replaces the controller and lets the game run as usual. Super Mario Bros. writes each script byte where the controller routine leaves the pad’s state, then runs the game engine4. Tetris writes held and newly pressed buttons into the same two bytes its controller routine fills9. Doom overwrites each player’s tic command before the player code acts on it12.

What else each game sets before a demo differs. Doom resets both random indices and restores the recorded skill, map, and options before a demo14. Tetris doesn’t use its randomizer for the demo’s pieces; it reads them from a fixed list10. Super Mario Bros. reseeds nothing: its generator’s state when the demo starts depends on the frames since reset7.

Quake records state instead: it shuts down any local server and reads the recorded server messages as if they arrived from the network3.

DataFed toKept in step byEnds when
Super Mario Bros.21 (buttons, frames) steps, 1,119 frames (computed)1the controller 1 byte, then the game engine4nothing reseeded7the script ends, Mario’s lose-life routine, or Start or Select6
Tetris (NES)256 (buttons, frames − 1) pairs9the held and pressed button bytes9a fixed piece list10the table runs out, or Start11
Doom13-byte header, then 4 bytes per player per tic2each player’s tic command12random indices reset, options restored14the 0x80 byte15
Quakethe server’s messages, each with 3 view angles17the client’s message parser3the messages’ time stamps18the recorded disconnect18

#Implementations

#Super Mario Bros.

The script pairs a button byte in DemoActionData with a frame count in DemoTimingData; the 21 counts total 1,119 frames, by the atlas’s sum1. The demo waits on the title screen for 24 ticks of a timer that ticks every 21 frames, and pressing Select puts it back5. It ends when the script’s closing 0 comes up, when the engine reaches the lose-life routine, or when a player presses Start or Select, and each of these restarts the title screen6.

#Tetris (NES)

The US build waits on the title screen for 1,025 to 1,280 frames (computed), then plays a game of type 0, A-type in the disassembly’s comment, from level 08. Its button table holds 256 pairs of a controller byte and a count, each held for its count plus one frame9. A recording branch in the same routine writes that format from the live controller, switched on by $FF in $D0, a value the code never writes there9.

The pieces come from a 256-byte list, three bits of each byte indexing the 7-entry spawn table. An index of 7 reads the byte past the table, which is the T piece’s, so a demo T comes from 2 of the 8 index values10. The demo returns to the title when the button table runs out or Start is pressed; the game-over routine has no demo case11.

#Doom

A demo lump starts with the version, skill, episode, map, options, the recording player, and which of the 4 players are in the game. Each tic then holds forward move, side move, turn, and buttons for every player in the game, until a 0x80 byte2. The turn keeps only its high byte, steps of 1/256 of a circle; while recording, the game reads each command back from the buffer, so the recording player plays with the rounded turn too13.

Playback restores the header’s settings and starts the level through G_InitNew, which resets the random indices; a demo of another version is refused14. When one ends, the attract loop moves on through the title, the credits, and demos 1 to 3, with a fourth in The Ultimate Doom; a key opens the menu over a running demo15. A recording ends on Q, a full buffer, a player leaving a net game, or an error16.

#Quake

A .dem file is a line naming a CD track, then one block for each server message other than one-byte keepalives: its length, the client’s three view angles, and the message17. Playback paces the blocks by the time stamps in the messages, and timedemo reads one a frame to measure the frame rate. When the recorded disconnect arrives, the next demo of the startdemos list plays18.

#History

Games

GameYearHowNote
Yoshi's Cookie1992the attract demo plays from a fixed seed and gives the generator back afterwards
Super Mario Bros.1985Input scriptthe title screen's demo is a recording of button presses, played through the ordinary game
Tetris (NES)1989Recorded input256 (buttons, frames − 1) pairs and a fixed 256-byte piece list; a recorder for the format is left in the code, switched off
Doom1993Recorded inputa 13-byte header, then 4 bytes per player per tic fed into each tic command; random indices reset at the start
Quake1996Recorded statethe server's messages with the client's view angles, read in place of the network; no server runs
Every claim about this mechanic, by game (20)
Super Mario Bros. 1985Super Mario Bros.' demo is a script of 21 controller bytes in DemoActionData, each held for the number of frames at the same place in DemoTimingData; DemoEngine moves to the next byte when the count runs out. The 21 counts add up to 1,119 frames (computed), about 18.6 seconds at the NES's 60.1 frames a second (computed).Code
The demo writes each byte into SavedJoypad1Bits, the byte the interrupt routine fills from controller 1 every frame, and then calls GameCoreRoutine, the same engine a game in play runs, so Mario moves by the ordinary player code.Code
Each time the title screen starts, InitializeGame sets DemoTimer to $18 (24). It is one of the interval timers, which the interrupt routine counts down once every 21 frames, so the demo starts about 500 frames after the title appears (computed: 24 × 21 = 504, less up to 20 for the first tick). Pressing Select, or B once world select is enabled, puts the timer back to $18.Code
The demo ends, and the title screen restarts from InitializeGame, in three ways: the script reaches its closing count of 0; the engine reaches its lose-life routine (GameEngineSubroutine 6); or a player presses Start (alone or with A) or Select on either controller; B, which the title screen checks for world select, isn't tested while the demo runs.Code
The demo doesn't reseed the random generator. PseudoRandomBitReg at $07A7 gets its seed $A5 only at reset, and the interrupt routine shifts it every frame; InitializeGame clears memory only up to $076F. The generator's state when the demo starts therefore depends on the number of frames since reset.Code
Super Mario Bros.: The Lost Levels 1986GameMenuRoutine and DemoEngine are Super Mario Bros.'s menu and demo, rewritten in part: the menu drops the one- or two-player choice and the world select, and starts the game at world 1, or at world A when A is held and the game has been beaten 8 times; Select switches between Mario and Luigi; the demo plays new button presses and durations through the same code.Code
Tetris (NES) 1989Tetris (NES, US build) starts its demo when the title screen has waited for Start until the frame counter's high byte, cleared as the title appears, reaches 5: between 1,025 and 1,280 frames (computed), about 17 to 21 seconds. The demo stores 0 as the game type, which the disassembly's comment names A-type, and 0 as the start level.Code
The US build's demo plays a table of 256 pairs, a controller byte and a count; the demo code holds each byte for its count plus one frame, about 4,780 frames in all (computed), some 80 seconds. It writes the held buttons and the newly pressed ones into the same two bytes the controller routine fills, so the pieces move by the ordinary play code. The @recording branch of the same routine writes such pairs from the live controller when $D0 holds $FF, but the code's only store to $D0 writes 0.Code
The demo's pieces come from a fixed list, not the randomizer: in demo mode chooseNextTetrimino reads the next byte of the 256-byte demoTetriminoTypeTable, from 0 at each game start, and takes bits 4-6 as an index into the 7-entry spawn table. An index of 7 reads the byte after the table, $02, the T piece's spawn orientation, so 2 of the 8 index values give a T; 45 of the table's 256 bytes carry a 7 (computed).Code
The demo stores 1 as the game mode, which the disassembly's comment names the title screen, when the button table runs out, its read address reaching the piece table's page, or when Start is pressed. The game-over routine has no demo case: a one-player game over, in the demo as in play, stores $03, which the comment names the level menu.Code
Yoshi's Cookie 1992The attract-mode demo saves the generator, plays from the fixed seed $FB, and puts the saved value back when it ends.Code
Doom 1993In the Linux 1.10 source, a Doom demo starts with a 13-byte header: the version (110), skill, episode, map, the deathmatch, respawn, fast, and no-monsters options, the recording player's number, and one in-game flag for each of the 4 players. Then, for every tic and every player in the game, come 4 bytes: forward move, side move, the high byte of the turn, and the buttons. A byte of 0x80 ends it. A one-player demo takes 140 bytes a second at 35 tics a second (computed).Code
Playback goes through the same path as play. Each tic, G_Ticker copies every player's command (ticcmd_t) from the network buffer, then, during a demo, overwrites its move, turn, and buttons from the demo; the player code then acts on that command as on a live one.Code
A demo keeps only the turn's high byte, rounded, so a recorded turn moves in steps of 1/256 of a full circle, about 1.4 degrees (computed). While recording, the game writes the command and reads it straight back ("make SURE it is exactly the same"), so the recording player also plays with the rounded turn and the recording matches what the player saw.Code
Playing a demo restores the recorded skill, episode, map, and options from its header and starts the level through G_InitNew, which resets both random indices to 0, as a recording started from the command line also does. A demo whose version byte isn't 110 is refused with "Demo is from a different game version!".Code
Playback ends at the 0x80 byte. A demo given with -playdemo then quits the game, and one given with -timedemo reports the tics and the real time taken. Otherwise the attract loop moves on: the title picture, demo1, the credits, demo2, a third page, demo3, and, in the retail Ultimate Doom only, demo4. During an attract demo, a key press that the menu doesn't take first, or a mouse or joystick button, opens the menu, and the demo keeps running behind it.Code
Recording (-record) ends when the player presses Q, when the buffer, 128 KiB unless -maxdemo sets another size in KiB, is within 16 bytes of full, when a player leaves a net game, or when the game stops on an error (I_Error); the game then writes the 0x80 byte and the file <name>.lmp, and exits with "Demo <name>.lmp recorded".Code
Quake 1996A Quake demo (.dem, WinQuake source) opens with a line of text, the CD track to force or -1, and then holds one block for each message the server sent, except one-byte keepalives (svc_nop), which are dropped: its length in 4 bytes, the client's three view angles as 4-byte floats, and the message itself. stop ends the recording with one last block holding a disconnect message.Code
A Quake demo records what the server told the client, not the player's inputs. Playing one first disconnects from any server, shutting down a local one, and the client then reads its server messages from the file instead of the network and parses them as live ones; the file's view angles set the view. No server runs during playback; the world arrives as the recorded messages.Code
Playback reads the next block only once the client's clock passes the time stamped in the last message (svc_time), so a demo plays at its recorded speed; timedemo instead reads one block a frame and reports frames, seconds, and frames per second. The file's closing disconnect ends the game, and while the attract loop runs the next demo of the startdemos list, up to 8, starts.Code

References

  1. ^ abcdSuper Mario Bros.' demo is a script of 21 controller bytes in DemoActionData, each held for the number of frames at the same place in DemoTimingData; DemoEngine moves to the next byte when the count runs out. The 21 counts add up to 1,119 frames (computed), about 18.6 seconds at the NES's 60.1 frames a second (computed).CodeXkeeper0/smb1
    How we know
    459DemoActionData:
    460	.db $01, $80, $02, $81, $41, $80, $01
    461	.db $42, $c2, $02, $80, $41, $c1, $41, $c1
    462	.db $01, $c1, $01, $02, $80, $00
    ⋮
    464DemoTimingData:
    465	.db $9b, $10, $18, $05, $2c, $20, $24
    466	.db $15, $5a, $10, $20, $28, $30, $20, $10
    467	.db $80, $20, $30, $30, $01, $ff, $00
    
    src/prg.asm lines 459–467 at 1cc3c92 · upstream ↗
    470	LDX DemoAction                               ; load current demo action
    471	LDA DemoActionTimer                          ; load current action timer
    472	BNE DoAction                                 ; if timer still counting down, skip
    473	INX
    474	INC DemoAction                               ; if expired, increment action, X, and
    ⋮
    476	LDA DemoTimingData-1,x                       ; get next timer
    477	STA DemoActionTimer                          ; store as current timer
    ⋮
    480	LDA DemoActionData-1,x                       ; get and perform action (current or next)
    481	STA SavedJoypad1Bits
    482	DEC DemoActionTimer                          ; decrement action timer
    
    src/prg.asm lines 469–485 at 1cc3c92 · upstream ↗
    104A_Button              = %10000000
    105B_Button              = %01000000
    ⋮
    110Left_Dir              = %00000010
    111Right_Dir             = %00000001
    
    src/defs.asm lines 104–111 at 1cc3c92 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Checked by a second AI model, 2026-10-22.

    In the claim ledger

  2. ^ abcdIn the Linux 1.10 source, a Doom demo starts with a 13-byte header: the version (110), skill, episode, map, the deathmatch, respawn, fast, and no-monsters options, the recording player's number, and one in-game flag for each of the 4 players. Then, for every tic and every player in the game, come 4 bytes: forward move, side move, the high byte of the turn, and the buttons. A byte of 0x80 ends it. A one-player demo takes 140 bytes a second at 35 tics a second (computed).Codeid-Software/DOOM
    How we know
    1553    demo_p = demobuffer;
    ⋮
    1555    *demo_p++ = VERSION;
    1556    *demo_p++ = gameskill; 
    1557    *demo_p++ = gameepisode; 
    1558    *demo_p++ = gamemap; 
    1559    *demo_p++ = deathmatch; 
    1560    *demo_p++ = respawnparm;
    1561    *demo_p++ = fastparm;
    1562    *demo_p++ = nomonsters;
    1563    *demo_p++ = consoleplayer;
    ⋮
    1565    for (i=0 ; i<MAXPLAYERS ; i++) 
    1566	*demo_p++ = playeringame[i]; 		 
    
    linuxdoom-1.10/g_game.c lines 1549–1567 at a77dfb9 · upstream ↗
    1488#define DEMOMARKER		0x80
    ⋮
    1510    *demo_p++ = cmd->forwardmove; 
    1511    *demo_p++ = cmd->sidemove; 
    1512    *demo_p++ = (cmd->angleturn+128)>>8; 
    1513    *demo_p++ = cmd->buttons; 
    
    linuxdoom-1.10/g_game.c lines 1488–1513 at a77dfb9 · upstream ↗
    660	if (playeringame[i]) 
    ⋮
    666	    if (demoplayback) 
    667		G_ReadDemoTiccmd (cmd); 
    668	    if (demorecording) 
    669		G_WriteDemoTiccmd (cmd);
    
    linuxdoom-1.10/g_game.c lines 660–669 at a77dfb9 · upstream ↗
    1677    if (demorecording) 
    ⋮
    1679	*demo_p++ = DEMOMARKER; 
    1680	M_WriteFile (demoname, demobuffer, demo_p - demobuffer); 
    
    linuxdoom-1.10/g_game.c lines 1677–1680 at a77dfb9 · upstream ↗
    33enum { VERSION =  110 };
    
    linuxdoom-1.10/doomdef.h line 33 at a77dfb9 · upstream ↗
    119#define MAXPLAYERS		4
    ⋮
    122#define TICRATE		35
    
    linuxdoom-1.10/doomdef.h lines 119–122 at a77dfb9 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Checked by a second AI model, 2026-10-22.

    In the claim ledger

  3. ^ abcdA Quake demo records what the server told the client, not the player's inputs. Playing one first disconnects from any server, shutting down a local one, and the client then reads its server messages from the file instead of the network and parses them as live ones; the file's view angles set the view. No server runs during playback; the world arrives as the recorded messages.Codeid-Software/Quake
    How we know
    28DEMO CODE
    ⋮
    30When a demo is playing back, all NET_SendMessages are skipped, and
    31NET_GetMessages are read from the demo file.
    ⋮
    33Whenever cl.time gets past the last received message, another message is
    34read from the demo file.
    
    WinQuake/cl_demo.c lines 28–34 at bf4ac42 · upstream ↗
    116	// get the next message
    117		fread (&net_message.cursize, 4, 1, cls.demofile);
    118		VectorCopy (cl.mviewangles[0], cl.mviewangles[1]);
    ⋮
    122			cl.mviewangles[0][i] = LittleFloat (f);
    ⋮
    128		r = fread (net_message.data, net_message.cursize, 1, cls.demofile);
    
    WinQuake/cl_demo.c lines 116–128 at bf4ac42 · upstream ↗
    286	CL_Disconnect ();
    
    WinQuake/cl_demo.c line 286 at bf4ac42 · upstream ↗
    99void CL_Disconnect (void)
    ⋮
    107// if running a local server, shut it down
    108	if (cls.demoplayback)
    109		CL_StopPlayback ();
    110	else if (cls.state == ca_connected)
    ⋮
    123		if (sv.active)
    124			Host_ShutdownServer(false);
    
    WinQuake/cl_main.c lines 99–124 at bf4ac42 · upstream ↗
    643		ret = CL_GetMessage ();
    ⋮
    650		CL_ParseServerMessage ();
    
    WinQuake/cl_main.c lines 641–650 at bf4ac42 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Checked by a second AI model, 2026-10-22.

    In the claim ledger

  4. ^ abcThe demo writes each byte into SavedJoypad1Bits, the byte the interrupt routine fills from controller 1 every frame, and then calls GameCoreRoutine, the same engine a game in play runs, so Mario moves by the ordinary player code.CodeXkeeper0/smb1
    How we know
    74NonMaskableInterrupt:
    
    src/prg.asm line 74 at 1cc3c92 · upstream ↗
    116	JSR ReadJoypads                              ; read joypads
    
    src/prg.asm line 116 at 1cc3c92 · upstream ↗
    1895	STA SavedJoypadBits,x                        ; save controller status here always
    
    src/prg.asm line 1895 at 1cc3c92 · upstream ↗
    480	LDA DemoActionData-1,x                       ; get and perform action (current or next)
    481	STA SavedJoypad1Bits
    
    src/prg.asm lines 480–481 at 1cc3c92 · upstream ↗
    343	LDX DemoTimer                                ; otherwise check demo timer
    344	BNE ChkWorldSel                              ; if demo timer not expired, branch to check world selection
    345	STA SelectTimer                              ; set controller bits here if running demo
    346	JSR DemoEngine                               ; run through the demo actions
    347	BCS ResetTitle                               ; if carry flag set, demo over, thus branch
    348	JMP RunDemo                                  ; otherwise, run game engine for demo
    
    src/prg.asm lines 343–348 at 1cc3c92 · upstream ↗
    390RunDemo:
    391	JSR GameCoreRoutine                          ; run game engine
    
    src/prg.asm lines 390–391 at 1cc3c92 · upstream ↗
    31SavedJoypadBits       = $06fc
    32SavedJoypad1Bits      = $06fc
    
    src/ram.asm lines 31–32 at 1cc3c92 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Checked by a second AI model, 2026-10-22.

    In the claim ledger

  5. ^ abEach time the title screen starts, InitializeGame sets DemoTimer to $18 (24). It is one of the interval timers, which the interrupt routine counts down once every 21 frames, so the demo starts about 500 frames after the title appears (computed: 24 × 21 = 504, less up to 20 for the first tick). Pressing Select, or B once world select is enabled, puts the timer back to $18.CodeXkeeper0/smb1
    How we know
    316TitleScreenMode:
    317	LDA OperMode_Task
    318	JSR JumpEngine
    ⋮
    320	.dw InitializeGame
    
    src/prg.asm lines 316–323 at 1cc3c92 · upstream ↗
    2131	LDA #$18                                     ; set demo timer
    2132	STA DemoTimer
    
    src/prg.asm lines 2131–2132 at 1cc3c92 · upstream ↗
    74NonMaskableInterrupt:
    
    src/prg.asm line 74 at 1cc3c92 · upstream ↗
    127	LDX #$14                                     ; load end offset for end of frame timers
    128	DEC IntervalTimerControl                     ; decrement interval timer control,
    129	BPL DecTimersLoop                            ; if not expired, only frame timers will decrement
    130	LDA #$14
    131	STA IntervalTimerControl                     ; if control for interval timers expired,
    132	LDX #$23                                     ; interval timers will decrement along with frame timers
    ⋮
    136	DEC Timers,x                                 ; otherwise decrement the current timer
    
    src/prg.asm lines 126–140 at 1cc3c92 · upstream ↗
    60Timers                = $0780
    ⋮
    82DemoTimer             = $07a2
    
    src/ram.asm lines 60–82 at 1cc3c92 · upstream ↗
    356	LDA DemoTimer                                ; if select or B pressed, check demo timer one last time
    357	BEQ ResetTitle                               ; if demo timer expired, branch to reset title screen mode
    358	LDA #$18                                     ; otherwise reset demo timer
    359	STA DemoTimer
    
    src/prg.asm lines 355–359 at 1cc3c92 · upstream ↗
    349ChkWorldSel:
    350	LDX WorldSelectEnableFlag                    ; check to see if world selection has been enabled
    351	BEQ NullJoypad
    352	CMP #B_Button                                ; if so, check to see if the B button was pressed
    
    src/prg.asm lines 348–354 at 1cc3c92 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Checked by a second AI model, 2026-10-22.

    In the claim ledger

  6. ^ abcThe demo ends, and the title screen restarts from InitializeGame, in three ways: the script reaches its closing count of 0; the engine reaches its lose-life routine (GameEngineSubroutine 6); or a player presses Start (alone or with A) or Select on either controller; B, which the title screen checks for world select, isn't tested while the demo runs.CodeXkeeper0/smb1
    How we know
    474	INC DemoAction                               ; if expired, increment action, X, and
    475	SEC                                          ; set carry by default for demo over
    476	LDA DemoTimingData-1,x                       ; get next timer
    477	STA DemoActionTimer                          ; store as current timer
    478	BEQ DemoOver                                 ; if timer already at zero, skip
    
    src/prg.asm lines 474–478 at 1cc3c92 · upstream ↗
    391	JSR GameCoreRoutine                          ; run game engine
    392	LDA GameEngineSubroutine                     ; check to see if we're running lose life routine
    393	CMP #$06
    394	BNE ExitMenu                                 ; if not, do not do all the resetting below
    395ResetTitle:
    396	LDA #$00                                     ; reset game modes, disable
    397	STA OperMode                                 ; sprite 0 check and disable
    398	STA OperMode_Task                            ; screen output
    
    src/prg.asm lines 390–400 at 1cc3c92 · upstream ↗
    4266GameRoutines:
    ⋮
    4276	.dw PlayerLoseLife
    
    src/prg.asm lines 4266–4276 at 1cc3c92 · upstream ↗
    332	LDA SavedJoypad1Bits                         ; check to see if either player pressed
    333	ORA SavedJoypad2Bits                         ; only the start button (either joypad)
    334	CMP #Start_Button
    335	BEQ StartGame
    336	CMP #A_Button+Start_Button                   ; check to see if A + start was pressed
    ⋮
    341	CMP #Select_Button                           ; check to see if the select button was pressed
    342	BEQ SelectBLogic                             ; if so, branch reset demo timer
    
    src/prg.asm lines 330–342 at 1cc3c92 · upstream ↗
    343	LDX DemoTimer                                ; otherwise check demo timer
    344	BNE ChkWorldSel                              ; if demo timer not expired, branch to check world selection
    
    src/prg.asm lines 343–344 at 1cc3c92 · upstream ↗
    349ChkWorldSel:
    350	LDX WorldSelectEnableFlag                    ; check to see if world selection has been enabled
    ⋮
    352	CMP #B_Button                                ; if so, check to see if the B button was pressed
    ⋮
    355SelectBLogic:
    356	LDA DemoTimer                                ; if select or B pressed, check demo timer one last time
    357	BEQ ResetTitle                               ; if demo timer expired, branch to reset title screen mode
    
    src/prg.asm lines 348–357 at 1cc3c92 · upstream ↗
    402ChkContinue:
    403	LDY DemoTimer                                ; if timer for demo has expired, reset modes
    404	BEQ ResetTitle
    
    src/prg.asm lines 402–404 at 1cc3c92 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Checked by a second AI model, 2026-10-22.

    In the claim ledger

  7. ^ abcThe demo doesn't reseed the random generator. PseudoRandomBitReg at $07A7 gets its seed $A5 only at reset, and the interrupt routine shifts it every frame; InitializeGame clears memory only up to $076F. The generator's state when the demo starts therefore depends on the number of frames since reset.CodeXkeeper0/smb1
    How we know
    29ColdBoot:
    30	JSR InitializeMemory                         ; clear memory using pointer in Y
    ⋮
    33	LDA #$a5                                     ; set warm boot flag
    34	STA WarmBootValidation
    35	STA PseudoRandomBitReg                       ; set seed for pseudorandom register
    
    src/prg.asm lines 29–35 at 1cc3c92 · upstream ↗
    74NonMaskableInterrupt:
    
    src/prg.asm line 74 at 1cc3c92 · upstream ↗
    142PauseSkip:
    ⋮
    145	LDA PseudoRandomBitReg                       ; get first memory location of LSFR bytes
    ⋮
    155	ROR PseudoRandomBitReg,x                     ; rotate carry into d7, and rotate last bit into carry
    
    src/prg.asm lines 142–158 at 1cc3c92 · upstream ↗
    2124	LDY #$6f                                     ; clear all memory as in initialization procedure,
    2125	JSR InitializeMemory                         ; but this time, clear only as far as $076f
    
    src/prg.asm lines 2124–2125 at 1cc3c92 · upstream ↗
    237PseudoRandomBitReg    = $07a7
    
    src/ram.asm line 237 at 1cc3c92 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Checked by a second AI model, 2026-10-22.

    In the claim ledger

  8. ^ abTetris (NES, US build) starts its demo when the title screen has waited for Start until the frame counter's high byte, cleared as the title appears, reaches 5: between 1,025 and 1,280 frames (computed), about 17 to 21 seconds. The demo stores 0 as the game type, which the disassembly's comment names A-type, and 0 as the start level.CodeCelestialAmber/TetrisNESDisasmrevised
    How we know
    603        lda     #$00
    604        sta     frameCounter+1
    ⋮
    609@waitForStartButton:
    610        jsr     updateAudioWaitForNmiAndResetOamStaging
    611        lda     newlyPressedButtons_player1
    612        cmp     #BUTTON_START
    613        beq     @startButtonPressed
    614        lda     frameCounter+1
    615        cmp     #$05
    616        beq     @timeout
    ⋮
    627; Start demo
    628@timeout:
    ⋮
    
    main.asm lines 603–633 at 94581c0 · upstream ↗
    263        lda     frameCounter
    264        clc
    265        adc     #$01
    266        sta     frameCounter
    267        lda     #$00
    268        adc     frameCounter+1
    269        sta     frameCounter+1
    
    main.asm lines 263–269 at 94581c0 · upstream ↗
    3735gameMode_startDemo:
    3736        lda     #$00
    3737        sta     gameType
    3738        sta     player1_startLevel
    ⋮
    3741        lda     #$05
    3742        sta     gameMode
    
    main.asm lines 3735–3743 at 94581c0 · upstream ↗
    132gameMode        := $00C0                        ; 0=legal, 1=title, 2=type menu, 3=level menu, 4=play and ending and high score, 5=demo, 6=start demo
    133gameType        := $00C1                        ; A=0, B=1
    
    main.asm lines 132–133 at 94581c0 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Revised after a check by a second AI model, 2026-10-22.

    In the claim ledger

  9. ^ abcdefThe US build's demo plays a table of 256 pairs, a controller byte and a count; the demo code holds each byte for its count plus one frame, about 4,780 frames in all (computed), some 80 seconds. It writes the held buttons and the newly pressed ones into the same two bytes the controller routine fills, so the pieces move by the ordinary play code. The @recording branch of the same routine writes such pairs from the live controller when $D0 holds $FF, but the code's only store to $D0 writes 0.CodeCelestialAmber/TetrisNESDisasmrevised
    How we know
    1; $80-a $40-b $20-select $10-start $08-up $04-down $02-left $01-right $00-no input
    2;2nd number is number of frames the input is held for
    3demoButtonsTable:
    
    data/demo_data.asm lines 1–3 at 94581c0 · upstream ↗
    69.else
    70        .byte   $00,$31,$40,$06,$00,$04,$40,$07
    
    data/demo_data.asm lines 69–71 at 94581c0 · upstream ↗
    3637@demoGameMode:
    3638        lda     $D0
    3639        cmp     #$FF
    3640        beq     @recording
    3641        jsr     pollController
    ⋮
    3645        lda     demo_repeats
    3646        beq     @finishedMove
    3647        dec     demo_repeats
    3648        jmp     @moveInProgress
    ⋮
    3650@finishedMove:
    3651        ldx     #$00
    3652        lda     (demoButtonsAddr,x)
    ⋮
    
    main.asm lines 3637–3676 at 94581c0 · upstream ↗
    5541diffOldAndNewButtons:
    ⋮
    5546        eor     heldButtons_player1,x
    5547        and     newlyPressedButtons_player1,x
    5548        sta     newlyPressedButtons_player1,x
    5549        sty     heldButtons_player1,x
    
    main.asm lines 5541–5549 at 94581c0 · upstream ↗
    282        jsr     pollControllerButtons
    
    main.asm line 282 at 94581c0 · upstream ↗
    3687@recording:
    3688        jsr     pollController
    ⋮
    3690        lda     heldButtons_player1
    ⋮
    3700        lda     heldButtons_player1
    3701        cmp     demo_heldButtons
    3702        beq     @buttonsNotChanged
    3703        ldx     #$00
    3704        lda     demo_heldButtons
    3705        sta     (demoButtonsAddr,x)
    ⋮
    3707        lda     demo_repeats
    3708        sta     (demoButtonsAddr,x)
    ⋮
    3713        lda     heldButtons_player1
    ⋮
    
    main.asm lines 3687–3720 at 94581c0 · upstream ↗
    563        sta     $D0
    
    main.asm line 563 at 94581c0 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Revised after a check by a second AI model, 2026-10-22.

    In the claim ledger

  10. ^ abcdThe demo's pieces come from a fixed list, not the randomizer: in demo mode chooseNextTetrimino reads the next byte of the 256-byte demoTetriminoTypeTable, from 0 at each game start, and takes bits 4-6 as an index into the 7-entry spawn table. An index of 7 reads the byte after the table, $02, the T piece's spawn orientation, so 2 of the 8 index values give a T; 45 of the table's 256 bytes carry a 7 (computed).CodeCelestialAmber/TetrisNESDisasm
    How we know
    2949        lda     gameMode
    2950        cmp     #$05
    2951        bne     pickRandomTetrimino
    2952        ldx     demoIndex
    2953        inc     demoIndex
    2954        lda     demoTetriminoTypeTable,x
    2955        lsr     a
    2956        lsr     a
    2957        lsr     a
    2958        lsr     a
    2959        and     #$07
    2960        tax
    ⋮
    
    main.asm lines 2949–2962 at 94581c0 · upstream ↗
    3013spawnTable:
    3014        .byte   tDown, jDown, zHoriz, oFixed, sHoriz, lDown, iHoriz
    3015; unused portion of spawnTable:
    3016.if NWC = 1
    3017        .byte   $12
    3018.else
    3019        .byte   $02
    
    main.asm lines 3013–3020 at 94581c0 · upstream ↗
    123tDown  = $02
    
    constants.asm line 123 at 94581c0 · upstream ↗
    1229        sta     allegro
    1230        sta     demo_heldButtons
    1231        sta     demo_repeats
    1232        sta     demoIndex
    
    main.asm lines 1229–1236 at 94581c0 · upstream ↗
    136demoTetriminoTypeTable:
    137        .byte   $00,$14,$8A,$45,$22,$11,$88,$44
    
    data/demo_data.asm lines 136–137 at 94581c0 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Checked by a second AI model, 2026-10-22.

    In the claim ledger

  11. ^ abcThe demo stores 1 as the game mode, which the disassembly's comment names the title screen, when the button table runs out, its read address reaching the piece table's page, or when Start is pressed. The game-over routine has no demo case: a one-player game over, in the demo as in play, stores $03, which the comment names the level menu.CodeCelestialAmber/TetrisNESDisasmrevised
    How we know
    412@checkForDemoDataExhaustion:
    413        lda     gameMode
    414        cmp     #$05
    415        bne     @continue
    416        lda     demoButtonsAddr+1
    417        cmp     #>demoTetriminoTypeTable
    418        bne     @continue
    ⋮
    423        lda     #$01
    424        sta     gameMode
    
    main.asm lines 412–424 at 94581c0 · upstream ↗
    3642        lda     newlyPressedButtons_player1
    3643        cmp     #BUTTON_START
    3644        beq     @startButtonPressed
    
    main.asm lines 3642–3644 at 94581c0 · upstream ↗
    3678@startButtonPressed:
    3679        lda     #>demoButtonsTable
    3680        sta     demoButtonsAddr+1
    3681        lda     #$00
    3682        sta     frameCounter+1
    3683        lda     #$01
    3684        sta     gameMode
    
    main.asm lines 3678–3684 at 94581c0 · upstream ↗
    3545gameModeState_handleGameOver:
    ⋮
    3567@onePlayerGameOver:
    3568        lda     #$03
    ⋮
    3573        jsr     handleHighScoreIfNecessary
    ⋮
    3589        lda     #$03
    3590        sta     gameMode
    
    main.asm lines 3545–3591 at 94581c0 · upstream ↗
    132gameMode        := $00C0                        ; 0=legal, 1=title, 2=type menu, 3=level menu, 4=play and ending and high score, 5=demo, 6=start demo
    
    main.asm line 132 at 94581c0 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Revised after a check by a second AI model, 2026-10-22.

    In the claim ledger

  12. ^ abcPlayback goes through the same path as play. Each tic, G_Ticker copies every player's command (ticcmd_t) from the network buffer, then, during a demo, overwrites its move, turn, and buttons from the demo; the player code then acts on that command as on a live one.Codeid-Software/DOOM
    How we know
    654    // get commands, check consistancy,
    ⋮
    662	    cmd = &players[i].cmd; 
    ⋮
    664	    memcpy (cmd, &netcmds[i][buf], sizeof(ticcmd_t)); 
    ⋮
    666	    if (demoplayback) 
    667		G_ReadDemoTiccmd (cmd); 
    
    linuxdoom-1.10/g_game.c lines 654–667 at a77dfb9 · upstream ↗
    1491void G_ReadDemoTiccmd (ticcmd_t* cmd) 
    ⋮
    1499    cmd->forwardmove = ((signed char)*demo_p++); 
    1500    cmd->sidemove = ((signed char)*demo_p++); 
    1501    cmd->angleturn = ((unsigned char)*demo_p++)<<8; 
    1502    cmd->buttons = (unsigned char)*demo_p++; 
    
    linuxdoom-1.10/g_game.c lines 1491–1503 at a77dfb9 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Checked by a second AI model, 2026-10-22.

    In the claim ledger

  13. ^ abA demo keeps only the turn's high byte, rounded, so a recorded turn moves in steps of 1/256 of a full circle, about 1.4 degrees (computed). While recording, the game writes the command and reads it straight back ("make SURE it is exactly the same"), so the recording player also plays with the rounded turn and the recording matches what the player saw.Codeid-Software/DOOM
    How we know
    1512    *demo_p++ = (cmd->angleturn+128)>>8; 
    1513    *demo_p++ = cmd->buttons; 
    1514    demo_p -= 4; 
    ⋮
    1522    G_ReadDemoTiccmd (cmd);         // make SURE it is exactly the same 
    
    linuxdoom-1.10/g_game.c lines 1506–1522 at a77dfb9 · upstream ↗
    1501    cmd->angleturn = ((unsigned char)*demo_p++)<<8; 
    
    linuxdoom-1.10/g_game.c line 1501 at a77dfb9 · upstream ↗
    40    short	angleturn;	// <<16 for angle delta
    
    linuxdoom-1.10/d_ticcmd.h line 40 at a77dfb9 · upstream ↗
    69#define ANG90			0x40000000
    
    linuxdoom-1.10/tables.h line 69 at a77dfb9 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Checked by a second AI model, 2026-10-22.

    In the claim ledger

  14. ^ abcdPlaying a demo restores the recorded skill, episode, map, and options from its header and starts the level through G_InitNew, which resets both random indices to 0, as a recording started from the command line also does. A demo whose version byte isn't 110 is refused with "Demo is from a different game version!".Codeid-Software/DOOM
    How we know
    1588    demobuffer = demo_p = W_CacheLumpName (defdemoname, PU_STATIC); 
    1589    if ( *demo_p++ != VERSION)
    ⋮
    1591      fprintf( stderr, "Demo is from a different game version!\n");
    ⋮
    1596    skill = *demo_p++; 
    1597    episode = *demo_p++; 
    1598    map = *demo_p++; 
    1599    deathmatch = *demo_p++;
    ⋮
    1615    G_InitNew (skill, episode, map); 
    
    linuxdoom-1.10/g_game.c lines 1586–1615 at a77dfb9 · upstream ↗
    1414    M_ClearRandom (); 
    
    linuxdoom-1.10/g_game.c line 1414 at a77dfb9 · upstream ↗
    69void M_ClearRandom (void)
    ⋮
    71    rndindex = prndindex = 0;
    
    linuxdoom-1.10/m_random.c lines 69–72 at a77dfb9 · upstream ↗
    1127    p = M_CheckParm ("-record");
    ⋮
    1131	G_RecordDemo (myargv[p+1]);
    1132	autostart = true;
    
    linuxdoom-1.10/d_main.c lines 1127–1132 at a77dfb9 · upstream ↗
    1161    if ( gameaction != ga_loadgame )
    ⋮
    1163	if (autostart || netgame)
    1164	    G_InitNew (startskill, startepisode, startmap);
    
    linuxdoom-1.10/d_main.c lines 1161–1164 at a77dfb9 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Checked by a second AI model, 2026-10-22.

    In the claim ledger

  15. ^ abcPlayback ends at the 0x80 byte. A demo given with -playdemo then quits the game, and one given with -timedemo reports the tics and the real time taken. Otherwise the attract loop moves on: the title picture, demo1, the credits, demo2, a third page, demo3, and, in the retail Ultimate Doom only, demo4. During an attract demo, a key press that the menu doesn't take first, or a mouse or joystick button, opens the menu, and the demo keeps running behind it.Codeid-Software/DOOM
    How we know
    1493    if (*demo_p == DEMOMARKER) 
    ⋮
    1495	// end of demo data stream 
    1496	G_CheckDemoStatus (); 
    1497	return; 
    
    linuxdoom-1.10/g_game.c lines 1493–1497 at a77dfb9 · upstream ↗
    1651    if (timingdemo) 
    ⋮
    1654	I_Error ("timed %i gametics in %i realtics",gametic 
    ⋮
    1658    if (demoplayback) 
    ⋮
    1660	if (singledemo) 
    1661	    I_Quit (); 
    ⋮
    1673	D_AdvanceDemo (); 
    
    linuxdoom-1.10/g_game.c lines 1651–1674 at a77dfb9 · upstream ↗
    1135    p = M_CheckParm ("-playdemo");
    ⋮
    1138	singledemo = true;              // quit after one demo
    ⋮
    1143    p = M_CheckParm ("-timedemo");
    ⋮
    1146	G_TimeDemo (myargv[p+1]);
    
    linuxdoom-1.10/d_main.c lines 1135–1146 at a77dfb9 · upstream ↗
    462    if ( gamemode == retail )
    463      demosequence = (demosequence+1)%7;
    464    else
    465      demosequence = (demosequence+1)%6;
    ⋮
    472	else
    ⋮
    475	pagename = "TITLEPIC";
    ⋮
    478	else
    ⋮
    482	G_DeferedPlayDemo ("demo1");
    ⋮
    487	pagename = "CREDIT";
    ⋮
    490	G_DeferedPlayDemo ("demo2");
    ⋮
    497	    pagename = "TITLEPIC";
    ⋮
    500	else
    ⋮
    
    linuxdoom-1.10/d_main.c lines 462–518 at a77dfb9 · upstream ↗
    520    // any other key pops up menu if in demos
    521    if (gameaction == ga_nothing && !singledemo && 
    522	(demoplayback || gamestate == GS_DEMOSCREEN) 
    ⋮
    529	    M_StartControlPanel (); 
    
    linuxdoom-1.10/g_game.c lines 520–530 at a77dfb9 · upstream ↗
    173	if (M_Responder (ev))
    174	    continue;               // menu ate the event
    175	G_Responder (ev);
    
    linuxdoom-1.10/d_main.c lines 173–175 at a77dfb9 · upstream ↗
    138    // pause if in menu and at least one tic has been run
    139    if ( !netgame
    140	 && menuactive
    141	 && !demoplayback
    
    linuxdoom-1.10/p_tick.c lines 138–144 at a77dfb9 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Checked by a second AI model, 2026-10-22.

    In the claim ledger

  16. ^ abRecording (-record) ends when the player presses Q, when the buffer, 128 KiB unless -maxdemo sets another size in KiB, is within 16 bytes of full, when a player leaves a net game, or when the game stops on an error (I_Error); the game then writes the 0x80 byte and the file <name>.lmp, and exits with "Demo <name>.lmp recorded".Codeid-Software/DOOM
    How we know
    1508    if (gamekeydown['q'])           // press q to end demo recording 
    1509	G_CheckDemoStatus (); 
    ⋮
    1515    if (demo_p > demoend - 16)
    ⋮
    1517	// no more space 
    1518	G_CheckDemoStatus (); 
    
    linuxdoom-1.10/g_game.c lines 1508–1518 at a77dfb9 · upstream ↗
    1536    strcpy (demoname, name); 
    1537    strcat (demoname, ".lmp"); 
    1538    maxsize = 0x20000;
    1539    i = M_CheckParm ("-maxdemo");
    1540    if (i && i<myargc-1)
    1541	maxsize = atoi(myargv[i+1])*1024;
    ⋮
    1543    demoend = demobuffer + maxsize;
    
    linuxdoom-1.10/g_game.c lines 1536–1543 at a77dfb9 · upstream ↗
    162void I_Error (char *error, ...)
    ⋮
    176    if (demorecording)
    177	G_CheckDemoStatus();
    
    linuxdoom-1.10/i_system.c lines 162–177 at a77dfb9 · upstream ↗
    289	    strcpy (exitmsg, "Player 1 left the game");
    ⋮
    292	    if (demorecording)
    293		G_CheckDemoStatus ();
    
    linuxdoom-1.10/d_net.c lines 289–293 at a77dfb9 · upstream ↗
    1677    if (demorecording) 
    ⋮
    1679	*demo_p++ = DEMOMARKER; 
    1680	M_WriteFile (demoname, demobuffer, demo_p - demobuffer); 
    1681	Z_Free (demobuffer); 
    1682	demorecording = false; 
    1683	I_Error ("Demo %s recorded",demoname); 
    
    linuxdoom-1.10/g_game.c lines 1677–1683 at a77dfb9 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Checked by a second AI model, 2026-10-22.

    In the claim ledger

  17. ^ abcA Quake demo (.dem, WinQuake source) opens with a line of text, the CD track to force or -1, and then holds one block for each message the server sent, except one-byte keepalives (svc_nop), which are dropped: its length in 4 bytes, the client's three view angles as 4-byte floats, and the message itself. stop ends the recording with one last block holding a disconnect message.Codeid-Software/Quakerevised
    How we know
    244	COM_DefaultExtension (name, ".dem");
    ⋮
    255	fprintf (cls.demofile, "%i\n", cls.forcetrack);
    
    WinQuake/cl_demo.c lines 244–255 at bf4ac42 · upstream ↗
    66void CL_WriteDemoMessage (void)
    ⋮
    72	len = LittleLong (net_message.cursize);
    73	fwrite (&len, 4, 1, cls.demofile);
    74	for (i=0 ; i<3 ; i++)
    ⋮
    76		f = LittleFloat (cl.viewangles[i]);
    77		fwrite (&f, 4, 1, cls.demofile);
    ⋮
    79	fwrite (net_message.data, net_message.cursize, 1, cls.demofile);
    
    WinQuake/cl_demo.c lines 66–79 at bf4ac42 · upstream ↗
    145	// discard nop keepalive message
    146		if (net_message.cursize == 1 && net_message.data[0] == svc_nop)
    ⋮
    152	if (cls.demorecording)
    153		CL_WriteDemoMessage ();
    
    WinQuake/cl_demo.c lines 145–153 at bf4ac42 · upstream ↗
    177// write a disconnect message to the demo file
    178	SZ_Clear (&net_message);
    179	MSG_WriteByte (&net_message, svc_disconnect);
    180	CL_WriteDemoMessage ();
    
    WinQuake/cl_demo.c lines 177–180 at bf4ac42 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Revised after a check by a second AI model, 2026-10-22.

    In the claim ledger

  18. ^ abcdPlayback reads the next block only once the client's clock passes the time stamped in the last message (svc_time), so a demo plays at its recorded speed; timedemo instead reads one block a frame and reports frames, seconds, and frames per second. The file's closing disconnect ends the game, and while the attract loop runs the next demo of the startdemos list, up to 8, starts.Codeid-Software/Quake
    How we know
    100			if (cls.timedemo)
    ⋮
    102				if (host_framecount == cls.td_lastframe)
    103					return 0;		// allready read this frame's message
    104				cls.td_lastframe = host_framecount;
    ⋮
    110			else if ( /* cl.time > 0 && */ cl.time <= cl.mtime[0])
    ⋮
    112					return 0;		// don't need another message yet
    
    WinQuake/cl_demo.c lines 98–113 at bf4ac42 · upstream ↗
    773		case svc_time:
    774			cl.mtime[1] = cl.mtime[0];
    775			cl.mtime[0] = MSG_ReadFloat ();			
    
    WinQuake/cl_parse.c lines 773–775 at bf4ac42 · upstream ↗
    337	Con_Printf ("%i frames %5.1f seconds %5.1f fps\n", frames, time, frames/time);
    
    WinQuake/cl_demo.c line 337 at bf4ac42 · upstream ↗
    789		case svc_disconnect:
    790			Host_EndGame ("Server disconnected\n");
    
    WinQuake/cl_parse.c lines 789–790 at bf4ac42 · upstream ↗
    106	if (cls.demonum != -1)
    107		CL_NextDemo ();
    
    WinQuake/host.c lines 106–107 at bf4ac42 · upstream ↗
    228	if (!cls.demos[cls.demonum][0] || cls.demonum == MAX_DEMOS)
    ⋮
    230		cls.demonum = 0;
    ⋮
    239	sprintf (str,"playdemo %s\n", cls.demos[cls.demonum]);
    240	Cbuf_InsertText (str);
    241	cls.demonum++;
    
    WinQuake/cl_main.c lines 228–241 at bf4ac42 · upstream ↗
    98#define	MAX_DEMOS		8
    
    WinQuake/client.h line 98 at bf4ac42 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Checked by a second AI model, 2026-10-22.

    In the claim ledger

  19. ^At the start of each level generatePillsReserve deals 128 pills, shared by both players. Each pill is one of 9 color pairs, found by adding a random 0 to 15 to the previous pill's index and subtracting 9 until it fits; the game reads the reserve in a loop, so the same 128 pills come round again.CodeNostaljipi/dr-mario-disassembly
    How we know
    170        lda #pillsReserveSize   ;Equals to the number of pills to generate           
    ⋮
    180        dex                      
    181        lda rng0                ;Max the rng value at 15 
    182        and #$0F                 
    ⋮
    184        adc pillId               
    ⋮
    187        cmp #colorCombination_right - colorCombination_left                
    188        bcc @storePill          ;If within the array size (9), is a valid pill index  
    ⋮
    191        sbc #colorCombination_right - colorCombination_left                
    ⋮
    194        sta pillsReserve,X      ;Starts from the end of the array
    
    prg/drmario_prg_level_init.asm lines 169–194 at ecb9899 · upstream ↗
    124        jsr generatePillsReserve        ;Same reserve used by both players
    
    prg/drmario_prg_level_init.asm line 124 at ecb9899 · upstream ↗
    464        ldx currentP_pillsCounter           ;If not demo, then use the player's pill reserve
    465        lda pillsReserve,X     
    ⋮
    472        inc currentP_pillsCounter           ;Increase the pills counter
    ⋮
    474        and #pillsReserveSize-1             ;Wraparound over $7F because we have a maximum of 128 pills stored        
    
    prg/drmario_prg_game_logic.asm lines 464–475 at ecb9899 · upstream ↗
    824colorCombination_left:
    825.db $00,$00,$00,$01,$01,$01,$02,$02
    826.db $02
    827colorCombination_right:
    828.db $00,$01,$02,$00,$01,$02,$00,$01
    829.db $02
    
    data/drmario_data_game.asm lines 824–829 at ecb9899 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-21.

    Checked by a second AI model, 2026-10-21.

    In the claim ledger

Sources

Code