Skip to content

Latest commit

 

History

History
214 lines (184 loc) · 14.1 KB

File metadata and controls

214 lines (184 loc) · 14.1 KB

Progress Log

Running log of work on the Tetris (GB) → Watara Supervision port. Newest entries at the top. Every entry should be traceable to ROM bytes/code.

Status board

  • Confirm ROM identity (header decode, checksums) — docs/01
  • Disassembly toolchain (RGBDS 1.0.1 + cc65 2.18 + mgbdis 3.0, inherited from SML)
  • Reassembling disassembly, byte-identical rebuild ✓ SHA1 74591cc9… — docs/02
  • [~] Phase 2: jump tables recovered (state table + serial table); boot/menu/gameplay engine RE'd and capture-verified (docs/03); 2P link, rocket ending, B-type win, sound engine still to do
  • Phase 3: extraction scripts (tiles, tilemaps, tables, music, sfx) — tools/extract_*.py
  • Phase 4: Supervision hardware reference (docs/20, from the SML port)
  • [~] Phase 5: 65C02 port — single-player game complete and verified against the GB (demo lock-step, forced endings/hi-score paths, pixel-exact VRAM, music A + title note-verified, SFX ids); music: all 17 tracks × 4 channels machine-verified, the wave channel on the DMA voice; runs on real hardware (2026-09-19); 2P link out of scope
  • Port's own boot splash (ELEFAS-RETRODEV, as in the SML port) — docs/23

2026-09-19 (15) REAL HARDWARE: runs on the Watara Supervision (SuperPico) — project closed

  • User flashed SUPERPICO-TETRIS-CHEAT.uf2 (the 128 KB image, stock SuperPico firmware): graphics and controls fine; music and sound play, including the DMA bass voice (first time on a console) — "interesting" through the unit's old speaker; the owner's call: no further work.
  • State at close: single-player game complete and verified against the original (logic pass by pass, music on all four channels, effects, endings by playtest). Not ported: 2-player link. Known limits: docs/21 §4a and §7.

2026-09-19 (14) Third playtest (tetris_sv9, cheat build, RetroArch Potator): endings pass

  • User: A-type — all three rocket endings render fine; B-type — dancers fine, shuttle launch fine.

2026-09-19 (13) Second playtest: mirrored dancers split in half, OBP1

  • User screenshots: some dancers' halves swapped. The blitter ignored the OAM flip bits while the metasprite engine mirrors tile positions → flip_tile (X/Y). The GB side-by-side then showed the jumper white-bodied on the GB: OBP1 ($C4: colour 2 → opaque white) → blit_obp1. svflick/svpix model both. Shuttle launch confirmed "almost flicker free".

2026-09-19 (12) First playtest report: flicker, a missing stage, a cheat build

  • User played B-type 9-5 to the end: "dancers and the spaceship flicker". tools/svflick.py (VRAM at frame end vs the state it was rendered from): dancers flickered on 70 % of frames and ran 25 % slow. Sprite pass rewritten (docs/21 §4a): incremental, GB priority order, per-sprite repaint → dancers 0.1 %, GB length; shuttle ascent ~20 % partial.
  • The side-by-side with the GB showed a REAL bug the memory comparisons had reported and I had glossed over ("shadow map: 122 cells differ"): state $22 copied the dancers' stage from bank 2 without cold_on — the 9-5 victory scene kept the playfield as background. Fixed; audited every bank-2 copy site. passcmp over the whole 9-5 sequence (win → dancers → shuttle → ascent): 3170/3175 passes identical, the rest the random preview piece.
  • make cheat: playtest ROM (paused + SELECT: B lines − 1 / A score + 10000).

2026-09-19 (11) Pitch wobble, cursor blink — and what verifying them uncovered

  • Pitch wobble ($6D67) ported exactly: note table holds the GB word, gbx_to_f converts at run time (all 2048 x tested), verifier square tolerance 0, zero same-frame pitch differences.
  • verify_music.py --sfx: effects fired mid-track on both machines. Found: the stream player played a delay of d as d+1 frames (every effect slow, sweeps at half speed, since day one); the 4-line effect took the melody square (GB: ch1 then a wave sweep, ch2 untouched); interrupted notes revived at the effect's pitch (GB: silent until the next note-on); wave effects must gate all four music channels. All fixed; 9 effects over two tracks match.
  • Cursor blink: the one-frame offset was a sampling artefact — PyBoy ticks end mid-pass. svcmp samples the GB at the end of its pass now; every report is exactly OK (no more ±1 timer notes). A dense run (every frame, copyright → menus → game) then found one real difference: the menu exits (A/START/B) un-hide the cursor and redraw the sprites in the same pass on the GB; the port drew a frame late and, from the music screen, un-hid the wrong cursor. Fixed: 554/590 frames exact, the rest are screen-load frames (ambiguous sampling).
  • tools/lrwav.c: headless libretro frontend → WAV (Gambatte reference captures).

2026-09-18 (10) Forced paths: heart mode, music menu, B-type hi-scores — two real bugs

  • Tools: button chords in scripts (DS4), tools/gbscript.py (GB-only state trace for writing scripts), svcmp applies inputs/pokes by logical frame (the wall-clock script reached the port 1–2 frames off after screen loads: constant fall_ctr offsets), and compares the heart flag and the playing music track.
  • Verified in lock-step: Down+Start heart mode (flag $88, heart tile by the level, gravity level+10, heart letter $27 in name entry), game-type/music menu navigation with the track changes (A/B/C/off), music B through pause, top-out, game over jingle and back to the menu, B-type level/height selection, forced B-type win → dancers → tally → B hi-score entry (level 2/height 1 and level 6/height 5).
  • BUG 1 (B-type, any height): the garbage generator's end test compared the mirror pointer's high nibble with the GB's ($9A vs $10), so it never stopped at the bottom row and ran on through the $9C00 map mirror and the hi-score tables.
  • BUG 2 (B-type garbage content): the DIV stand-in advanced by fixed steps, which froze the parity that decides block/hole. Replaced by a virtual DIV advanced by the GB's exact T-cycle cost between reads, in both generators (docs/21 §7). tools/verify_rng.py: 12/12 GB garbage fields reproduced cell for cell, 45/45 piece candidates.

2026-09-13 (9) Music verified machine-vs-machine; the wave channel gets a real voice

  • tools/verify_music.py: every track on both machines (PyBoy APU-write hooks vs the port under py65 with a DMA + done-IRQ model), per-frame (F, vol) per channel. Final: 17/17 tracks, 4/4 channels, 0 mismatch frames.
  • Found and fixed on the way (all traced to $6C5E-$6D39, docs/13): the SFX hook-site finder skipped the NRx2 := $08 silence writes (recursive-descent decoder now); ch3 level from the instrument's 3rd byte + the 6-ticks fade to 50 %; instruments persist across songs (dance4 inherits the B-win jingle's); only $A0-$AF are lengths; the byte after a length is always a note; notes are byte-addressed (misaligned words for odd bytes; 256-entry note table).
  • The GB wave channel now plays on the SV DMA-audio channel: tools/extract_wave.py renders whole-period buffers of the game's own wave patterns (150 buffers, ~36 KB, banks 3–6 of a 128 KB image), mus_ch3t triggers them and the DMA-done IRQ loops them. Proven on the Potator core (irq_count, svshot now pulls audio per frame).
  • Layout: 128 KB, FIXED = last 16 KB (cfg, harnesses, firmware script updated).

2026-09-13 (8) ELEFAS-RETRODEV boot splash (docs/23)

  • src/splash.inc: the SML port's power-on splash, ported to the Tetris port's primitives (FIXED bank, no relocation needed; in-game font, - = $25). Called from reset before soft_reset, so power-on only. Verified: Potator frames (slide, meeting, settled tag), py65 trace of the square-1 writes (C-E-G-C at 4-frame spacing, silence, registers zeroed), and svcmp on the demo identical to a no-splash baseline. svcmp port run extended by 130 frames. Shipped ~/Desktop/tetris_sv3.sv + rebuilt SUPERPICO-TETRIS.uf2.

2026-09-13 (7) SuperPico UF2 packaging (firmware/)

2026-09-13 (6) Music A note-verified; docs; build shipped to Desktop (tetris_sv1.sv)

2026-09-13 (5) Forced-path verification: endings, B-type win, hi-scores

  • svcmp --poke (RAM pokes applied to both machines at the same script frame) drives the paths a scripted demo never reaches: a top-out with a 120000 score (rows of 9 cells poked into the playfield) → GAME OVER → rocket ending → level select → name entry; a B-type win (lines := 1 during the B demo) → dancers → win screen → tally → wait. Both chains now match the GB state for state and the screens are pixel-exact (svpix on the dumps).
  • Bugs found and fixed on the way: the VBlank row chain's address loop never decremented its counter (hang at task 3 after any line clear); the collapse loop dropped the source pointer's high byte; two screen loads copied from the wrong bank (win screen, GAME OVER box); the ending map fill loaded its value before the pointer macro clobbered A; the tally's product digits were one cell off (the GB keeps its cell pointer decremented); the hi-score write loop's bpl :- bound to an inner anonymous label (only one byte stored); the pointer- decrement helper clobbered the blank tile; sprites 4-px-aligned but not 8-px aligned (the rockets) left a stale column; sprites above the GB's top edge are now clipped instead of painting the Supervision's extra band.
  • Anonymous-label discipline: a backward :- must never have an inner : between it and the loop head; a scan in tools/ checks the sources.

2026-09-13 (4) Audio wired; ROM re-laid out (audio prefix in every bank)

  • src/audio.inc: the SML 65C02 sequencer + stream player adapted (per-frame ticks, one data blob with the drum table at MUS_DRUMS, TWO SFX slots because the GB drives $DFE0 and $DFF8 effects independently, the GB's SFX priority gates $6583..$6595, pause = driver freeze + the three pips). Mailboxes keep their GB addresses; sound_update = SoundUpdate_64D3's order.
  • Title music verified at the note level: py65 register log of the port vs a PyBoy hook capture of the GB — same pitches (SV F ↔ GB x), same 12-frame cadence, same rest/gap structure.
  • extract_sfx: a PyBoy hook on a byte INSIDE a jp z operand ($6835) made the GB's wave-2 handler run forever; hook sites now skip operand bytes (all 14 effects capture cleanly, 1.4 KB).
  • ROM layout: [BANK0 = audio 7K + menu/game tiles + demo script][BANK1 = audio
    • ending tiles][BANK2 = audio + cold tilemaps][FIXED = code + sprite/table data]; screen loads map bank 2 (cold_on) then the tile bank.
  • svcmp/svpix/demo all still green after the re-layout.

2026-09-13 (3) Phase 5: the port plays — demo in lock-step with the GB

  • src/: main.s (boot per SML real-HW laws, NMI frame loop, GB-layout input, state dispatch), render.inc (BG-map mirrors + dirty-cell blitter, masked sprites with OBJ priority, unrolled blits ~250 cyc/cell), sprites.inc (the $2A89 metasprite engine), states.inc/states2.inc/endings.inc (all 1P states), engine.inc (game init, gameplay, RNG, DAS, lock, lines, collapse, scoring, level-up), hiscore.inc, vblank.inc (row-copy chain side jobs, BCD drawing).
  • RAM map = GB addresses minus fixed offsets (docs/21 §2) so tools/svcmp.py diffs the two machines mechanically: same input script on PyBoy and on the real Potator core (build/svshot), aligned by logical frame (frame counter), comparing HRAM vars, objects, OAM, shadow map and the visible BG map.
  • Result: menus and the whole A-type DEMO (title → StartDemo → game init → ~1400 frames of scripted play) match cell-for-cell; the only residual is a one-frame snapshot-phase offset of the counters (PyBoy ticks land mid- iteration), verified with tools/svtrace.py (py65 tracer with injected NMIs).
  • LCD-off screen loads: the GB loses iterations while the LCD is off; the port idles the measured count (lcd_stall: 3 for tile loads, 1 map-only).
  • tools/svpix.py = pixel-exact check of the port's VRAM against its own map mirror + tile set + shadow OAM (composited in Python): menus and gameplay frames PIXEL-EXACT. It caught the renderer having drawn garbage since the soft-reset path started wiping the port's row/mask tables.
  • Bugs found by the harness, all fixed: BSS placed over the mirrored OAM, an include that landed in the wrong bank, a #<addr+n macro precedence slip, a mis-read jr nz in state $06 (misaligned first disassembly), the DAS idle value ($17, not 0), a 255-iteration anonymous-label loop.

2026-09-13 (2) Phase 2: state machine + gameplay engine RE'd

  • symbols/tetris.sym: main state table (55 states), serial table, ~100 named routines/tables; bank-0 data islands and the whole $2B64–$3FFF / bank-1 $4000–$64D1 data marked. mgbdis lesson: block lengths are hex; .code:N blocks that cut an instruction emit db + misaligned code — labels + .data only.
  • docs/03: boot, main loop, VBlank row-copy chain, state graph (captured: $07→$08→$0E→$10→$11→$0A→$00→$01→$0D→$04), piece objects + metasprite renderer, RNG (rDIV-seeded, OR-test reject, 2-deep pipeline), gravity table, fall/soft-drop, rotate/move (DAS 23/9), collision via rendered sprite coords, lock, line check, clear animation, collapse, scoring, level-up, hi-scores, name entry, pause, demo record/playback, RAM/HRAM map.
  • PyBoy (tools/gbharness.py): screens/tiles matched to ROM regions cell-for-cell; gravity 53 f/row @L0, DAS 23/9, soft drop 3 f, A/B rotation directions — all match the bytes. Save states in build/states/.

2026-09-13 Project start — Phase 1 DONE

  • ROM: tetris.gb, 32 KB, ROM-only (no MBC, no RAM), version byte 1, header and global checksums recomputed and matching (unmodified image).
  • Repo skeleton mirrors the SML port: tools/ (mgbdis + regen.sh), symbols/ (tetris.sym, tracked), docs/, gitignore per rule 5.
  • tools/regen.sh → OK, byte-identical.
  • First look at the vectors: rst $28 = jump-table dispatch (index in A, LE word table inline after the call); VBlank → $017E; STAT and Timer share $26BE; Serial → $005B (link play). mgbdis mis-decodes the inline tables as code — Phase 2 starts by marking them.