# Space Taxi port plan

Roadmap for bringing the JoeyLib port to functional parity with the
C64 original. Sequenced so each phase delivers something testable on
its own and unblocks the next. Each phase ends with a user-test
checkpoint -- regressions must be caught before moving on.

## Workflow per subsystem

Same loop every time, so it stays cheap to iterate:

1. **Find** -- grep the live RAM dump for relevant register writes
   (`$DC00`, `$D015`, `$D400-$D418`, `$07F8-$07FF`, etc.) or use
   `callGraph.py` to find routines touching specific addresses.
2. **Read** -- pull the routine into a window via `disLive.py
   <dump> <out> <start> <end>`, study the annotated listing.
3. **Document** -- append behavior summary to `MECHANICS.md` in
   plain English. Add new labels/vars to `stuff/spacetaxi/labels.txt`.
4. **Reannotate** -- `python3 disLive.py mem0000-level1.bin
   live-level1.lst` to regenerate the (annotated) listing from
   the dump + labels.txt in one step. The .lst file is the only
   readable form -- labels are baked in directly.
5. **Port** -- update the matching JoeyLib C file
   (`stEngine.c`, `stPassenger.c`, `stRender.c`, `stAudio.c`,
   `stLevel.c`, `spacetaxi.c`).
6. **Build + ship** -- `make -f make/dos.mk EXAMPLE=spacetaxi`
   first, then once stable also amiga / atarist / iigs.
7. **User test** -- run `scripts/run-dos.sh staxi`, report
   behavior. Do not start the next phase before this.

## Phase 0 -- foundation (DONE)

- Disassembly toolchain in `stuff/spacetaxi/`
- Full live-RAM listing + annotated variant
- `MECHANICS.md` baseline
- Asset pipeline (`tools/assetbake/assetbake.py`, extractor realigned)
- Bugfixes: sprite size in tile units, palette clobber,
  tile-bank load clamp, fire-not-thrust, palette/sheet collision
- Template-velocity physics in `stEngine.c`

## Phase 1 -- physics + collision (NEXT)

Make the cab fly correctly. Highest impact: nothing else works
until the player can hover, land, and crash.

| Task | Investigate | Update                                |
| ---- | ----------- | ------------------------------------- |
| 1.1  | Trace `$7D95/96` gravity word writers -- when is gravity on? | `stEngine.c`: gate gravity on the same conditions |
| 1.2  | Find writers of `$721D` (player state byte). High bit = "dying". What sets it? | `stEngine.c`: crash trigger logic |
| 1.3  | Trace phase-2 handler at `$6B24` -- this is what runs after `$6A72` advances. Likely the crash / fall-to-ground sequence | New `stEngine.c` crash-anim path  |
| 1.4  | Find pad-vs-wall predicate. Hardware `$D01F` says "we hit bg"; some code looks at char codes near taxi feet to decide PAD vs WALL | `stLevel.c`: pad detection by tile code |
| 1.5  | Confirm screen-edge wrap at `$6AED` -- when X-velocity carries the taxi off the right edge, does it wrap or stop? | `stEngine.c`: edge behavior |

**Checkpoint**: cab flies smoothly, can land on a pad, crashes hard.

## Phase 2 -- visuals + animation

Make the screen match the original's look frame-by-frame.

| Task | Investigate | Update                                |
| ---- | ----------- | ------------------------------------- |
| 2.1  | Confirm taxi has one cel only. The 2 writes to `$07F8` (taxi sprite ptr) -- are they per-level (color swap) or per-frame? Read those sites | `stRender.c`: reduce `ST_TAXI_CEL_COUNT` to 1, kill `thrustFrame` |
| 2.2  | What renders the engine flame under the taxi during thrust? Either a second sprite or a tile overwrite | `stRender.c`: flame overlay |
| 2.3  | Passenger walk: trace updates to passenger sprite Y/X in the sprite state table (NOT `$D004-$D00D`; the table memory) | `stPassenger.c`: walk frame timing |
| 2.4  | Title-screen layout: the C64 title positions logo / credits / instructions at specific char rows. The extractor pulls the screen layout; check the .txt against the disassembly's title-init routine to confirm we render the same layout | `assets/levels/title.txt`, `stRender.c` title overlay |

**Checkpoint**: the title screen, gameplay, and game-over screens
each look like the C64.

## Phase 3 -- audio

Authentic feel needs the SID engine. JoeyLib audio is per-platform,
so the abstraction is "SFX event id" -- the SID details only land in
the DOS / Amiga / ST tone-generator backend.

| Task | Investigate                                | Update |
| ---- | ------------------------------------------ | ------ |
| 3.1  | Music engine: voice 1+2 melodic table. Find where tunes are stored, how the row pointer advances, how note-to-freq lookup works | `stAudio.c`: music playback state machine |
| 3.2  | Thrust SFX: confirmed voice 1 freq sweep from `$721B`. Find writer of `$721B` (start of thrust) and what triggers it to stop | `stAudioSfxThrust` envelope |
| 3.3  | Crash SFX: voice 3 noise burst. `LDA #$81 STA $D412 ... LDA #$80`. Find duration / pitch envelope | `stAudioSfxCrash` |
| 3.4  | Land SFX: a successful landing must trigger some audible cue. Trace from collision dispatch when sprite-bg collision is at low vy | `stAudioSfxLand` |
| 3.5  | Door / passenger SFX: passenger enters/exits the cab on a pad. Probably also voice 3 noise or a brief tone | new SFX |

**Checkpoint**: thrust, land, crash, and at least one music loop
all play audibly on DOS. Approximate timbres OK; pitch + envelope
should roughly match.

## Phase 4 -- game logic

Make the game progress. State machine, scoring, lives.

| Task | Investigate                              | Update |
| ---- | ---------------------------------------- | ------ |
| 4.1  | Passenger spawn: when does fare N+1 appear after fare N is delivered? Look for writes to the passenger-active flags in the state table | `stPassenger.c` |
| 4.2  | Patience timer: a passenger has a countdown. Find the per-frame decrement and what happens at zero (passenger leaves? lose pad?) | `stPassenger.c` |
| 4.3  | Scoring: base fare from `$7D...`, tip math for time-on-board. BCD arithmetic on a 4-byte score | `stHud.c`, score storage |
| 4.4  | Level transitions: `$7215 == $19` is the "level complete" sentinel. What writes that, and how does the game pick the next level? | `spacetaxi.c` main switch |
| 4.5  | Lives: where is the lives counter? How does the game decide game-over? | `stEngine.c` crash branch, `spacetaxi.c` |

**Checkpoint**: can complete a level, advance, lose all lives,
see game-over, restart from title.

## Phase 5 -- level data

Match the original's 25 levels (or however many ship in the disk
image; verify from `SPACETAX.D64`).

| Task | Investigate                              | Update |
| ---- | ---------------------------------------- | ------ |
| 5.1  | Level table layout in RAM: where are per-level pad coords stored? Per-level music ID? Per-level taxi spawn? Per-level fare list? | `stLevel.c` STL1 format |
| 5.2  | Extract every level from the D64 image (we already have level1 / level2 / title dumps; need a way to dump every level state). Either replay through the game in VICE and snapshot each level, or read the level table once and reconstruct | extractor: bulk-export |
| 5.3  | Verify the title screen extraction matches the original byte-for-byte (chars + colors)                              | `title.dat` regenerate |

**Checkpoint**: all original levels playable, named correctly,
ordered correctly.

## Phase 6 -- cross-platform parity

DOS lands first. Then bring the other three up.

| Target | Specific work                                                |
| ------ | ------------------------------------------------------------ |
| Amiga  | Verify build still runs; PT replayer for music; joystick     |
| Atari ST | Same; YM2149 for SFX, MOD for music                        |
| IIgs   | Resolve ROOT-segment limit so the binary fits; wire asset    |
|        | pipeline into the disk packager (3 options in earlier note); |
|        | Ensoniq sound match                                          |

**Checkpoint**: each of the four targets boots, shows title,
plays at least one level end-to-end.

## Working notes

- All chat-side analysis must stay in plain English -- disassembly
  excerpts go in local files (`MECHANICS.md`, `labels.txt`), not
  pasted back into the session.
- One subsystem per session is the right cadence. Trying to "do
  them all at once" without user-test feedback compounds errors.
- Keep this plan up to date: as each task lands, mark it DONE
  with the commit SHA or date, and add new tasks discovered
  during the work.
