More optimizing
This commit is contained in:
parent
786ff257c3
commit
57dab02e21
27 changed files with 3605 additions and 1856 deletions
85
MECHANICS.md
85
MECHANICS.md
|
|
@ -275,6 +275,37 @@ The phase byte `$7164` is the dispatcher; `$7165/$7166` form a
|
|||
frame-rate gate (count down each frame, reload when zero) so each
|
||||
phase handler runs every N frames, not every frame.
|
||||
|
||||
**What a read sees (VERIFIED 2026-10-06 against VICE, all 25 screens).**
|
||||
The latches hold until read, and the main loop reads them (`$6966`, after
|
||||
the second `$404B`) two frames after its last read: one frame still
|
||||
shows the sprites the previous tick flushed, the next shows this tick's
|
||||
(`$4293` runs right after the first `$404B`, raster `$FC`). So a read
|
||||
is the OR of both frames -- a sprite pulling away from a contact is still
|
||||
touching on the tick it leaves (the pad light at `$6EF0` stays on one
|
||||
tick longer than one frame would give). The older frame is drawn while
|
||||
the CPU waits at `$5F94`, i.e. over the picture this tick's work has
|
||||
already changed: a melting level-P flake whose windsock flipped under it
|
||||
that tick does not touch it in the old frame either.
|
||||
|
||||
**Ticks that overrun a frame.** All of the above assumes a tick's work
|
||||
between two reads fits in one frame. When it does not -- level V's row
|
||||
shift (every 16th tick, `$7DA6`), the speech player (`$6C7F` blocks) --
|
||||
the raster latch is already set when `$5F94` is reached, so the flush
|
||||
lands mid-frame (VICE: raster 36 and 58 on V's shift ticks) and the new
|
||||
positions are only drawn below that line before the read; a sprite whose
|
||||
new top line has already passed keeps drawing its old start's remaining
|
||||
lines at the new X. A collision at the top of the screen on such a tick
|
||||
reaches the read one tick late. The port does not model this (it would
|
||||
need the C64's cycle timing); it is the one difference the stsim gate
|
||||
knows of, seen on level V when the cab hits the top border on a shift
|
||||
tick. And `$D01E` has the bits of
|
||||
EVERY pair of shown sprites: the hooks test groups of bits (`AND #$F8`
|
||||
at `$69F9` and in level G's `$7E35` also catch a hook sprite touching the
|
||||
exhaust flame), so the cab is not the only side that matters. The
|
||||
framePresent read at `$6920` clears the latch, but the start-of-screen
|
||||
jingle then shows that frame for as long as it plays, so the first
|
||||
tick's read still includes it.
|
||||
|
||||
### Collision dispatch trace (VERIFIED via emulator)
|
||||
|
||||
Verified by running `$6966` from `Tracer.from_dump('raw.bin')` with
|
||||
|
|
@ -667,18 +698,21 @@ actually 4 cels organized as 2 bases x 2 flicker variants.
|
|||
|
||||
### `$61BD` -- passengerEventDraw
|
||||
|
||||
If `$716F != 0` (a passenger event just happened): peek `$71CD`
|
||||
(event type) and dispatch:
|
||||
CORRECTED 2026-10-06 (checked against VICE by the stsim gate): once the
|
||||
touchdown bob is over (`$716F == 0`) a landing's event `$71CD`, set by
|
||||
padDetect at `$64C9..$64F5`, is voiced:
|
||||
|
||||
| `$71CD` | Event | Pointer source for $6C7F |
|
||||
| ------- | ----------- | ---------------------------------- |
|
||||
| 1 | (drop-off?) | $6A/$6B = $6C6C |
|
||||
| 2 | (pickup?) | $6A/$6B = $6C7A |
|
||||
| other | (takeoff?) | $6A/$6B = $6C7D |
|
||||
| `$71CD` | Landed... | Action |
|
||||
| ------- | ------------------------------------------- | --------------------------------------- |
|
||||
| 1 | while a fare waits on another pad ($64F3) | speak `$6C6C` "HT" (HEY TAXI) |
|
||||
| 2 | with a fare aboard, on the wrong pad ($64DF) | `$43A5` (the meter is LOST), speak `$6C7A` "P<digit>" |
|
||||
| 3 | the same, the fare wanting UP ($64E7) | `$43A5`, speak `$6C7D` "U" |
|
||||
|
||||
`$6C7F` is "render text/scoreboard": marshals shadow, flushes,
|
||||
then draws indirect text. So this routine surfaces a HUD blurb
|
||||
based on what just happened.
|
||||
`$6C7F` is the speech player (one sample per character); it zeroes
|
||||
`$71CD` (`$6C99`) and sets both release timers `$7216/$7217` to 1. So any
|
||||
phrase spoken before the bob ends cancels the event. `$6F18` raises event
|
||||
1 for the third screen's takeoff fare too; the prelude speaks it at
|
||||
`$5F30` ("P1?", `$6C72`) before the first tick.
|
||||
|
||||
### `$63DD` -- fireButtonEdge
|
||||
|
||||
|
|
@ -877,26 +911,25 @@ kills all audio, then either loops waiting for release or transitions.
|
|||
|
||||
### `$6419` -- fuelBarHud
|
||||
|
||||
```
|
||||
LDA #$0B; STA $DBA2,$DBA3,$DBCA,$DBCB ; pad color cells
|
||||
INC $71BE; AND #$07; STA $71BE ; mod-8 ticker
|
||||
CMP #$05; BPL RTS ; only first 5 of 8 frames
|
||||
CORRECTED 2026-10-06 (the old transcript here had the branches wrong):
|
||||
|
||||
LDA $714F (Y-vel hi); BMI down ; sign-test
|
||||
BEQ check_X ; Y vel = 0
|
||||
A=$02, X=$28; JMP write
|
||||
check_X:
|
||||
LDA $714E
|
||||
BMI down
|
||||
A=$03, X=$00; JMP write
|
||||
down:
|
||||
A=$07, X=$00
|
||||
```
|
||||
LDA #$0B; STA $DBA2,$DBA3,$DBCA,$DBCB ; both lamp pairs dark grey
|
||||
INC $71BE; AND #$07; STA $71BE ; mod-8 ticker
|
||||
CMP #$05; BPL RTS ; lit on 5 ticks of 8
|
||||
|
||||
LDA $714F (Y-vel hi); BMI $6449 ; climbing -> cyan
|
||||
BEQ $6444
|
||||
A=$02, X=$28; JMP write ; hi > 0 -> red, row 24
|
||||
$6444: LDA $714E; BMI $6450 ; hi 0, lo >= $80 -> yellow
|
||||
$6449: A=$03, X=$00; JMP write ; else -> cyan
|
||||
$6450: A=$07, X=$00
|
||||
write: STA $DBA2,X; STA $DBA3,X; RTS
|
||||
```
|
||||
|
||||
So the fuel bar's color depends on cab Y-velocity sign (rising vs
|
||||
descending vs hovering). The `$DBA2-$DBCB` cells form the in-flight
|
||||
indicator strip.
|
||||
So it is a descent-rate lamp: red on row 24 when the cab falls a whole
|
||||
sub-row a tick or faster, yellow on row 23 from half that, cyan on row
|
||||
23 otherwise (any climb, a hover, a slow fall).
|
||||
|
||||
## IRQ handler (`$CF52`) -- music only, no animation
|
||||
|
||||
|
|
|
|||
2
Makefile
2
Makefile
|
|
@ -3,7 +3,7 @@
|
|||
# and program.mk describes the game to them.
|
||||
#
|
||||
# make every target whose toolchain is installed
|
||||
# make <port> one target: iigs amiga atarist dos x68000 dvx
|
||||
# make <port> one target: iigs amiga atarist dos x68000 dvx sdl3
|
||||
# make check build, then run the gates (scripts/verify.sh)
|
||||
# make clean remove build/
|
||||
# make help JoeyLib's project targets
|
||||
|
|
|
|||
18
README.md
18
README.md
|
|
@ -9,9 +9,10 @@ The game is a routine-for-routine re-expression of the C64 original in C,
|
|||
written from its disassembly (MECHANICS.md): the flight physics, the fare
|
||||
machine, the 24 levels' hook programs, the title and level intros, the
|
||||
high-score table and name entry, the music (captured from the game's own
|
||||
SID player) and the speech. The simulation (stSim.c) is verified tick for
|
||||
tick against VICE traces of the C64's four attract-demo rides (VERIFIED.md),
|
||||
and the gates below hold it there.
|
||||
SID player) and the speech. The simulation (stSim.c) is checked tick for
|
||||
tick against the original game running in VICE: a ride on each of the 25
|
||||
screens, A..X and the MYSTERY SCREEN, traces byte for byte as on the C64
|
||||
(tools/stsim; VERIFIED.md), and the gates below hold it there.
|
||||
|
||||
## Setting up
|
||||
|
||||
|
|
@ -97,7 +98,7 @@ $ scripts/verify.sh disk # or name the ones to run
|
|||
|
||||
| Gate | What it proves |
|
||||
|------|----------------|
|
||||
| `stsim` | Every hook level's 4000-tick simulation trace is identical to its golden (`tools/stsim/gate.sh`; the four C64 attract rides and thirteen synthesised ones), and every cel a ride shows is pre-compiled (`celGate.sh`) |
|
||||
| `stsim` | Every screen's ride (all 25: the four C64 attract recordings and twenty-one synthesised streams, up to 4000 ticks, crashes costing a cab as in a real game) traces exactly as the real C64 traced it -- `gateHashes.txt` holds the md5s of the C64's traces (`tools/stsim/gate.sh`) -- and every cel a ride shows is pre-compiled (`celGate.sh`) |
|
||||
| `harness` | The screens above the simulation -- the high-score table, name entry, its save -- on JoeyLib's host BLANK port (`tools/staxiharness`) |
|
||||
| `disk`, `disk01` | `STAXI.2mg` boots, launches the game and draws on a ROM 3 and a ROM 01 IIgs |
|
||||
| `iigsaudio` | The IIgs sound interrupt is NTP's, the DOC is audible and its sound slots carry waveforms |
|
||||
|
|
@ -109,8 +110,13 @@ missing, or the game data not imported).
|
|||
|
||||
## Tools
|
||||
|
||||
- `tools/stsim` -- the simulation on the host: prints the per-tick state a
|
||||
VICE trace records, so `compare.py` can diff the port against the C64.
|
||||
- `tools/stsim` -- the simulation on the host, and its C64 counterpart:
|
||||
`stsim` prints one line of state per tick, `c64trace.py` rides the same
|
||||
input on the original disk in VICE and prints the same line from the
|
||||
C64's memory, and `gate.sh c64` compares the two for every screen
|
||||
(`compare.py` shows where they part). That needs VICE's `x64sc` and the
|
||||
original `SPACETAX.D64` in `stuff/`; `gate.sh c64hashes` is how
|
||||
`gateHashes.txt` is made. `recorder` searches for the synthesised rides.
|
||||
- `tools/staxiharness` -- the front-end screen checks.
|
||||
- `tools/staxibake` -- writes the cel bank the build pre-compiles per machine.
|
||||
- `assets/` -- the generators behind the committed C64 tables
|
||||
|
|
|
|||
35
TODO.md
35
TODO.md
|
|
@ -19,3 +19,38 @@ moved out of JoeyLib's tree into this repository; JoeyLib's own list
|
|||
per-call overhead in save-and-draw and restore (~1.75k and ~1.25k
|
||||
cycles a star), not bytes moved, so it becomes feasible with a leaner
|
||||
save/draw/restore path for small cels in JoeyLib.
|
||||
The ST's palette warp was slow for a LIBRARY reason, fixed in JoeyLib
|
||||
2026-10-06 with no game change: every jlPaletteSet made the ST HAL
|
||||
requantize its whole per-scanline palette table (3200 entries, ~38 ms
|
||||
of 8 MHz 68000 each, Hatari profiler); it now requantizes only the
|
||||
palette that changed. Warp on the ST, page flips per second inside the
|
||||
warp from a Hatari io_write trace (same method before and after):
|
||||
13.5 -> 53.6 fps; the title's logo 17.4 -> 20.7. (The 23.3 above
|
||||
predates the 2026-10-05 intro-pacing change; this method gives 13.5 on
|
||||
the tree just before the fix.) JoeyLib also gained an STe build, which
|
||||
Space Taxi gets with `make ste` (build/ste, STe and Mega STe only): the
|
||||
warp runs the same 53.6 on an STe and 56.8 on a Mega STe. Other ports'
|
||||
palette paths were not touched by this fix.
|
||||
|
||||
- [ ] **Does $4523 really leave sprites 0-6 enabled?** The level-name setup
|
||||
($4523..$4665, stIntroEnter in stTitle.c) never clears the enable bits
|
||||
of sprites 0-6 on the C64, but the port does. Settle it from the
|
||||
disassembly (every $D015 write on the path into the intro), and check
|
||||
it in VICE if the bytes leave doubt. Dropped out of JoeyLib's TODO in
|
||||
the 2026-10-05 split; restored 2026-10-06.
|
||||
|
||||
- [ ] **What the C64 gate cannot reach.** `tools/stsim/gate.sh c64` holds
|
||||
all 25 screens to the real game, but only along the attract demo's
|
||||
machinery: its table RNG ($446A; a real game reads the SID noise
|
||||
oscillator, which the port stands in for with an LCG), one cabbie, and
|
||||
whatever the searched streams happen to do. Two-to-four-cabbie turns,
|
||||
the bonus cab at $3.00, refuelling on a fuel pad ($6E23's pump half),
|
||||
a crash with an UP fare aboard and the shift's end are exercised by no
|
||||
ride (gcov over the 25 rides shows exactly what is reached; level A's
|
||||
ride covers the hatch exit, $6BFB).
|
||||
- [ ] **Ticks that overrun a frame.** When a tick's work does not fit in
|
||||
one frame (level V's row shift, the speech player), the C64 flushes
|
||||
its sprites mid-frame and a collision near the top of the screen
|
||||
reaches the read a tick late (MECHANICS.md). The port assumes every
|
||||
tick fits; modelling it needs the C64's cycle timing per tick. Seen
|
||||
only on a level-V ride whose cab hit the top border on a shift tick.
|
||||
|
|
|
|||
53
VERIFIED.md
53
VERIFIED.md
|
|
@ -68,6 +68,59 @@ plus the inline scripts run during verification sessions.
|
|||
| `$704E` hudArchive copies 7 bytes from `$07C2/$07E0` to `$71D3,X*8` / `$71F3,X*8` | With `$7214 = 2` (fare idx -> X = 16), `$07C2 = AA BB CC DD EE FF 11` and `$07E0 = 22 33 44 55 66 77 88`: after call, `$71E3 = AA BB CC DD EE FF 11 00` and `$7203 = 22 33 44 55 66 77 88 00`. Per-fare 8-byte slot stride confirmed; only 7 bytes written, 8th stays at prior value. |
|
||||
| `$43A5` hudInit copies template `$43B1-$43B7 -> $07E0-$07E6` | After clobbering `$07E0` with `$FF * 7`, calling `$43A5`: `$07E0 = $66 $66 $66 $74 $77 $74 $74` matching template byte-for-byte. |
|
||||
|
||||
## VERIFIED against the C64 in VICE: every screen (2026-10-06)
|
||||
|
||||
`tools/stsim/gate.sh c64` rides every screen -- A..X and the MYSTERY
|
||||
SCREEN, Y -- on the original disk in VICE (`c64trace.py`) and on the port's
|
||||
simulation (`stsim`), and the two per-tick traces are byte-identical: the
|
||||
cab's position and velocity, pad, fare stage, crash phase, input, sprite 0
|
||||
and 1 shadows, the shown sprites' positions and enable mask, the demo
|
||||
playback offset and timer, the RNG, fuel, fare slots, destination, the
|
||||
score, fare and fuel rows, border and background, and FNV hashes of all of
|
||||
screen RAM, colour RAM and the charset. `gateHashes.txt` is the md5 of each
|
||||
C64 trace, so `make check` holds the port to the real machine without an
|
||||
emulator.
|
||||
|
||||
A ride is the attract demo's machinery driven to every screen: the demo
|
||||
slots ($50C1) name the screen, the playback buffer ($0902) holds the same
|
||||
640 bytes the sim plays, the screens-played count ($7215) makes it the
|
||||
screen's place in the 24-hour shift (so every screen after B starts with
|
||||
the takeoff fare, and Y with the finished-shift HUD), and a finished wreck
|
||||
takes the real game's path ($6B52 -> $6B64: a cab lost, the cab respawned
|
||||
at $5F27) instead of the demo's return to the title. H, T, W and X ride
|
||||
the C64's own attract recordings; the other 21 ride streams searched for by
|
||||
`tools/stsim/recorder`.
|
||||
|
||||
What the comparison found wrong in the port, each fixed from the asm:
|
||||
|
||||
| Fix | Evidence |
|
||||
| --- | -------- |
|
||||
| The descent lamp (`fuelBarHud`) lit by the wrong rule | `$6419`: hi byte of `$714F` negative -> cyan, zero with `$714E` bit 7 -> yellow, positive -> red on row 24 (`X=$28`) |
|
||||
| Collisions saw one frame; the latch holds two | `$6966` reads two frames after the last read (`$404B` x2), so both the old and new sprite positions are in the read |
|
||||
| Only cab pairs were latched | Hooks test `AND #$F8` (G `$7E35`, dispatch `$69F9`): level G re-hops when the flame touches an orb |
|
||||
| Hazards' background bits were wiped when the cab hit too | `bg = 1` replaced the hazards' `$D01F` bits; now OR'd |
|
||||
| The older frame's `$D01F` used the previous read's picture | VICE: at tick 2743 of level P, `$D01F` was still `$00` at `$5F97` -- the windsock at `$0677` flipped before the wait, so the melting flake no longer touched it |
|
||||
| Level O had no gaps in its barriers, rolled the wrong glyph, ran at the wrong hook | prelude0 `$7D9C` blanks 4 x 8 cells; `$7E6E` rolls `$2BE0` = glyph `$7C`; per-tick is `$7D6F` |
|
||||
| Level R's maze flip missed two cells | `$7D9C` writes `$0709..$070D` (five cells, not three) |
|
||||
| Level K's and U's contact verdicts | `$7DC4` `LDA #$FF` (kill, no wreck); `$7DDA` `LDA #$00` (safe) |
|
||||
| Level V scrolled every tick, columns 0..38, silently | `$7DA6`: every 16th tick (`AND #$0F`), columns 1..38 (`$7E0E`/`$7E35`), SFX `$7D9D` |
|
||||
| Level E grew its beanstalk only when the fare wanted UP -- backwards | `$7DD5`: `CMP #$0B; BNE` grow, `RTS` when UP |
|
||||
| Level I drew its bullet's climb and direction in the wrong order; hires bullets | `$7E21` rng(2) before `$7E2F` rng(4); prelude1 `$7DB1` sets `$D01C = $FF` |
|
||||
| Level Q cycled cels every tick, hires, and never buzzed | `$7DFF` every other tick; prelude1 `$7D9B` `$D01C = $FF`; `$7E55` buzz SFX with rng(100) while `$7217` is 0 |
|
||||
| Voice release timers missing | `$42E9` arms `$7216+voice`, `$4320` counts down, `$6C85` sets 1, `$6906` clears; read by Q and Y |
|
||||
| A landing's event never voiced; wrong-pad landings kept the fare | `$61BD`: event 2/3 runs `$43A5` (meter lost) and speaks `$6C7A`/`$6C7D`; `$6C7F` clears `$71CD` |
|
||||
| Respawn re-ran the level's first prelude and the takeoff fare | `$6BCD JMP $5F27` skips `$6946`, `$7D66`, `$6F18`: a waiting passenger stays put across a crash |
|
||||
| The message blank was 11 characters | `$6C38` is 12 spaces; the takeoff text was 13 (`$6C1E` is 12) |
|
||||
| Level F (TAXI PONG) and Y (MYSTERY SCREEN) had no hooks at all | F `$7D9C`/`$7DDB`; Y `$7D9B`/`$7E02`/`$7E21`, its program running on to `$80CA` (STL5 ships `$7D98..$80CA`); Y's sprites come from extra scene `$23` |
|
||||
| The gate's own start differed from the C64's | `$61FB` wraps the turn and counts the screen before `$62F0` (the port's game did; stsim did not) |
|
||||
|
||||
Known and not modelled: a tick whose work overruns a frame (level V's row
|
||||
shift, speech) flushes the sprites mid-frame, so a collision at the top
|
||||
of the screen can reach the C64's read a tick later than the port's
|
||||
(MECHANICS.md, "Ticks that overrun a frame"). Found on a level-V ride
|
||||
whose cab hit the top border on a shift tick (VICE: flush at raster 58);
|
||||
the gate's V ride does not do that.
|
||||
|
||||
## UNVERIFIED (acknowledged, not yet proven)
|
||||
|
||||
| Claim | Why unverified |
|
||||
|
|
|
|||
|
|
@ -9,12 +9,13 @@
|
|||
# mkstlevel tool stays in tree for hand-authored .txt levels.
|
||||
#
|
||||
# Usage:
|
||||
# romToLevel.py <raw-dump> <out-dir> # all 24 levels
|
||||
# romToLevel.py <raw-dump> <out-dir> # all 25 screens + title
|
||||
# romToLevel.py <raw-dump> <out-dir> <scene_id> # one specific
|
||||
#
|
||||
# Writes:
|
||||
# <out-dir>/level01.dat ... level24.dat (scene 0 = "A" = level01)
|
||||
# <out-dir>/tbank0.png (charset extracted from $2800)
|
||||
# <out-dir>/level01.dat ... level25.dat (scene 0 = "A" = level01,
|
||||
# scene 24 = "Y" = level25)
|
||||
# <out-dir>/title.dat
|
||||
|
||||
import os
|
||||
import struct
|
||||
|
|
@ -49,11 +50,18 @@ ST_TILE_PX = 8
|
|||
VIC_X_ORIGIN = 24
|
||||
VIC_Y_ORIGIN = 50
|
||||
PAD_SURFACE_ORIGIN = 36
|
||||
LEVEL_LETTERS = [chr(ord('A') + i) for i in range(24)]
|
||||
# Screens A..X, then Y: the 25th, " MYSTERY SCREEN !!! ", which $5167
|
||||
# loads once the 24-hour shifts have cleared A..X.
|
||||
LEVEL_LETTERS = [chr(ord('A') + i) for i in range(25)]
|
||||
# The hook blob each level ships: its six trampolines' targets and
|
||||
# tables from $7D98 up to the last byte any hook reads. Level Y's
|
||||
# program runs on past $8000; its last SFX program is $80C2..$80CA.
|
||||
HOOK_FIRST = 0x7D98
|
||||
HOOK_END = 0x80CB
|
||||
|
||||
|
||||
def write_level(path, level_index, after, screen, colormap, sprite_blocks):
|
||||
"""Write an STL4 level .dat straight from post-decompression RAM.
|
||||
"""Write an STL5 level .dat straight from post-decompression RAM.
|
||||
|
||||
Everything stays in the C64's own units so the runtime can mirror the
|
||||
original routines byte for byte:
|
||||
|
|
@ -71,16 +79,16 @@ def write_level(path, level_index, after, screen, colormap, sprite_blocks):
|
|||
passenger X frac, passenger X col, unused
|
||||
tilemap[1000] screen RAM (all 25 rows, the HUD rows included)
|
||||
colormap[1000] colour RAM low nibbles
|
||||
levelIndex 0..23 = A..X, 24 = title
|
||||
levelIndex 0..23 = A..X, 24 = Y, 25 = title
|
||||
spriteCount N, then N x (ptr, 63 bytes): the per-level sprite
|
||||
bitmaps the level's extra scene loads at $2000+
|
||||
hookData[616] $7D98-$7FFF: the level's hook code and tables
|
||||
hookData[819] $7D98-$80CA: the level's hook code and tables
|
||||
"""
|
||||
cells = ST_TILEMAP_W * ST_PLAYFIELD_ROWS
|
||||
assert len(screen) == cells, f'screen is {len(screen)}, need {cells}'
|
||||
assert len(colormap) == cells, f'colormap is {len(colormap)}, need {cells}'
|
||||
with open(path, 'wb') as fp:
|
||||
fp.write(b'STL4')
|
||||
fp.write(b'STL5')
|
||||
fp.write(bytes(after[0x7D78:0x7D78 + 22]))
|
||||
fp.write(bytes(after[0x7D00:0x7D09]))
|
||||
fp.write(bytes([after[0x7D5A], after[0x7D09], after[0x7D60]]))
|
||||
|
|
@ -100,7 +108,7 @@ def write_level(path, level_index, after, screen, colormap, sprite_blocks):
|
|||
for ptr, data in sprite_blocks:
|
||||
fp.write(bytes([ptr]))
|
||||
fp.write(bytes(data))
|
||||
fp.write(bytes(after[0x7D98:0x8000]))
|
||||
fp.write(bytes(after[HOOK_FIRST:HOOK_END]))
|
||||
|
||||
|
||||
# Levels whose data blob flags an extra scene ($7D97 != 0) load a second
|
||||
|
|
@ -138,23 +146,6 @@ def extract_one_level(ram, scene_id, out_dir):
|
|||
return out_path, after[0x7D5A], len(sprite_blocks)
|
||||
|
||||
|
||||
def extract_screen_y(ram, out_dir):
|
||||
"""Screen Y, the 25th screen ("MYSTERY SCREEN !!!"), is scene 24.
|
||||
|
||||
$5010 dispatches on the screens-completed counter: at 24 it jumps to
|
||||
$5167, which loads #$59 ('Y') as the pending screen; the shift only
|
||||
ends when the counter reaches 25. So a full 24-hour shift plays A..X
|
||||
and then Y -- the 25 screens the intro advertises.
|
||||
"""
|
||||
screen_buf, color_buf, after = run_scene(ram, 24)
|
||||
cells = ST_TILEMAP_W * ST_PLAYFIELD_ROWS
|
||||
screen = list(screen_buf[:cells])
|
||||
colormap = [(b & 0x0F) for b in color_buf[:cells]]
|
||||
out_path = os.path.join(out_dir, 'level25.dat')
|
||||
write_level(out_path, 24, after, screen, colormap, [])
|
||||
return out_path
|
||||
|
||||
|
||||
def extract_title(ram, out_dir):
|
||||
"""The title screen is scene 25 (what $477A decompresses).
|
||||
Pads, spawn and templates are dummies."""
|
||||
|
|
@ -183,22 +174,17 @@ def main():
|
|||
if len(sys.argv) >= 4:
|
||||
scene_ids = [int(sys.argv[3], 0)]
|
||||
else:
|
||||
# Levels A..X = scenes 0..23. The title comes from screen RAM
|
||||
# at $0400 (the dump was taken on the title screen), not from
|
||||
# a decompressed scene.
|
||||
scene_ids = list(range(24))
|
||||
# Screens A..Y = scenes 0..24, then the title (scene 25).
|
||||
scene_ids = list(range(len(LEVEL_LETTERS)))
|
||||
|
||||
for sid in scene_ids:
|
||||
path, pad_count, nspr = extract_one_level(ram, sid, out_dir)
|
||||
size = os.path.getsize(path)
|
||||
print(f' scene {sid:2d} (level {LEVEL_LETTERS[sid]}): pads={pad_count} sprites={nspr} size={size} -> {path}')
|
||||
|
||||
# Screen Y and the title are not part of the A..X sweep.
|
||||
if len(sys.argv) < 4:
|
||||
y_path = extract_screen_y(ram, out_dir)
|
||||
print(f' scene 24 (screen Y) : -> {y_path} ({os.path.getsize(y_path)} bytes)')
|
||||
title_path = extract_title(ram, out_dir)
|
||||
print(f' title (screen RAM $0400) : -> {title_path} ({os.path.getsize(title_path)} bytes)')
|
||||
print(f' title (scene 25) : -> {title_path} ({os.path.getsize(title_path)} bytes)')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
|||
24
program.mk
24
program.mk
|
|
@ -41,13 +41,14 @@ STAXI_GEN := $(PROJECT_BUILD)/generated/$(ASSET_PLATFORM)
|
|||
STAXI_RUN := $(call programDataDir,STAXI)
|
||||
STAXI_OPT := $(call programOptionalDir,STAXI)
|
||||
|
||||
# 24 lettered levels (A..X) from scenes 0..23 plus the title from scene 24,
|
||||
# extracted by assets/romToLevel.py, which runs the captured decompressor
|
||||
# through a Python 6502 emulator over the raw RAM dump and writes STL2 .dat
|
||||
# files directly. (mkstlevel + title.txt is no longer used: the C64 title
|
||||
# encodes its screen with the game's custom charset, so a hand-authored tile
|
||||
# layout with a different glyph mapping renders as garbage.)
|
||||
STAXI_LEVEL_NUMS := 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
||||
# The 25 screens -- A..X from scenes 0..23 and the MYSTERY SCREEN, Y, from
|
||||
# scene 24 -- plus the title from scene 25, extracted by assets/romToLevel.py,
|
||||
# which runs the captured decompressor through a Python 6502 emulator over the
|
||||
# raw RAM dump and writes STL5 .dat files directly. (mkstlevel + title.txt is
|
||||
# no longer used: the C64 title encodes its screen with the game's custom
|
||||
# charset, so a hand-authored tile layout with a different glyph mapping
|
||||
# renders as garbage.)
|
||||
STAXI_LEVEL_NUMS := 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
||||
STAXI_LEVELS := $(foreach n,$(STAXI_LEVEL_NUMS),levels/level$(n).dat) levels/title.dat
|
||||
ROMTOLEVEL := $(STAXI_HOME)/assets/romToLevel.py
|
||||
|
||||
|
|
@ -58,16 +59,17 @@ ROMTOLEVEL := $(STAXI_HOME)/assets/romToLevel.py
|
|||
# cels at entry -- so they are optional data, which the IIgs data disk packs
|
||||
# last, while there is room.
|
||||
# ...except on the X68000, where they do not fit. The Human68k floppy is
|
||||
# 1232 KB and these eight banks are 787 KB of it (level09 alone is 236 KB,
|
||||
# level10 175, level17 165), which with the 296 KB binary, the levels and
|
||||
# the speech put the disk about 300 KB over however small speech gets.
|
||||
# 1232 KB and, measured when there were eight, the banks were 787 KB of it
|
||||
# (level09 alone 236 KB, level10 175, level17 165), which with the 296 KB
|
||||
# binary, the levels and the speech put the disk about 300 KB over however
|
||||
# small speech gets.
|
||||
# stRenderLevelCels already handles their absence the right way: it builds
|
||||
# and compiles the level's cels at ENTRY, behind the LOADING bar, so the
|
||||
# ride itself still never compiles. That is also what spacetaxi.c's
|
||||
# codegen-arena comment says the 68k ports are meant to do -- their cels
|
||||
# are big and their JIT is fast. Ship them there again if the payload ever
|
||||
# moves to a hard disk image.
|
||||
STAXI_CEL_LEVELS := $(if $(filter x68000,$(PLATFORM)),,07 08 09 10 12 16 17 21)
|
||||
STAXI_CEL_LEVELS := $(if $(filter x68000,$(PLATFORM)),,06 07 08 09 10 12 16 17 21 25)
|
||||
|
||||
# The C64-exact cel bank, generated by staxibake (from the shared cel table
|
||||
# stCels.c + C64 bitmaps) and compiled for this target by the host
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
#!/usr/bin/env bash
|
||||
# verify.sh - Space Taxi's gates, run one after another, with a summary.
|
||||
#
|
||||
# stsim every hook level's 4000-tick simulation trace is identical to
|
||||
# its golden (tools/stsim/gate.sh), and every cel a ride shows
|
||||
# is baked (tools/stsim/celGate.sh)
|
||||
# stsim every screen's ride (all 25) traces byte for byte as the real
|
||||
# C64 traced it in VICE -- gateHashes.txt holds the C64 traces'
|
||||
# md5s (tools/stsim/gate.sh) -- and every cel a ride shows is
|
||||
# baked (tools/stsim/celGate.sh)
|
||||
# harness the front-end screens above the simulation -- the high-score
|
||||
# table, name entry and its save -- on JoeyLib's host BLANK
|
||||
# port (tools/staxiharness)
|
||||
|
|
|
|||
98
stFare.c
98
stFare.c
|
|
@ -8,6 +8,8 @@
|
|||
// pad has been used the destination becomes "UP PLEASE" and the top
|
||||
// hatch opens.
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "stSim.h"
|
||||
|
||||
// Screen-code strings ($6C1E..$6C7D). All ASCII-compatible glyphs.
|
||||
|
|
@ -21,6 +23,20 @@ static const uint8_t kSpeechUp[] = "U?"; // $6C6F
|
|||
static const uint8_t kSpeechPad[] = "PX?"; // $6C72 (X = digit)
|
||||
static const uint8_t kSpeechThanks[] = "!"; // $6C76
|
||||
static const uint8_t kSpeechHey[] = "H"; // $6C78
|
||||
static const uint8_t kSpeechPadAgain[] = "PX"; // $6C7A (X = digit)
|
||||
static const uint8_t kSpeechUpAgain[] = "U"; // $6C7D
|
||||
// $6727/$6F3A/$6F3D patch the destination digit into "PAD PLEASE"
|
||||
// ($6C22, the message's fifth cell) and the two pad phrases ($6C73,
|
||||
// $6C7B: the second character).
|
||||
#define ST_TEXT_PAD_DIGIT_AT 4u
|
||||
#define ST_SPEECH_DIGIT_AT 1u
|
||||
#define ST_DIGIT_GLYPH_BASE 0x30u
|
||||
// $6F20: from the third screen on, the cab starts with a fare aboard
|
||||
// who wants pad 1.
|
||||
#define ST_TAKEOFF_LEVEL 2u
|
||||
#define ST_TAKEOFF_PAD 1u
|
||||
// $6C85: speaking leaves voices 1 and 2 one tick from their gate-off.
|
||||
#define ST_SPEECH_RELEASE 1u
|
||||
// Waiting wave cycle ($66D6) and the base-fare blob ($43B9 = +5.00).
|
||||
static const uint8_t kWaveCels[4] = { 0xC6, 0xC7, 0xD9, 0xC7 };
|
||||
static const uint8_t kFareBlob[7] = { 0x66, 0x66, 0x66, 0x6F, 0x77, 0x74, 0x74 };
|
||||
|
|
@ -28,9 +44,11 @@ static const uint8_t kFareBlob[7] = { 0x66, 0x66, 0x66, 0x6F, 0x77, 0x74, 0
|
|||
|
||||
static void beamIn(StSimT *sim);
|
||||
static bool decayStep(StSimT *sim);
|
||||
static void drawPadPlease(StSimT *sim);
|
||||
static void idleTicker(StSimT *sim);
|
||||
static void lightOnAndLatch(StSimT *sim);
|
||||
static void newFare(StSimT *sim);
|
||||
static void sayPadPhrase(StSimT *sim, const uint8_t *phrase);
|
||||
static void stageBoard(StSimT *sim);
|
||||
static void stageCleanup(StSimT *sim);
|
||||
static void stageDropIn(StSimT *sim);
|
||||
|
|
@ -76,6 +94,13 @@ static bool decayStep(StSimT *sim) {
|
|||
}
|
||||
|
||||
|
||||
// "PAD n PLEASE" on the message row, n = the fare's destination.
|
||||
static void drawPadPlease(StSimT *sim) {
|
||||
stSimDrawText(sim, ST_MESSAGE_COL, ST_MESSAGE_ROW, kTextPadPlease, 1u);
|
||||
stSimPutChar(sim, (uint8_t)(ST_MESSAGE_COL + ST_TEXT_PAD_DIGIT_AT), ST_MESSAGE_ROW, (uint8_t)(sim->activeSpriteIdx + ST_DIGIT_GLYPH_BASE));
|
||||
}
|
||||
|
||||
|
||||
// $660B -- a new fare turns up on a 3% roll per tick or when the
|
||||
// 100-tick timer runs out, never mid-crash.
|
||||
static void idleTicker(StSimT *sim) {
|
||||
|
|
@ -123,6 +148,17 @@ static void newFare(StSimT *sim) {
|
|||
}
|
||||
|
||||
|
||||
// $6C7F on a pad phrase ($6C72 "PX?" or $6C7A "PX"), the destination's
|
||||
// digit in place of the X.
|
||||
static void sayPadPhrase(StSimT *sim, const uint8_t *phrase) {
|
||||
uint8_t speech[sizeof(kSpeechPad)];
|
||||
|
||||
memcpy(speech, phrase, strlen((const char *)phrase) + 1u);
|
||||
speech[ST_SPEECH_DIGIT_AT] = (uint8_t)(sim->activeSpriteIdx + ST_DIGIT_GLYPH_BASE);
|
||||
stFareBoardingSound(sim, speech);
|
||||
}
|
||||
|
||||
|
||||
// $66DD -- the passenger shrinks into the cab, then picks where to go.
|
||||
static void stageBoard(StSimT *sim) {
|
||||
if (!decayStep(sim)) {
|
||||
|
|
@ -139,16 +175,8 @@ static void stageBoard(StSimT *sim) {
|
|||
if (sim->activeSpriteIdx == ST_FARE_DEST_UP) {
|
||||
return;
|
||||
}
|
||||
stSimDrawText(sim, 14u, 24u, kTextPadPlease, 1u);
|
||||
stSimPutChar(sim, 18u, 24u, (uint8_t)(sim->activeSpriteIdx + 0x30u));
|
||||
{
|
||||
uint8_t speech[4];
|
||||
speech[0] = kSpeechPad[0];
|
||||
speech[1] = (uint8_t)(sim->activeSpriteIdx + 0x30u);
|
||||
speech[2] = kSpeechPad[2];
|
||||
speech[3] = 0u;
|
||||
stFareBoardingSound(sim, speech);
|
||||
}
|
||||
drawPadPlease(sim);
|
||||
sayPadPhrase(sim, kSpeechPad);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -251,7 +279,8 @@ static void walkToTarget(StSimT *sim) {
|
|||
|
||||
// $6C7F -- the passenger speaks: one sample per character.
|
||||
void stFareBoardingSound(StSimT *sim, const uint8_t *speech) {
|
||||
(void)sim;
|
||||
memset(sim->sidRelTimer, ST_SPEECH_RELEASE, sizeof(sim->sidRelTimer)); // $6C85
|
||||
sim->eventDispatchType = ST_EVENT_NONE; // $6C99
|
||||
stAudioSilence();
|
||||
stAudioNoise(false);
|
||||
stAudioSpeechPhrase(speech);
|
||||
|
|
@ -283,6 +312,28 @@ uint8_t stFareChooseDestination(StSimT *sim) {
|
|||
}
|
||||
|
||||
|
||||
// $61BD -- once the touchdown bob has settled, a landing that left an
|
||||
// event ($645C) is voiced: a fare waiting on another pad calls "HEY
|
||||
// TAXI" ($6C6C); a fare aboard set down on the wrong pad loses what is
|
||||
// on its meter ($43A5) and names its destination again, "PAD n" ($6C7A)
|
||||
// or "UP" ($6C7D).
|
||||
void stFareEventVoice(StSimT *sim) {
|
||||
if (sim->bobTimer != 0u || sim->eventDispatchType == ST_EVENT_NONE) {
|
||||
return;
|
||||
}
|
||||
if (sim->eventDispatchType == ST_EVENT_HAIL) {
|
||||
stFareBoardingSound(sim, kSpeechHeyTaxi);
|
||||
return;
|
||||
}
|
||||
stSimHudInit(sim);
|
||||
if (sim->eventDispatchType == ST_EVENT_WRONG_PAD) {
|
||||
sayPadPhrase(sim, kSpeechPadAgain);
|
||||
return;
|
||||
}
|
||||
stFareBoardingSound(sim, kSpeechUpAgain);
|
||||
}
|
||||
|
||||
|
||||
// $6EF0 -- while the passenger walks (to or from the cab) the pad stays
|
||||
// lit only while the two sprites touch.
|
||||
void stFarePadLightingGate(StSimT *sim) {
|
||||
|
|
@ -410,6 +461,31 @@ void stFareStageDispatch(StSimT *sim) {
|
|||
}
|
||||
|
||||
|
||||
// $6F18 -- from the third screen on, the cab sets out with a fare aboard
|
||||
// who wants pad 1, and the prelude will have them say so ($5F30).
|
||||
void stFareTakeoffSetup(StSimT *sim) {
|
||||
sim->eventDispatchType = ST_EVENT_NONE;
|
||||
sim->stage = ST_STAGE_IDLE;
|
||||
if (sim->levelState < ST_TAKEOFF_LEVEL) {
|
||||
return;
|
||||
}
|
||||
sim->stage = ST_STAGE_RIDING;
|
||||
sim->activeSpriteIdx = ST_TAKEOFF_PAD;
|
||||
sim->activeDyingSlot = ST_TAKEOFF_PAD;
|
||||
drawPadPlease(sim);
|
||||
sim->eventDispatchType = ST_EVENT_HAIL;
|
||||
}
|
||||
|
||||
|
||||
// $5F30 -- the end of the prelude: the takeoff fare names its pad.
|
||||
void stFareTakeoffVoice(StSimT *sim) {
|
||||
if (sim->eventDispatchType == ST_EVENT_NONE) {
|
||||
return;
|
||||
}
|
||||
sayPadPhrase(sim, kSpeechPad);
|
||||
}
|
||||
|
||||
|
||||
// $6CB8 -- no fare pad left: the passenger wants to leave the screen,
|
||||
// so the top hatch opens.
|
||||
void stFareUpPlease(StSimT *sim) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Space Taxi -- where each level's hook program keeps its tables.
|
||||
//
|
||||
// Every address is a byte in the level's shipped hook blob ($7D98..$7FFF,
|
||||
// Every address is a byte in the level's shipped hook blob ($7D98..$80CA,
|
||||
// StLevelT.hookData), so the same numbers serve the hook programs
|
||||
// (stHooks.c, reading through the live simulation) and the cel manifest
|
||||
// (stLevelCels.c, reading the level file alone -- the offline cel baker
|
||||
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
// Level indices (0 = A).
|
||||
#define ST_LEVEL_E 4u
|
||||
#define ST_LEVEL_F 5u
|
||||
#define ST_LEVEL_G 6u
|
||||
#define ST_LEVEL_H 7u
|
||||
#define ST_LEVEL_I 8u
|
||||
|
|
@ -26,10 +27,20 @@
|
|||
#define ST_LEVEL_V 21u
|
||||
#define ST_LEVEL_W 22u
|
||||
#define ST_LEVEL_X 23u
|
||||
#define ST_LEVEL_Y 24u
|
||||
|
||||
// The level header's shared sprite multicolours ($7D05/$7D06 -> $D025/$D026).
|
||||
#define ST_HDR_SPRITE_MC0 5u
|
||||
#define ST_HDR_SPRITE_MC1 6u
|
||||
|
||||
// Hardware sprites 3..7 are the hook sprites on every level that has any.
|
||||
#define ST_HOOK_FIRST_SPRITE 3u
|
||||
|
||||
// ---- level F "TAXI PONG" ----
|
||||
#define ST_HK_F_SPRITE 3u // the ball
|
||||
#define ST_HK_F_CEL 0x80u
|
||||
#define ST_HK_F_COLOR 1u
|
||||
|
||||
// ---- level G "TELEPORTS" ----
|
||||
#define ST_HK_G_CEL 0x7DD4u // 6 cels the orbs cycle through
|
||||
#define ST_HK_G_CEL_PHASES 6u
|
||||
|
|
@ -82,4 +93,19 @@
|
|||
#define ST_HK_U_CEL_PHASES 4u
|
||||
#define ST_HK_U_COLOR 7u
|
||||
|
||||
// ---- level Y "MYSTERY SCREEN" ----
|
||||
// Sprite 3 is a multicolour saucer, sprite 4 a hires walker, sprite 5
|
||||
// the walker's shot ($83 rising, then $84/$85 bursting).
|
||||
#define ST_HK_Y_SAUCER 3u
|
||||
#define ST_HK_Y_WALKER 4u
|
||||
#define ST_HK_Y_SHOT 5u
|
||||
#define ST_HK_Y_CEL_SAUCER 0x80u
|
||||
#define ST_HK_Y_CEL_WALK 0x81u
|
||||
#define ST_HK_Y_CEL_WALK_ALT 0x82u
|
||||
#define ST_HK_Y_CEL_SHOT 0x83u
|
||||
#define ST_HK_Y_CEL_SHOT_END 0x86u // one past the last burst cel
|
||||
#define ST_HK_Y_COLOR_SAUCER 1u
|
||||
#define ST_HK_Y_COLOR_WALKER 0x0Fu
|
||||
#define ST_HK_Y_MULTICOLOR 0x0Fu // $7E02: $D01C, sprites 0..3
|
||||
|
||||
#endif // ST_HOOK_TABLES_H
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Space Taxi -- level loader (JoeyLib data path + the shared STL4 parser).
|
||||
// Space Taxi -- level loader (JoeyLib data path + the shared STL5 parser).
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
|
|
|
|||
|
|
@ -46,6 +46,9 @@ uint8_t stLevelCelList(const StLevelT *level, StCelDefT *out, uint8_t max) {
|
|||
uint8_t c;
|
||||
|
||||
switch (level->levelIndex) {
|
||||
case ST_LEVEL_F:
|
||||
celAdd(out, &n, max, ST_HK_F_CEL, ST_HK_F_COLOR, 0u, 0u, 0u);
|
||||
break;
|
||||
case ST_LEVEL_G:
|
||||
for (k = 0u; k < ST_HK_G_CEL_PHASES; k++) {
|
||||
celAdd(out, &n, max, HKL(level, ST_HK_G_CEL + k), ST_HK_G_ORB_COLOR, 0u, 0u, 0u);
|
||||
|
|
@ -58,14 +61,15 @@ uint8_t stLevelCelList(const StLevelT *level, StCelDefT *out, uint8_t max) {
|
|||
break;
|
||||
case ST_LEVEL_I:
|
||||
// Every cel in the idle colour and in each of the three the
|
||||
// launcher picks at random.
|
||||
// launcher picks at random; multicolour, like every sprite
|
||||
// on this level ($7DB1).
|
||||
for (c = 0u; c <= ST_HK_I_COLORS; c++) {
|
||||
uint8_t color = (c == 0u) ? ST_HK_I_COLOR_IDLE : HKL(level, ST_HK_I_COLOR + c - 1u);
|
||||
for (k = 0u; k < ST_HK_I_RISE_PHASES; k++) {
|
||||
celAdd(out, &n, max, HKL(level, ST_HK_I_CEL_RISE + k), color, 0u, 0u, 0u);
|
||||
celAdd(out, &n, max, HKL(level, ST_HK_I_CEL_RISE + k), color, 1u, level->header[ST_HDR_SPRITE_MC0], level->header[ST_HDR_SPRITE_MC1]);
|
||||
}
|
||||
for (k = 0u; k < ST_HK_I_BURST_PHASES; k++) {
|
||||
celAdd(out, &n, max, HKL(level, ST_HK_I_CEL_BURST + k), color, 0u, 0u, 0u);
|
||||
celAdd(out, &n, max, HKL(level, ST_HK_I_CEL_BURST + k), color, 1u, level->header[ST_HDR_SPRITE_MC0], level->header[ST_HDR_SPRITE_MC1]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
@ -91,15 +95,10 @@ uint8_t stLevelCelList(const StLevelT *level, StCelDefT *out, uint8_t max) {
|
|||
}
|
||||
break;
|
||||
case ST_LEVEL_Q:
|
||||
// The hook's cels show HIRES in colour 6 for the whole recorded
|
||||
// ride (tools/stsim STSIM_CELS census, 2026-09-22); the
|
||||
// multicolour variant (the level's own $D025/$D026) stays
|
||||
// listed for the phases the census did not reach.
|
||||
// Multicolour: the level's second prelude hook ($7D9B) sets
|
||||
// $D01C to $FF after the cab spawn set it to $07.
|
||||
for (k = 0u; k < ST_HK_Q_CEL_PHASES; k++) {
|
||||
celAdd(out, &n, max, HKL(level, ST_HK_Q_CEL + k), ST_HK_Q_COLOR, 0u, 0u, 0u);
|
||||
}
|
||||
for (k = 0u; k < ST_HK_Q_CEL_PHASES; k++) {
|
||||
celAdd(out, &n, max, HKL(level, ST_HK_Q_CEL + k), ST_HK_Q_COLOR, 1u, level->header[5], level->header[6]);
|
||||
celAdd(out, &n, max, HKL(level, ST_HK_Q_CEL + k), ST_HK_Q_COLOR, 1u, level->header[ST_HDR_SPRITE_MC0], level->header[ST_HDR_SPRITE_MC1]);
|
||||
}
|
||||
break;
|
||||
case ST_LEVEL_U:
|
||||
|
|
@ -107,6 +106,14 @@ uint8_t stLevelCelList(const StLevelT *level, StCelDefT *out, uint8_t max) {
|
|||
celAdd(out, &n, max, HKL(level, ST_HK_U_CEL + k), ST_HK_U_COLOR, 0u, 0u, 0u);
|
||||
}
|
||||
break;
|
||||
case ST_LEVEL_Y:
|
||||
// The saucer is multicolour ($7E02 sets $D01C to $0F); the
|
||||
// walker and its shot are hires.
|
||||
celAdd(out, &n, max, ST_HK_Y_CEL_SAUCER, ST_HK_Y_COLOR_SAUCER, 1u, level->header[ST_HDR_SPRITE_MC0], level->header[ST_HDR_SPRITE_MC1]);
|
||||
for (k = ST_HK_Y_CEL_WALK; k < ST_HK_Y_CEL_SHOT_END; k++) {
|
||||
celAdd(out, &n, max, k, ST_HK_Y_COLOR_WALKER, 0u, 0u, 0u);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
// Space Taxi -- STL4 level file parser (plain stdio, no JoeyLib), shared
|
||||
// Space Taxi -- STL5 level file parser (plain stdio, no JoeyLib), shared
|
||||
// by the game and the host-side simulation harness.
|
||||
//
|
||||
// Layout (all bytes, written by assets/romToLevel.py):
|
||||
// "STL4"
|
||||
// "STL5"
|
||||
// name[22] $7D78 level name in screen codes
|
||||
// header[9] $7D00 VIC colour block
|
||||
// padCount, specialPad, fuelRate
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
// screen[1000], color[1000]
|
||||
// levelIndex
|
||||
// spriteCount, then spriteCount x (ptr, 63 bytes)
|
||||
// hookData[616] $7D98..$7FFF
|
||||
// hookData[819] $7D98..$80CA
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
|
@ -36,7 +36,7 @@ bool stLevelParse(StLevelT *out, FILE *fp) {
|
|||
uint8_t k;
|
||||
|
||||
memset(out, 0, sizeof(*out));
|
||||
if (!readBlock(fp, hdr, 4u) || hdr[0] != 'S' || hdr[1] != 'T' || hdr[2] != 'L' || hdr[3] != '4') {
|
||||
if (!readBlock(fp, hdr, 4u) || hdr[0] != 'S' || hdr[1] != 'T' || hdr[2] != 'L' || hdr[3] != '5') {
|
||||
return false;
|
||||
}
|
||||
if (!readBlock(fp, out->name, ST_LEVEL_NAME_CHARS)) {
|
||||
|
|
|
|||
|
|
@ -1222,7 +1222,13 @@ static bool prewarmReport(uint16_t expect) {
|
|||
ST_LOG("spacetaxi: ! only %u of %u cels built", used, expect);
|
||||
return false;
|
||||
}
|
||||
#if defined(JOEYLIB_SPRITE_INTERPRETED)
|
||||
// A port with no sprite emitter (joey/platform.h) interprets every cel
|
||||
// by design: none compiled is how it always runs.
|
||||
return true;
|
||||
#else
|
||||
return hot != 0u;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
463
stSim.c
463
stSim.c
|
|
@ -15,9 +15,23 @@
|
|||
#define ST_JINGLE_SONG_BONUS 4u // level-25 start and the bonus cab
|
||||
#define ST_JINGLE_LEVEL_25 0x19u // $7215 compared against #$19
|
||||
|
||||
// The descent lamp ($6419): two cell pairs beside the fuel gauge, the
|
||||
// colours they light in, and the Y velocities that pick one.
|
||||
#define ST_LAMP_CELL_SLOW ST_CELL(23, 10) // $DBA2
|
||||
#define ST_LAMP_CELL_FAST ST_CELL(24, 10) // $DBCA
|
||||
#define ST_LAMP_OFF 0x0Bu // dark grey
|
||||
#define ST_LAMP_SLOW 0x03u // cyan
|
||||
#define ST_LAMP_FALL 0x07u // yellow
|
||||
#define ST_LAMP_FAST 0x02u // red
|
||||
#define ST_LAMP_FALL_VEL 0x0080 // high byte 0, low bit 7
|
||||
#define ST_LAMP_FAST_VEL 0x0100 // high byte positive
|
||||
#define ST_LAMP_TICK_MASK 7u // $71BE counts mod 8...
|
||||
#define ST_LAMP_LIT_TICKS 5u // ...and lights on 0..4
|
||||
|
||||
// Screen-code strings the engine writes (all ASCII-compatible glyphs).
|
||||
// $6C38 -- the 11-character blank that clears the message row.
|
||||
static const uint8_t kTextBlank[] = " ";
|
||||
// $6C38 -- the 12-character blank that clears the message row (every
|
||||
// message is 12 characters: "PAD PLEASE", " UP PLEASE! " and the rest).
|
||||
static const uint8_t kTextBlank[] = " ";
|
||||
// $43B1 -- the fare meter's blank template: three leading blanks, then
|
||||
// "0.00" in the inverse-video digit glyphs.
|
||||
static const uint8_t kHudTemplate[ST_NUMBER_CHARS] = { 0x66, 0x66, 0x66, 0x74, 0x77, 0x74, 0x74 };
|
||||
|
|
@ -44,6 +58,8 @@ static void drawCabIcons(StSimT *sim);
|
|||
static void drawScreensCount(StSimT *sim);
|
||||
static void edgeReflect(StSimT *sim);
|
||||
static void fireButtonEdge(StSimT *sim);
|
||||
static uint8_t frameBgCollisions(StSimT *sim);
|
||||
static uint8_t frameSpriteCollisions(StSimT *sim);
|
||||
static void fuelBarHud(StSimT *sim);
|
||||
static void fuelTick(StSimT *sim);
|
||||
static void hudDraw(StSimT *sim);
|
||||
|
|
@ -58,13 +74,16 @@ static uint8_t readInput(StSimT *sim);
|
|||
static void rowWindow(const uint8_t *row, uint8_t s, uint8_t *w);
|
||||
static void setCell(StSimT *sim, uint16_t cell, uint8_t ch);
|
||||
static void setColorCell(StSimT *sim, uint16_t cell, uint8_t color);
|
||||
static void sfxEnvelopeTick(StSimT *sim);
|
||||
static void spriteMasks(StSimT *sim, uint8_t idx);
|
||||
static bool spriteRowsTouchBackground(const StSimT *sim, uint8_t idx, int16_t firstCol, int16_t py, uint8_t phase);
|
||||
static bool spriteTouchesBackground(StSimT *sim, uint8_t idx);
|
||||
static void spritesOffReset(StSimT *sim);
|
||||
static bool spritesTouch(StSimT *sim, uint8_t a, uint8_t b, int16_t dx, int16_t dy);
|
||||
static void taxiSpawnInit(StSimT *sim);
|
||||
static void taxiSpriteCelSelect(StSimT *sim);
|
||||
static uint8_t validateDigit(uint8_t ch);
|
||||
static void voicesOff(StSimT *sim);
|
||||
|
||||
|
||||
// $6112 -- add the X velocity to the 17-bit X position (msb:col:frac)
|
||||
|
|
@ -250,7 +269,7 @@ static void collisionPhase1(StSimT *sim) {
|
|||
sim->collisionPhase++;
|
||||
sim->phaseReload = 2u;
|
||||
sim->phaseTimer = 2u;
|
||||
stAudioSfx(stC64SfxProgram(ST_SFX_IMPACT));
|
||||
stSimSfx(sim, stC64SfxProgram(ST_SFX_IMPACT));
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -274,8 +293,6 @@ static void collisionPhase2(StSimT *sim) {
|
|||
|
||||
// $6B4C -- 70-tick pause, then the life bookkeeping.
|
||||
static StTickResultE collisionPhase3(StSimT *sim) {
|
||||
uint8_t p;
|
||||
|
||||
sim->phaseTimer--;
|
||||
if (sim->phaseTimer != 0u) {
|
||||
return ST_TICK_CONTINUE;
|
||||
|
|
@ -283,142 +300,18 @@ static StTickResultE collisionPhase3(StSimT *sim) {
|
|||
if (sim->demoMode != 0u) {
|
||||
return ST_TICK_CRASH_DONE;
|
||||
}
|
||||
if (sim->stage == ST_STAGE_RIDING) {
|
||||
stSimHudInit(sim);
|
||||
if (sim->fareSlotCount != 0u) {
|
||||
sim->spriteSlots[sim->activeDyingSlot] = 0u;
|
||||
sim->fareSlotCount--;
|
||||
if (sim->activeSpriteIdx == ST_FARE_DEST_UP) {
|
||||
uint8_t k;
|
||||
for (k = 0u; k < 4u; k++) {
|
||||
setCell(sim, (uint16_t)(ST_CELL_TRANSPORTER + k), ST_CHAR_TRANSPORTER);
|
||||
}
|
||||
} else {
|
||||
sim->spriteSlots[sim->activeSpriteIdx] = 0u;
|
||||
sim->fareSlotCount--;
|
||||
}
|
||||
}
|
||||
sim->activeSpriteIdx = 0u;
|
||||
sim->stage = ST_STAGE_IDLE;
|
||||
stSimClearMessage(sim);
|
||||
}
|
||||
// $6BBA
|
||||
p = sim->player;
|
||||
sim->cabs[p]--;
|
||||
if (sim->cabs[p] == 0u) {
|
||||
return ST_TICK_PLAYER_OUT;
|
||||
}
|
||||
setCell(sim, (uint16_t)(ST_CELL_CAB_ICONS + sim->cabs[p]), ST_CHAR_BLANK);
|
||||
return ST_TICK_LIFE_LOST;
|
||||
return stSimLoseCab(sim);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// The C64 reads two VIC collision registers each frame: $D01F (a sprite
|
||||
// touched the background) and $D01E (two sprites touched). Only the cab
|
||||
// (sprite 0) can drive a gameplay outcome from them: the crash dispatch
|
||||
// returns unless the cab or the background is touched, and the hooks
|
||||
// read a bit set by cab contact -- except levels J and P, which watch
|
||||
// their own hazards hit the scenery and opt those sprites in through
|
||||
// bgCollSprites. So the full all-pairs register is not reproduced --
|
||||
// only sprite 0 versus the background and versus the other sprites,
|
||||
// plus the opted-in sprites versus the background, and the exhaust
|
||||
// flame (sprite 2, the cab's own, always overlapping it) is skipped.
|
||||
// Non-cab pairs (hazard vs hazard, passenger vs hazard with the cab
|
||||
// clear) never change the dispatch outcome, so their bits are left
|
||||
// unset. Proven against all four demo traces: the observable state
|
||||
// (positions, crash phase, score) is byte-identical.
|
||||
// $6966 -- what its read of $D01E/$D01F sees: this tick's frame OR the
|
||||
// frame drawn before the flush (see prevFrameSpriteColl in StSimT).
|
||||
static void computeCollisions(StSimT *sim) {
|
||||
uint8_t ss = 0u;
|
||||
uint8_t bg = 0u;
|
||||
uint8_t b;
|
||||
uint8_t r;
|
||||
uint8_t ss = frameSpriteCollisions(sim);
|
||||
|
||||
for (b = 1u; b < ST_HW_SPRITES; b++) {
|
||||
if ((sim->bgCollSprites & kStBit[b]) == 0u || (sim->frame.enableMask & kStBit[b]) == 0u) {
|
||||
continue;
|
||||
}
|
||||
spriteMasks(sim, b);
|
||||
if (spriteTouchesBackground(sim, b)) {
|
||||
bg |= kStBit[b];
|
||||
}
|
||||
}
|
||||
if ((sim->frame.enableMask & 1u) == 0u) {
|
||||
sim->spriteSpriteColl = 0u;
|
||||
sim->spriteBgColl = bg;
|
||||
return;
|
||||
}
|
||||
spriteMasks(sim, 0u);
|
||||
if (spriteTouchesBackground(sim, 0u)) {
|
||||
bg = 1u;
|
||||
}
|
||||
for (b = 1u; b < ST_HW_SPRITES; b++) {
|
||||
int16_t dx;
|
||||
int16_t dy;
|
||||
uint8_t s;
|
||||
int16_t q;
|
||||
uint8_t kLo;
|
||||
uint8_t kHi;
|
||||
uint8_t rLo;
|
||||
uint8_t rHi;
|
||||
int16_t lo;
|
||||
int16_t hi;
|
||||
const uint8_t *win;
|
||||
const uint8_t *bRow;
|
||||
// Skip the exhaust flame (sprite 2) and any disabled sprite.
|
||||
if (b == 2u || (sim->frame.enableMask & kStBit[b]) == 0u) {
|
||||
continue;
|
||||
}
|
||||
dx = (int16_t)((int16_t)sim->frame.x[b] - (int16_t)sim->frame.x[0]);
|
||||
dy = (int16_t)((int16_t)sim->frame.y[b] - (int16_t)sim->frame.y[0]);
|
||||
if (dx <= -ST_SPRITE_W || dx >= ST_SPRITE_W || dy <= -ST_SPRITE_H || dy >= ST_SPRITE_H) {
|
||||
continue;
|
||||
}
|
||||
spriteMasks(sim, b);
|
||||
// Sprite b sits dx pixels right of the cab; with dx = 8q - s
|
||||
// (s in 0..7) its row byte k lines up with byte k + q of the
|
||||
// cab's phase-s windows, and only 0 <= k + q <= 3 can overlap.
|
||||
s = (uint8_t)((0 - dx) & (ST_ROW_PHASES - 1));
|
||||
q = (int16_t)(((dx + (int16_t)s + 32) >> 3) - 4);
|
||||
kLo = (q < 0) ? (uint8_t)(0 - q) : 0u;
|
||||
kHi = (q > 1) ? (uint8_t)(3 - q) : 2u;
|
||||
// Rows r of the cab meet rows r - dy of sprite b; walk only the
|
||||
// rows where both have pixels, both pointers advancing in step.
|
||||
if (sim->collRowFirst[b] == ST_SPRITE_H || sim->collRowFirst[0] == ST_SPRITE_H) {
|
||||
continue;
|
||||
}
|
||||
lo = (int16_t)sim->collRowFirst[0];
|
||||
hi = (int16_t)(sim->collRowLast[0] + 1);
|
||||
if ((int16_t)sim->collRowFirst[b] + dy > lo) {
|
||||
lo = (int16_t)((int16_t)sim->collRowFirst[b] + dy);
|
||||
}
|
||||
if ((int16_t)sim->collRowLast[b] + 1 + dy < hi) {
|
||||
hi = (int16_t)((int16_t)sim->collRowLast[b] + 1 + dy);
|
||||
}
|
||||
if (lo >= hi) {
|
||||
continue;
|
||||
}
|
||||
rLo = (uint8_t)lo;
|
||||
rHi = (uint8_t)hi;
|
||||
win = cabWindows(sim, s) + (uint16_t)rLo * 4u;
|
||||
bRow = sim->collMask[b][(uint8_t)((int16_t)rLo - dy)];
|
||||
for (r = rLo; r < rHi; r++, win += 4, bRow += 3) {
|
||||
bool hit = false;
|
||||
uint8_t k;
|
||||
for (k = kLo; k <= kHi; k++) {
|
||||
if ((bRow[k] & win[(uint8_t)((int16_t)k + q)]) != 0u) {
|
||||
hit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (hit) {
|
||||
ss |= (uint8_t)(1u | kStBit[b]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
sim->spriteSpriteColl = ss;
|
||||
sim->spriteBgColl = bg;
|
||||
sim->spriteSpriteColl = (uint8_t)(ss | sim->prevFrameSpriteColl);
|
||||
sim->spriteBgColl = (uint8_t)(frameBgCollisions(sim) | sim->prevFrameBgColl);
|
||||
sim->prevFrameSpriteColl = ss;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -446,12 +339,12 @@ static void crashStart(StSimT *sim) {
|
|||
sim->phaseTimer = 2u;
|
||||
sim->phaseReload = 2u;
|
||||
stAudioNoise(false);
|
||||
sim->eventDispatchType = 0u;
|
||||
sim->eventDispatchType = ST_EVENT_NONE;
|
||||
sim->activePad = 0u;
|
||||
sim->spr[2].enable = 0u;
|
||||
sim->spr[2].row = 0u;
|
||||
sim->velY = 0x0303;
|
||||
stAudioSfx(stC64SfxProgram(ST_SFX_CRASH));
|
||||
stSimSfx(sim, stC64SfxProgram(ST_SFX_CRASH));
|
||||
sim->thrustSweep = 0xA0u;
|
||||
if (sim->stage == ST_STAGE_WALK_TO_CAB) {
|
||||
sim->stage--;
|
||||
|
|
@ -529,7 +422,7 @@ static void fireButtonEdge(StSimT *sim) {
|
|||
}
|
||||
sim->fireWasHeld = 0x10u;
|
||||
sim->spr[0].ptr ^= 1u;
|
||||
stAudioSfx(stC64SfxProgram(ST_SFX_GEAR));
|
||||
stSimSfx(sim, stC64SfxProgram(ST_SFX_GEAR));
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -558,29 +451,91 @@ void stSimFlameUpdate(StSimT *sim) {
|
|||
}
|
||||
|
||||
|
||||
// $6419 -- the climb/descend indicator: colour RAM cells beside the
|
||||
// fuel gauge flash red (rising), cyan (level) or yellow (falling).
|
||||
static void fuelBarHud(StSimT *sim) {
|
||||
uint16_t cell;
|
||||
uint8_t color;
|
||||
// The displayed frame's $D01F bits over the current picture -- only the
|
||||
// bits anyone reads: the cab's, and those of the hazards levels J and P
|
||||
// opt in through bgCollSprites.
|
||||
static uint8_t frameBgCollisions(StSimT *sim) {
|
||||
uint8_t bg = 0u;
|
||||
uint8_t b;
|
||||
|
||||
setColorCell(sim, ST_CELL(23, 10), 0x0Bu);
|
||||
setColorCell(sim, ST_CELL(23, 11), 0x0Bu);
|
||||
setColorCell(sim, ST_CELL(24, 10), 0x0Bu);
|
||||
setColorCell(sim, ST_CELL(24, 11), 0x0Bu);
|
||||
sim->fuelBarTick = (uint8_t)((sim->fuelBarTick + 1u) & 7u);
|
||||
if (sim->fuelBarTick >= 5u) {
|
||||
for (b = 0u; b < ST_HW_SPRITES; b++) {
|
||||
if ((sim->frame.enableMask & kStBit[b]) == 0u) {
|
||||
continue;
|
||||
}
|
||||
if (b != 0u && (sim->bgCollSprites & kStBit[b]) == 0u) {
|
||||
continue;
|
||||
}
|
||||
spriteMasks(sim, b);
|
||||
if (spriteTouchesBackground(sim, b)) {
|
||||
bg |= kStBit[b];
|
||||
}
|
||||
}
|
||||
return bg;
|
||||
}
|
||||
|
||||
|
||||
// The displayed frame's $D01E: both bits of every pair of shown sprites
|
||||
// whose pixels meet -- any pair, because the hooks test whole groups of
|
||||
// bits (G's and the dispatch's AND #$F8 also catch a hook sprite touching
|
||||
// the exhaust flame or another hook sprite, H's and U's walk every bit,
|
||||
// and the pad light's AND #$02 catches the passenger touching anything).
|
||||
static uint8_t frameSpriteCollisions(StSimT *sim) {
|
||||
uint8_t shown = sim->frame.enableMask;
|
||||
uint8_t ss = 0u;
|
||||
uint8_t a;
|
||||
uint8_t b;
|
||||
|
||||
for (a = 0u; a < (uint8_t)(ST_HW_SPRITES - 1u); a++) {
|
||||
if ((shown & kStBit[a]) == 0u) {
|
||||
continue;
|
||||
}
|
||||
for (b = (uint8_t)(a + 1u); b < ST_HW_SPRITES; b++) {
|
||||
uint8_t pair = (uint8_t)(kStBit[a] | kStBit[b]);
|
||||
int16_t dx;
|
||||
int16_t dy;
|
||||
// A pair already latched on both sides cannot add a bit, and
|
||||
// most pairs are too far apart to test at all.
|
||||
if ((shown & kStBit[b]) == 0u || (ss & pair) == pair) {
|
||||
continue;
|
||||
}
|
||||
dx = (int16_t)((int16_t)sim->frame.x[b] - (int16_t)sim->frame.x[a]);
|
||||
dy = (int16_t)((int16_t)sim->frame.y[b] - (int16_t)sim->frame.y[a]);
|
||||
if (dx <= -ST_SPRITE_W || dx >= ST_SPRITE_W || dy <= -ST_SPRITE_H || dy >= ST_SPRITE_H) {
|
||||
continue;
|
||||
}
|
||||
if (spritesTouch(sim, a, b, dx, dy)) {
|
||||
ss |= pair;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ss;
|
||||
}
|
||||
|
||||
|
||||
// $6419 -- the descent lamp beside the fuel gauge. Every tick its two
|
||||
// cell pairs ($DBA2 on row 23, $DBCA on row 24) go dark grey; then, on
|
||||
// five ticks of every eight, one pair lights by the Y velocity
|
||||
// ($714F/$714E, positive = falling): red on row 24 when the high byte is
|
||||
// positive ($643D), yellow on row 23 when it is zero and the low byte has
|
||||
// bit 7 set ($6450), and cyan on row 23 otherwise ($6449) -- any climb,
|
||||
// a hover and a slow fall alike.
|
||||
static void fuelBarHud(StSimT *sim) {
|
||||
uint16_t cell = ST_LAMP_CELL_SLOW;
|
||||
uint8_t color = ST_LAMP_SLOW;
|
||||
|
||||
setColorCell(sim, ST_LAMP_CELL_SLOW, ST_LAMP_OFF);
|
||||
setColorCell(sim, (uint16_t)(ST_LAMP_CELL_SLOW + 1u), ST_LAMP_OFF);
|
||||
setColorCell(sim, ST_LAMP_CELL_FAST, ST_LAMP_OFF);
|
||||
setColorCell(sim, (uint16_t)(ST_LAMP_CELL_FAST + 1u), ST_LAMP_OFF);
|
||||
sim->fuelBarTick = (uint8_t)((sim->fuelBarTick + 1u) & ST_LAMP_TICK_MASK);
|
||||
if (sim->fuelBarTick >= ST_LAMP_LIT_TICKS) {
|
||||
return;
|
||||
}
|
||||
if (sim->velY < 0) {
|
||||
color = 0x02u;
|
||||
cell = ST_CELL(24, 10);
|
||||
} else if (sim->velY == 0) {
|
||||
color = 0x03u;
|
||||
cell = ST_CELL(23, 10);
|
||||
} else {
|
||||
color = 0x07u;
|
||||
cell = ST_CELL(23, 10);
|
||||
if (sim->velY >= ST_LAMP_FAST_VEL) {
|
||||
color = ST_LAMP_FAST;
|
||||
cell = ST_LAMP_CELL_FAST;
|
||||
} else if (sim->velY >= ST_LAMP_FALL_VEL) {
|
||||
color = ST_LAMP_FALL;
|
||||
}
|
||||
setColorCell(sim, cell, color);
|
||||
setColorCell(sim, (uint16_t)(cell + 1u), color);
|
||||
|
|
@ -603,7 +558,7 @@ static void fuelTick(StSimT *sim) {
|
|||
}
|
||||
sim->padAnimTick = (uint8_t)((sim->padAnimTick + 1u) & 7u);
|
||||
if (sim->padAnimTick == 0u) {
|
||||
stAudioSfx(stC64SfxProgram(ST_SFX_CASH));
|
||||
stSimSfx(sim, stC64SfxProgram(ST_SFX_CASH));
|
||||
} else if (sim->padAnimTick < 5u) {
|
||||
stAudioVoice1Freq(kPumpFreq[sim->padAnimTick]);
|
||||
}
|
||||
|
|
@ -619,7 +574,7 @@ static void fuelTick(StSimT *sim) {
|
|||
}
|
||||
setCell(sim, (uint16_t)(ST_CELL_FUEL + x), ST_CHAR_BLANK);
|
||||
if (sim->screen[ST_CELL_FUEL_LAST] == ST_CHAR_BLANK) {
|
||||
stAudioSfx(stC64SfxProgram(ST_SFX_FUEL_FULL));
|
||||
stSimSfx(sim, stC64SfxProgram(ST_SFX_FUEL_FULL));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
@ -632,7 +587,7 @@ static void fuelTick(StSimT *sim) {
|
|||
return;
|
||||
}
|
||||
if (sim->fuelCells < 3u) {
|
||||
stAudioSfx(stC64SfxProgram(ST_SFX_FUEL_LOW));
|
||||
stSimSfx(sim, stC64SfxProgram(ST_SFX_FUEL_LOW));
|
||||
}
|
||||
sim->fuelCountdown = L->fuelRate;
|
||||
x = sim->fuelCells;
|
||||
|
|
@ -716,12 +671,12 @@ static void padDetect(StSimT *sim) {
|
|||
}
|
||||
sim->activePad = (uint8_t)(i + 1);
|
||||
sim->activePadMirror = sim->activePad;
|
||||
sim->eventDispatchType = 0u;
|
||||
sim->eventDispatchType = ST_EVENT_NONE;
|
||||
if (sim->activePad != sim->activeSpriteIdx) {
|
||||
if (sim->stage == ST_STAGE_RIDING) {
|
||||
sim->eventDispatchType = (sim->activeSpriteIdx == ST_FARE_DEST_UP) ? 3u : 2u;
|
||||
sim->eventDispatchType = (sim->activeSpriteIdx == ST_FARE_DEST_UP) ? ST_EVENT_WRONG_PAD_UP : ST_EVENT_WRONG_PAD;
|
||||
} else if (sim->stage == ST_STAGE_WAIT) {
|
||||
sim->eventDispatchType = 1u;
|
||||
sim->eventDispatchType = ST_EVENT_HAIL;
|
||||
}
|
||||
}
|
||||
sim->dirMask = 0u;
|
||||
|
|
@ -729,11 +684,11 @@ static void padDetect(StSimT *sim) {
|
|||
stSimPadLight(sim, true);
|
||||
sim->bobTimer = 2u;
|
||||
if (((uint8_t)sim->velY & 0x80u) == 0u) {
|
||||
stAudioSfx(stC64SfxProgram(ST_SFX_LAND_SOFT));
|
||||
stSimSfx(sim, stC64SfxProgram(ST_SFX_LAND_SOFT));
|
||||
return;
|
||||
}
|
||||
sim->bobTimer = 8u;
|
||||
stAudioSfx(stC64SfxProgram(ST_SFX_LAND_HARD));
|
||||
stSimSfx(sim, stC64SfxProgram(ST_SFX_LAND_HARD));
|
||||
if (sim->stage == ST_STAGE_RIDING) {
|
||||
stSimHudInit(sim);
|
||||
}
|
||||
|
|
@ -959,6 +914,20 @@ static void setColorCell(StSimT *sim, uint16_t cell, uint8_t color) {
|
|||
}
|
||||
|
||||
|
||||
// $4320 -- the release countdown of voices 1 and 2. Gating a voice off
|
||||
// when its count runs out is the audio layer's business; the counts are
|
||||
// what the game reads.
|
||||
static void sfxEnvelopeTick(StSimT *sim) {
|
||||
uint8_t v;
|
||||
|
||||
for (v = 0u; v < ST_SID_TIMED_VOICES; v++) {
|
||||
if (sim->sidRelTimer[v] != 0u) {
|
||||
sim->sidRelTimer[v]--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 21 rows of 3-byte foreground masks for a displayed sprite, kept in
|
||||
// collMask[idx] and rebuilt only when its bitmap or multicolour mode
|
||||
// changes. A multicolour sprite's 2-bit pairs cover two pixels each: a
|
||||
|
|
@ -1068,13 +1037,9 @@ static bool spriteTouchesBackground(StSimT *sim, uint8_t idx) {
|
|||
}
|
||||
|
||||
|
||||
// $6946 -- all sprites off, latches cleared, nobody on a pad.
|
||||
// $6946 -- the VIC's sprites off ($D015; the shadow enables are $4248's
|
||||
// job), the collision latches cleared, nobody on a pad.
|
||||
static void spritesOffReset(StSimT *sim) {
|
||||
uint8_t i;
|
||||
|
||||
for (i = 0u; i < ST_HW_SPRITES; i++) {
|
||||
sim->spr[i].enable = 0u;
|
||||
}
|
||||
sim->spriteSpriteColl = 0u;
|
||||
sim->spriteBgColl = 0u;
|
||||
sim->activePad = 0u;
|
||||
|
|
@ -1082,6 +1047,71 @@ static void spritesOffReset(StSimT *sim) {
|
|||
}
|
||||
|
||||
|
||||
// Whether shown sprites a < b, b sitting dx right of a and dy below it
|
||||
// (both inside a sprite's size), have a pixel in common. With dx = 8q - s
|
||||
// (s in 0..7), b's row byte k lines up with byte k + q of a's row shifted
|
||||
// to phase s, and only 0 <= k + q <= 3 can overlap; rows r of a meet rows
|
||||
// r - dy of b, and only the rows where both have pixels are walked. The
|
||||
// cab's shifted rows are cached (cabWindows) -- it is a side of most
|
||||
// tests -- any other sprite's are shifted as they are needed.
|
||||
static bool spritesTouch(StSimT *sim, uint8_t a, uint8_t b, int16_t dx, int16_t dy) {
|
||||
uint8_t s;
|
||||
int16_t q;
|
||||
uint8_t kLo;
|
||||
uint8_t kHi;
|
||||
uint8_t r;
|
||||
uint8_t rHi;
|
||||
int16_t lo;
|
||||
int16_t hi;
|
||||
const uint8_t *win = 0;
|
||||
const uint8_t *bRow;
|
||||
uint8_t own[4];
|
||||
|
||||
spriteMasks(sim, a);
|
||||
spriteMasks(sim, b);
|
||||
if (sim->collRowFirst[a] == ST_SPRITE_H || sim->collRowFirst[b] == ST_SPRITE_H) {
|
||||
return false;
|
||||
}
|
||||
s = (uint8_t)((0 - dx) & (ST_ROW_PHASES - 1));
|
||||
q = (int16_t)(((dx + (int16_t)s + 32) >> 3) - 4);
|
||||
kLo = (q < 0) ? (uint8_t)(0 - q) : 0u;
|
||||
kHi = (q > 1) ? (uint8_t)(3 - q) : 2u;
|
||||
lo = (int16_t)sim->collRowFirst[a];
|
||||
hi = (int16_t)(sim->collRowLast[a] + 1);
|
||||
if ((int16_t)sim->collRowFirst[b] + dy > lo) {
|
||||
lo = (int16_t)((int16_t)sim->collRowFirst[b] + dy);
|
||||
}
|
||||
if ((int16_t)sim->collRowLast[b] + 1 + dy < hi) {
|
||||
hi = (int16_t)((int16_t)sim->collRowLast[b] + 1 + dy);
|
||||
}
|
||||
if (lo >= hi) {
|
||||
return false;
|
||||
}
|
||||
r = (uint8_t)lo;
|
||||
rHi = (uint8_t)hi;
|
||||
bRow = sim->collMask[b][(uint8_t)((int16_t)r - dy)];
|
||||
if (a == 0u) {
|
||||
win = cabWindows(sim, s) + (uint16_t)r * 4u;
|
||||
}
|
||||
for (; r < rHi; r++, bRow += 3) {
|
||||
const uint8_t *w = win;
|
||||
uint8_t k;
|
||||
if (a == 0u) {
|
||||
win += 4;
|
||||
} else {
|
||||
rowWindow(sim->collMask[a][r], s, own);
|
||||
w = own;
|
||||
}
|
||||
for (k = kLo; k <= kHi; k++) {
|
||||
if ((bRow[k] & w[(uint8_t)((int16_t)k + q)]) != 0u) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// $6888 -- place the cab at the level's spawn point with a full tank.
|
||||
static void taxiSpawnInit(StSimT *sim) {
|
||||
const StLevelT *L = sim->level;
|
||||
|
|
@ -1149,6 +1179,13 @@ static uint8_t validateDigit(uint8_t ch) {
|
|||
}
|
||||
|
||||
|
||||
// $6909 / $6C02 -- voices 1 and 2 gated off, their release timers cleared.
|
||||
static void voicesOff(StSimT *sim) {
|
||||
stAudioSilence();
|
||||
memset(sim->sidRelTimer, 0, sizeof(sim->sidRelTimer));
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public API
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -1382,26 +1419,15 @@ void stSimEnterLevel(StSimT *sim, const StLevelT *level) {
|
|||
}
|
||||
sim->hookLevel = level->levelIndex;
|
||||
stHookSceneLoad(sim);
|
||||
spritesOffReset(sim);
|
||||
stHookPrelude0(sim);
|
||||
stFareTakeoffSetup(sim);
|
||||
stSimRespawn(sim);
|
||||
}
|
||||
|
||||
|
||||
// $5F27 -- the respawn half of the prelude.
|
||||
void stSimRespawn(StSimT *sim) {
|
||||
spritesOffReset(sim);
|
||||
stHookPrelude0(sim);
|
||||
// $6F18 takeoffSetup: from the third screen on the cab starts with
|
||||
// a passenger aboard who wants pad 1.
|
||||
sim->eventDispatchType = 0u;
|
||||
sim->stage = ST_STAGE_IDLE;
|
||||
if (sim->levelState >= 2u) {
|
||||
static const uint8_t kPadPlease[] = "PAD 1 PLEASE";
|
||||
sim->stage = ST_STAGE_RIDING;
|
||||
sim->activeSpriteIdx = 1u;
|
||||
sim->activeDyingSlot = 1u;
|
||||
stSimDrawText(sim, ST_MESSAGE_COL, ST_MESSAGE_ROW, kPadPlease, 1u);
|
||||
sim->eventDispatchType = 1u;
|
||||
}
|
||||
taxiSpawnInit(sim);
|
||||
stHookPrelude1(sim);
|
||||
// $6906 framePresent: the start-of-screen jingle, then the SID (and
|
||||
|
|
@ -1409,7 +1435,7 @@ void stSimRespawn(StSimT *sim) {
|
|||
// screen rotates songs 0..3 on $5E9B. The host plays it with the sim
|
||||
// frozen, which is what the C64's blocking play-and-wait amounts to.
|
||||
stSimMarshal(sim);
|
||||
stAudioSilence();
|
||||
voicesOff(sim);
|
||||
sim->jingleRotate++;
|
||||
if (sim->levelState == ST_JINGLE_LEVEL_25) {
|
||||
sim->jingleRequest = (uint8_t)(ST_JINGLE_SONG_BONUS + 1u);
|
||||
|
|
@ -1418,8 +1444,12 @@ void stSimRespawn(StSimT *sim) {
|
|||
}
|
||||
sim->spriteSpriteColl = 0u;
|
||||
sim->spriteBgColl = 0u;
|
||||
// $6920 cleared the latch, but the VIC goes on showing this frame
|
||||
// through the jingle, so the first tick's read still sees it.
|
||||
sim->prevFrameSpriteColl = frameSpriteCollisions(sim);
|
||||
sim->rngT1 = 0x06u;
|
||||
sim->rngT2 = 0x17u;
|
||||
stFareTakeoffVoice(sim);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1462,6 +1492,43 @@ void stSimNewGame(StSimT *sim, uint8_t playerCount, bool demo) {
|
|||
}
|
||||
|
||||
|
||||
// $6B64..$6BCD -- the game's side of a finished wreck (the attract
|
||||
// demo returns to the title instead): a fare aboard is lost -- its meter,
|
||||
// its pads, an open hatch, its message -- then the player's cab count
|
||||
// drops. With cabs left the host respawns the cab (stSimRespawn, $5F27).
|
||||
StTickResultE stSimLoseCab(StSimT *sim) {
|
||||
uint8_t p;
|
||||
|
||||
if (sim->stage == ST_STAGE_RIDING) {
|
||||
stSimHudInit(sim);
|
||||
if (sim->fareSlotCount != 0u) {
|
||||
sim->spriteSlots[sim->activeDyingSlot] = 0u;
|
||||
sim->fareSlotCount--;
|
||||
if (sim->activeSpriteIdx == ST_FARE_DEST_UP) {
|
||||
uint8_t k;
|
||||
for (k = 0u; k < ST_TRANSPORTER_CELLS; k++) {
|
||||
setCell(sim, (uint16_t)(ST_CELL_TRANSPORTER + k), ST_CHAR_TRANSPORTER);
|
||||
}
|
||||
} else {
|
||||
sim->spriteSlots[sim->activeSpriteIdx] = 0u;
|
||||
sim->fareSlotCount--;
|
||||
}
|
||||
}
|
||||
sim->activeSpriteIdx = 0u;
|
||||
sim->stage = ST_STAGE_IDLE;
|
||||
stSimClearMessage(sim);
|
||||
}
|
||||
// $6BBA
|
||||
p = sim->player;
|
||||
sim->cabs[p]--;
|
||||
if (sim->cabs[p] == 0u) {
|
||||
return ST_TICK_PLAYER_OUT;
|
||||
}
|
||||
setCell(sim, (uint16_t)(ST_CELL_CAB_ICONS + sim->cabs[p]), ST_CHAR_BLANK);
|
||||
return ST_TICK_LIFE_LOST;
|
||||
}
|
||||
|
||||
|
||||
// Everything must be repainted.
|
||||
// A glyph's bits changed: the renderer has to repaint every cell showing
|
||||
// that character. The list is short (one or two per frame), so a linear
|
||||
|
|
@ -1625,6 +1692,17 @@ void stSimSetDemoStream(StSimT *sim, const uint8_t *stream, uint16_t len) {
|
|||
}
|
||||
|
||||
|
||||
// $42E9 -- an SFX program starts; voices 0 and 1 arm their release timer.
|
||||
void stSimSfx(StSimT *sim, const uint8_t *program9) {
|
||||
uint8_t voice = program9[ST_SFX_VOICE_AT];
|
||||
|
||||
if (voice < ST_SID_TIMED_VOICES) {
|
||||
sim->sidRelTimer[voice] = program9[ST_SFX_RELEASE_AT];
|
||||
}
|
||||
stAudioSfx(program9);
|
||||
}
|
||||
|
||||
|
||||
// $411B -- add a signed byte to a sprite's 17-bit X, carrying into the
|
||||
// msb shadow.
|
||||
void stSimSpriteAddX(StSimT *sim, uint8_t idx, uint8_t delta) {
|
||||
|
|
@ -1671,18 +1749,20 @@ StTickResultE stSimTick(StSimT *sim) {
|
|||
taxiSpriteCelSelect(sim);
|
||||
if (levelEndCheck(sim)) {
|
||||
stAudioNoise(false);
|
||||
stAudioSilence();
|
||||
voicesOff(sim);
|
||||
stSimArchiveHud(sim);
|
||||
return ST_TICK_LEVEL_EXIT;
|
||||
}
|
||||
padLandingBob(sim);
|
||||
fuelTick(sim);
|
||||
// $61BD passengerEventDraw only re-voices the pickup/drop-off
|
||||
// messages already drawn by the fare machine.
|
||||
stFareEventVoice(sim);
|
||||
}
|
||||
stFarePostTickGate(sim);
|
||||
stFareStageDispatch(sim);
|
||||
stHookPerTick2(sim);
|
||||
// $5F94: the frame drawn while the CPU waits shows the last flush's
|
||||
// sprites over the picture as this tick has left it.
|
||||
sim->prevFrameBgColl = frameBgCollisions(sim);
|
||||
stSimMarshal(sim);
|
||||
stSimRotateGlyph(sim, ST_CHAR_TRANSPORTER);
|
||||
stHookPerTick3(sim);
|
||||
|
|
@ -1703,6 +1783,7 @@ StTickResultE stSimTick(StSimT *sim) {
|
|||
}
|
||||
}
|
||||
hudDraw(sim);
|
||||
sfxEnvelopeTick(sim);
|
||||
// $4FCB runStopWatcher: any joystick input ends the demo.
|
||||
if (sim->demoMode != 0u && (sim->rawInput & 0x1Fu) != 0u) {
|
||||
return ST_TICK_DEMO_INPUT;
|
||||
|
|
|
|||
106
stSim.h
106
stSim.h
|
|
@ -52,13 +52,14 @@
|
|||
#define ST_HW_SPRITES 8u
|
||||
|
||||
// Fixed screen cells the game writes directly (C64 screen addresses).
|
||||
#define ST_CELL_TRANSPORTER ST_CELL(0, 18) // $0412..$0415, 4 cells
|
||||
#define ST_CELL_TRANSPORTER ST_CELL(0, 18) // $0412..$0415
|
||||
#define ST_TRANSPORTER_CELLS 4u
|
||||
#define ST_CELL_CAB_ICONS ST_CELL(23, 1) // $0799..$079F
|
||||
#define ST_CELL_FUEL ST_CELL(23, 14) // $07A6..$07B1, 12 cells
|
||||
#define ST_CELL_FUEL_LAST ST_CELL(23, 25) // $07B1
|
||||
#define ST_CELL_SCREENS ST_CELL(23, 32) // $07B8..$07BC
|
||||
#define ST_CELL_SCORE ST_CELL(24, 2) // $07C2..$07C8, 7 chars
|
||||
#define ST_CELL_MESSAGE ST_CELL(24, 14) // $07CE, 11 chars
|
||||
#define ST_CELL_MESSAGE ST_CELL(24, 14) // $07CE, 12 chars
|
||||
#define ST_MESSAGE_COL 14u
|
||||
#define ST_MESSAGE_ROW 24u
|
||||
#define ST_CELL_PAD_DIGIT ST_CELL(24, 18) // $07D2
|
||||
|
|
@ -79,7 +80,7 @@
|
|||
#define ST_CHAR_FUEL_EMPTY 0x7Au
|
||||
#define ST_CHAR_TRANSPORTER 0x67u // animated top-wall hatch
|
||||
#define ST_CHAR_CAB_ICON 0xC8u
|
||||
#define ST_CHAR_ELECTROID 0x6Fu // level O's scrolling barrier glyph
|
||||
#define ST_CHAR_ELECTROID 0x7Cu // level O's barrier glyph ($7E6E rolls $2BE0)
|
||||
#define ST_FARE_DEST_UP 0x0Bu // $7D8E: the fare aboard wants UP
|
||||
|
||||
// ---- level-intro starfield --------------------------------------------
|
||||
|
|
@ -149,9 +150,16 @@ typedef char stStarRingCountIsPow2[((ST_STAR_RING_COUNT & (ST_STAR_RING_COUNT -
|
|||
// Distinct characters whose glyph can change in one frame before the
|
||||
// renderer gives up and repaints everything.
|
||||
#define ST_CHAR_DIRTY_MAX 8u
|
||||
// A 9-byte SFX program ($42E9): SID registers 0..6, then the release
|
||||
// timer and the voice. Only voices 0 and 1 have a timer ($7216/$7217;
|
||||
// voice 2's lands on $7218, the voice 1 gate mask, which only the audio
|
||||
// layer uses).
|
||||
#define ST_SFX_RELEASE_AT 7u
|
||||
#define ST_SFX_VOICE_AT 8u
|
||||
#define ST_SID_TIMED_VOICES 2u
|
||||
// Characters that may hold an edited glyph at once. The game edits six
|
||||
// across the whole game (the transporter hatch $67, level O's barrier
|
||||
// $6F, the title logo $84 and level W's three laser glyphs $91..$93), so
|
||||
// $7C, the title logo $84 and level W's three laser glyphs $91..$93), so
|
||||
// this is generous; an overflow parks the edit in the spill entry, where
|
||||
// it is simply never displayed rather than corrupting another character.
|
||||
#define ST_GLYPH_OVERRIDES 16u
|
||||
|
|
@ -160,14 +168,16 @@ typedef struct {
|
|||
uint8_t rows[8];
|
||||
} StGlyphT;
|
||||
|
||||
// Level image as loaded from a .dat (STL4). Kept in the C64's units.
|
||||
// Level image as loaded from a .dat (STL5). Kept in the C64's units.
|
||||
#define ST_MAX_PADS 10u
|
||||
#define ST_MAX_LEVEL_SPRITES 16u
|
||||
// Per-level hook code + tables live at $7D98..$7FFF in the level's data
|
||||
// blob; the port keeps the bytes (for the tables) and re-expresses the
|
||||
// code in stHooks.c.
|
||||
// Per-level hook code + tables live from $7D98 in the level's data blob;
|
||||
// the port keeps the bytes (for the tables) and re-expresses the code in
|
||||
// stHooks.c. Most levels end by $7FFF, but level Y's program runs on past
|
||||
// $8000, its last table being the SFX program at $80C2..$80CA.
|
||||
#define ST_HOOK_BASE 0x7D98u
|
||||
#define ST_HOOK_BYTES (0x8000u - ST_HOOK_BASE)
|
||||
#define ST_HOOK_END 0x80CBu
|
||||
#define ST_HOOK_BYTES (ST_HOOK_END - ST_HOOK_BASE)
|
||||
|
||||
// Per-level hook scratch. The original keeps a hook's running state
|
||||
// inside the level's own data block, at fixed C64 addresses interleaved
|
||||
|
|
@ -190,6 +200,13 @@ typedef struct {
|
|||
uint8_t tone; // $7E56 rising chime
|
||||
} StHookHT;
|
||||
|
||||
typedef struct {
|
||||
uint16_t vx; // $7DD1/$7DD2 the ball's X step
|
||||
uint16_t x; // $7DD5/$7DD6 its 9-bit sprite X
|
||||
uint16_t y; // $7DD7/$7DD8 its row, 8.8
|
||||
uint16_t vy; // $7DD9/$7DDA 8.8
|
||||
} StHookFT;
|
||||
|
||||
typedef struct {
|
||||
uint8_t celPhase; // $7DD2 orb cel cycle 0..5
|
||||
uint8_t tick; // $7E00
|
||||
|
|
@ -215,12 +232,34 @@ typedef struct {
|
|||
|
||||
typedef struct {
|
||||
uint8_t phase[ST_HW_SPRITES]; // $7E83 interference cel phase
|
||||
uint8_t parity; // $7DFE the cels step every other tick
|
||||
} StHookQT;
|
||||
|
||||
typedef struct {
|
||||
uint8_t gateShut; // $7D9C
|
||||
} StHookTT;
|
||||
|
||||
typedef struct {
|
||||
uint8_t tick; // $7DE2 shift every sixteenth
|
||||
} StHookVT;
|
||||
|
||||
typedef struct {
|
||||
uint8_t humStep; // $7ECC the saucer's hum pitch index
|
||||
uint8_t walkerDir; // $7EC2 0 left, 1 right
|
||||
uint8_t walkerCount; // $7EC9
|
||||
uint8_t walkerParity; // $7ECB
|
||||
uint8_t lightTick; // $7F10
|
||||
uint8_t lightCell; // $7F13 0..119 over rows 19..21
|
||||
uint8_t lightSaveChar; // $7F11
|
||||
uint8_t lightSaveColor; // $7F12
|
||||
uint8_t blockTimer; // $7F66
|
||||
uint8_t blockDir; // $7168 0 left, 1 right
|
||||
uint8_t blockCol; // $7F69
|
||||
uint8_t blockFrame; // $7F6A
|
||||
uint8_t shotDelay; // $803B
|
||||
uint8_t shotLive; // $808C
|
||||
} StHookYT;
|
||||
|
||||
typedef struct {
|
||||
uint8_t colorPhase; // $7DAB
|
||||
uint8_t state[ST_HOOK_W_LASERS]; // $7DAC 0 idle, 1 extending, 2 fading
|
||||
|
|
@ -262,6 +301,7 @@ typedef struct {
|
|||
|
||||
typedef union {
|
||||
StHookET e;
|
||||
StHookFT f;
|
||||
StHookGT g;
|
||||
StHookHT h;
|
||||
StHookIT i;
|
||||
|
|
@ -272,8 +312,10 @@ typedef union {
|
|||
StHookQT q;
|
||||
StHookTT t;
|
||||
StHookUT u;
|
||||
StHookVT v;
|
||||
StHookWT w;
|
||||
StHookXT x;
|
||||
StHookYT y;
|
||||
} StHookScratchT;
|
||||
|
||||
typedef struct {
|
||||
|
|
@ -309,7 +351,7 @@ typedef struct {
|
|||
uint8_t levelIndex; // 0..23 = A..X, 24 = Y, 25 = title
|
||||
uint8_t spriteCount;
|
||||
StLevelSpriteT sprites[ST_MAX_LEVEL_SPRITES];
|
||||
uint8_t hookData[ST_HOOK_BYTES]; // $7D98..$7FFF as shipped
|
||||
uint8_t hookData[ST_HOOK_BYTES]; // $7D98..$80CA as shipped
|
||||
} StLevelT;
|
||||
|
||||
// One VIC sprite as the game sees it through its shadow tables. Padded
|
||||
|
|
@ -358,6 +400,16 @@ typedef enum {
|
|||
ST_STAGE_CLEANUP // $67C6
|
||||
} StStageE;
|
||||
|
||||
// What a landing has left to say ($71CD), voiced once the touchdown bob
|
||||
// settles ($61BD) and cleared by every phrase the game speaks ($6C7F).
|
||||
typedef enum {
|
||||
ST_EVENT_NONE = 0,
|
||||
ST_EVENT_HAIL, // $64F3: a fare waits on another pad; also
|
||||
// $6F51: the takeoff fare's call ($5F30)
|
||||
ST_EVENT_WRONG_PAD, // $64DF: a fare aboard, set down on the wrong pad
|
||||
ST_EVENT_WRONG_PAD_UP // $64E7: ...and the fare wants UP
|
||||
} StEventE;
|
||||
|
||||
|
||||
// What a tick asked the host to do next (the original abandons the
|
||||
// main loop with PLA/PLA on these).
|
||||
|
|
@ -460,6 +512,10 @@ typedef struct {
|
|||
uint8_t bobTimer; // $716F
|
||||
uint8_t flameParity; // $716C
|
||||
uint8_t thrustSweep; // $721B
|
||||
// Voice 1 and 2 release timers ($7216/$7217): $42E9 loads one from an
|
||||
// SFX program and $4320 counts it down every tick. Game state, not
|
||||
// just sound: levels Q and Y only buzz while voice 2's has run out.
|
||||
uint8_t sidRelTimer[ST_SID_TIMED_VOICES];
|
||||
|
||||
// ---- collision / crash ----
|
||||
uint8_t collisionPhase; // $7164
|
||||
|
|
@ -468,6 +524,18 @@ typedef struct {
|
|||
uint8_t hitDispatchResult; // $721D
|
||||
uint8_t spriteSpriteColl; // $71CA latched $D01E
|
||||
uint8_t spriteBgColl; // $71CB latched $D01F
|
||||
// The older of the two frames a read sees (derived state, not C64
|
||||
// memory). The VIC latches $D01E/$D01F until they are read, and the
|
||||
// main loop reads them two frames after the last read: one frame still
|
||||
// shows the previous tick's sprites -- drawn while the CPU waits at
|
||||
// $5F94, so over the picture this tick's work has already changed --
|
||||
// and the next shows this tick's. A read is the OR of both, so a
|
||||
// sprite pulling away from a contact is still seen touching on the
|
||||
// tick it leaves. Sprite-sprite contact does not depend on the
|
||||
// picture and carries over from the last read; sprite-background
|
||||
// contact is worked out again before the flush (stSimTick).
|
||||
uint8_t prevFrameSpriteColl;
|
||||
uint8_t prevFrameBgColl;
|
||||
// Sprites besides the cab whose $D01F bit a hook reads (levels J
|
||||
// and P watch their hazards hit the scenery); the rest are never
|
||||
// tested (derived state, not C64 memory).
|
||||
|
|
@ -571,8 +639,14 @@ typedef struct {
|
|||
// fresh screen: copy the image, restore the player's HUD rows, draw
|
||||
// the cab icons, spawn the cab, refill the fuel.
|
||||
void stSimEnterLevel(StSimT *sim, const StLevelT *level);
|
||||
// Re-entry after a crash with cabs left ($5F27): spawn + refill only.
|
||||
// Re-entry after a crash with cabs left ($5F27, where $6BCD jumps): spawn,
|
||||
// refill, the second prelude hook and the jingle -- the fare machine and
|
||||
// the hooks' first prelude carry on from where the crash left them.
|
||||
void stSimRespawn(StSimT *sim);
|
||||
// $6B64 -- a finished wreck costs a fare aboard and a cab: LIFE_LOST
|
||||
// (respawn with stSimRespawn) or PLAYER_OUT. What a tick returns in a
|
||||
// real game; the attract demo returns CRASH_DONE instead.
|
||||
StTickResultE stSimLoseCab(StSimT *sim);
|
||||
// One game tick = one $5F40 iteration (two video frames on the C64).
|
||||
StTickResultE stSimTick(StSimT *sim);
|
||||
// Seed the whole struct for a new game ($5EC4 + $4207 + $48AD).
|
||||
|
|
@ -609,7 +683,7 @@ void stSimGlyphReset(StSimT *sim);
|
|||
// The 17-bit VIC sprite X (msb:col), read and signed-byte add ($411B).
|
||||
uint16_t stSimSpriteX(const StSimT *sim, uint8_t idx);
|
||||
void stSimSpriteAddX(StSimT *sim, uint8_t idx, uint8_t delta);
|
||||
// $6C38 -- blank the 11-character message row.
|
||||
// $6C38 -- blank the 12-character message row.
|
||||
void stSimClearMessage(StSimT *sim);
|
||||
// A glyph's bits changed: every cell showing it has to be repainted.
|
||||
void stSimMarkChar(StSimT *sim, uint8_t ch);
|
||||
|
|
@ -637,8 +711,10 @@ void stFareStageDispatch(StSimT *sim); // $65C1
|
|||
void stFarePostTickGate(StSimT *sim); // $67E3
|
||||
void stFarePadLightingGate(StSimT *sim); // $6EF0
|
||||
void stFareUpPlease(StSimT *sim); // $6CB8
|
||||
void stFareEventVoice(StSimT *sim); // $61BD
|
||||
void stFareTakeoffSetup(StSimT *sim); // $6F18
|
||||
void stFareTakeoffVoice(StSimT *sim); // $5F30
|
||||
uint8_t stFareChooseDestination(StSimT *sim); // $6537: reserves a pad, returns its number
|
||||
void stFareDrawMessage(StSimT *sim, const uint8_t *text);
|
||||
void stFareSquashed(StSimT *sim); // $69B4..$69F8
|
||||
void stFareBoardingSound(StSimT *sim, const uint8_t *speech); // $6C7F
|
||||
void stFareWaveStep(StSimT *sim); // $66B7
|
||||
|
|
@ -661,6 +737,10 @@ uint8_t stHookHitVerdict(StSimT *sim); // $7D75: 0 = passenger contact is
|
|||
// Seed a converted level's scratch from the shipped blob (scene load).
|
||||
void stHookSceneLoad(StSimT *sim);
|
||||
|
||||
// $42E9 -- start a 9-byte SFX program (stAudioSfx) and arm its voice's
|
||||
// release timer.
|
||||
void stSimSfx(StSimT *sim, const uint8_t *program9);
|
||||
|
||||
// ---- audio sink (host provides; stAudio.c / stsim stub) ----
|
||||
void stAudioSfx(const uint8_t *program9); // $42E9 load
|
||||
void stAudioNoise(bool on); // $D412 = $81 / $80
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ $(OUT)/harness: harness.c $(REPO)/spacetaxi.c $(SRCS)
|
|||
$(CC) $(CFLAGS) harness.c $(SRCS) $(WRAPS) -o $@
|
||||
|
||||
# The title screen's level file is read through DATA/levels (the DOS bake
|
||||
# is the cross-platform STL4 form), so run from a directory that has one.
|
||||
# is the cross-platform STL5 form), so run from a directory that has one.
|
||||
run: $(OUT)/harness
|
||||
@test -f $(GEN)/levels/title.dat || { echo "staxiharness: no $(GEN)/levels/title.dat (build the game for DOS first)" >&2; exit 2; }
|
||||
@mkdir -p $(OUT)/DATA
|
||||
|
|
|
|||
442
tools/stsim/c64trace.py
Executable file
442
tools/stsim/c64trace.py
Executable file
|
|
@ -0,0 +1,442 @@
|
|||
#!/usr/bin/env python3
|
||||
# c64trace.py -- ride a gate stream on the real game in VICE and print
|
||||
# the trace stsim prints, line for line, so the two can be compared byte
|
||||
# for byte (gate.sh c64).
|
||||
#
|
||||
# c64trace.py <level letter> <ticks> <demobuf.json> [rawOut]
|
||||
# c64trace.py --prepare make the cached snapshot (below) and exit
|
||||
#
|
||||
# demobuf.json is stsim's STSIM_DEMOBUF line: the whole 640-byte playback
|
||||
# buffer ($0902) as the sim starts the ride with it. rawOut, if given,
|
||||
# receives each tick's screen RAM, colour RAM and charset in stsim's
|
||||
# STSIM_RAW layout.
|
||||
#
|
||||
# How the ride is set up on the C64, all from the game's own attract demo:
|
||||
# * The original disk boots once (the crack's intro is skipped at its
|
||||
# key test, the trainer menu answered with START GAME, every cheat
|
||||
# off) to the point the first attract demo picks its screen ($50C6);
|
||||
# VICE's snapshot of that moment is cached in build/c64trace/.
|
||||
# * Each ride restores the snapshot, makes every demo slot ($50C1..
|
||||
# $50C4) the wanted screen, and when the screen and its recording are
|
||||
# loaded ($625F, the disk load is skipped) writes the sim's playback
|
||||
# buffer over $0902, zeroes the flame parity, wave index and walk
|
||||
# parity ($716C/$716E/$7161) that the title leaves behind -- the sim
|
||||
# starts its rides from a fresh game -- and sets the screens-played
|
||||
# count so that $6211's INC makes it the screen's place in the 24-hour
|
||||
# shift (A = 1 .. Y = 25), as stsim's rides have it.
|
||||
# * A finished wreck takes the real game's path ($6B52 -> $6B64: the cab
|
||||
# count drops and the cab respawns at $5F27) instead of the demo's
|
||||
# return to the title, as stsim's rides do. A ride ends when the cab
|
||||
# leaves the screen ($6BFB, end 1), the cabs run out ($6BD0, end 4),
|
||||
# the playback reaches the buffer's last pair (end "stream"), or after
|
||||
# <ticks> ticks.
|
||||
# * Every $5F40 (the main loop's top) the state is read and printed.
|
||||
#
|
||||
# Needs VICE's x64sc (VICE=path, else on PATH; VICE_DATA=its ROM folder
|
||||
# when x64sc cannot find its own), Xvfb, and the original disk image
|
||||
# (STAXI_D64, default stuff/SPACETAX.D64). Exits 2 when one is missing.
|
||||
# The disk and everything derived from it stay out of the repository.
|
||||
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import signal
|
||||
import socket
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
REPO = os.path.normpath(os.path.join(HERE, '..', '..'))
|
||||
CACHE = os.path.join(REPO, 'build', 'c64trace')
|
||||
D64 = os.environ.get('STAXI_D64', os.path.join(REPO, 'stuff', 'SPACETAX.D64'))
|
||||
LIFE_S = 900 # an emulator outlives no ride by more than this
|
||||
|
||||
# VICE binary monitor protocol (src/monitor/monitor_binary.c).
|
||||
STX = 0x02
|
||||
API_VERSION = 0x02
|
||||
CMD_MEM_GET = 0x01
|
||||
CMD_MEM_SET = 0x02
|
||||
CMD_CP_SET = 0x12
|
||||
CMD_CP_DELETE = 0x13
|
||||
CMD_REGS_SET = 0x32
|
||||
CMD_REGS_GET = 0x31
|
||||
CMD_DUMP = 0x41
|
||||
CMD_UNDUMP = 0x42
|
||||
CMD_KEYS = 0x72
|
||||
CMD_PING = 0x81
|
||||
CMD_BANKS = 0x82
|
||||
CMD_EXIT = 0xAA
|
||||
CMD_QUIT = 0xBB
|
||||
EV_CHECKPOINT = 0x11
|
||||
EV_JAM = 0x61
|
||||
EV_STOPPED = 0x62
|
||||
REG_PC = 0x03
|
||||
OP_EXEC = 0x04
|
||||
|
||||
# The game's addresses.
|
||||
INTRO_KEY_TEST = 0x0994 # the crack's "space pressed?" test
|
||||
INTRO_GO = 0x099E # ...and where it goes when it is
|
||||
DEMO_PICK = 0x50C6 # $5010's demo branch: a demo picks its screen
|
||||
DEMO_SLOTS = 0x50C1 # "HWTX", the demo rotation
|
||||
DEMO_LOAD = 0x625F # JSR $98C4: load the screen's recording
|
||||
DEMO_LOADED = 0x6262 # ...returned
|
||||
DEMO_BUFFER = 0x0902
|
||||
CARRIED = (0x716C, 0x716E, 0x7161)
|
||||
SCREENS_PLAYED = 0x7215
|
||||
TICK_TOP = 0x5F40
|
||||
CAB_LEFT = 0x6BFB
|
||||
WRECK_DONE = 0x6B52
|
||||
LOSE_CAB = 0x6B64
|
||||
PLAYER_OUT = 0x6BD0
|
||||
LAST_PAIR = 638 # the playback buffer's last (mask, count) pair
|
||||
END_EXIT = 1 # StTickResultE: ST_TICK_LEVEL_EXIT
|
||||
END_OUT = 4 # ST_TICK_PLAYER_OUT
|
||||
TRAINER_ROWS = 12 # cursor-downs from the first option to START GAME
|
||||
KEY_DOWN = '\x11'
|
||||
KEY_RETURN = '\x0d'
|
||||
KEY_WAIT_S = 0.3
|
||||
INTRO_WAIT_S = 6
|
||||
BOOT_WAIT_S = 300
|
||||
STOP_WAIT_S = 120
|
||||
|
||||
|
||||
class Monitor:
|
||||
"""A VICE binary-monitor connection."""
|
||||
|
||||
def __init__(self, port):
|
||||
self.sock = None
|
||||
for _ in range(200):
|
||||
try:
|
||||
self.sock = socket.create_connection(('127.0.0.1', port), timeout=2)
|
||||
break
|
||||
except OSError:
|
||||
time.sleep(0.1)
|
||||
if self.sock is None:
|
||||
raise RuntimeError('no VICE monitor on port %d' % port)
|
||||
self.sock.settimeout(None)
|
||||
self.reqId = 1
|
||||
self.events = []
|
||||
self.buf = b''
|
||||
|
||||
def recvExact(self, n):
|
||||
while len(self.buf) < n:
|
||||
chunk = self.sock.recv(1 << 20)
|
||||
if not chunk:
|
||||
raise EOFError('VICE closed the monitor')
|
||||
self.buf += chunk
|
||||
out, self.buf = self.buf[:n], self.buf[n:]
|
||||
return out
|
||||
|
||||
def packet(self):
|
||||
hdr = self.recvExact(12)
|
||||
length = struct.unpack_from('<I', hdr, 2)[0]
|
||||
return hdr[6], hdr[7], struct.unpack_from('<I', hdr, 8)[0], self.recvExact(length)
|
||||
|
||||
def cmd(self, ctype, body=b''):
|
||||
rid = self.reqId
|
||||
self.reqId += 1
|
||||
self.sock.sendall(struct.pack('<BBIIB', STX, API_VERSION, len(body), rid, ctype) + body)
|
||||
while True:
|
||||
rtype, err, r, b = self.packet()
|
||||
if r == rid:
|
||||
if err != 0:
|
||||
raise RuntimeError('monitor command %02X failed (%02X)' % (ctype, err))
|
||||
return b
|
||||
self.events.append((rtype, b))
|
||||
|
||||
def mem(self, start, end, bank):
|
||||
b = self.cmd(CMD_MEM_GET, struct.pack('<BHHBH', 0, start, end, 0, bank))
|
||||
return b[2:2 + struct.unpack_from('<H', b, 0)[0]]
|
||||
|
||||
def poke(self, start, data, bank):
|
||||
self.cmd(CMD_MEM_SET, struct.pack('<BHHBH', 0, start, start + len(data) - 1, 0, bank) + bytes(data))
|
||||
|
||||
def stopAt(self, addr):
|
||||
b = self.cmd(CMD_CP_SET, struct.pack('<HHBBBB', addr, addr, 1, 1, OP_EXEC, 0))
|
||||
return struct.unpack_from('<I', b, 0)[0]
|
||||
|
||||
def unstop(self, num):
|
||||
self.cmd(CMD_CP_DELETE, struct.pack('<I', num))
|
||||
|
||||
def pc(self):
|
||||
b = self.cmd(CMD_REGS_GET, b'\x00')
|
||||
off = 2
|
||||
for _ in range(struct.unpack_from('<H', b, 0)[0]):
|
||||
if b[off + 1] == REG_PC:
|
||||
return struct.unpack_from('<H', b, off + 2)[0]
|
||||
off += b[off] + 1
|
||||
raise RuntimeError('no PC in the register dump')
|
||||
|
||||
def setPc(self, value):
|
||||
self.cmd(CMD_REGS_SET, struct.pack('<BH', 0, 1) + struct.pack('<BBH', 3, REG_PC, value))
|
||||
|
||||
def banks(self):
|
||||
b = self.cmd(CMD_BANKS)
|
||||
out = {}
|
||||
off = 2
|
||||
for _ in range(struct.unpack_from('<H', b, 0)[0]):
|
||||
nameLen = b[off + 3]
|
||||
out[b[off + 4:off + 4 + nameLen].decode()] = struct.unpack_from('<H', b, off + 1)[0]
|
||||
off += b[off] + 1
|
||||
return out
|
||||
|
||||
def go(self):
|
||||
self.events = []
|
||||
self.cmd(CMD_EXIT)
|
||||
|
||||
def halt(self):
|
||||
self.cmd(CMD_PING)
|
||||
|
||||
def waitStop(self, timeout):
|
||||
self.sock.settimeout(timeout)
|
||||
try:
|
||||
while True:
|
||||
if self.events:
|
||||
rtype, b = self.events.pop(0)
|
||||
else:
|
||||
rtype, _err, _rid, b = self.packet()
|
||||
if rtype == EV_STOPPED:
|
||||
return struct.unpack_from('<H', b, 0)[0]
|
||||
if rtype == EV_JAM:
|
||||
raise RuntimeError('the C64 jammed at $%04X' % struct.unpack_from('<H', b, 0)[0])
|
||||
finally:
|
||||
self.sock.settimeout(None)
|
||||
|
||||
def keys(self, text):
|
||||
t = text.encode('latin-1')
|
||||
self.cmd(CMD_KEYS, bytes([len(t)]) + t)
|
||||
|
||||
def snapshotSave(self, path):
|
||||
p = path.encode()
|
||||
self.cmd(CMD_DUMP, bytes([0, 0, len(p)]) + p)
|
||||
|
||||
def snapshotLoad(self, path):
|
||||
p = path.encode()
|
||||
self.cmd(CMD_UNDUMP, bytes([len(p)]) + p)
|
||||
|
||||
|
||||
class Vice:
|
||||
"""x64sc on a private Xvfb display, driven through its monitor."""
|
||||
|
||||
def __init__(self, autostart=None):
|
||||
exe = os.environ.get('VICE') or shutil.which('x64sc')
|
||||
if not exe or not shutil.which('Xvfb'):
|
||||
raise FileNotFoundError('VICE (x64sc) or Xvfb not found')
|
||||
self.procs = []
|
||||
display = self.freeDisplay()
|
||||
# Each in a session of its own, so close() can take down the
|
||||
# timeout guard and what it runs together.
|
||||
self.procs.append(subprocess.Popen(['timeout', '-s', 'KILL', str(LIFE_S), 'Xvfb', display, '-screen', '0', '640x480x24'],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, start_new_session=True))
|
||||
os.makedirs(os.path.join(CACHE, 'home'), exist_ok=True)
|
||||
env = dict(os.environ, DISPLAY=display, XDG_CONFIG_HOME=os.path.join(CACHE, 'home'), XDG_CACHE_HOME=os.path.join(CACHE, 'home'))
|
||||
port = self.freePort()
|
||||
args = ['timeout', '-s', 'KILL', str(LIFE_S), exe, '-binarymonitor', '-binarymonitoraddress', 'ip4://127.0.0.1:%d' % port,
|
||||
'-sounddev', 'dummy', '-warp', '+confirmonexit']
|
||||
if os.environ.get('VICE_DATA'):
|
||||
args += ['-directory', os.environ['VICE_DATA']]
|
||||
if autostart:
|
||||
args += ['-autostart-warp', '-autostart', autostart]
|
||||
self.procs.append(subprocess.Popen(args, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, env=env, start_new_session=True))
|
||||
try:
|
||||
self.mon = Monitor(port)
|
||||
except BaseException:
|
||||
self.close()
|
||||
raise
|
||||
|
||||
@staticmethod
|
||||
def freePort():
|
||||
s = socket.socket()
|
||||
s.bind(('127.0.0.1', 0))
|
||||
port = s.getsockname()[1]
|
||||
s.close()
|
||||
return port
|
||||
|
||||
@staticmethod
|
||||
def freeDisplay():
|
||||
n = 100 + os.getpid() % 800
|
||||
while os.path.exists('/tmp/.X%d-lock' % n):
|
||||
n += 1
|
||||
return ':%d' % n
|
||||
|
||||
def close(self):
|
||||
try:
|
||||
self.mon.cmd(CMD_QUIT)
|
||||
except Exception:
|
||||
pass
|
||||
for p in reversed(self.procs):
|
||||
try:
|
||||
os.killpg(p.pid, signal.SIGKILL)
|
||||
except ProcessLookupError:
|
||||
pass
|
||||
p.wait()
|
||||
|
||||
|
||||
def fnv(data):
|
||||
h = 2166136261
|
||||
for b in data:
|
||||
h = ((h ^ b) * 16777619) & 0xFFFFFFFF
|
||||
return h
|
||||
|
||||
|
||||
def demoOffset(mon, ram):
|
||||
zp = mon.mem(0x003F, 0x0040, ram)
|
||||
return ((zp[1] << 8) | zp[0]) - DEMO_BUFFER
|
||||
|
||||
|
||||
def stateLine(mon, tick, ram, io, raw):
|
||||
"""stsim's printState, from the C64's memory."""
|
||||
scr = mon.mem(0x0400, 0x07E7, ram)
|
||||
chs = mon.mem(0x2800, 0x2FFF, ram)
|
||||
vic = mon.mem(0xD020, 0xD021, io)
|
||||
col = bytes(b & 0x0F for b in mon.mem(0xD800, 0xDBE7, io))
|
||||
g = mon.mem(0x7140, 0x71FF, ram)
|
||||
p = mon.mem(0x7D5A, 0x7D9F, ram)
|
||||
G = lambda a: g[a - 0x7140]
|
||||
P = lambda a: p[a - 0x7D5A]
|
||||
en = G(0x7196)
|
||||
msb = G(0x718D)
|
||||
if raw is not None:
|
||||
raw.write(scr + col + chs)
|
||||
# A hidden sprite's registers print as dashes, as stsim's do.
|
||||
sprx = ''
|
||||
for k in range(8):
|
||||
x = G(0x71A7 + 2 * k) | (0x100 if (msb >> k) & 1 else 0)
|
||||
sprx += '%03X%02X' % (x, G(0x71A8 + 2 * k)) if (en >> k) & 1 else '-----'
|
||||
if en & 2:
|
||||
spr1 = '"ptr1":"%02X","s1":"%02X%02X.%02X",' % (G(0x7198), G(0x7186), G(0x7176), G(0x717E))
|
||||
else:
|
||||
spr1 = '"ptr1":"--","s1":"--",'
|
||||
s = ('{"tick":%u,"x":"%02X%02X.%02X","y":"%02X.%02X","vx":"%04X","vy":"%04X",'
|
||||
'"pad":%u,"st":%u,"ph":%u,"in":"%02X","dir":"%02X","ptr0":"%02X",' % (
|
||||
tick, P(0x7D63), P(0x7D62), P(0x7D61), P(0x7D65), P(0x7D64),
|
||||
(G(0x714D) << 8) | G(0x714C), (G(0x714F) << 8) | G(0x714E),
|
||||
G(0x7150), G(0x7163), G(0x7164), G(0x7169), G(0x716A), G(0x7197)))
|
||||
s += spr1
|
||||
s += ('"en":"%02X","t4740":"%02X","off":%u,"rng":"%02X%02X","fuel":%u,"slots":%u,"asi":%u,"score":"' % (
|
||||
en, mon.mem(0x4740, 0x4740, ram)[0], demoOffset(mon, ram), G(0x7171), G(0x7172), G(0x71C9), G(0x715C), P(0x7D8E)))
|
||||
s += ''.join('%02X' % scr[0x3C2 + k] for k in range(7))
|
||||
s += '","fare":"' + ''.join('%02X' % scr[0x3E0 + k] for k in range(7))
|
||||
s += '","fuelc":"' + ''.join('%02X' % scr[0x3A6 + k] for k in range(12))
|
||||
s += '","sprx":"' + sprx
|
||||
s += '","scr":"%08X","col":"%08X","chr":"%08X","bd":"%02X","bg":"%02X"}' % (
|
||||
fnv(scr), fnv(col), fnv(chs), vic[0] & 0x0F, vic[1] & 0x0F)
|
||||
return s
|
||||
|
||||
|
||||
def snapshot():
|
||||
"""The cached VICE snapshot of the first attract demo picking its
|
||||
screen, made from the original disk on first use."""
|
||||
path = os.path.join(CACHE, 'attract.vsf')
|
||||
if os.path.exists(path):
|
||||
return path
|
||||
if not os.path.exists(D64):
|
||||
raise FileNotFoundError('no disk image at %s (STAXI_D64)' % D64)
|
||||
os.makedirs(CACHE, exist_ok=True)
|
||||
vice = Vice(autostart=D64)
|
||||
try:
|
||||
mon = vice.mon
|
||||
cp = mon.stopAt(INTRO_KEY_TEST)
|
||||
mon.go()
|
||||
mon.waitStop(BOOT_WAIT_S)
|
||||
mon.unstop(cp)
|
||||
mon.setPc(INTRO_GO)
|
||||
mon.go()
|
||||
time.sleep(INTRO_WAIT_S)
|
||||
mon.halt()
|
||||
for _ in range(TRAINER_ROWS):
|
||||
mon.keys(KEY_DOWN)
|
||||
mon.go()
|
||||
time.sleep(KEY_WAIT_S)
|
||||
mon.halt()
|
||||
mon.keys(KEY_RETURN)
|
||||
cp = mon.stopAt(DEMO_PICK)
|
||||
mon.go()
|
||||
mon.waitStop(BOOT_WAIT_S)
|
||||
mon.unstop(cp)
|
||||
mon.snapshotSave(path + '.part')
|
||||
finally:
|
||||
vice.close()
|
||||
os.replace(path + '.part', path)
|
||||
return path
|
||||
|
||||
|
||||
def ride(letter, ticks, demobuf, out, raw):
|
||||
vice = Vice()
|
||||
try:
|
||||
mon = vice.mon
|
||||
mon.snapshotLoad(snapshot())
|
||||
banks = mon.banks()
|
||||
ram = banks['ram']
|
||||
io = banks['io']
|
||||
if mon.pc() != DEMO_PICK:
|
||||
raise RuntimeError('the snapshot is not at $%04X' % DEMO_PICK)
|
||||
mon.poke(DEMO_SLOTS, bytes([ord(letter)] * 4), ram)
|
||||
cp = mon.stopAt(DEMO_LOAD)
|
||||
mon.go()
|
||||
mon.waitStop(STOP_WAIT_S)
|
||||
mon.unstop(cp)
|
||||
mon.poke(DEMO_BUFFER, demobuf, ram)
|
||||
for a in CARRIED:
|
||||
mon.poke(a, b'\x00', ram)
|
||||
mon.poke(SCREENS_PLAYED, bytes([ord(letter) - ord('A')]), ram)
|
||||
mon.setPc(DEMO_LOADED)
|
||||
for a in (TICK_TOP, CAB_LEFT, WRECK_DONE, PLAYER_OUT):
|
||||
mon.stopAt(a)
|
||||
tick = 0
|
||||
exited = False
|
||||
while tick < ticks:
|
||||
mon.go()
|
||||
pc = mon.waitStop(STOP_WAIT_S)
|
||||
if pc == TICK_TOP:
|
||||
if demoOffset(mon, ram) >= LAST_PAIR:
|
||||
out.write('{"tick":%u,"end":"stream"}\n' % (tick - 1))
|
||||
break
|
||||
out.write(stateLine(mon, tick, ram, io, raw) + '\n')
|
||||
tick += 1
|
||||
elif pc == CAB_LEFT:
|
||||
exited = True
|
||||
elif pc == WRECK_DONE:
|
||||
if exited:
|
||||
out.write('{"tick":%u,"end":%d}\n' % (tick - 1, END_EXIT))
|
||||
break
|
||||
mon.setPc(LOSE_CAB)
|
||||
elif pc == PLAYER_OUT:
|
||||
out.write('{"tick":%u,"end":%d}\n' % (tick - 1, END_OUT))
|
||||
break
|
||||
else:
|
||||
raise RuntimeError('unexpected stop at $%04X' % pc)
|
||||
finally:
|
||||
vice.close()
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) == 2 and sys.argv[1] == '--prepare':
|
||||
try:
|
||||
snapshot()
|
||||
except FileNotFoundError as e:
|
||||
print('c64trace: %s' % e, file=sys.stderr)
|
||||
return 2
|
||||
return 0
|
||||
if len(sys.argv) < 4:
|
||||
print('usage: c64trace.py <level letter> <ticks> <demobuf.json> [rawOut] | --prepare', file=sys.stderr)
|
||||
return 2
|
||||
letter = sys.argv[1]
|
||||
ticks = int(sys.argv[2], 0)
|
||||
demobuf = bytes.fromhex(json.load(open(sys.argv[3]))['demobuf'])
|
||||
raw = open(sys.argv[4], 'wb') if len(sys.argv) > 4 else None
|
||||
try:
|
||||
ride(letter, ticks, demobuf, sys.stdout, raw)
|
||||
except FileNotFoundError as e:
|
||||
print('c64trace: %s' % e, file=sys.stderr)
|
||||
return 2
|
||||
finally:
|
||||
if raw is not None:
|
||||
raw.close()
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
# sprites on the fly" hitch. Runs the same rides gate.sh does with the
|
||||
# stsim census (STSIM_CELS=1) and fails on any MISSING line.
|
||||
#
|
||||
# celGate.sh check every hook level
|
||||
# celGate.sh check every screen's ride
|
||||
set -u
|
||||
|
||||
here=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
|
|
|
|||
|
|
@ -1,79 +1,72 @@
|
|||
#!/usr/bin/env python3
|
||||
# Compare stsim output against a VICE trace segment.
|
||||
# compare.py <trace.jsonl> <startTick> <stsim-output.jsonl> [maxReports]
|
||||
import json, sys
|
||||
# Compare two ride traces in stsim's line format -- stsim's against the
|
||||
# real C64's (c64trace.py), or a golden against the current tree -- field
|
||||
# by field.
|
||||
#
|
||||
# compare.py <a.jsonl> <b.jsonl> [maxReports]
|
||||
# the first maxReports ticks that differ (default 12), with the
|
||||
# fields that differ, then how often and from when each field does
|
||||
# compare.py --picture <a.raw> <b.raw> <tick>
|
||||
# the screen cells, colour cells and glyphs that differ on one tick,
|
||||
# from the two machines' picture dumps (STSIM_RAW / c64trace's rawOut)
|
||||
import json
|
||||
import sys
|
||||
|
||||
trace = [json.loads(l) for l in open(sys.argv[1])]
|
||||
start = int(sys.argv[2])
|
||||
sim = [json.loads(l) for l in open(sys.argv[3])]
|
||||
maxRep = int(sys.argv[4]) if len(sys.argv) > 4 else 12
|
||||
SCREEN_CELLS = 1000
|
||||
SCREEN_COLS = 40
|
||||
CHARSET = 2048
|
||||
GLYPH_BYTES = 8
|
||||
RAW_BYTES = SCREEN_CELLS * 2 + CHARSET
|
||||
|
||||
def fromTrace(r):
|
||||
g = bytes.fromhex(r['g7140'])
|
||||
pos = bytes.fromhex(r['pos'])
|
||||
hud = bytes.fromhex(r['hud']) # 0x0799 .. 0x07E6
|
||||
def G(a): return g[a - 0x7140]
|
||||
def H(a): return hud[a - 0x0799]
|
||||
d = {}
|
||||
d['x'] = '%02X%02X.%02X' % (pos[9], pos[8], pos[7]) # 7D63 7D62 7D61
|
||||
d['y'] = '%02X.%02X' % (pos[11], pos[10])
|
||||
d['vx'] = '%02X%02X' % (G(0x714D), G(0x714C))
|
||||
d['vy'] = '%02X%02X' % (G(0x714F), G(0x714E))
|
||||
d['pad'] = G(0x7150)
|
||||
d['st'] = G(0x7163)
|
||||
d['ph'] = G(0x7164)
|
||||
d['in'] = '%02X' % G(0x7169)
|
||||
d['dir'] = '%02X' % G(0x716A)
|
||||
d['ptr0'] = '%02X' % G(0x7197)
|
||||
d['ptr1'] = '%02X' % G(0x7198)
|
||||
d['s1'] = '%02X%02X.%02X' % (G(0x7186), G(0x7176), G(0x717E))
|
||||
d['en'] = '%02X' % G(0x7196)
|
||||
d['t4740']= r['t4740'].upper()
|
||||
d['rng'] = '%02X%02X' % (G(0x7171), G(0x7172))
|
||||
d['fuel'] = G(0x71C9)
|
||||
d['slots']= G(0x715C)
|
||||
d['asi'] = pos[0x7D8E - 0x7D5A] if len(pos) > 0x7D8E - 0x7D5A else -1
|
||||
d['score']= ''.join('%02X' % H(0x07C2 + k) for k in range(7))
|
||||
d['fare'] = ''.join('%02X' % H(0x07E0 + k) for k in range(7))
|
||||
d['fuelc']= ''.join('%02X' % H(0x07A6 + k) for k in range(12))
|
||||
# sprite shadow positions $71A7.. interleaved X,Y for 8 sprites + msb $718D
|
||||
msb = G(0x718D)
|
||||
sx = ''
|
||||
for k in range(8):
|
||||
x = G(0x71A7 + 2*k) | (0x100 if (msb >> k) & 1 else 0)
|
||||
sx += '%03X%02X' % (x, G(0x71A8 + 2*k))
|
||||
d['sprx'] = sx
|
||||
return d
|
||||
|
||||
fields = ['x','y','vx','vy','pad','st','ph','in','dir','ptr0','ptr1','s1','en','t4740','rng','fuel','slots','score','fare','fuelc','sprx']
|
||||
reports = 0
|
||||
firstBad = None
|
||||
for s in sim:
|
||||
if 'end' in s:
|
||||
print('sim ended at tick', s['tick'], 'result', s['end'])
|
||||
break
|
||||
t = s['tick']
|
||||
if start + t >= len(trace):
|
||||
print('trace exhausted at sim tick', t); break
|
||||
r = fromTrace(trace[start + t])
|
||||
# Disabled sprites keep stale shadow values on the C64; only compare
|
||||
# positions/pointers of enabled ones.
|
||||
en = int(r['en'], 16)
|
||||
def spr(sx):
|
||||
return ''.join(sx[k*5:k*5+5] if (en >> k) & 1 else '-----' for k in range(8))
|
||||
s2 = dict(s); r2 = dict(r)
|
||||
s2['sprx'] = spr(s['sprx']); r2['sprx'] = spr(r['sprx'])
|
||||
if not (en & 2):
|
||||
s2['ptr1'] = r2['ptr1'] = '--'; s2['s1'] = r2['s1'] = '--'
|
||||
bad = [f for f in fields if f in s2 and str(s2[f]) != str(r2[f])]
|
||||
s = s2; r = r2
|
||||
if bad:
|
||||
if firstBad is None:
|
||||
firstBad = t
|
||||
if reports < maxRep:
|
||||
print(f'tick {t}: ' + ' '.join(f'{f}: sim={s[f]} c64={r[f]}' for f in bad))
|
||||
def traces(pathA, pathB, maxReports):
|
||||
a = [json.loads(line) for line in open(pathA)]
|
||||
b = [json.loads(line) for line in open(pathB)]
|
||||
counts = {}
|
||||
first = {}
|
||||
reports = 0
|
||||
for i in range(min(len(a), len(b))):
|
||||
x, y = a[i], b[i]
|
||||
fields = [k for k in x if x.get(k) != y.get(k)] + [k for k in y if k not in x]
|
||||
for k in fields:
|
||||
counts[k] = counts.get(k, 0) + 1
|
||||
first.setdefault(k, i)
|
||||
if fields and reports < maxReports:
|
||||
print('line %d: ' % i + ' '.join('%s: %s vs %s' % (k, x.get(k), y.get(k)) for k in fields))
|
||||
reports += 1
|
||||
if firstBad is None:
|
||||
print('ALL', len(sim), 'ticks match')
|
||||
else:
|
||||
print('first mismatch at tick', firstBad)
|
||||
if len(a) != len(b):
|
||||
print('lengths differ: %d vs %d lines' % (len(a), len(b)))
|
||||
if not counts:
|
||||
print('every common line matches' if len(a) != len(b) else 'identical')
|
||||
return
|
||||
print('fields that differ (lines, first line): ' + ', '.join('%s %d @%d' % (k, counts[k], first[k]) for k in sorted(counts, key=first.get)))
|
||||
|
||||
|
||||
def picture(pathA, pathB, tick):
|
||||
a = open(pathA, 'rb').read()[tick * RAW_BYTES:(tick + 1) * RAW_BYTES]
|
||||
b = open(pathB, 'rb').read()[tick * RAW_BYTES:(tick + 1) * RAW_BYTES]
|
||||
for i in range(SCREEN_CELLS):
|
||||
if a[i] != b[i]:
|
||||
print('screen row %d col %d: %02X vs %02X' % (i // SCREEN_COLS, i % SCREEN_COLS, a[i], b[i]))
|
||||
for i in range(SCREEN_CELLS):
|
||||
if a[SCREEN_CELLS + i] != b[SCREEN_CELLS + i]:
|
||||
print('colour row %d col %d: %X vs %X' % (i // SCREEN_COLS, i % SCREEN_COLS, a[SCREEN_CELLS + i], b[SCREEN_CELLS + i]))
|
||||
for ch in range(CHARSET // GLYPH_BYTES):
|
||||
o = SCREEN_CELLS * 2 + ch * GLYPH_BYTES
|
||||
if a[o:o + GLYPH_BYTES] != b[o:o + GLYPH_BYTES]:
|
||||
print('glyph %02X: %s vs %s' % (ch, a[o:o + GLYPH_BYTES].hex(), b[o:o + GLYPH_BYTES].hex()))
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) == 5 and sys.argv[1] == '--picture':
|
||||
picture(sys.argv[2], sys.argv[3], int(sys.argv[4]))
|
||||
return 0
|
||||
if len(sys.argv) not in (3, 4):
|
||||
print('usage: compare.py <a.jsonl> <b.jsonl> [maxReports] | --picture <a.raw> <b.raw> <tick>', file=sys.stderr)
|
||||
return 2
|
||||
traces(sys.argv[1], sys.argv[2], int(sys.argv[3]) if len(sys.argv) == 4 else 12)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
|
|
|
|||
|
|
@ -1,80 +1,155 @@
|
|||
#!/usr/bin/env bash
|
||||
# Behaviour gate for the Space Taxi simulation.
|
||||
# Behaviour gate for the Space Taxi simulation: every screen's ride --
|
||||
# A..X and the 25th, Y -- checked against the real C64.
|
||||
#
|
||||
# Runs every hook level through stsim and compares the per-tick trace
|
||||
# against a golden capture. A refactor that changes nothing observable
|
||||
# leaves all seventeen traces byte-identical; anything else is a regression.
|
||||
# stsim rides a screen for up to 4000 ticks and prints one line of state
|
||||
# a tick: the cab, the fare machine, the demo playback, the RNG, the HUD,
|
||||
# the sprites shown and hashes of screen RAM, colour RAM and the charset.
|
||||
# c64trace.py rides the same input on the original game in VICE and
|
||||
# prints the same line from the C64's memory. gateHashes.txt holds the
|
||||
# md5 of each C64 trace, so `check` -- which needs no emulator -- is a
|
||||
# byte-for-byte comparison with the real machine.
|
||||
#
|
||||
# H, W, T and X ride the C64's own attract recordings (the ones checked
|
||||
# against VICE traces). The other thirteen ride synthesised fixtures from
|
||||
# gateStreams.h -- a regression gate only, not evidence of C64 fidelity.
|
||||
# H, W, T and X ride the C64's own attract recordings; the other screens
|
||||
# ride synthesised streams from gateStreams.h (tools/stsim/recorder).
|
||||
#
|
||||
# gate.sh capture [dir] record goldens from the CURRENT tree
|
||||
# gate.sh check [dir] compare the current tree against them, or, with
|
||||
# none captured, against gateHashes.txt (the md5
|
||||
# of each golden trace, the committed reference)
|
||||
# gate.sh hashes print one md5 per level (compact reference)
|
||||
# gate.sh check [dir] every ride's md5 against gateHashes.txt; a ride
|
||||
# that differs is diffed against its golden in dir,
|
||||
# if one was captured, to show where
|
||||
# gate.sh c64 [L...] ride the C64 too (VICE, c64trace.py) and compare
|
||||
# the two traces byte for byte, showing the first
|
||||
# fields that differ; every screen by default
|
||||
# gate.sh c64hashes the C64 traces' md5s in gateHashes.txt's form --
|
||||
# the only way gateHashes.txt is made
|
||||
# gate.sh capture [dir] record goldens from the current tree
|
||||
# gate.sh hashes print the current tree's md5 per screen
|
||||
#
|
||||
# Capture from a tree you trust, refactor, then check. The rides run on the
|
||||
# game's level bake, so build the game first (gateLib.sh finds it).
|
||||
# The rides run on the game's level bake, so build the game first
|
||||
# (gateLib.sh finds it). The c64 modes keep their traces in
|
||||
# build/c64trace/ and run C64_JOBS rides at once (default 4); they exit 2
|
||||
# when VICE or the original disk is missing (see c64trace.py).
|
||||
set -u
|
||||
|
||||
here=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
mode=${1:-check}
|
||||
dir=${2:-$here/golden}
|
||||
source "$here/gateLib.sh"
|
||||
traces=$here/../../build/c64trace/traces
|
||||
|
||||
# simTrace L NN FILE: the current tree's ride.
|
||||
simTrace() {
|
||||
"$here/stsim" "$levels/level$2.dat" "$1" "$ticks" > "$3" 2>/dev/null
|
||||
}
|
||||
|
||||
# c64Ride L NN: ride screen L on the C64 into $traces/L.jsonl and compare
|
||||
# it with stsim's; prints one verdict line, returns 1 on a difference. A
|
||||
# ride that could not be traced leaves $traces/L.failed behind.
|
||||
c64Ride() {
|
||||
local l=$1 n=$2
|
||||
rm -f "$traces/$l.failed"
|
||||
STSIM_DEMOBUF=1 "$here/stsim" "$levels/level$n.dat" "$l" 0 > "$traces/$l.buf.json"
|
||||
simTrace "$l" "$n" "$traces/$l.sim.jsonl"
|
||||
if ! timeout -s KILL 900 python3 "$here/c64trace.py" "$l" "$ticks" "$traces/$l.buf.json" > "$traces/$l.jsonl"; then
|
||||
echo " $l: *** the C64 ride failed ***"
|
||||
touch "$traces/$l.failed"
|
||||
return 1
|
||||
fi
|
||||
if cmp -s "$traces/$l.sim.jsonl" "$traces/$l.jsonl"; then
|
||||
printf ' %s: IDENTICAL to the C64 (%s lines)\n' "$l" "$(wc -l < "$traces/$l.jsonl")"
|
||||
return 0
|
||||
fi
|
||||
echo " $l: *** DIFFERS from the C64 ***"
|
||||
python3 "$here/compare.py" "$traces/$l.sim.jsonl" "$traces/$l.jsonl" 3 | sed 's/^/ /'
|
||||
return 1
|
||||
}
|
||||
|
||||
# c64All [L...]: every named screen (all of them by default), C64_JOBS at
|
||||
# a time.
|
||||
c64All() {
|
||||
local want=" ${*:-} " fail=0 p l n
|
||||
python3 "$here/c64trace.py" --prepare || exit $?
|
||||
mkdir -p "$traces"
|
||||
for p in $pairs; do
|
||||
l=${p%%:*}; n=${p##*:}
|
||||
if [ -n "${*:-}" ] && [[ "$want" != *" $l "* ]]; then
|
||||
continue
|
||||
fi
|
||||
while [ "$(jobs -rp | wc -l)" -ge "${C64_JOBS:-4}" ]; do
|
||||
wait -n || fail=1
|
||||
done
|
||||
c64Ride "$l" "$n" &
|
||||
done
|
||||
while [ "$(jobs -rp | wc -l)" -gt 0 ]; do
|
||||
wait -n || fail=1
|
||||
done
|
||||
return $fail
|
||||
}
|
||||
|
||||
case "$mode" in
|
||||
capture)
|
||||
dir=${2:-$here/golden}
|
||||
mkdir -p "$dir"
|
||||
for p in $pairs; do
|
||||
l=${p%%:*}; n=${p##*:}
|
||||
"$here/stsim" "$levels/level$n.dat" "$l" "$ticks" > "$dir/demo$l.jsonl" 2>/dev/null
|
||||
printf ' %s: %s ticks\n' "$l" "$(wc -l < "$dir/demo$l.jsonl")"
|
||||
simTrace "$l" "$n" "$dir/demo$l.jsonl"
|
||||
printf ' %s: %s lines\n' "$l" "$(wc -l < "$dir/demo$l.jsonl")"
|
||||
done
|
||||
echo "gate: captured to $dir"
|
||||
;;
|
||||
check)
|
||||
dir=${2:-$here/golden}
|
||||
fail=0
|
||||
for p in $pairs; do
|
||||
l=${p%%:*}; n=${p##*:}
|
||||
got=$(mktemp)
|
||||
"$here/stsim" "$levels/level$n.dat" "$l" "$ticks" > "$got" 2>/dev/null
|
||||
if [ -f "$dir/demo$l.jsonl" ]; then
|
||||
if cmp -s "$dir/demo$l.jsonl" "$got"; then
|
||||
printf ' %s: IDENTICAL (%s ticks)\n' "$l" "$(wc -l < "$got")"
|
||||
else
|
||||
echo " $l: *** DIFFERS ***"
|
||||
diff "$dir/demo$l.jsonl" "$got" | head -3
|
||||
fail=1
|
||||
fi
|
||||
simTrace "$l" "$n" "$got"
|
||||
want=$(awk -v l="$l" '$1 == l { print $2 }' "$here/gateHashes.txt")
|
||||
have=$(md5sum < "$got" | cut -d' ' -f1)
|
||||
if [ "$have" = "$want" ]; then
|
||||
printf ' %s: IDENTICAL to the C64 (%s lines, md5 as gateHashes.txt)\n' "$l" "$(wc -l < "$got")"
|
||||
else
|
||||
want=$(awk -v l="$l" '$1 == l { print $2 }' "$here/gateHashes.txt")
|
||||
have=$(md5sum < "$got" | cut -d' ' -f1)
|
||||
if [ "$have" = "$want" ]; then
|
||||
printf ' %s: IDENTICAL (%s ticks, md5 as gateHashes.txt)\n' "$l" "$(wc -l < "$got")"
|
||||
else
|
||||
echo " $l: *** DIFFERS *** (md5 $have, gateHashes.txt ${want:-has none}; capture goldens from a trusted tree to see where)"
|
||||
fail=1
|
||||
echo " $l: *** DIFFERS from the C64 *** (md5 $have, gateHashes.txt ${want:-has none}; gate.sh c64 $l shows where)"
|
||||
if [ -f "$dir/demo$l.jsonl" ]; then
|
||||
python3 "$here/compare.py" "$dir/demo$l.jsonl" "$got" 3 | sed 's/^/ golden vs now: /'
|
||||
fi
|
||||
fail=1
|
||||
fi
|
||||
rm -f "$got"
|
||||
done
|
||||
[ $fail -eq 0 ] && echo "gate: PASS" || echo "gate: FAIL"
|
||||
# The cel census rides the same recordings: a cel a ride displays that
|
||||
# its manifest does not bake is a mid-ride interpreted sprite.
|
||||
# The cel census rides the same streams: a cel a ride displays that its
|
||||
# manifest does not bake is a mid-ride interpreted sprite.
|
||||
"$here/celGate.sh" || fail=1
|
||||
exit $fail
|
||||
;;
|
||||
c64)
|
||||
shift
|
||||
c64All "$@"
|
||||
fail=$?
|
||||
[ $fail -eq 0 ] && echo "gate c64: PASS" || echo "gate c64: FAIL"
|
||||
exit $fail
|
||||
;;
|
||||
c64hashes)
|
||||
c64All >&2
|
||||
if compgen -G "$traces/*.failed" > /dev/null; then
|
||||
echo "gate c64hashes: a C64 ride failed; no hashes" >&2
|
||||
exit 1
|
||||
fi
|
||||
for p in $pairs; do
|
||||
l=${p%%:*}
|
||||
printf '%s %s\n' "$l" "$(md5sum < "$traces/$l.jsonl" | cut -d' ' -f1)"
|
||||
done
|
||||
;;
|
||||
hashes)
|
||||
for p in $pairs; do
|
||||
l=${p%%:*}; n=${p##*:}
|
||||
h=$("$here/stsim" "$levels/level$n.dat" "$l" "$ticks" 2>/dev/null | md5sum | cut -d' ' -f1)
|
||||
printf '%s %s\n' "$l" "$h"
|
||||
got=$(mktemp)
|
||||
simTrace "$l" "$n" "$got"
|
||||
printf '%s %s\n' "$l" "$(md5sum < "$got" | cut -d' ' -f1)"
|
||||
rm -f "$got"
|
||||
done
|
||||
;;
|
||||
*)
|
||||
sed -n '2,20p' "$0"
|
||||
sed -n '2,33p' "$0"
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
|
|
|||
|
|
@ -1,21 +1,31 @@
|
|||
# Space Taxi behaviour gate -- md5 of each level's 4000-tick trace.
|
||||
# Regenerate with: tools/stsim/gate.sh hashes
|
||||
# H/W/T/X ride the C64's attract recordings; the rest ride
|
||||
# synthesised fixtures from gateStreams.h (regression gate only).
|
||||
H 760501b0e48f3922bfb0f23fd2831935
|
||||
T c0b3f9e9a74e0d526703cf9b1875557b
|
||||
W 92226455989e8326577a6ea20df8c8ea
|
||||
X 87251d616a1be6e0c9c9317987b0fe20
|
||||
G 73530651372c60042f1f46b5c0cbada0
|
||||
I 7fe4415429d2b72a50884424269cb685
|
||||
K 567be2ed37219356a055866459971345
|
||||
O 02455280933e915e57803a4059aaefd9
|
||||
Q a7732866d54ed06d52e5dc7406b5b229
|
||||
R 066a9be80107446893b4571d247ee5cc
|
||||
U 02dd2f26245442ab016ef02e10fde611
|
||||
V cc2cb13c5e393d07fbece9c0c465e387
|
||||
E 21ff29d69d8318bcb2e870b9602e910e
|
||||
J 4c22a7515949b1701ed4fedabb7823dd
|
||||
L a0383cbde2800bb09353df15214934ef
|
||||
P f345d0adc9e011662d582c4277ee663e
|
||||
S daa3cd12320e32d3e9b6b97b19b0a6d0
|
||||
# Space Taxi behaviour gate -- the md5 of each screen's ride as the REAL
|
||||
# C64 traced it: the original game in VICE, ridden by c64trace.py, one
|
||||
# line of state per tick (stsim prints the same line from the port).
|
||||
# `gate.sh check` compares the port's rides against these, so a pass means
|
||||
# byte-identical to the C64 on all 25 screens.
|
||||
# Regenerate (needs VICE and the original disk): tools/stsim/gate.sh c64hashes
|
||||
A e5cc75d60b41a3fb6ad70a8475a21a7a
|
||||
B 4348bfdde94d3fc6d28d1ee7ca4e73bb
|
||||
C 6669b71a3cdeebcbab62e1a29eda5faa
|
||||
D 2df4ea48591d8fc7bafe20638a1eaf00
|
||||
E 7595460b2296bd863b740060547bdc3d
|
||||
F 96d1f6589336430399e4fd04941b079d
|
||||
G 9f07d6ed41c634ba8acf7d0162fca172
|
||||
H 5682e029bbf147d429bba1f774deca13
|
||||
I ecb6dbe5b9765bbf3c0bf4638dc9f72f
|
||||
J 171cfb357d771abd7db4b6d3ac3b4402
|
||||
K d7bc29828c42cda46f66485d2252bb4c
|
||||
L 05a2098c950e76c78799227fa748b1d3
|
||||
M 77fe51edf453736f7000f91ad9c52d19
|
||||
N 1e7fd970699f408b7e6ea246882fa778
|
||||
O 0af4d661248ae1e0bf57eaa24973051a
|
||||
P 740e45a7fe08cfde34d9ee6b6d35c116
|
||||
Q d4da1893385a16b09f8ea383e7d2c4ce
|
||||
R 5089cfb129fc29b3c6ebffa2dd2f6923
|
||||
S 561f7a0c757e82118331dbc8dd582214
|
||||
T 7223a56296bd4f0023fcc3073afaf955
|
||||
U b4178bc4c5aef13d4e87a006ed0fab75
|
||||
V 69976c2e32a03448d8f3adf7a2fe2df8
|
||||
W 5c0b20833ede9e848660bb98a67be208
|
||||
X f3182a3975c023b961c1eee85bcab687
|
||||
Y 67f5ace2d1688717de126b313c436149
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
# Sourced by gate.sh and celGate.sh, which set $here first: the rides both
|
||||
# gates replay, and the levels they replay them on.
|
||||
|
||||
# Every hook level as letter:level file number, and how long each ride runs.
|
||||
# Every screen as letter:level file number -- A..X and the 25th, Y -- and
|
||||
# how long a ride may run.
|
||||
ticks=4000
|
||||
pairs="H:08 T:20 W:23 X:24 G:07 I:09 K:11 O:15 Q:17 R:18 U:21 V:22 E:05 J:10 L:12 P:16 S:19"
|
||||
pairs="A:01 B:02 C:03 D:04 E:05 F:06 G:07 H:08 I:09 J:10 K:11 L:12 M:13 N:14 O:15 P:16 Q:17 R:18 S:19 T:20 U:21 V:22 W:23 X:24 Y:25"
|
||||
|
||||
# The game's own level bake (program.mk). It is the same STL4 bytes for every
|
||||
# The game's own level bake (program.mk). It is the same STL5 bytes for every
|
||||
# target, so whichever target was built first will do; STSIM_LEVELS names
|
||||
# another folder.
|
||||
levels=${STSIM_LEVELS:-}
|
||||
if [ -z "$levels" ]; then
|
||||
for port in iigs dos amiga atarist x68000; do
|
||||
if [ -f "$here/../../build/generated/$port/levels/level01.dat" ]; then
|
||||
levels=$(cd "$here/../../build/generated/$port/levels" && pwd)
|
||||
for bake in "$here"/../../build/generated/*/levels; do
|
||||
if [ -f "$bake/level25.dat" ]; then
|
||||
levels=$(cd "$bake" && pwd)
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
|
|
|||
|
|
@ -1,89 +1,313 @@
|
|||
// Synthesised demo input streams -- a TEST FIXTURE, not game content.
|
||||
//
|
||||
// The C64 ships attract recordings for four screens only (H, W, T and X);
|
||||
// those live in stDemoStreams.h and are the streams
|
||||
// checked against VICE traces. The thirteen streams below were searched for
|
||||
// by tools/stsim's recorder against this port's own simulation, scored on
|
||||
// how long the cab survives and how much of the level's hook state, fare
|
||||
// machine and screen animation the ride reaches.
|
||||
// The C64 ships attract recordings for four screens only (H, W, T and X;
|
||||
// stDemoStreams.h). The streams below cover the other twenty-one: each was
|
||||
// searched for by tools/stsim's recorder against this port's simulation,
|
||||
// scored on how long the cab survives and how much of the level's hook
|
||||
// state, fare machine and screen animation the ride reaches.
|
||||
//
|
||||
// So they prove one thing and not another: they are a REGRESSION gate --
|
||||
// refactor the hooks and the tick-by-tick trace must not move -- but they
|
||||
// are NOT evidence that a hook matches the original, because no C64 trace
|
||||
// exists for these screens. Treat a change to these bytes as invalidating
|
||||
// the goldens, and never put them in the attract rotation.
|
||||
// Every ride, these and the four recordings alike, is then ridden on the
|
||||
// original game in VICE (gate.sh c64, c64trace.py) and must trace byte
|
||||
// for byte as the simulation does; gateHashes.txt holds the C64's traces.
|
||||
// A change to these bytes therefore needs `gate.sh c64hashes` on a
|
||||
// machine with VICE. Never put them in the attract rotation.
|
||||
|
||||
#ifndef ST_GATE_STREAMS_H
|
||||
#define ST_GATE_STREAMS_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// level G -- 1400 ticks, 5 hook bytes, 10 fare stages, 256 pictures, 13 landings
|
||||
// regenerate: tools/stsim/recorder levels/level??.dat G 20260921
|
||||
// level A -- 1397 ticks, 0 hook bytes, 8 fare stages, 256 pictures, 8 landings, left through the hatch
|
||||
// regenerate: tools/stsim/recorder levels/level01.dat A 20261006 60 2000
|
||||
static const uint8_t kGateStreamA[320] = {
|
||||
0x88, 0x06, 0x82, 0x0B, 0x84, 0x0E, 0x90, 0x06, 0x84, 0x0B, 0x82, 0x08,
|
||||
0x88, 0x0C, 0x84, 0x0B, 0x81, 0x0E, 0x90, 0x0E, 0x90, 0x0E, 0x84, 0x0D,
|
||||
0x81, 0x0C, 0x84, 0x01, 0x88, 0x01, 0x82, 0x03, 0x81, 0x01, 0x88, 0x0C,
|
||||
0x81, 0x01, 0x88, 0x0C, 0x90, 0x0D, 0x81, 0x01, 0x81, 0x0E, 0x81, 0x0D,
|
||||
0x81, 0x0C, 0x82, 0x09, 0x84, 0x09, 0x90, 0x0E, 0x81, 0x01, 0x81, 0x04,
|
||||
0x82, 0x0B, 0x81, 0x0D, 0x81, 0x03, 0x88, 0x01, 0x84, 0x06, 0x82, 0x0D,
|
||||
0x81, 0x0A, 0x81, 0x08, 0x90, 0x0C, 0x90, 0x0D, 0x84, 0x0E, 0x84, 0x07,
|
||||
0x90, 0x0B, 0x88, 0x0C, 0x82, 0x08, 0x81, 0x01, 0x81, 0x06, 0x90, 0x0D,
|
||||
0x84, 0x02, 0x84, 0x0D, 0x88, 0x05, 0x82, 0x09, 0x81, 0x01, 0x88, 0x04,
|
||||
0x84, 0x09, 0x88, 0x01, 0x81, 0x05, 0x88, 0x06, 0x81, 0x03, 0x81, 0x0A,
|
||||
0x84, 0x06, 0x81, 0x05, 0x82, 0x01, 0x84, 0x0E, 0x84, 0x09, 0x90, 0x02,
|
||||
0x81, 0x03, 0x82, 0x0A, 0x84, 0x06, 0x84, 0x0E, 0x82, 0x07, 0x88, 0x04,
|
||||
0x90, 0x0D, 0x88, 0x09, 0x81, 0x04, 0x88, 0x0E, 0x81, 0x08, 0x88, 0x0C,
|
||||
0x84, 0x0D, 0x81, 0x08, 0x84, 0x09, 0x82, 0x06, 0x88, 0x0E, 0x84, 0x06,
|
||||
0x81, 0x04, 0x88, 0x0C, 0x84, 0x03, 0x88, 0x0E, 0x84, 0x09, 0x90, 0x0E,
|
||||
0x82, 0x0E, 0x88, 0x0C, 0x82, 0x06, 0x81, 0x0C, 0x90, 0x0C, 0x81, 0x07,
|
||||
0x82, 0x0E, 0x81, 0x04, 0x81, 0x07, 0x84, 0x0D, 0x84, 0x07, 0x81, 0x0C,
|
||||
0x84, 0x03, 0x81, 0x05, 0x82, 0x0E, 0x90, 0x04, 0x90, 0x0A, 0x90, 0x0E,
|
||||
0x81, 0x0D, 0x82, 0x08, 0x82, 0x0D, 0x88, 0x0D, 0x81, 0x09, 0x81, 0x0B,
|
||||
0x81, 0x0C, 0x84, 0x0E, 0x81, 0x0D, 0x82, 0x0C, 0x82, 0x08, 0x90, 0x08,
|
||||
0x81, 0x0B, 0x82, 0x09, 0x88, 0x0A, 0x88, 0x02, 0x82, 0x03, 0x90, 0x08,
|
||||
0x81, 0x08, 0x82, 0x08, 0x81, 0x0A, 0x81, 0x06, 0x88, 0x06, 0x81, 0x04,
|
||||
0x84, 0x01, 0x90, 0x0E, 0x81, 0x07, 0x84, 0x0D, 0x82, 0x08, 0x90, 0x06,
|
||||
0x82, 0x0E, 0x90, 0x0D, 0x81, 0x03, 0x90, 0x0B, 0x82, 0x0A, 0x88, 0x0A,
|
||||
0x84, 0x0C, 0x81, 0x03, 0x81, 0x01, 0x81, 0x0B, 0x88, 0x0E, 0x81, 0x07,
|
||||
0x82, 0x04, 0x85, 0x02, 0x81, 0x0D, 0x81, 0x08, 0x84, 0x0D, 0x82, 0x04,
|
||||
0x82, 0x0A, 0x81, 0x0C, 0x81, 0x07, 0x88, 0x0A,
|
||||
};
|
||||
|
||||
// level B -- 1400 ticks, 0 hook bytes, 10 fare stages, 256 pictures, 6 landings
|
||||
// regenerate: tools/stsim/recorder levels/level02.dat B 20261006 60 2000
|
||||
static const uint8_t kGateStreamB[320] = {
|
||||
0x81, 0x04, 0x88, 0x02, 0x89, 0x01, 0x89, 0x02, 0x81, 0x01, 0x88, 0x05,
|
||||
0x81, 0x04, 0x84, 0x0D, 0x84, 0x06, 0x82, 0x0C, 0x84, 0x08, 0x88, 0x07,
|
||||
0x88, 0x06, 0x81, 0x0B, 0x88, 0x0E, 0x82, 0x07, 0x88, 0x0D, 0x81, 0x07,
|
||||
0x84, 0x06, 0x81, 0x03, 0x82, 0x0C, 0x88, 0x02, 0x84, 0x04, 0x84, 0x0E,
|
||||
0x81, 0x02, 0x82, 0x09, 0x80, 0x0B, 0x84, 0x09, 0x88, 0x07, 0x90, 0x0B,
|
||||
0x81, 0x0C, 0x81, 0x0A, 0x90, 0x0B, 0x81, 0x01, 0x81, 0x07, 0x92, 0x09,
|
||||
0x81, 0x04, 0x82, 0x09, 0x81, 0x0E, 0x88, 0x09, 0x88, 0x0D, 0x81, 0x02,
|
||||
0x82, 0x03, 0x84, 0x0A, 0x82, 0x05, 0x90, 0x0C, 0x81, 0x07, 0x81, 0x0D,
|
||||
0x84, 0x05, 0x90, 0x03, 0x88, 0x0D, 0x82, 0x0C, 0x88, 0x07, 0x90, 0x0E,
|
||||
0x81, 0x05, 0x90, 0x02, 0x88, 0x06, 0x82, 0x0B, 0x81, 0x05, 0x88, 0x09,
|
||||
0x82, 0x06, 0x84, 0x07, 0x84, 0x01, 0x88, 0x07, 0x81, 0x0B, 0x90, 0x0C,
|
||||
0x82, 0x0D, 0x88, 0x07, 0x81, 0x05, 0x88, 0x0C, 0x84, 0x0E, 0x90, 0x0A,
|
||||
0x84, 0x08, 0x90, 0x09, 0x81, 0x07, 0x84, 0x03, 0x82, 0x02, 0x81, 0x03,
|
||||
0x88, 0x06, 0x90, 0x08, 0x90, 0x01, 0x81, 0x03, 0x82, 0x0C, 0x81, 0x05,
|
||||
0x88, 0x05, 0x84, 0x07, 0x84, 0x08, 0x90, 0x09, 0x88, 0x0D, 0x81, 0x0D,
|
||||
0x90, 0x0C, 0x88, 0x02, 0x88, 0x0E, 0x81, 0x04, 0x81, 0x0E, 0x82, 0x0C,
|
||||
0x82, 0x0A, 0x90, 0x07, 0x81, 0x06, 0x82, 0x0E, 0x82, 0x01, 0x81, 0x07,
|
||||
0x90, 0x0D, 0x90, 0x04, 0x81, 0x09, 0x84, 0x0D, 0x84, 0x08, 0x81, 0x07,
|
||||
0x88, 0x03, 0x84, 0x01, 0x81, 0x01, 0x88, 0x07, 0x88, 0x0C, 0x82, 0x09,
|
||||
0x81, 0x03, 0x81, 0x03, 0x88, 0x0D, 0x91, 0x01, 0x84, 0x0C, 0x82, 0x08,
|
||||
0x81, 0x03, 0x90, 0x0C, 0x88, 0x0D, 0x82, 0x06, 0x82, 0x01, 0x81, 0x07,
|
||||
0x90, 0x06, 0x84, 0x04, 0x90, 0x0A, 0x81, 0x06, 0x88, 0x0E, 0x90, 0x03,
|
||||
0x81, 0x03, 0x90, 0x0D, 0x84, 0x0C, 0x88, 0x0E, 0x81, 0x04, 0x90, 0x0B,
|
||||
0x88, 0x0B, 0x82, 0x08, 0x81, 0x0C, 0x81, 0x0B, 0x81, 0x02, 0x84, 0x08,
|
||||
0x84, 0x04, 0x82, 0x0E, 0x84, 0x04, 0x81, 0x05, 0x90, 0x0C, 0x90, 0x08,
|
||||
0x8A, 0x0D, 0x88, 0x06, 0x81, 0x01, 0x81, 0x04, 0x81, 0x01, 0x90, 0x0A,
|
||||
0x88, 0x0E, 0x82, 0x07, 0x81, 0x03, 0x82, 0x04,
|
||||
};
|
||||
|
||||
// level C -- 1400 ticks, 0 hook bytes, 10 fare stages, 256 pictures, 24 landings
|
||||
// regenerate: tools/stsim/recorder levels/level03.dat C 20261006 60 2000
|
||||
static const uint8_t kGateStreamC[320] = {
|
||||
0x81, 0x04, 0x84, 0x01, 0x84, 0x09, 0x89, 0x03, 0x90, 0x05, 0x84, 0x0D,
|
||||
0x81, 0x04, 0x88, 0x0E, 0x88, 0x0A, 0x82, 0x0C, 0x84, 0x02, 0x84, 0x07,
|
||||
0x81, 0x06, 0x81, 0x0B, 0x88, 0x08, 0x82, 0x07, 0x84, 0x0A, 0x81, 0x0D,
|
||||
0x84, 0x0B, 0x81, 0x02, 0x82, 0x0C, 0x88, 0x0E, 0x84, 0x0B, 0x88, 0x0E,
|
||||
0x81, 0x0C, 0x81, 0x08, 0x81, 0x02, 0x90, 0x0E, 0x81, 0x08, 0x90, 0x0E,
|
||||
0x84, 0x02, 0x81, 0x03, 0x84, 0x0E, 0x88, 0x04, 0x84, 0x06, 0x90, 0x06,
|
||||
0x81, 0x01, 0x90, 0x09, 0x81, 0x05, 0x88, 0x09, 0x84, 0x0E, 0x90, 0x05,
|
||||
0x90, 0x04, 0x82, 0x06, 0x84, 0x0D, 0x81, 0x09, 0x82, 0x07, 0x90, 0x0E,
|
||||
0x88, 0x0C, 0x84, 0x0E, 0x81, 0x0E, 0x81, 0x0B, 0x84, 0x0B, 0x81, 0x01,
|
||||
0x88, 0x0B, 0x81, 0x06, 0x88, 0x0B, 0x82, 0x08, 0x88, 0x04, 0x81, 0x09,
|
||||
0x81, 0x02, 0x81, 0x08, 0x84, 0x06, 0x81, 0x0A, 0x90, 0x07, 0x84, 0x01,
|
||||
0x82, 0x0D, 0x88, 0x0D, 0x82, 0x06, 0x88, 0x09, 0x88, 0x0A, 0x84, 0x05,
|
||||
0x84, 0x0E, 0x81, 0x01, 0x88, 0x03, 0x84, 0x09, 0x88, 0x0C, 0x81, 0x0A,
|
||||
0x82, 0x0A, 0x90, 0x0A, 0x90, 0x0D, 0x81, 0x03, 0x90, 0x0B, 0x82, 0x03,
|
||||
0x84, 0x03, 0x84, 0x01, 0x81, 0x02, 0x90, 0x01, 0x84, 0x0C, 0x81, 0x01,
|
||||
0x90, 0x09, 0x81, 0x0B, 0x82, 0x05, 0x82, 0x0D, 0x90, 0x0D, 0x82, 0x0A,
|
||||
0x90, 0x07, 0x81, 0x03, 0x90, 0x0B, 0x88, 0x02, 0x84, 0x07, 0x84, 0x0E,
|
||||
0x88, 0x07, 0x88, 0x0D, 0x81, 0x02, 0x88, 0x0B, 0x90, 0x07, 0x81, 0x01,
|
||||
0x84, 0x0A, 0x90, 0x08, 0x84, 0x08, 0x90, 0x08, 0x81, 0x0A, 0x90, 0x04,
|
||||
0x88, 0x0B, 0x82, 0x09, 0x84, 0x0E, 0x81, 0x06, 0x84, 0x04, 0x88, 0x0A,
|
||||
0x81, 0x0B, 0x81, 0x02, 0x88, 0x0C, 0x82, 0x0E, 0x84, 0x07, 0x82, 0x05,
|
||||
0x88, 0x0A, 0x82, 0x09, 0x81, 0x04, 0x90, 0x03, 0x84, 0x0A, 0x82, 0x0B,
|
||||
0x81, 0x08, 0x90, 0x06, 0x82, 0x0C, 0x82, 0x03, 0x84, 0x08, 0x81, 0x07,
|
||||
0x90, 0x0E, 0x82, 0x0C, 0x81, 0x0E, 0x88, 0x09, 0x81, 0x01, 0x90, 0x0E,
|
||||
0x90, 0x0A, 0x88, 0x09, 0x81, 0x01, 0x90, 0x06, 0x90, 0x09, 0x81, 0x02,
|
||||
0x90, 0x0E, 0x81, 0x02, 0x90, 0x0C, 0x84, 0x06, 0x84, 0x09, 0x81, 0x03,
|
||||
0x90, 0x0D, 0x82, 0x0C, 0x81, 0x06, 0x81, 0x0D,
|
||||
};
|
||||
|
||||
// level D -- 1400 ticks, 0 hook bytes, 10 fare stages, 256 pictures, 9 landings
|
||||
// regenerate: tools/stsim/recorder levels/level04.dat D 20261006 60 2000
|
||||
static const uint8_t kGateStreamD[320] = {
|
||||
0x81, 0x04, 0x82, 0x02, 0x81, 0x01, 0x90, 0x02, 0x84, 0x01, 0x88, 0x05,
|
||||
0x81, 0x04, 0x84, 0x0B, 0x81, 0x0E, 0x84, 0x07, 0x81, 0x0E, 0x82, 0x0D,
|
||||
0x81, 0x05, 0x81, 0x01, 0x82, 0x01, 0x88, 0x03, 0x84, 0x0C, 0x88, 0x07,
|
||||
0x88, 0x07, 0x84, 0x06, 0x81, 0x04, 0x81, 0x03, 0x88, 0x06, 0x84, 0x03,
|
||||
0x81, 0x06, 0x84, 0x0A, 0x81, 0x0D, 0x84, 0x0E, 0x81, 0x0B, 0x81, 0x07,
|
||||
0x82, 0x06, 0x81, 0x04, 0x84, 0x09, 0x90, 0x06, 0x88, 0x07, 0x80, 0x01,
|
||||
0x84, 0x0D, 0x84, 0x09, 0x88, 0x0A, 0x90, 0x08, 0x88, 0x05, 0x81, 0x0E,
|
||||
0x84, 0x0E, 0x84, 0x0E, 0x81, 0x02, 0x90, 0x0C, 0x90, 0x0D, 0x84, 0x06,
|
||||
0x84, 0x01, 0x88, 0x03, 0x90, 0x09, 0x82, 0x08, 0x85, 0x05, 0x81, 0x04,
|
||||
0x81, 0x06, 0x81, 0x01, 0x81, 0x05, 0x88, 0x06, 0x84, 0x0A, 0x81, 0x0A,
|
||||
0x88, 0x01, 0x81, 0x02, 0x82, 0x0B, 0x84, 0x03, 0x90, 0x0B, 0x81, 0x03,
|
||||
0x84, 0x03, 0x82, 0x03, 0x84, 0x06, 0x88, 0x0E, 0x84, 0x0B, 0x81, 0x01,
|
||||
0x81, 0x02, 0x81, 0x09, 0x81, 0x04, 0x88, 0x06, 0x81, 0x04, 0x88, 0x0D,
|
||||
0x81, 0x0D, 0x81, 0x08, 0x88, 0x06, 0x82, 0x0E, 0x81, 0x0E, 0x84, 0x02,
|
||||
0x81, 0x01, 0x84, 0x0B, 0x81, 0x0E, 0x81, 0x0A, 0x83, 0x07, 0x90, 0x0B,
|
||||
0x84, 0x0A, 0x88, 0x0D, 0x90, 0x03, 0x88, 0x0B, 0x82, 0x0B, 0x88, 0x01,
|
||||
0x80, 0x0B, 0x81, 0x07, 0x81, 0x07, 0x81, 0x0C, 0x81, 0x03, 0x90, 0x0C,
|
||||
0x88, 0x0B, 0x81, 0x0E, 0x82, 0x01, 0x81, 0x0C, 0x81, 0x0E, 0x81, 0x07,
|
||||
0x90, 0x0C, 0x90, 0x01, 0x88, 0x03, 0x88, 0x03, 0x88, 0x07, 0x81, 0x02,
|
||||
0x88, 0x06, 0x90, 0x0E, 0x88, 0x08, 0x81, 0x04, 0x81, 0x0D, 0x84, 0x07,
|
||||
0x90, 0x01, 0x82, 0x07, 0x81, 0x05, 0x81, 0x01, 0x84, 0x0B, 0x84, 0x01,
|
||||
0x81, 0x02, 0x90, 0x0E, 0x82, 0x02, 0x81, 0x01, 0x84, 0x02, 0x81, 0x01,
|
||||
0x81, 0x09, 0x81, 0x0D, 0x84, 0x05, 0x8A, 0x0E, 0x81, 0x08, 0x85, 0x0B,
|
||||
0x81, 0x02, 0x81, 0x0D, 0x90, 0x02, 0x82, 0x0D, 0x84, 0x02, 0x82, 0x06,
|
||||
0x81, 0x0D, 0x82, 0x03, 0x84, 0x04, 0x81, 0x05, 0x90, 0x0E, 0x81, 0x08,
|
||||
0x8A, 0x0D, 0x88, 0x0B, 0x90, 0x04, 0x81, 0x06, 0x84, 0x09, 0x81, 0x08,
|
||||
0x88, 0x0E, 0x80, 0x07, 0x82, 0x05, 0x90, 0x0C,
|
||||
};
|
||||
|
||||
// level E -- 1400 ticks, 4 hook bytes, 10 fare stages, 256 pictures, 3 landings
|
||||
// regenerate: tools/stsim/recorder levels/level05.dat E 20261006 60 2000
|
||||
static const uint8_t kGateStreamE[320] = {
|
||||
0x88, 0x04, 0x81, 0x02, 0x84, 0x01, 0x90, 0x02, 0x88, 0x01, 0x84, 0x08,
|
||||
0x81, 0x04, 0x88, 0x0B, 0x81, 0x0E, 0x88, 0x07, 0x81, 0x0E, 0x82, 0x0D,
|
||||
0x81, 0x05, 0x81, 0x02, 0x84, 0x01, 0x84, 0x03, 0x84, 0x0E, 0x88, 0x08,
|
||||
0x88, 0x07, 0x84, 0x0D, 0x81, 0x07, 0x81, 0x03, 0x84, 0x0B, 0x84, 0x02,
|
||||
0x81, 0x06, 0x88, 0x09, 0x81, 0x0C, 0x84, 0x0B, 0x81, 0x0B, 0x81, 0x07,
|
||||
0x82, 0x06, 0x81, 0x04, 0x84, 0x09, 0x84, 0x04, 0x88, 0x05, 0x90, 0x04,
|
||||
0x84, 0x0D, 0x84, 0x04, 0x88, 0x0C, 0x90, 0x07, 0x81, 0x09, 0x90, 0x0B,
|
||||
0x81, 0x0A, 0x88, 0x0C, 0x88, 0x02, 0x81, 0x01, 0x81, 0x06, 0x84, 0x0D,
|
||||
0x90, 0x0E, 0x84, 0x05, 0x84, 0x03, 0x81, 0x09, 0x81, 0x02, 0x81, 0x04,
|
||||
0x81, 0x0C, 0x81, 0x01, 0x90, 0x05, 0x91, 0x0B, 0x84, 0x0A, 0x81, 0x0A,
|
||||
0x81, 0x01, 0x81, 0x02, 0x82, 0x01, 0x84, 0x0C, 0x90, 0x0E, 0x84, 0x04,
|
||||
0x88, 0x0E, 0x82, 0x03, 0x88, 0x02, 0x88, 0x0E, 0x82, 0x0B, 0x82, 0x01,
|
||||
0x90, 0x02, 0x88, 0x09, 0x81, 0x0B, 0x88, 0x07, 0x81, 0x04, 0x88, 0x05,
|
||||
0x81, 0x0D, 0x81, 0x0C, 0x88, 0x04, 0x82, 0x0D, 0x81, 0x0E, 0x88, 0x06,
|
||||
0x81, 0x01, 0x84, 0x0C, 0x81, 0x0E, 0x81, 0x0A, 0x82, 0x07, 0x82, 0x0A,
|
||||
0x84, 0x02, 0x90, 0x0B, 0x81, 0x05, 0x84, 0x0B, 0x82, 0x03, 0x81, 0x06,
|
||||
0x84, 0x0D, 0x81, 0x0C, 0x81, 0x05, 0x81, 0x06, 0x88, 0x0E, 0x84, 0x0A,
|
||||
0x90, 0x0A, 0x81, 0x0A, 0x90, 0x05, 0x82, 0x05, 0x84, 0x06, 0x84, 0x06,
|
||||
0x81, 0x0B, 0x81, 0x05, 0x88, 0x0E, 0x84, 0x0E, 0x81, 0x0E, 0x81, 0x0C,
|
||||
0x81, 0x05, 0x90, 0x0E, 0x81, 0x0A, 0x88, 0x0C, 0x8A, 0x05, 0x84, 0x03,
|
||||
0x81, 0x04, 0x88, 0x02, 0x80, 0x06, 0x84, 0x05, 0x88, 0x06, 0x84, 0x07,
|
||||
0x82, 0x0B, 0x82, 0x07, 0x82, 0x07, 0x89, 0x05, 0x88, 0x02, 0x81, 0x04,
|
||||
0x81, 0x01, 0x88, 0x09, 0x81, 0x0B, 0x84, 0x01, 0x81, 0x0D, 0x84, 0x01,
|
||||
0x81, 0x0C, 0x84, 0x0E, 0x88, 0x09, 0x81, 0x08, 0x90, 0x09, 0x82, 0x09,
|
||||
0x81, 0x05, 0x84, 0x0C, 0x81, 0x0B, 0x81, 0x0C, 0x90, 0x09, 0x82, 0x0E,
|
||||
0x81, 0x0E, 0x82, 0x09, 0x81, 0x04, 0x81, 0x02, 0x88, 0x08, 0x84, 0x06,
|
||||
0x90, 0x06, 0x81, 0x09, 0x82, 0x06, 0x81, 0x01,
|
||||
};
|
||||
|
||||
// level F -- 1400 ticks, 8 hook bytes, 10 fare stages, 256 pictures, 5 landings
|
||||
// regenerate: tools/stsim/recorder levels/level06.dat F 20261006 60 2000
|
||||
static const uint8_t kGateStreamF[320] = {
|
||||
0x81, 0x02, 0x88, 0x02, 0x84, 0x09, 0x89, 0x03, 0x81, 0x01, 0x82, 0x05,
|
||||
0x81, 0x04, 0x80, 0x0D, 0x84, 0x08, 0x82, 0x05, 0x84, 0x09, 0x88, 0x07,
|
||||
0x88, 0x06, 0x81, 0x0A, 0x90, 0x0E, 0x82, 0x07, 0x81, 0x07, 0x88, 0x0E,
|
||||
0x88, 0x0B, 0x88, 0x08, 0x82, 0x0C, 0x84, 0x0B, 0x81, 0x08, 0x81, 0x0B,
|
||||
0x90, 0x02, 0x88, 0x0E, 0x88, 0x0E, 0x90, 0x04, 0x81, 0x05, 0x84, 0x06,
|
||||
0x88, 0x01, 0x88, 0x03, 0x81, 0x01, 0x88, 0x04, 0x81, 0x0E, 0x88, 0x04,
|
||||
0x88, 0x08, 0x82, 0x08, 0x84, 0x0E, 0x88, 0x04, 0x90, 0x0E, 0x81, 0x0C,
|
||||
0x90, 0x06, 0x84, 0x0B, 0x82, 0x06, 0x88, 0x03, 0x81, 0x01, 0x81, 0x05,
|
||||
0x81, 0x03, 0x81, 0x06, 0x88, 0x0A, 0x88, 0x01, 0x90, 0x0C, 0x88, 0x02,
|
||||
0x81, 0x06, 0x81, 0x03, 0x81, 0x03, 0x88, 0x02, 0x82, 0x0B, 0x90, 0x09,
|
||||
0x88, 0x08, 0x84, 0x0E, 0x90, 0x01, 0x84, 0x0D, 0x81, 0x08, 0x88, 0x05,
|
||||
0x90, 0x07, 0x81, 0x08, 0x84, 0x05, 0x82, 0x07, 0x90, 0x02, 0x90, 0x06,
|
||||
0x81, 0x01, 0x82, 0x03, 0x88, 0x09, 0x88, 0x07, 0x82, 0x0D, 0x84, 0x08,
|
||||
0x81, 0x0B, 0x81, 0x01, 0x81, 0x0E, 0x81, 0x0D, 0x81, 0x0B, 0x82, 0x0C,
|
||||
0x84, 0x0A, 0x82, 0x0E, 0x88, 0x01, 0x81, 0x05, 0x90, 0x0E, 0x81, 0x06,
|
||||
0x82, 0x09, 0x90, 0x06, 0x81, 0x09, 0x81, 0x03, 0x82, 0x04, 0x82, 0x0D,
|
||||
0x84, 0x0E, 0x88, 0x08, 0x88, 0x07, 0x81, 0x0B, 0x81, 0x0A, 0x81, 0x05,
|
||||
0x84, 0x0A, 0x88, 0x0A, 0x84, 0x0D, 0x82, 0x07, 0x81, 0x08, 0x82, 0x0B,
|
||||
0x81, 0x08, 0x88, 0x0D, 0x88, 0x0B, 0x88, 0x06, 0x84, 0x0D, 0x82, 0x0C,
|
||||
0x88, 0x06, 0x90, 0x02, 0x81, 0x03, 0x81, 0x0E, 0x81, 0x0A, 0x88, 0x0A,
|
||||
0x90, 0x08, 0x81, 0x09, 0x81, 0x05, 0x82, 0x08, 0x84, 0x09, 0x88, 0x01,
|
||||
0x81, 0x02, 0x84, 0x0D, 0x8A, 0x09, 0x81, 0x01, 0x84, 0x02, 0x81, 0x02,
|
||||
0x90, 0x0E, 0x81, 0x0D, 0x82, 0x05, 0x8A, 0x0E, 0x81, 0x0A, 0x81, 0x0A,
|
||||
0x84, 0x02, 0x81, 0x0A, 0x84, 0x04, 0x82, 0x0D, 0x81, 0x0A, 0x82, 0x06,
|
||||
0x84, 0x06, 0x81, 0x02, 0x84, 0x0C, 0x81, 0x0C, 0x81, 0x03, 0x81, 0x05,
|
||||
0x84, 0x03, 0x82, 0x08, 0x88, 0x06, 0x82, 0x0C, 0x84, 0x01, 0x88, 0x08,
|
||||
0x88, 0x0A, 0x81, 0x03, 0x90, 0x0E, 0x84, 0x03,
|
||||
};
|
||||
|
||||
// level G -- 1400 ticks, 5 hook bytes, 10 fare stages, 256 pictures, 11 landings
|
||||
// regenerate: tools/stsim/recorder levels/level07.dat G 20261006 60 2000
|
||||
static const uint8_t kGateStreamG[320] = {
|
||||
0x88, 0x05, 0x81, 0x09, 0x81, 0x05, 0x81, 0x04, 0x81, 0x06, 0x84, 0x0C,
|
||||
0x81, 0x01, 0x89, 0x02, 0x81, 0x09, 0x82, 0x0D, 0x88, 0x05, 0x81, 0x03,
|
||||
0x80, 0x09, 0x81, 0x01, 0x90, 0x02, 0x81, 0x04, 0x90, 0x08, 0x81, 0x03,
|
||||
0x90, 0x0A, 0x88, 0x0E, 0x88, 0x0C, 0x90, 0x0C, 0x81, 0x04, 0x81, 0x0D,
|
||||
0x90, 0x0A, 0x81, 0x0D, 0x84, 0x0E, 0x81, 0x09, 0x81, 0x08, 0x81, 0x0C,
|
||||
0x82, 0x04, 0x81, 0x02, 0x81, 0x0E, 0x81, 0x02, 0x84, 0x01, 0x88, 0x0A,
|
||||
0x88, 0x05, 0x90, 0x09, 0x81, 0x02, 0x84, 0x09, 0x84, 0x05, 0x81, 0x0D,
|
||||
0x84, 0x09, 0x88, 0x03, 0x84, 0x06, 0x84, 0x0D, 0x81, 0x05, 0x84, 0x0C,
|
||||
0x88, 0x0C, 0x88, 0x0C, 0x88, 0x03, 0x88, 0x09, 0x81, 0x0A, 0x81, 0x09,
|
||||
0x81, 0x06, 0x81, 0x07, 0x81, 0x05, 0x88, 0x09, 0x88, 0x02, 0x81, 0x0B,
|
||||
0x84, 0x0A, 0x84, 0x05, 0x88, 0x03, 0x81, 0x05, 0x81, 0x0B, 0x81, 0x05,
|
||||
0x90, 0x05, 0x84, 0x09, 0x81, 0x06, 0x81, 0x07, 0x90, 0x0D, 0x81, 0x04,
|
||||
0x81, 0x0E, 0x88, 0x0D, 0x88, 0x0D, 0x88, 0x06, 0x82, 0x02, 0x88, 0x0D,
|
||||
0x90, 0x0E, 0x81, 0x04, 0x81, 0x0B, 0x84, 0x0B, 0x81, 0x05, 0x84, 0x07,
|
||||
0x88, 0x0A, 0x80, 0x04, 0x84, 0x07, 0x84, 0x0B, 0x88, 0x0D, 0x90, 0x0E,
|
||||
0x90, 0x06, 0x88, 0x08, 0x82, 0x0C, 0x81, 0x02, 0x81, 0x0E, 0x88, 0x0A,
|
||||
0x90, 0x0B, 0x81, 0x04, 0x88, 0x08, 0x90, 0x07, 0x84, 0x03, 0x82, 0x08,
|
||||
0x81, 0x05, 0x81, 0x03, 0x81, 0x03, 0x81, 0x06, 0x90, 0x0B, 0x84, 0x0E,
|
||||
0x82, 0x0E, 0x88, 0x08, 0x88, 0x04, 0x81, 0x0D, 0x88, 0x04, 0x81, 0x08,
|
||||
0x88, 0x0C, 0x81, 0x06, 0x81, 0x04, 0x90, 0x0B, 0x81, 0x08, 0x84, 0x04,
|
||||
0x90, 0x08, 0x84, 0x09, 0x88, 0x09, 0x81, 0x0B, 0x81, 0x07, 0x84, 0x0D,
|
||||
0x88, 0x0A, 0x88, 0x09, 0x90, 0x0B, 0x90, 0x02, 0x84, 0x0D, 0x81, 0x0E,
|
||||
0x90, 0x0D, 0x84, 0x07, 0x82, 0x0C, 0x81, 0x0E, 0x81, 0x06, 0x88, 0x09,
|
||||
0x90, 0x02, 0x90, 0x04, 0x81, 0x09, 0x84, 0x0A, 0x90, 0x06, 0x81, 0x04,
|
||||
0x90, 0x03, 0x90, 0x08, 0x84, 0x0B, 0x81, 0x02, 0x90, 0x04, 0x84, 0x03,
|
||||
0x81, 0x03, 0x84, 0x05, 0x90, 0x0C, 0x81, 0x01, 0x90, 0x0D, 0x81, 0x02,
|
||||
0x90, 0x05, 0x90, 0x0E, 0x81, 0x03, 0x90, 0x0A,
|
||||
0x81, 0x04, 0x88, 0x04, 0x81, 0x01, 0x81, 0x03, 0x88, 0x01, 0x88, 0x06,
|
||||
0x81, 0x04, 0x88, 0x0D, 0x84, 0x0D, 0x84, 0x03, 0x84, 0x08, 0x81, 0x07,
|
||||
0x88, 0x06, 0x81, 0x0B, 0x81, 0x0E, 0x88, 0x01, 0x84, 0x0E, 0x81, 0x0D,
|
||||
0x84, 0x06, 0x81, 0x03, 0x82, 0x08, 0x81, 0x06, 0x84, 0x0B, 0x81, 0x0A,
|
||||
0x84, 0x02, 0x88, 0x06, 0x81, 0x01, 0x81, 0x09, 0x88, 0x0E, 0x81, 0x0A,
|
||||
0x81, 0x03, 0x84, 0x05, 0x88, 0x0A, 0x81, 0x01, 0x84, 0x07, 0x88, 0x08,
|
||||
0x81, 0x0A, 0x81, 0x03, 0x81, 0x09, 0x81, 0x02, 0x81, 0x01, 0x81, 0x09,
|
||||
0x81, 0x01, 0x81, 0x0C, 0x82, 0x0A, 0x90, 0x0D, 0x88, 0x0E, 0x81, 0x0D,
|
||||
0x84, 0x03, 0x88, 0x09, 0x84, 0x0D, 0x88, 0x0D, 0x81, 0x05, 0x88, 0x03,
|
||||
0x81, 0x08, 0x90, 0x0E, 0x81, 0x02, 0x88, 0x0D, 0x82, 0x06, 0x88, 0x08,
|
||||
0x81, 0x06, 0x82, 0x03, 0x81, 0x01, 0x88, 0x0D, 0x81, 0x05, 0x81, 0x07,
|
||||
0x81, 0x0B, 0x90, 0x0E, 0x84, 0x01, 0x81, 0x0B, 0x82, 0x0A, 0x90, 0x08,
|
||||
0x84, 0x02, 0x82, 0x0C, 0x88, 0x05, 0x84, 0x08, 0x81, 0x0D, 0x81, 0x0B,
|
||||
0x84, 0x0D, 0x88, 0x0B, 0x84, 0x0C, 0x81, 0x07, 0x88, 0x07, 0x88, 0x08,
|
||||
0x81, 0x05, 0x90, 0x0A, 0x90, 0x0C, 0x82, 0x09, 0x81, 0x07, 0x81, 0x0A,
|
||||
0x82, 0x0B, 0x88, 0x0D, 0x81, 0x01, 0x90, 0x04, 0x90, 0x01, 0x81, 0x0B,
|
||||
0x82, 0x05, 0x84, 0x07, 0x81, 0x08, 0x80, 0x04, 0x88, 0x04, 0x81, 0x0A,
|
||||
0x89, 0x02, 0x88, 0x03, 0x82, 0x05, 0x90, 0x0D, 0x90, 0x0E, 0x81, 0x0B,
|
||||
0x84, 0x01, 0x82, 0x0A, 0x83, 0x04, 0x81, 0x09, 0x84, 0x0C, 0x81, 0x0E,
|
||||
0x81, 0x01, 0x82, 0x0D, 0x81, 0x0C, 0x81, 0x03, 0x82, 0x01, 0x81, 0x0C,
|
||||
0x88, 0x05, 0x81, 0x08, 0x88, 0x0C, 0x82, 0x0E, 0x84, 0x0B, 0x81, 0x0B,
|
||||
0x88, 0x0C, 0x81, 0x0C, 0x81, 0x05, 0x84, 0x08, 0x82, 0x02, 0x82, 0x0A,
|
||||
0x81, 0x0C, 0x84, 0x0B, 0x81, 0x02, 0x90, 0x08, 0x81, 0x06, 0x90, 0x0E,
|
||||
0x88, 0x03, 0x82, 0x0B, 0x84, 0x0E, 0x84, 0x0B, 0x84, 0x0D, 0x88, 0x04,
|
||||
0x84, 0x05, 0x81, 0x06, 0x84, 0x06, 0x90, 0x0D, 0x90, 0x0C, 0x88, 0x03,
|
||||
0x81, 0x0E, 0x82, 0x02, 0x84, 0x05, 0x90, 0x0B, 0x82, 0x08, 0x84, 0x09,
|
||||
0x81, 0x07, 0x84, 0x0D, 0x81, 0x02, 0x90, 0x04,
|
||||
};
|
||||
|
||||
// level I -- 1400 ticks, 20 hook bytes, 10 fare stages, 256 pictures, 22 landings
|
||||
// regenerate: tools/stsim/recorder levels/level??.dat I 20260918
|
||||
// level I -- 1400 ticks, 20 hook bytes, 10 fare stages, 256 pictures, 4 landings
|
||||
// regenerate: tools/stsim/recorder levels/level09.dat I 20261006 60 2000
|
||||
static const uint8_t kGateStreamI[320] = {
|
||||
0x84, 0x0E, 0x81, 0x06, 0x84, 0x0E, 0x88, 0x0B, 0x88, 0x05, 0x81, 0x03,
|
||||
0x82, 0x07, 0x90, 0x0A, 0x81, 0x08, 0x88, 0x0E, 0x88, 0x0E, 0x90, 0x0B,
|
||||
0x82, 0x0D, 0x81, 0x01, 0x81, 0x0A, 0x90, 0x0A, 0x82, 0x03, 0x81, 0x07,
|
||||
0x82, 0x05, 0x81, 0x0B, 0x81, 0x05, 0x82, 0x05, 0x82, 0x0C, 0x81, 0x08,
|
||||
0x88, 0x0C, 0x90, 0x04, 0x81, 0x02, 0x88, 0x08, 0x81, 0x03, 0x81, 0x05,
|
||||
0x81, 0x0A, 0x88, 0x0E, 0x81, 0x06, 0x84, 0x0E, 0x82, 0x07, 0x82, 0x01,
|
||||
0x81, 0x04, 0x82, 0x08, 0x90, 0x0C, 0x90, 0x05, 0x81, 0x08, 0x82, 0x0D,
|
||||
0x90, 0x06, 0x81, 0x0A, 0x84, 0x0B, 0x84, 0x0C, 0x84, 0x0C, 0x88, 0x09,
|
||||
0x81, 0x06, 0x81, 0x0C, 0x81, 0x04, 0x82, 0x09, 0x88, 0x05, 0x82, 0x0E,
|
||||
0x81, 0x0C, 0x81, 0x07, 0x88, 0x01, 0x90, 0x0E, 0x81, 0x02, 0x90, 0x0E,
|
||||
0x81, 0x02, 0x90, 0x07, 0x84, 0x05, 0x84, 0x06, 0x88, 0x06, 0x88, 0x0C,
|
||||
0x81, 0x0C, 0x84, 0x0B, 0x82, 0x09, 0x81, 0x05, 0x81, 0x0B, 0x82, 0x09,
|
||||
0x90, 0x0E, 0x90, 0x06, 0x88, 0x0D, 0x84, 0x03, 0x82, 0x0B, 0x84, 0x0D,
|
||||
0x90, 0x0B, 0x90, 0x04, 0x81, 0x0B, 0x81, 0x08, 0x88, 0x08, 0x90, 0x0A,
|
||||
0x81, 0x03, 0x81, 0x01, 0x81, 0x0E, 0x81, 0x04, 0x88, 0x02, 0x90, 0x05,
|
||||
0x81, 0x07, 0x81, 0x01, 0x82, 0x09, 0x90, 0x06, 0x81, 0x09, 0x90, 0x0E,
|
||||
0x88, 0x05, 0x82, 0x0D, 0x88, 0x03, 0x81, 0x06, 0x84, 0x0C, 0x82, 0x01,
|
||||
0x81, 0x09, 0x82, 0x0E, 0x90, 0x01, 0x81, 0x01, 0x84, 0x05, 0x82, 0x06,
|
||||
0x88, 0x0A, 0x81, 0x05, 0x82, 0x03, 0x90, 0x07, 0x90, 0x08, 0x81, 0x02,
|
||||
0x90, 0x0D, 0x88, 0x0D, 0x81, 0x08, 0x90, 0x0E, 0x82, 0x0D, 0x88, 0x04,
|
||||
0x81, 0x0C, 0x82, 0x09, 0x90, 0x04, 0x81, 0x0C, 0x82, 0x06, 0x90, 0x0E,
|
||||
0x90, 0x0D, 0x90, 0x07, 0x90, 0x03, 0x82, 0x03, 0x88, 0x06, 0x81, 0x07,
|
||||
0x90, 0x08, 0x81, 0x02, 0x90, 0x09, 0x81, 0x04, 0x90, 0x06, 0x82, 0x0A,
|
||||
0x90, 0x05, 0x88, 0x05, 0x88, 0x06, 0x90, 0x0B, 0x81, 0x03, 0x82, 0x02,
|
||||
0x90, 0x0B, 0x81, 0x02, 0x90, 0x0C, 0x90, 0x07, 0x81, 0x01, 0x84, 0x08,
|
||||
0x90, 0x0C, 0x81, 0x0E, 0x88, 0x08, 0x88, 0x0C, 0x81, 0x0C, 0x90, 0x04,
|
||||
0x84, 0x0D, 0x81, 0x01, 0x88, 0x08, 0x88, 0x09,
|
||||
0x81, 0x04, 0x88, 0x02, 0x89, 0x01, 0x89, 0x02, 0x81, 0x01, 0x88, 0x05,
|
||||
0x81, 0x04, 0x80, 0x0E, 0x84, 0x06, 0x82, 0x05, 0x84, 0x08, 0x88, 0x07,
|
||||
0x88, 0x06, 0x81, 0x0B, 0x82, 0x05, 0x82, 0x07, 0x81, 0x0E, 0x81, 0x02,
|
||||
0x81, 0x03, 0x82, 0x03, 0x82, 0x0C, 0x88, 0x06, 0x84, 0x0B, 0x84, 0x0E,
|
||||
0x81, 0x02, 0x81, 0x06, 0x81, 0x02, 0x84, 0x09, 0x90, 0x0E, 0x81, 0x02,
|
||||
0x81, 0x07, 0x81, 0x02, 0x90, 0x08, 0x81, 0x02, 0x84, 0x04, 0x88, 0x09,
|
||||
0x88, 0x05, 0x81, 0x0C, 0x88, 0x0E, 0x84, 0x0E, 0x88, 0x08, 0x90, 0x09,
|
||||
0x90, 0x05, 0x84, 0x09, 0x82, 0x0A, 0x90, 0x0C, 0x88, 0x07, 0x88, 0x09,
|
||||
0x84, 0x05, 0x81, 0x04, 0x81, 0x02, 0x81, 0x03, 0x81, 0x08, 0x88, 0x0B,
|
||||
0x90, 0x05, 0x81, 0x0A, 0x81, 0x08, 0x82, 0x0E, 0x82, 0x09, 0x90, 0x09,
|
||||
0x81, 0x07, 0x81, 0x07, 0x81, 0x08, 0x88, 0x0D, 0x82, 0x0C, 0x81, 0x08,
|
||||
0x81, 0x02, 0x81, 0x03, 0x82, 0x04, 0x84, 0x0B, 0x81, 0x05, 0x88, 0x02,
|
||||
0x88, 0x07, 0x90, 0x0D, 0x88, 0x0B, 0x84, 0x02, 0x81, 0x02, 0x84, 0x09,
|
||||
0x88, 0x0C, 0x81, 0x06, 0x88, 0x09, 0x81, 0x02, 0x84, 0x0A, 0x88, 0x06,
|
||||
0x88, 0x0C, 0x81, 0x05, 0x82, 0x03, 0x88, 0x02, 0x81, 0x06, 0x81, 0x08,
|
||||
0x82, 0x03, 0x82, 0x0E, 0x81, 0x03, 0x81, 0x02, 0x81, 0x0C, 0x82, 0x0B,
|
||||
0x84, 0x05, 0x88, 0x0C, 0x84, 0x0C, 0x81, 0x07, 0x81, 0x06, 0x88, 0x07,
|
||||
0x88, 0x0C, 0x90, 0x0E, 0x90, 0x03, 0x81, 0x0C, 0x84, 0x0C, 0x81, 0x03,
|
||||
0x89, 0x01, 0x88, 0x0D, 0x81, 0x05, 0x81, 0x06, 0x90, 0x04, 0x83, 0x0D,
|
||||
0x81, 0x01, 0x88, 0x05, 0x84, 0x05, 0x81, 0x01, 0x84, 0x09, 0x81, 0x02,
|
||||
0x90, 0x05, 0x81, 0x0D, 0x80, 0x09, 0x81, 0x0C, 0x88, 0x02, 0x84, 0x0C,
|
||||
0x82, 0x0E, 0x82, 0x0C, 0x90, 0x02, 0x81, 0x07, 0x88, 0x04, 0x88, 0x0D,
|
||||
0x88, 0x09, 0x81, 0x0D, 0x88, 0x0C, 0x88, 0x0E, 0x88, 0x0E, 0x82, 0x0B,
|
||||
0x82, 0x0B, 0x81, 0x0C, 0x82, 0x0C, 0x90, 0x0B, 0x81, 0x0A, 0x84, 0x08,
|
||||
0x81, 0x08, 0x82, 0x0B, 0x81, 0x09, 0x84, 0x09, 0x81, 0x0B, 0x81, 0x03,
|
||||
0x8A, 0x0D, 0x88, 0x0B, 0x90, 0x04, 0x81, 0x0E, 0x88, 0x07, 0x81, 0x08,
|
||||
0x88, 0x03, 0x80, 0x0E, 0x84, 0x05, 0x84, 0x07,
|
||||
};
|
||||
|
||||
// level K -- 1400 ticks, 1 hook bytes, 10 fare stages, 256 pictures, 16 landings
|
||||
// regenerate: tools/stsim/recorder levels/level??.dat K 11
|
||||
// level J -- 1400 ticks, 10 hook bytes, 10 fare stages, 256 pictures, 6 landings
|
||||
// regenerate: tools/stsim/recorder levels/level10.dat J 20261006 60 2000
|
||||
static const uint8_t kGateStreamJ[320] = {
|
||||
0x81, 0x04, 0x88, 0x02, 0x84, 0x01, 0x81, 0x02, 0x90, 0x01, 0x82, 0x05,
|
||||
0x81, 0x04, 0x88, 0x0D, 0x88, 0x0C, 0x82, 0x0C, 0x81, 0x03, 0x84, 0x0A,
|
||||
0x81, 0x06, 0x84, 0x04, 0x88, 0x05, 0x88, 0x08, 0x81, 0x0E, 0x88, 0x06,
|
||||
0x81, 0x0A, 0x81, 0x05, 0x81, 0x0C, 0x84, 0x0D, 0x81, 0x09, 0x90, 0x0D,
|
||||
0x81, 0x0A, 0x81, 0x09, 0x84, 0x0E, 0x88, 0x0C, 0x88, 0x03, 0x88, 0x07,
|
||||
0x88, 0x0D, 0x82, 0x04, 0x82, 0x0D, 0x88, 0x0B, 0x81, 0x03, 0x81, 0x0A,
|
||||
0x81, 0x0A, 0x81, 0x0C, 0x81, 0x05, 0x82, 0x04, 0x88, 0x0E, 0x82, 0x0C,
|
||||
0x82, 0x09, 0x88, 0x07, 0x88, 0x0B, 0x81, 0x09, 0x82, 0x03, 0x81, 0x0A,
|
||||
0x81, 0x03, 0x88, 0x0C, 0x88, 0x05, 0x81, 0x04, 0x81, 0x01, 0x81, 0x0E,
|
||||
0x90, 0x07, 0x84, 0x01, 0x84, 0x0C, 0x88, 0x05, 0x88, 0x0C, 0x90, 0x0D,
|
||||
0x90, 0x0C, 0x82, 0x09, 0x90, 0x03, 0x81, 0x07, 0x90, 0x0C, 0x81, 0x04,
|
||||
0x81, 0x04, 0x81, 0x0B, 0x88, 0x0C, 0x90, 0x0B, 0x88, 0x0C, 0x81, 0x0C,
|
||||
0x82, 0x0A, 0x82, 0x07, 0x81, 0x0A, 0x81, 0x01, 0x84, 0x07, 0x82, 0x09,
|
||||
0x84, 0x0E, 0x90, 0x01, 0x84, 0x01, 0x81, 0x0B, 0x81, 0x05, 0x88, 0x01,
|
||||
0x82, 0x02, 0x81, 0x05, 0x81, 0x05, 0x80, 0x0E, 0x84, 0x09, 0x90, 0x0D,
|
||||
0x90, 0x0B, 0x81, 0x05, 0x81, 0x08, 0x84, 0x03, 0x81, 0x08, 0x88, 0x0D,
|
||||
0x88, 0x0A, 0x81, 0x0A, 0x82, 0x06, 0x88, 0x06, 0x84, 0x0B, 0x81, 0x06,
|
||||
0x90, 0x0B, 0x81, 0x07, 0x82, 0x0E, 0x84, 0x0A, 0x81, 0x03, 0x82, 0x01,
|
||||
0x88, 0x09, 0x82, 0x01, 0x82, 0x08, 0x81, 0x06, 0x81, 0x0C, 0x84, 0x07,
|
||||
0x90, 0x08, 0x81, 0x02, 0x84, 0x0B, 0x84, 0x04, 0x84, 0x0D, 0x81, 0x06,
|
||||
0x81, 0x07, 0x88, 0x0E, 0x88, 0x04, 0x82, 0x02, 0x90, 0x09, 0x88, 0x03,
|
||||
0x81, 0x04, 0x90, 0x0C, 0x81, 0x0B, 0x82, 0x0C, 0x81, 0x0B, 0x81, 0x0E,
|
||||
0x82, 0x0E, 0x81, 0x0D, 0x84, 0x0B, 0x81, 0x0E, 0x88, 0x0E, 0x84, 0x0C,
|
||||
0x84, 0x03, 0x90, 0x0D, 0x82, 0x0A, 0x82, 0x07, 0x90, 0x0C, 0x82, 0x0A,
|
||||
0x81, 0x06, 0x81, 0x06, 0x84, 0x08, 0x88, 0x07, 0x90, 0x05, 0x81, 0x01,
|
||||
0x81, 0x03, 0x82, 0x03, 0x82, 0x0D, 0x84, 0x0B, 0x81, 0x06, 0x90, 0x0A,
|
||||
0x88, 0x03, 0x90, 0x04, 0x81, 0x04, 0x90, 0x06,
|
||||
};
|
||||
|
||||
// level K -- searched 2026-09 by the recorder of then (seed 11), from a
|
||||
// fresh game's first screen; kept because, ridden as the shift reaches
|
||||
// the screen, it covers more than the 2026-10-06 search did
|
||||
static const uint8_t kGateStreamK[320] = {
|
||||
0x84, 0x06, 0x82, 0x0B, 0x90, 0x0C, 0x82, 0x0E, 0x90, 0x05, 0x88, 0x03,
|
||||
0x88, 0x02, 0x82, 0x0E, 0x88, 0x07, 0x84, 0x02, 0x84, 0x0B, 0x84, 0x08,
|
||||
|
|
@ -114,8 +338,105 @@ static const uint8_t kGateStreamK[320] = {
|
|||
0x82, 0x09, 0x90, 0x01, 0x88, 0x07, 0x88, 0x08,
|
||||
};
|
||||
|
||||
// level O -- 1400 ticks, 1 hook bytes, 3 fare stages, 256 pictures, 0 landings
|
||||
// regenerate: tools/stsim/recorder levels/level??.dat O 20260918
|
||||
// level L -- 1400 ticks, 0 hook bytes, 10 fare stages, 256 pictures, 4 landings
|
||||
// regenerate: tools/stsim/recorder levels/level12.dat L 20261006 60 2000
|
||||
static const uint8_t kGateStreamL[320] = {
|
||||
0x81, 0x04, 0x84, 0x02, 0x81, 0x01, 0x81, 0x02, 0x90, 0x01, 0x88, 0x05,
|
||||
0x81, 0x04, 0x88, 0x0E, 0x84, 0x06, 0x88, 0x01, 0x88, 0x08, 0x88, 0x07,
|
||||
0x88, 0x06, 0x81, 0x0B, 0x84, 0x0E, 0x82, 0x07, 0x81, 0x0C, 0x88, 0x06,
|
||||
0x81, 0x0A, 0x81, 0x07, 0x81, 0x0C, 0x81, 0x0D, 0x81, 0x09, 0x84, 0x0B,
|
||||
0x86, 0x0E, 0x81, 0x0E, 0x84, 0x0E, 0x81, 0x0A, 0x84, 0x05, 0x88, 0x06,
|
||||
0x84, 0x09, 0x88, 0x01, 0x82, 0x0D, 0x84, 0x0D, 0x81, 0x06, 0x88, 0x09,
|
||||
0x84, 0x0E, 0x82, 0x08, 0x82, 0x04, 0x82, 0x04, 0x81, 0x09, 0x88, 0x0D,
|
||||
0x90, 0x05, 0x88, 0x0D, 0x90, 0x0B, 0x84, 0x0C, 0x88, 0x0D, 0x88, 0x09,
|
||||
0x84, 0x0A, 0x82, 0x0B, 0x84, 0x06, 0x88, 0x0D, 0x88, 0x07, 0x88, 0x09,
|
||||
0x88, 0x04, 0x81, 0x0B, 0x81, 0x0B, 0x88, 0x0C, 0x82, 0x05, 0x88, 0x0C,
|
||||
0x81, 0x05, 0x84, 0x09, 0x84, 0x03, 0x88, 0x04, 0x81, 0x05, 0x90, 0x03,
|
||||
0x90, 0x0A, 0x82, 0x0E, 0x84, 0x06, 0x90, 0x07, 0x90, 0x0C, 0x81, 0x06,
|
||||
0x82, 0x08, 0x81, 0x09, 0x81, 0x08, 0x81, 0x01, 0x81, 0x07, 0x81, 0x07,
|
||||
0x90, 0x0E, 0x90, 0x01, 0x81, 0x07, 0x81, 0x0B, 0x81, 0x0C, 0x84, 0x0C,
|
||||
0x82, 0x0B, 0x82, 0x09, 0x88, 0x05, 0x80, 0x08, 0x90, 0x06, 0x81, 0x04,
|
||||
0x81, 0x05, 0x81, 0x0C, 0x81, 0x0A, 0x88, 0x03, 0x81, 0x08, 0x90, 0x0D,
|
||||
0x84, 0x09, 0x81, 0x0B, 0x82, 0x06, 0x81, 0x03, 0x81, 0x0B, 0x81, 0x0E,
|
||||
0x90, 0x0A, 0x81, 0x0E, 0x84, 0x0E, 0x81, 0x0A, 0x81, 0x0C, 0x82, 0x01,
|
||||
0x88, 0x02, 0x90, 0x01, 0x82, 0x06, 0x81, 0x07, 0x81, 0x0C, 0x90, 0x02,
|
||||
0x82, 0x0D, 0x81, 0x0E, 0x82, 0x08, 0x81, 0x09, 0x81, 0x08, 0x81, 0x08,
|
||||
0x81, 0x0E, 0x81, 0x07, 0x80, 0x05, 0x84, 0x0B, 0x84, 0x0B, 0x81, 0x01,
|
||||
0x84, 0x0D, 0x8C, 0x06, 0x84, 0x08, 0x81, 0x07, 0x81, 0x0B, 0x88, 0x06,
|
||||
0x81, 0x06, 0x81, 0x0C, 0x81, 0x03, 0x84, 0x08, 0x81, 0x0C, 0x81, 0x05,
|
||||
0x88, 0x03, 0x82, 0x07, 0x88, 0x0C, 0x82, 0x05, 0x88, 0x0A, 0x81, 0x05,
|
||||
0x89, 0x0B, 0x81, 0x06, 0x86, 0x0D, 0x81, 0x0D, 0x90, 0x0A, 0x81, 0x0A,
|
||||
0x88, 0x0A, 0x81, 0x02, 0x90, 0x08, 0x81, 0x02, 0x90, 0x05, 0x84, 0x09,
|
||||
0x81, 0x06, 0x81, 0x06, 0x88, 0x02, 0x82, 0x02,
|
||||
};
|
||||
|
||||
// level M -- 1400 ticks, 0 hook bytes, 8 fare stages, 256 pictures, 9 landings
|
||||
// regenerate: tools/stsim/recorder levels/level13.dat M 20261006 60 2000
|
||||
static const uint8_t kGateStreamM[320] = {
|
||||
0x88, 0x06, 0x88, 0x06, 0x84, 0x04, 0x89, 0x03, 0x90, 0x09, 0x82, 0x05,
|
||||
0x81, 0x04, 0x88, 0x0E, 0x81, 0x07, 0x84, 0x0A, 0x84, 0x09, 0x90, 0x08,
|
||||
0x84, 0x01, 0x90, 0x03, 0x81, 0x08, 0x82, 0x0A, 0x81, 0x03, 0x84, 0x06,
|
||||
0x88, 0x02, 0x81, 0x04, 0x81, 0x02, 0x90, 0x0E, 0x84, 0x0E, 0x84, 0x0B,
|
||||
0x90, 0x0E, 0x84, 0x0E, 0x90, 0x06, 0x82, 0x03, 0x88, 0x07, 0x88, 0x03,
|
||||
0x88, 0x04, 0x81, 0x05, 0x88, 0x03, 0x81, 0x05, 0x84, 0x0A, 0x81, 0x04,
|
||||
0x88, 0x07, 0x90, 0x09, 0x88, 0x0E, 0x84, 0x03, 0x84, 0x0A, 0x81, 0x05,
|
||||
0x88, 0x0B, 0x84, 0x0A, 0x84, 0x0E, 0x90, 0x0C, 0x82, 0x08, 0x84, 0x07,
|
||||
0x81, 0x05, 0x81, 0x06, 0x84, 0x05, 0x81, 0x01, 0x82, 0x06, 0x84, 0x01,
|
||||
0x90, 0x08, 0x84, 0x04, 0x81, 0x0B, 0x81, 0x06, 0x88, 0x0C, 0x81, 0x07,
|
||||
0x90, 0x0B, 0x81, 0x0B, 0x90, 0x04, 0x90, 0x0C, 0x90, 0x09, 0x90, 0x06,
|
||||
0x84, 0x05, 0x90, 0x0E, 0x81, 0x05, 0x88, 0x01, 0x82, 0x01, 0x90, 0x0C,
|
||||
0x90, 0x0E, 0x88, 0x01, 0x81, 0x07, 0x84, 0x0E, 0x82, 0x06, 0x88, 0x0C,
|
||||
0x88, 0x08, 0x90, 0x0B, 0x90, 0x0B, 0x81, 0x0D, 0x84, 0x06, 0x90, 0x0C,
|
||||
0x90, 0x0C, 0x84, 0x0D, 0x84, 0x0D, 0x88, 0x0C, 0x90, 0x0D, 0x88, 0x0C,
|
||||
0x88, 0x0C, 0x88, 0x0D, 0x81, 0x02, 0x82, 0x02, 0x90, 0x09, 0x84, 0x0D,
|
||||
0x88, 0x04, 0x88, 0x01, 0x88, 0x0E, 0x90, 0x0A, 0x81, 0x09, 0x82, 0x09,
|
||||
0x90, 0x0E, 0x81, 0x07, 0x84, 0x0D, 0x88, 0x05, 0x88, 0x08, 0x82, 0x06,
|
||||
0x84, 0x0D, 0x84, 0x03, 0x82, 0x0C, 0x84, 0x02, 0x90, 0x03, 0x81, 0x07,
|
||||
0x82, 0x0B, 0x81, 0x09, 0x84, 0x0C, 0x88, 0x0E, 0x81, 0x05, 0x82, 0x09,
|
||||
0x81, 0x08, 0x84, 0x0E, 0x90, 0x0E, 0x88, 0x04, 0x84, 0x04, 0x88, 0x01,
|
||||
0x81, 0x05, 0x82, 0x08, 0x81, 0x0E, 0x82, 0x09, 0x88, 0x06, 0x81, 0x0D,
|
||||
0x90, 0x0C, 0x84, 0x09, 0x81, 0x05, 0x81, 0x02, 0x82, 0x08, 0x81, 0x01,
|
||||
0x90, 0x03, 0x90, 0x02, 0x84, 0x0D, 0x84, 0x03, 0x88, 0x0D, 0x88, 0x0D,
|
||||
0x84, 0x0A, 0x90, 0x06, 0x88, 0x06, 0x82, 0x03, 0x82, 0x0C, 0x88, 0x0E,
|
||||
0x84, 0x0A, 0x81, 0x06, 0x82, 0x05, 0x90, 0x0E, 0x81, 0x0C, 0x84, 0x0C,
|
||||
0x88, 0x07, 0x84, 0x0C, 0x88, 0x0E, 0x90, 0x04,
|
||||
};
|
||||
|
||||
// level N -- 1400 ticks, 0 hook bytes, 1 fare stages, 45 pictures, 26 landings
|
||||
// regenerate: tools/stsim/recorder levels/level14.dat N 20261006 60 2000
|
||||
static const uint8_t kGateStreamN[320] = {
|
||||
0x81, 0x04, 0x88, 0x03, 0x82, 0x01, 0x89, 0x02, 0x90, 0x01, 0x84, 0x0B,
|
||||
0x81, 0x04, 0x88, 0x0D, 0x84, 0x0C, 0x82, 0x03, 0x81, 0x09, 0x81, 0x04,
|
||||
0x88, 0x06, 0x81, 0x0D, 0x82, 0x0D, 0x90, 0x0A, 0x84, 0x03, 0x81, 0x08,
|
||||
0x88, 0x06, 0x81, 0x03, 0x88, 0x0C, 0x88, 0x03, 0x84, 0x04, 0x84, 0x0E,
|
||||
0x82, 0x02, 0x88, 0x09, 0x80, 0x03, 0x84, 0x09, 0x81, 0x07, 0x81, 0x0B,
|
||||
0x81, 0x05, 0x81, 0x03, 0x84, 0x0C, 0x81, 0x02, 0x81, 0x0D, 0x82, 0x04,
|
||||
0x90, 0x08, 0x82, 0x09, 0x82, 0x04, 0x84, 0x0A, 0x88, 0x01, 0x81, 0x0B,
|
||||
0x90, 0x0B, 0x81, 0x0A, 0x82, 0x0B, 0x90, 0x07, 0x81, 0x08, 0x81, 0x04,
|
||||
0x82, 0x0E, 0x90, 0x09, 0x81, 0x0A, 0x82, 0x08, 0x81, 0x03, 0x90, 0x07,
|
||||
0x88, 0x0B, 0x81, 0x08, 0x84, 0x04, 0x90, 0x0B, 0x84, 0x06, 0x88, 0x0A,
|
||||
0x84, 0x0A, 0x81, 0x03, 0x90, 0x0E, 0x81, 0x07, 0x90, 0x0E, 0x84, 0x04,
|
||||
0x84, 0x06, 0x82, 0x0A, 0x81, 0x0A, 0x90, 0x08, 0x84, 0x0D, 0x82, 0x0B,
|
||||
0x81, 0x0D, 0x84, 0x08, 0x90, 0x0A, 0x81, 0x09, 0x90, 0x08, 0x88, 0x0A,
|
||||
0x82, 0x07, 0x84, 0x07, 0x90, 0x0A, 0x81, 0x04, 0x90, 0x0B, 0x84, 0x0A,
|
||||
0x81, 0x0B, 0x81, 0x01, 0x90, 0x02, 0x82, 0x0D, 0x84, 0x0C, 0x81, 0x08,
|
||||
0x90, 0x0D, 0x82, 0x07, 0x88, 0x0C, 0x81, 0x03, 0x90, 0x0D, 0x81, 0x07,
|
||||
0x90, 0x0C, 0x82, 0x0C, 0x81, 0x02, 0x90, 0x08, 0x88, 0x0B, 0x81, 0x0E,
|
||||
0x82, 0x07, 0x90, 0x0E, 0x82, 0x07, 0x81, 0x06, 0x84, 0x05, 0x81, 0x04,
|
||||
0x90, 0x0E, 0x84, 0x03, 0x81, 0x0A, 0x82, 0x08, 0x90, 0x03, 0x90, 0x05,
|
||||
0x84, 0x09, 0x81, 0x03, 0x90, 0x0C, 0x81, 0x03, 0x82, 0x06, 0x81, 0x0A,
|
||||
0x81, 0x03, 0x88, 0x06, 0x81, 0x02, 0x88, 0x02, 0x90, 0x02, 0x88, 0x0B,
|
||||
0x84, 0x0A, 0x82, 0x08, 0x88, 0x08, 0x81, 0x03, 0x81, 0x02, 0x82, 0x04,
|
||||
0x90, 0x06, 0x81, 0x0D, 0x90, 0x0C, 0x82, 0x0E, 0x81, 0x08, 0x81, 0x07,
|
||||
0x81, 0x0E, 0x81, 0x02, 0x81, 0x07, 0x84, 0x03, 0x88, 0x0C, 0x81, 0x0B,
|
||||
0x90, 0x0A, 0x84, 0x09, 0x88, 0x04, 0x83, 0x06, 0x84, 0x09, 0x88, 0x09,
|
||||
0x81, 0x09, 0x81, 0x01, 0x90, 0x0A, 0x84, 0x0B, 0x82, 0x08, 0x81, 0x09,
|
||||
0x88, 0x08, 0x82, 0x0C, 0x81, 0x06, 0x88, 0x06,
|
||||
};
|
||||
|
||||
// level O -- searched 2026-09 by the recorder of then (seed 20260918), from a
|
||||
// fresh game's first screen; kept because, ridden as the shift reaches
|
||||
// the screen, it covers more than the 2026-10-06 search did
|
||||
static const uint8_t kGateStreamO[320] = {
|
||||
0x84, 0x0E, 0x90, 0x08, 0x82, 0x07, 0x81, 0x0B, 0x84, 0x02, 0x81, 0x09,
|
||||
0x82, 0x06, 0x90, 0x0C, 0x81, 0x08, 0x88, 0x01, 0x88, 0x02, 0x90, 0x04,
|
||||
|
|
@ -146,40 +467,73 @@ static const uint8_t kGateStreamO[320] = {
|
|||
0x81, 0x0E, 0x81, 0x08, 0x90, 0x0D, 0x84, 0x0D,
|
||||
};
|
||||
|
||||
// level Q -- 1400 ticks, 5 hook bytes, 10 fare stages, 256 pictures, 10 landings
|
||||
// regenerate: tools/stsim/recorder levels/level??.dat Q 20260918
|
||||
static const uint8_t kGateStreamQ[320] = {
|
||||
0x84, 0x0E, 0x81, 0x04, 0x81, 0x03, 0x84, 0x09, 0x88, 0x0A, 0x81, 0x09,
|
||||
0x82, 0x06, 0x88, 0x0C, 0x81, 0x08, 0x82, 0x06, 0x88, 0x0E, 0x84, 0x0B,
|
||||
0x88, 0x08, 0x81, 0x09, 0x81, 0x0A, 0x84, 0x05, 0x84, 0x01, 0x88, 0x01,
|
||||
0x81, 0x08, 0x84, 0x0B, 0x81, 0x05, 0x82, 0x05, 0x80, 0x0D, 0x81, 0x08,
|
||||
0x88, 0x0A, 0x88, 0x04, 0x84, 0x08, 0x81, 0x06, 0x81, 0x0C, 0x81, 0x01,
|
||||
0x90, 0x0A, 0x84, 0x08, 0x81, 0x0A, 0x81, 0x0A, 0x82, 0x08, 0x81, 0x06,
|
||||
0x81, 0x09, 0x90, 0x06, 0x81, 0x09, 0x90, 0x0D, 0x88, 0x04, 0x82, 0x05,
|
||||
0x81, 0x0B, 0x84, 0x08, 0x81, 0x08, 0x81, 0x03, 0x82, 0x0D, 0x82, 0x0A,
|
||||
0x84, 0x0E, 0x81, 0x05, 0x81, 0x0E, 0x81, 0x08, 0x84, 0x0A, 0x82, 0x0C,
|
||||
0x84, 0x09, 0x84, 0x01, 0x82, 0x06, 0x88, 0x0E, 0x81, 0x09, 0x90, 0x07,
|
||||
0x81, 0x09, 0x81, 0x07, 0x84, 0x03, 0x81, 0x0B, 0x84, 0x01, 0x90, 0x08,
|
||||
0x90, 0x06, 0x82, 0x03, 0x88, 0x06, 0x88, 0x0C, 0x84, 0x0A, 0x81, 0x03,
|
||||
0x82, 0x0E, 0x81, 0x07, 0x81, 0x04, 0x81, 0x0D, 0x84, 0x02, 0x81, 0x09,
|
||||
0x82, 0x08, 0x88, 0x04, 0x81, 0x09, 0x84, 0x0C, 0x81, 0x0B, 0x84, 0x0D,
|
||||
0x81, 0x02, 0x82, 0x03, 0x81, 0x05, 0x88, 0x0E, 0x84, 0x0D, 0x81, 0x0B,
|
||||
0x88, 0x08, 0x84, 0x0C, 0x90, 0x07, 0x82, 0x0B, 0x81, 0x03, 0x90, 0x05,
|
||||
0x84, 0x05, 0x84, 0x09, 0x81, 0x04, 0x81, 0x0B, 0x81, 0x08, 0x82, 0x0E,
|
||||
0x81, 0x0A, 0x81, 0x03, 0x81, 0x06, 0x88, 0x09, 0x84, 0x0A, 0x88, 0x0B,
|
||||
0x81, 0x05, 0x81, 0x0D, 0x88, 0x03, 0x88, 0x0D, 0x81, 0x01, 0x90, 0x06,
|
||||
0x88, 0x07, 0x82, 0x02, 0x81, 0x04, 0x84, 0x09, 0x81, 0x07, 0x90, 0x06,
|
||||
0x82, 0x03, 0x84, 0x07, 0x81, 0x05, 0x90, 0x0B, 0x81, 0x0B, 0x88, 0x05,
|
||||
0x81, 0x0D, 0x81, 0x0B, 0x92, 0x0E, 0x90, 0x05, 0x82, 0x02, 0x88, 0x0C,
|
||||
0x84, 0x08, 0x81, 0x0A, 0x81, 0x0D, 0x81, 0x05, 0x81, 0x07, 0x81, 0x01,
|
||||
0x81, 0x04, 0x88, 0x01, 0x81, 0x0E, 0x84, 0x04, 0x90, 0x03, 0x81, 0x0D,
|
||||
0x90, 0x09, 0x81, 0x02, 0x81, 0x08, 0x90, 0x07, 0x82, 0x0B, 0x90, 0x08,
|
||||
0x81, 0x03, 0x90, 0x0C, 0x81, 0x0B, 0x88, 0x08, 0x90, 0x04, 0x81, 0x02,
|
||||
0x90, 0x0C, 0x88, 0x05, 0x81, 0x01, 0x90, 0x03,
|
||||
// level P -- 1400 ticks, 11 hook bytes, 10 fare stages, 256 pictures, 5 landings
|
||||
// regenerate: tools/stsim/recorder levels/level16.dat P 20261006 60 2000
|
||||
static const uint8_t kGateStreamP[320] = {
|
||||
0x81, 0x04, 0x88, 0x02, 0x84, 0x05, 0x89, 0x02, 0x81, 0x01, 0x88, 0x05,
|
||||
0x81, 0x04, 0x80, 0x0D, 0x84, 0x0C, 0x84, 0x03, 0x84, 0x08, 0x88, 0x07,
|
||||
0x88, 0x01, 0x81, 0x05, 0x88, 0x0E, 0x82, 0x01, 0x81, 0x0C, 0x81, 0x0C,
|
||||
0x88, 0x03, 0x80, 0x0C, 0x88, 0x0E, 0x88, 0x03, 0x84, 0x04, 0x84, 0x0E,
|
||||
0x81, 0x07, 0x88, 0x06, 0x80, 0x08, 0x84, 0x09, 0x81, 0x0E, 0x90, 0x0A,
|
||||
0x81, 0x03, 0x81, 0x05, 0x84, 0x09, 0x81, 0x01, 0x88, 0x07, 0x88, 0x08,
|
||||
0x81, 0x05, 0x81, 0x0C, 0x82, 0x0D, 0x88, 0x0E, 0x88, 0x07, 0x81, 0x02,
|
||||
0x81, 0x03, 0x84, 0x0C, 0x88, 0x06, 0x88, 0x05, 0x81, 0x07, 0x81, 0x0A,
|
||||
0x84, 0x09, 0x90, 0x06, 0x84, 0x09, 0x81, 0x0B, 0x88, 0x01, 0x81, 0x03,
|
||||
0x81, 0x06, 0x90, 0x0C, 0x88, 0x06, 0x81, 0x0B, 0x81, 0x06, 0x90, 0x06,
|
||||
0x82, 0x06, 0x82, 0x04, 0x82, 0x01, 0x81, 0x02, 0x81, 0x07, 0x90, 0x0E,
|
||||
0x90, 0x0B, 0x84, 0x07, 0x81, 0x03, 0x84, 0x0E, 0x84, 0x08, 0x84, 0x0C,
|
||||
0x81, 0x0D, 0x81, 0x0A, 0x81, 0x0E, 0x88, 0x0C, 0x88, 0x0B, 0x81, 0x05,
|
||||
0x90, 0x04, 0x88, 0x0A, 0x82, 0x0B, 0x84, 0x0D, 0x81, 0x0C, 0x84, 0x0C,
|
||||
0x90, 0x05, 0x84, 0x0D, 0x81, 0x02, 0x81, 0x08, 0x81, 0x03, 0x88, 0x06,
|
||||
0x90, 0x0E, 0x81, 0x05, 0x84, 0x0B, 0x88, 0x08, 0x81, 0x09, 0x84, 0x0E,
|
||||
0x81, 0x0A, 0x81, 0x05, 0x88, 0x07, 0x81, 0x02, 0x81, 0x0C, 0x81, 0x03,
|
||||
0x8A, 0x0C, 0x84, 0x0B, 0x80, 0x03, 0x85, 0x04, 0x81, 0x04, 0x82, 0x0A,
|
||||
0x85, 0x07, 0x84, 0x0D, 0x88, 0x08, 0x80, 0x07, 0x81, 0x01, 0x80, 0x0D,
|
||||
0x80, 0x04, 0x81, 0x05, 0x82, 0x01, 0x82, 0x0B, 0x84, 0x05, 0x88, 0x0A,
|
||||
0x90, 0x01, 0x90, 0x08, 0x81, 0x08, 0x81, 0x08, 0x81, 0x05, 0x81, 0x07,
|
||||
0x81, 0x01, 0x81, 0x0D, 0x88, 0x02, 0x81, 0x01, 0x88, 0x03, 0x90, 0x02,
|
||||
0x88, 0x0A, 0x81, 0x0D, 0x82, 0x05, 0x88, 0x01, 0x81, 0x02, 0x85, 0x0A,
|
||||
0x84, 0x0C, 0x88, 0x0C, 0x88, 0x05, 0x88, 0x03, 0x90, 0x08, 0x81, 0x08,
|
||||
0x84, 0x0E, 0x82, 0x0C, 0x84, 0x0E, 0x90, 0x0C, 0x82, 0x0E, 0x90, 0x08,
|
||||
0x84, 0x0D, 0x88, 0x0B, 0x84, 0x0C, 0x82, 0x0C, 0x84, 0x08, 0x90, 0x0A,
|
||||
0x88, 0x0E, 0x81, 0x07, 0x90, 0x03, 0x88, 0x0C,
|
||||
};
|
||||
|
||||
// level R -- 1400 ticks, 0 hook bytes, 3 fare stages, 256 pictures, 0 landings
|
||||
// regenerate: tools/stsim/recorder levels/level??.dat R 20260918
|
||||
// level Q -- 1400 ticks, 6 hook bytes, 10 fare stages, 256 pictures, 6 landings
|
||||
// regenerate: tools/stsim/recorder levels/level17.dat Q 20261006 60 2000
|
||||
static const uint8_t kGateStreamQ[320] = {
|
||||
0x81, 0x04, 0x88, 0x02, 0x84, 0x0E, 0x89, 0x06, 0x81, 0x01, 0x82, 0x05,
|
||||
0x81, 0x04, 0x80, 0x05, 0x84, 0x0C, 0x82, 0x0C, 0x81, 0x06, 0x88, 0x07,
|
||||
0x81, 0x06, 0x81, 0x0B, 0x88, 0x0E, 0x84, 0x07, 0x88, 0x0E, 0x84, 0x06,
|
||||
0x81, 0x0A, 0x81, 0x05, 0x81, 0x0C, 0x81, 0x0D, 0x81, 0x09, 0x88, 0x0D,
|
||||
0x86, 0x0A, 0x81, 0x09, 0x84, 0x09, 0x88, 0x0E, 0x81, 0x05, 0x88, 0x06,
|
||||
0x80, 0x09, 0x84, 0x04, 0x82, 0x0A, 0x84, 0x0C, 0x81, 0x0E, 0x90, 0x09,
|
||||
0x81, 0x0D, 0x84, 0x07, 0x90, 0x03, 0x82, 0x04, 0x82, 0x06, 0x81, 0x0E,
|
||||
0x84, 0x09, 0x88, 0x06, 0x88, 0x0C, 0x88, 0x07, 0x81, 0x02, 0x84, 0x0D,
|
||||
0x88, 0x04, 0x81, 0x05, 0x88, 0x05, 0x81, 0x05, 0x88, 0x02, 0x90, 0x0E,
|
||||
0x81, 0x06, 0x84, 0x02, 0x81, 0x02, 0x84, 0x05, 0x81, 0x0C, 0x90, 0x05,
|
||||
0x81, 0x0C, 0x81, 0x09, 0x81, 0x07, 0x82, 0x04, 0x81, 0x05, 0x81, 0x04,
|
||||
0x81, 0x0A, 0x82, 0x0E, 0x90, 0x09, 0x81, 0x07, 0x90, 0x0C, 0x82, 0x06,
|
||||
0x82, 0x08, 0x81, 0x01, 0x82, 0x0B, 0x81, 0x01, 0x81, 0x07, 0x81, 0x07,
|
||||
0x90, 0x03, 0x81, 0x01, 0x88, 0x01, 0x81, 0x0B, 0x81, 0x07, 0x84, 0x06,
|
||||
0x88, 0x01, 0x81, 0x05, 0x81, 0x08, 0x88, 0x0E, 0x81, 0x08, 0x90, 0x05,
|
||||
0x82, 0x07, 0x84, 0x0A, 0x81, 0x01, 0x81, 0x0D, 0x81, 0x0E, 0x84, 0x06,
|
||||
0x90, 0x0A, 0x81, 0x06, 0x82, 0x06, 0x84, 0x01, 0x90, 0x03, 0x90, 0x04,
|
||||
0x81, 0x03, 0x81, 0x08, 0x90, 0x05, 0x82, 0x05, 0x88, 0x06, 0x88, 0x06,
|
||||
0x81, 0x0B, 0x84, 0x05, 0x84, 0x0E, 0x82, 0x0E, 0x81, 0x0E, 0x81, 0x0C,
|
||||
0x81, 0x05, 0x90, 0x0C, 0x81, 0x0C, 0x88, 0x0B, 0x81, 0x05, 0x88, 0x03,
|
||||
0x81, 0x04, 0x88, 0x06, 0x88, 0x07, 0x88, 0x05, 0x84, 0x06, 0x90, 0x0A,
|
||||
0x82, 0x0B, 0x82, 0x07, 0x81, 0x01, 0x89, 0x0D, 0x88, 0x02, 0x84, 0x04,
|
||||
0x81, 0x01, 0x88, 0x09, 0x84, 0x0B, 0x84, 0x07, 0x90, 0x0E, 0x82, 0x01,
|
||||
0x81, 0x0C, 0x84, 0x0E, 0x81, 0x02, 0x84, 0x0B, 0x81, 0x08, 0x82, 0x09,
|
||||
0x81, 0x0E, 0x81, 0x0D, 0x90, 0x06, 0x81, 0x0D, 0x90, 0x0E, 0x81, 0x0A,
|
||||
0x81, 0x0E, 0x81, 0x02, 0x81, 0x05, 0x88, 0x05, 0x88, 0x08, 0x84, 0x09,
|
||||
0x84, 0x0D, 0x80, 0x0A, 0x84, 0x02, 0x81, 0x0C,
|
||||
};
|
||||
|
||||
// level R -- searched 2026-09 by the recorder of then (seed 20260918), from a
|
||||
// fresh game's first screen; kept because, ridden as the shift reaches
|
||||
// the screen, it covers more than the 2026-10-06 search did
|
||||
static const uint8_t kGateStreamR[320] = {
|
||||
0x90, 0x0E, 0x81, 0x02, 0x82, 0x07, 0x81, 0x05, 0x88, 0x0B, 0x88, 0x09,
|
||||
0x90, 0x0D, 0x90, 0x06, 0x81, 0x08, 0x81, 0x01, 0x90, 0x02, 0x88, 0x04,
|
||||
|
|
@ -210,228 +564,132 @@ static const uint8_t kGateStreamR[320] = {
|
|||
0x90, 0x0A, 0x90, 0x01, 0x84, 0x0A, 0x81, 0x0B,
|
||||
};
|
||||
|
||||
// level U -- 896 ticks, 21 hook bytes, 3 fare stages, 256 pictures, 0 landings
|
||||
// regenerate: tools/stsim/recorder levels/level??.dat U 44
|
||||
static const uint8_t kGateStreamU[320] = {
|
||||
0x90, 0x01, 0x90, 0x0B, 0x88, 0x08, 0x90, 0x07, 0x88, 0x0D, 0x82, 0x0A,
|
||||
0x82, 0x06, 0x88, 0x0C, 0x90, 0x03, 0x81, 0x04, 0x84, 0x0B, 0x81, 0x0C,
|
||||
0x88, 0x02, 0x84, 0x08, 0x84, 0x01, 0x81, 0x04, 0x90, 0x05, 0x84, 0x06,
|
||||
0x84, 0x08, 0x84, 0x05, 0x81, 0x03, 0x81, 0x09, 0x81, 0x08, 0x88, 0x07,
|
||||
0x81, 0x08, 0x84, 0x06, 0x81, 0x0B, 0x88, 0x0D, 0x90, 0x08, 0x81, 0x01,
|
||||
0x82, 0x01, 0x88, 0x03, 0x81, 0x0E, 0x82, 0x01, 0x81, 0x0D, 0x81, 0x07,
|
||||
0x88, 0x05, 0x88, 0x07, 0x88, 0x0E, 0x84, 0x07, 0x81, 0x03, 0x81, 0x01,
|
||||
0x84, 0x03, 0x81, 0x04, 0x81, 0x08, 0x90, 0x06, 0x81, 0x0C, 0x82, 0x05,
|
||||
0x81, 0x08, 0x82, 0x09, 0x84, 0x06, 0x81, 0x06, 0x90, 0x0C, 0x84, 0x0E,
|
||||
0x82, 0x0C, 0x81, 0x03, 0x81, 0x06, 0x84, 0x0D, 0x88, 0x03, 0x84, 0x0C,
|
||||
0x81, 0x02, 0x81, 0x09, 0x81, 0x0E, 0x88, 0x01, 0x90, 0x06, 0x88, 0x08,
|
||||
0x81, 0x08, 0x81, 0x0C, 0x81, 0x0C, 0x82, 0x06, 0x82, 0x08, 0x88, 0x01,
|
||||
0x81, 0x07, 0x88, 0x0D, 0x84, 0x03, 0x81, 0x02, 0x82, 0x06, 0x81, 0x05,
|
||||
0x81, 0x0E, 0x81, 0x04, 0x90, 0x01, 0x81, 0x02, 0x90, 0x03, 0x81, 0x0A,
|
||||
0x81, 0x04, 0x84, 0x02, 0x81, 0x06, 0x81, 0x0E, 0x81, 0x06, 0x84, 0x07,
|
||||
0x84, 0x04, 0x81, 0x07, 0x81, 0x0E, 0x81, 0x0D, 0x90, 0x0D, 0x90, 0x05,
|
||||
0x88, 0x0C, 0x88, 0x02, 0x81, 0x0B, 0x90, 0x07, 0x88, 0x06, 0x82, 0x0D,
|
||||
0x84, 0x08, 0x84, 0x08, 0x81, 0x01, 0x81, 0x06, 0x82, 0x05, 0x84, 0x06,
|
||||
0x81, 0x06, 0x84, 0x0D, 0x81, 0x04, 0x90, 0x07, 0x88, 0x0D, 0x81, 0x09,
|
||||
0x88, 0x0D, 0x81, 0x0E, 0x81, 0x03, 0x84, 0x0E, 0x81, 0x03, 0x81, 0x0C,
|
||||
0x84, 0x0A, 0x81, 0x06, 0x90, 0x02, 0x81, 0x01, 0x88, 0x02, 0x90, 0x02,
|
||||
0x81, 0x08, 0x84, 0x01, 0x88, 0x0B, 0x88, 0x0B, 0x81, 0x04, 0x81, 0x0C,
|
||||
0x81, 0x07, 0x82, 0x06, 0x88, 0x0C, 0x81, 0x03, 0x90, 0x02, 0x88, 0x0A,
|
||||
0x84, 0x08, 0x81, 0x0E, 0x81, 0x05, 0x88, 0x0C, 0x81, 0x0D, 0x81, 0x05,
|
||||
0x81, 0x04, 0x90, 0x0D, 0x88, 0x03, 0x88, 0x06, 0x81, 0x0B, 0x81, 0x0D,
|
||||
0x84, 0x0D, 0x81, 0x01, 0x81, 0x0D, 0x81, 0x01, 0x82, 0x07, 0x88, 0x02,
|
||||
0x81, 0x09, 0x90, 0x0B, 0x88, 0x0D, 0x82, 0x02,
|
||||
};
|
||||
|
||||
// level V -- 1400 ticks, 0 hook bytes, 6 fare stages, 256 pictures, 23 landings
|
||||
// regenerate: tools/stsim/recorder levels/level??.dat V 20260918
|
||||
static const uint8_t kGateStreamV[320] = {
|
||||
0x84, 0x0E, 0x81, 0x04, 0x81, 0x05, 0x88, 0x0B, 0x84, 0x01, 0x81, 0x03,
|
||||
0x82, 0x06, 0x84, 0x0C, 0x81, 0x08, 0x84, 0x07, 0x82, 0x03, 0x88, 0x0A,
|
||||
0x90, 0x03, 0x81, 0x0D, 0x81, 0x0D, 0x82, 0x05, 0x81, 0x02, 0x81, 0x0E,
|
||||
0x90, 0x06, 0x81, 0x05, 0x90, 0x05, 0x81, 0x0E, 0x82, 0x0D, 0x90, 0x0E,
|
||||
0x88, 0x0E, 0x81, 0x03, 0x90, 0x07, 0x84, 0x0A, 0x81, 0x02, 0x90, 0x07,
|
||||
0x82, 0x07, 0x81, 0x02, 0x81, 0x06, 0x82, 0x08, 0x81, 0x04, 0x88, 0x0C,
|
||||
0x81, 0x06, 0x88, 0x0D, 0x90, 0x0D, 0x81, 0x05, 0x90, 0x03, 0x84, 0x0D,
|
||||
0x84, 0x0A, 0x88, 0x0A, 0x88, 0x0D, 0x81, 0x08, 0x90, 0x08, 0x90, 0x01,
|
||||
0x84, 0x06, 0x82, 0x0B, 0x81, 0x0D, 0x82, 0x07, 0x82, 0x0C, 0x81, 0x01,
|
||||
0x90, 0x08, 0x81, 0x02, 0x81, 0x04, 0x90, 0x0C, 0x84, 0x04, 0x88, 0x04,
|
||||
0x82, 0x03, 0x88, 0x07, 0x81, 0x03, 0x90, 0x05, 0x81, 0x02, 0x81, 0x03,
|
||||
0x88, 0x01, 0x90, 0x05, 0x81, 0x08, 0x81, 0x06, 0x81, 0x07, 0x81, 0x08,
|
||||
0x81, 0x06, 0x81, 0x02, 0x84, 0x0B, 0x88, 0x0D, 0x81, 0x06, 0x90, 0x02,
|
||||
0x81, 0x07, 0x82, 0x0B, 0x88, 0x04, 0x84, 0x04, 0x84, 0x05, 0x81, 0x05,
|
||||
0x81, 0x02, 0x82, 0x09, 0x81, 0x03, 0x90, 0x0C, 0x88, 0x0B, 0x82, 0x01,
|
||||
0x88, 0x0D, 0x88, 0x05, 0x81, 0x02, 0x90, 0x0B, 0x81, 0x02, 0x90, 0x0A,
|
||||
0x88, 0x0C, 0x81, 0x01, 0x90, 0x0A, 0x88, 0x0E, 0x90, 0x0A, 0x84, 0x0B,
|
||||
0x81, 0x03, 0x90, 0x08, 0x88, 0x03, 0x82, 0x0E, 0x84, 0x06, 0x90, 0x0E,
|
||||
0x81, 0x03, 0x88, 0x0A, 0x90, 0x0A, 0x82, 0x09, 0x81, 0x04, 0x88, 0x0C,
|
||||
0x84, 0x04, 0x81, 0x07, 0x90, 0x03, 0x81, 0x01, 0x81, 0x01, 0x81, 0x01,
|
||||
0x82, 0x0E, 0x81, 0x05, 0x81, 0x06, 0x81, 0x08, 0x84, 0x08, 0x88, 0x0C,
|
||||
0x82, 0x09, 0x88, 0x05, 0x81, 0x02, 0x88, 0x04, 0x81, 0x04, 0x88, 0x0C,
|
||||
0x90, 0x07, 0x88, 0x0E, 0x81, 0x03, 0x81, 0x0A, 0x82, 0x0B, 0x81, 0x02,
|
||||
0x88, 0x02, 0x90, 0x0E, 0x88, 0x0D, 0x81, 0x03, 0x88, 0x09, 0x90, 0x04,
|
||||
0x84, 0x01, 0x88, 0x04, 0x90, 0x04, 0x84, 0x04, 0x81, 0x03, 0x90, 0x0C,
|
||||
0x88, 0x07, 0x84, 0x05, 0x81, 0x02, 0x90, 0x0B, 0x90, 0x04, 0x90, 0x09,
|
||||
0x81, 0x02, 0x81, 0x01, 0x90, 0x0B, 0x88, 0x04,
|
||||
};
|
||||
|
||||
// level E -- 1400 ticks, 4 hook bytes, 8 fare stages, 256 pictures, 11 landings
|
||||
// regenerate: tools/stsim/recorder levels/level??.dat E 20260921
|
||||
static const uint8_t kGateStreamE[320] = {
|
||||
0x84, 0x05, 0x82, 0x03, 0x82, 0x05, 0x81, 0x0B, 0x81, 0x07, 0x81, 0x02,
|
||||
0x81, 0x01, 0x89, 0x02, 0x90, 0x03, 0x81, 0x0A, 0x81, 0x05, 0x84, 0x0C,
|
||||
0x81, 0x02, 0x88, 0x07, 0x81, 0x0B, 0x90, 0x08, 0x90, 0x0A, 0x88, 0x02,
|
||||
0x81, 0x05, 0x84, 0x08, 0x81, 0x05, 0x90, 0x03, 0x84, 0x01, 0x84, 0x08,
|
||||
0x88, 0x0A, 0x84, 0x07, 0x81, 0x04, 0x84, 0x07, 0x90, 0x0C, 0x80, 0x08,
|
||||
0x90, 0x07, 0x88, 0x03, 0x90, 0x0B, 0x88, 0x01, 0x90, 0x07, 0x88, 0x04,
|
||||
0x81, 0x02, 0x88, 0x08, 0x84, 0x05, 0x81, 0x05, 0x81, 0x02, 0x81, 0x09,
|
||||
0x88, 0x05, 0x90, 0x0E, 0x81, 0x05, 0x81, 0x01, 0x90, 0x04, 0x90, 0x08,
|
||||
0x81, 0x06, 0x84, 0x0E, 0x81, 0x09, 0x81, 0x01, 0x84, 0x05, 0x81, 0x01,
|
||||
0x81, 0x02, 0x88, 0x0B, 0x90, 0x08, 0x88, 0x0C, 0x88, 0x05, 0x88, 0x08,
|
||||
0x84, 0x09, 0x88, 0x05, 0x90, 0x05, 0x82, 0x0A, 0x81, 0x07, 0x90, 0x0A,
|
||||
0x81, 0x07, 0x81, 0x03, 0x84, 0x0B, 0x84, 0x0E, 0x88, 0x09, 0x90, 0x02,
|
||||
0x81, 0x0E, 0x81, 0x06, 0x90, 0x0E, 0x81, 0x02, 0x90, 0x08, 0x84, 0x08,
|
||||
0x81, 0x0B, 0x88, 0x0D, 0x84, 0x0C, 0x81, 0x09, 0x90, 0x08, 0x82, 0x0B,
|
||||
0x88, 0x08, 0x81, 0x07, 0x82, 0x02, 0x84, 0x0B, 0x88, 0x0B, 0x81, 0x0D,
|
||||
0x84, 0x0E, 0x82, 0x01, 0x81, 0x03, 0x90, 0x0C, 0x81, 0x05, 0x90, 0x04,
|
||||
0x82, 0x0C, 0x81, 0x06, 0x90, 0x0E, 0x88, 0x0B, 0x88, 0x09, 0x81, 0x0E,
|
||||
0x82, 0x0E, 0x90, 0x0C, 0x81, 0x06, 0x81, 0x05, 0x84, 0x0E, 0x81, 0x0C,
|
||||
0x88, 0x0B, 0x90, 0x06, 0x82, 0x05, 0x81, 0x06, 0x88, 0x0D, 0x88, 0x07,
|
||||
0x81, 0x07, 0x84, 0x07, 0x88, 0x08, 0x81, 0x07, 0x90, 0x0C, 0x84, 0x04,
|
||||
0x84, 0x0A, 0x88, 0x0B, 0x80, 0x0A, 0x81, 0x0C, 0x81, 0x07, 0x88, 0x06,
|
||||
0x88, 0x09, 0x88, 0x09, 0x81, 0x0C, 0x80, 0x06, 0x81, 0x06, 0x81, 0x07,
|
||||
0x84, 0x08, 0x82, 0x09, 0x84, 0x0C, 0x90, 0x09, 0x90, 0x03, 0x90, 0x0D,
|
||||
0x81, 0x08, 0x84, 0x05, 0x84, 0x09, 0x88, 0x0B, 0x81, 0x01, 0x81, 0x0A,
|
||||
0x90, 0x0D, 0x88, 0x0E, 0x88, 0x0D, 0x88, 0x05, 0x84, 0x06, 0x81, 0x0E,
|
||||
0x81, 0x0A, 0x81, 0x08, 0x84, 0x0C, 0x88, 0x05, 0x81, 0x02, 0x90, 0x0E,
|
||||
0x84, 0x0D, 0x84, 0x0A, 0x81, 0x05, 0x90, 0x09,
|
||||
};
|
||||
|
||||
// level J -- 1400 ticks, 10 hook bytes, 10 fare stages, 256 pictures, 8 landings
|
||||
// regenerate: tools/stsim/recorder levels/level??.dat J 20260921
|
||||
static const uint8_t kGateStreamJ[320] = {
|
||||
0x88, 0x05, 0x82, 0x09, 0x81, 0x01, 0x81, 0x04, 0x90, 0x06, 0x81, 0x05,
|
||||
0x81, 0x01, 0x90, 0x09, 0x88, 0x05, 0x82, 0x08, 0x81, 0x0A, 0x81, 0x03,
|
||||
0x84, 0x07, 0x88, 0x0D, 0x81, 0x06, 0x81, 0x0A, 0x84, 0x05, 0x90, 0x08,
|
||||
0x81, 0x0B, 0x90, 0x0E, 0x84, 0x0E, 0x81, 0x03, 0x90, 0x09, 0x81, 0x02,
|
||||
0x88, 0x04, 0x88, 0x08, 0x84, 0x0E, 0x81, 0x04, 0x88, 0x0D, 0x90, 0x0E,
|
||||
0x84, 0x09, 0x90, 0x05, 0x81, 0x01, 0x81, 0x03, 0x88, 0x03, 0x88, 0x07,
|
||||
0x81, 0x0B, 0x90, 0x0B, 0x88, 0x08, 0x82, 0x03, 0x90, 0x09, 0x88, 0x03,
|
||||
0x84, 0x06, 0x88, 0x0A, 0x88, 0x0B, 0x84, 0x04, 0x81, 0x05, 0x88, 0x0E,
|
||||
0x81, 0x0A, 0x90, 0x0C, 0x85, 0x04, 0x81, 0x0E, 0x90, 0x02, 0x82, 0x0D,
|
||||
0x84, 0x01, 0x81, 0x02, 0x88, 0x09, 0x81, 0x0B, 0x81, 0x02, 0x82, 0x0C,
|
||||
0x84, 0x04, 0x81, 0x05, 0x81, 0x04, 0x81, 0x04, 0x81, 0x05, 0x90, 0x05,
|
||||
0x88, 0x09, 0x81, 0x02, 0x88, 0x01, 0x81, 0x05, 0x90, 0x04, 0x81, 0x0B,
|
||||
0x81, 0x05, 0x82, 0x0D, 0x88, 0x06, 0x88, 0x07, 0x88, 0x02, 0x84, 0x02,
|
||||
0x90, 0x07, 0x88, 0x09, 0x84, 0x0C, 0x81, 0x01, 0x88, 0x0B, 0x81, 0x0C,
|
||||
0x82, 0x0A, 0x81, 0x08, 0x82, 0x01, 0x88, 0x09, 0x84, 0x05, 0x81, 0x08,
|
||||
0x84, 0x0A, 0x84, 0x09, 0x88, 0x0E, 0x81, 0x01, 0x84, 0x03, 0x82, 0x0C,
|
||||
0x81, 0x0E, 0x84, 0x0A, 0x88, 0x02, 0x84, 0x0D, 0x81, 0x0A, 0x90, 0x0E,
|
||||
0x88, 0x09, 0x90, 0x09, 0x81, 0x05, 0x90, 0x03, 0x88, 0x09, 0x84, 0x06,
|
||||
0x81, 0x01, 0x88, 0x06, 0x81, 0x05, 0x81, 0x09, 0x81, 0x06, 0x90, 0x04,
|
||||
0x82, 0x0A, 0x90, 0x07, 0x81, 0x07, 0x81, 0x0A, 0x84, 0x0E, 0x84, 0x07,
|
||||
0x81, 0x0A, 0x90, 0x0C, 0x81, 0x01, 0x90, 0x01, 0x88, 0x0D, 0x81, 0x03,
|
||||
0x88, 0x05, 0x81, 0x03, 0x88, 0x01, 0x81, 0x01, 0x82, 0x02, 0x88, 0x07,
|
||||
0x83, 0x02, 0x90, 0x07, 0x81, 0x05, 0x81, 0x04, 0x81, 0x0E, 0x88, 0x09,
|
||||
0x81, 0x07, 0x90, 0x03, 0x82, 0x0B, 0x82, 0x0B, 0x90, 0x04, 0x81, 0x05,
|
||||
0x81, 0x03, 0x90, 0x0C, 0x82, 0x07, 0x88, 0x0E, 0x90, 0x04, 0x81, 0x0A,
|
||||
0x88, 0x02, 0x88, 0x0D, 0x82, 0x07, 0x90, 0x01, 0x81, 0x02, 0x81, 0x01,
|
||||
0x82, 0x07, 0x81, 0x08, 0x84, 0x0E, 0x81, 0x03,
|
||||
};
|
||||
|
||||
// level L -- 1400 ticks, 0 hook bytes, 10 fare stages, 256 pictures, 8 landings
|
||||
// regenerate: tools/stsim/recorder levels/level??.dat L 20260921
|
||||
static const uint8_t kGateStreamL[320] = {
|
||||
0x84, 0x05, 0x84, 0x09, 0x81, 0x06, 0x81, 0x04, 0x81, 0x08, 0x81, 0x05,
|
||||
0x81, 0x01, 0x84, 0x09, 0x90, 0x09, 0x82, 0x0D, 0x88, 0x07, 0x81, 0x06,
|
||||
0x81, 0x09, 0x82, 0x01, 0x81, 0x08, 0x88, 0x0B, 0x84, 0x0E, 0x81, 0x03,
|
||||
0x82, 0x02, 0x82, 0x06, 0x82, 0x03, 0x88, 0x0E, 0x84, 0x0C, 0x81, 0x06,
|
||||
0x88, 0x02, 0x81, 0x04, 0x88, 0x0E, 0x81, 0x04, 0x90, 0x0B, 0x81, 0x08,
|
||||
0x84, 0x09, 0x82, 0x09, 0x81, 0x01, 0x90, 0x03, 0x81, 0x05, 0x84, 0x07,
|
||||
0x81, 0x07, 0x90, 0x0B, 0x81, 0x07, 0x88, 0x09, 0x81, 0x0E, 0x88, 0x03,
|
||||
0x81, 0x01, 0x88, 0x09, 0x81, 0x07, 0x81, 0x0C, 0x88, 0x0C, 0x90, 0x0E,
|
||||
0x81, 0x0E, 0x90, 0x05, 0x84, 0x0E, 0x82, 0x0E, 0x82, 0x01, 0x84, 0x0A,
|
||||
0x88, 0x03, 0x90, 0x09, 0x82, 0x05, 0x81, 0x0E, 0x81, 0x05, 0x81, 0x07,
|
||||
0x81, 0x0D, 0x81, 0x0E, 0x90, 0x04, 0x90, 0x04, 0x81, 0x06, 0x81, 0x01,
|
||||
0x88, 0x0B, 0x81, 0x01, 0x90, 0x0B, 0x81, 0x05, 0x84, 0x0D, 0x81, 0x01,
|
||||
0x81, 0x0A, 0x84, 0x0D, 0x82, 0x09, 0x88, 0x06, 0x88, 0x04, 0x84, 0x0C,
|
||||
0x88, 0x0C, 0x84, 0x02, 0x81, 0x07, 0x82, 0x07, 0x84, 0x08, 0x88, 0x09,
|
||||
0x81, 0x01, 0x88, 0x01, 0x81, 0x0C, 0x81, 0x04, 0x84, 0x06, 0x82, 0x06,
|
||||
0x90, 0x0D, 0x81, 0x06, 0x81, 0x0D, 0x82, 0x0D, 0x82, 0x08, 0x81, 0x0C,
|
||||
0x84, 0x05, 0x82, 0x09, 0x81, 0x05, 0x84, 0x07, 0x82, 0x02, 0x84, 0x01,
|
||||
0x81, 0x02, 0x90, 0x02, 0x82, 0x06, 0x88, 0x03, 0x84, 0x0B, 0x81, 0x0E,
|
||||
0x88, 0x0E, 0x90, 0x07, 0x82, 0x08, 0x82, 0x02, 0x90, 0x02, 0x82, 0x03,
|
||||
0x84, 0x07, 0x82, 0x05, 0x84, 0x07, 0x81, 0x0B, 0x82, 0x0E, 0x90, 0x05,
|
||||
0x88, 0x04, 0x90, 0x05, 0x84, 0x0A, 0x82, 0x0A, 0x88, 0x0C, 0x81, 0x08,
|
||||
0x82, 0x0C, 0x81, 0x06, 0x84, 0x09, 0x81, 0x04, 0x84, 0x0B, 0x88, 0x0B,
|
||||
0x81, 0x0C, 0x81, 0x0C, 0x82, 0x0E, 0x81, 0x03, 0x82, 0x09, 0x88, 0x04,
|
||||
0x81, 0x05, 0x88, 0x04, 0x80, 0x0B, 0x84, 0x06, 0x81, 0x0E, 0x84, 0x0B,
|
||||
0x90, 0x0B, 0x90, 0x07, 0x82, 0x0D, 0x84, 0x0D, 0x81, 0x08, 0x81, 0x09,
|
||||
0x84, 0x0E, 0x81, 0x0B, 0x90, 0x04, 0x84, 0x0B, 0x84, 0x03, 0x81, 0x03,
|
||||
0x88, 0x0A, 0x81, 0x07, 0x84, 0x0B, 0x90, 0x0E,
|
||||
};
|
||||
|
||||
// level P -- 1400 ticks, 11 hook bytes, 10 fare stages, 256 pictures, 4 landings
|
||||
// regenerate: tools/stsim/recorder levels/level??.dat P 20260921
|
||||
static const uint8_t kGateStreamP[320] = {
|
||||
0x88, 0x05, 0x82, 0x09, 0x81, 0x0B, 0x81, 0x04, 0x81, 0x04, 0x84, 0x05,
|
||||
0x82, 0x01, 0x90, 0x0C, 0x81, 0x03, 0x81, 0x0D, 0x81, 0x01, 0x84, 0x0C,
|
||||
0x84, 0x0E, 0x88, 0x07, 0x81, 0x0B, 0x88, 0x0D, 0x90, 0x0A, 0x88, 0x02,
|
||||
0x81, 0x05, 0x84, 0x08, 0x81, 0x05, 0x90, 0x0C, 0x84, 0x01, 0x88, 0x08,
|
||||
0x88, 0x0A, 0x84, 0x0C, 0x81, 0x04, 0x84, 0x07, 0x90, 0x0C, 0x80, 0x09,
|
||||
0x90, 0x06, 0x81, 0x03, 0x81, 0x0C, 0x88, 0x01, 0x82, 0x03, 0x81, 0x04,
|
||||
0x84, 0x0D, 0x88, 0x09, 0x84, 0x05, 0x81, 0x05, 0x81, 0x02, 0x81, 0x09,
|
||||
0x81, 0x02, 0x90, 0x0E, 0x81, 0x05, 0x81, 0x01, 0x81, 0x03, 0x88, 0x0A,
|
||||
0x81, 0x06, 0x88, 0x05, 0x90, 0x0C, 0x90, 0x01, 0x84, 0x0E, 0x88, 0x01,
|
||||
0x81, 0x02, 0x82, 0x05, 0x90, 0x08, 0x88, 0x0A, 0x84, 0x0C, 0x90, 0x08,
|
||||
0x84, 0x0C, 0x81, 0x06, 0x81, 0x03, 0x88, 0x0A, 0x84, 0x08, 0x90, 0x0E,
|
||||
0x81, 0x02, 0x88, 0x0C, 0x81, 0x06, 0x84, 0x0C, 0x81, 0x09, 0x80, 0x07,
|
||||
0x88, 0x04, 0x81, 0x07, 0x81, 0x03, 0x81, 0x09, 0x81, 0x05, 0x86, 0x05,
|
||||
0x90, 0x09, 0x84, 0x06, 0x81, 0x03, 0x84, 0x09, 0x81, 0x0E, 0x88, 0x0B,
|
||||
0x90, 0x08, 0x88, 0x0A, 0x81, 0x04, 0x90, 0x0D, 0x88, 0x0E, 0x88, 0x09,
|
||||
0x81, 0x0B, 0x90, 0x0C, 0x84, 0x05, 0x81, 0x08, 0x84, 0x01, 0x82, 0x02,
|
||||
0x81, 0x03, 0x82, 0x0E, 0x88, 0x02, 0x88, 0x05, 0x81, 0x01, 0x81, 0x08,
|
||||
0x82, 0x03, 0x84, 0x0E, 0x81, 0x01, 0x81, 0x08, 0x84, 0x03, 0x82, 0x03,
|
||||
0x82, 0x05, 0x81, 0x0A, 0x81, 0x02, 0x90, 0x01, 0x81, 0x0E, 0x81, 0x0C,
|
||||
0x90, 0x0C, 0x88, 0x0A, 0x84, 0x03, 0x81, 0x03, 0x84, 0x0A, 0x84, 0x05,
|
||||
0x82, 0x09, 0x88, 0x0A, 0x81, 0x06, 0x84, 0x07, 0x84, 0x0C, 0x81, 0x0C,
|
||||
0x82, 0x07, 0x81, 0x06, 0x81, 0x03, 0x88, 0x09, 0x90, 0x01, 0x84, 0x0E,
|
||||
0x81, 0x01, 0x88, 0x06, 0x81, 0x0E, 0x81, 0x0A, 0x88, 0x0A, 0x81, 0x08,
|
||||
0x84, 0x08, 0x88, 0x0E, 0x81, 0x02, 0x84, 0x0E, 0x82, 0x05, 0x81, 0x06,
|
||||
0x88, 0x0C, 0x81, 0x0B, 0x81, 0x0D, 0x84, 0x02, 0x90, 0x0E, 0x84, 0x0E,
|
||||
0x81, 0x02, 0x90, 0x07, 0x88, 0x0C, 0x90, 0x0A, 0x81, 0x0E, 0x81, 0x0A,
|
||||
0x81, 0x0D, 0x81, 0x0B, 0x81, 0x08, 0x81, 0x03,
|
||||
};
|
||||
|
||||
// level S -- 1400 ticks, 0 hook bytes, 10 fare stages, 256 pictures, 23 landings
|
||||
// regenerate: tools/stsim/recorder levels/level??.dat S 20260921
|
||||
// level S -- 1400 ticks, 0 hook bytes, 10 fare stages, 256 pictures, 25 landings
|
||||
// regenerate: tools/stsim/recorder levels/level19.dat S 20261006 60 2000
|
||||
static const uint8_t kGateStreamS[320] = {
|
||||
0x90, 0x04, 0x90, 0x0E, 0x88, 0x08, 0x82, 0x0D, 0x88, 0x0C, 0x81, 0x03,
|
||||
0x81, 0x0A, 0x81, 0x0D, 0x88, 0x09, 0x88, 0x0C, 0x82, 0x0E, 0x81, 0x07,
|
||||
0x81, 0x0D, 0x81, 0x0A, 0x90, 0x0D, 0x84, 0x01, 0x90, 0x05, 0x88, 0x0E,
|
||||
0x81, 0x0A, 0x81, 0x0A, 0x90, 0x06, 0x88, 0x03, 0x82, 0x0B, 0x82, 0x0B,
|
||||
0x88, 0x03, 0x88, 0x0A, 0x90, 0x0C, 0x81, 0x0E, 0x81, 0x0E, 0x90, 0x06,
|
||||
0x88, 0x02, 0x82, 0x08, 0x81, 0x0A, 0x90, 0x0D, 0x90, 0x05, 0x81, 0x05,
|
||||
0x88, 0x0B, 0x81, 0x05, 0x90, 0x0B, 0x88, 0x0E, 0x82, 0x09, 0x90, 0x07,
|
||||
0x81, 0x03, 0x81, 0x0D, 0x88, 0x09, 0x88, 0x0E, 0x84, 0x0A, 0x88, 0x0A,
|
||||
0x82, 0x01, 0x90, 0x0C, 0x82, 0x01, 0x90, 0x0A, 0x82, 0x0A, 0x84, 0x03,
|
||||
0x81, 0x02, 0x90, 0x04, 0x84, 0x0E, 0x90, 0x09, 0x81, 0x0D, 0x90, 0x06,
|
||||
0x81, 0x02, 0x90, 0x0E, 0x90, 0x07, 0x82, 0x05, 0x81, 0x06, 0x90, 0x07,
|
||||
0x82, 0x03, 0x90, 0x07, 0x90, 0x0B, 0x82, 0x06, 0x88, 0x0E, 0x90, 0x08,
|
||||
0x84, 0x0A, 0x82, 0x07, 0x81, 0x04, 0x81, 0x03, 0x84, 0x05, 0x90, 0x08,
|
||||
0x81, 0x0C, 0x82, 0x01, 0x90, 0x0B, 0x81, 0x0A, 0x84, 0x05, 0x81, 0x02,
|
||||
0x82, 0x09, 0x90, 0x0E, 0x81, 0x02, 0x84, 0x03, 0x81, 0x0C, 0x81, 0x0C,
|
||||
0x81, 0x0D, 0x82, 0x08, 0x81, 0x03, 0x81, 0x0D, 0x81, 0x0A, 0x81, 0x0A,
|
||||
0x81, 0x0C, 0x82, 0x06, 0x90, 0x0D, 0x88, 0x0C, 0x84, 0x04, 0x81, 0x01,
|
||||
0x81, 0x0A, 0x81, 0x03, 0x90, 0x06, 0x88, 0x07, 0x88, 0x0E, 0x84, 0x0E,
|
||||
0x81, 0x0E, 0x82, 0x03, 0x90, 0x0E, 0x82, 0x05, 0x90, 0x02, 0x88, 0x03,
|
||||
0x81, 0x07, 0x81, 0x0A, 0x81, 0x0B, 0x81, 0x05, 0x84, 0x0D, 0x81, 0x01,
|
||||
0x82, 0x02, 0x90, 0x0C, 0x82, 0x03, 0x90, 0x08, 0x81, 0x05, 0x84, 0x08,
|
||||
0x84, 0x06, 0x88, 0x08, 0x81, 0x0E, 0x82, 0x01, 0x81, 0x01, 0x90, 0x08,
|
||||
0x82, 0x03, 0x90, 0x0C, 0x84, 0x02, 0x81, 0x09, 0x82, 0x04, 0x88, 0x0D,
|
||||
0x90, 0x07, 0x88, 0x0E, 0x82, 0x05, 0x81, 0x0C, 0x90, 0x0A, 0x81, 0x04,
|
||||
0x84, 0x06, 0x82, 0x02, 0x81, 0x09, 0x90, 0x0D, 0x82, 0x06, 0x82, 0x01,
|
||||
0x90, 0x09, 0x81, 0x0B, 0x84, 0x04, 0x88, 0x07, 0x81, 0x0B, 0x81, 0x05,
|
||||
0x82, 0x03, 0x81, 0x07, 0x90, 0x06, 0x82, 0x07,
|
||||
0x82, 0x04, 0x88, 0x0E, 0x88, 0x02, 0x88, 0x0D, 0x81, 0x04, 0x82, 0x06,
|
||||
0x81, 0x0D, 0x81, 0x09, 0x81, 0x07, 0x88, 0x09, 0x84, 0x07, 0x81, 0x01,
|
||||
0x90, 0x0C, 0x81, 0x09, 0x82, 0x0A, 0x88, 0x0D, 0x81, 0x0A, 0x84, 0x03,
|
||||
0x88, 0x0E, 0x82, 0x05, 0x81, 0x08, 0x90, 0x0C, 0x81, 0x0C, 0x88, 0x0A,
|
||||
0x82, 0x02, 0x81, 0x01, 0x90, 0x05, 0x84, 0x0A, 0x88, 0x0D, 0x82, 0x02,
|
||||
0x90, 0x02, 0x81, 0x05, 0x88, 0x08, 0x81, 0x08, 0x88, 0x01, 0x82, 0x01,
|
||||
0x90, 0x08, 0x81, 0x0B, 0x81, 0x0A, 0x88, 0x0A, 0x90, 0x04, 0x81, 0x02,
|
||||
0x82, 0x01, 0x90, 0x07, 0x82, 0x05, 0x90, 0x07, 0x81, 0x0D, 0x81, 0x0E,
|
||||
0x81, 0x07, 0x88, 0x09, 0x84, 0x04, 0x82, 0x01, 0x90, 0x0C, 0x81, 0x09,
|
||||
0x81, 0x0A, 0x81, 0x05, 0x81, 0x03, 0x88, 0x03, 0x88, 0x02, 0x82, 0x01,
|
||||
0x81, 0x05, 0x90, 0x0B, 0x82, 0x08, 0x88, 0x05, 0x84, 0x0B, 0x90, 0x0A,
|
||||
0x81, 0x0E, 0x81, 0x05, 0x88, 0x0B, 0x82, 0x0B, 0x81, 0x07, 0x90, 0x08,
|
||||
0x84, 0x02, 0x81, 0x02, 0x81, 0x0C, 0x82, 0x01, 0x81, 0x05, 0x90, 0x06,
|
||||
0x82, 0x04, 0x84, 0x04, 0x90, 0x04, 0x84, 0x0C, 0x81, 0x05, 0x81, 0x03,
|
||||
0x81, 0x01, 0x88, 0x01, 0x90, 0x01, 0x81, 0x0E, 0x88, 0x07, 0x82, 0x02,
|
||||
0x81, 0x06, 0x90, 0x03, 0x81, 0x0C, 0x82, 0x04, 0x88, 0x0B, 0x90, 0x09,
|
||||
0x81, 0x0C, 0x88, 0x0D, 0x81, 0x02, 0x82, 0x03, 0x81, 0x06, 0x90, 0x02,
|
||||
0x90, 0x03, 0x84, 0x0E, 0x82, 0x03, 0x81, 0x05, 0x88, 0x02, 0x90, 0x01,
|
||||
0x84, 0x0D, 0x84, 0x09, 0x82, 0x08, 0x82, 0x02, 0x84, 0x03, 0x84, 0x07,
|
||||
0x90, 0x0D, 0x82, 0x02, 0x88, 0x07, 0x84, 0x01, 0x90, 0x05, 0x81, 0x09,
|
||||
0x82, 0x02, 0x90, 0x06, 0x90, 0x04, 0x81, 0x05, 0x82, 0x06, 0x88, 0x0B,
|
||||
0x90, 0x03, 0x82, 0x04, 0x81, 0x06, 0x84, 0x08, 0x81, 0x0D, 0x81, 0x03,
|
||||
0x81, 0x02, 0x82, 0x0B, 0x88, 0x0B, 0x81, 0x0E, 0x90, 0x06, 0x90, 0x08,
|
||||
0x88, 0x06, 0x81, 0x02, 0x81, 0x0D, 0x88, 0x03, 0x84, 0x07, 0x81, 0x07,
|
||||
0x81, 0x05, 0x81, 0x06, 0x81, 0x06, 0x81, 0x03, 0x82, 0x06, 0x81, 0x03,
|
||||
0x88, 0x0A, 0x84, 0x08, 0x90, 0x0B, 0x90, 0x03, 0x82, 0x06, 0x84, 0x02,
|
||||
0x84, 0x07, 0x90, 0x05, 0x82, 0x0E, 0x90, 0x04,
|
||||
};
|
||||
|
||||
// level U -- 802 ticks, 21 hook bytes, 1 fare stages, 4 pictures, 0 landings
|
||||
// regenerate: tools/stsim/recorder levels/level21.dat U 20261006 60 2000
|
||||
static const uint8_t kGateStreamU[320] = {
|
||||
0x81, 0x04, 0x88, 0x02, 0x84, 0x08, 0x89, 0x02, 0x90, 0x01, 0x84, 0x05,
|
||||
0x84, 0x08, 0x88, 0x0D, 0x84, 0x0A, 0x82, 0x03, 0x82, 0x08, 0x90, 0x0B,
|
||||
0x88, 0x07, 0x90, 0x02, 0x90, 0x07, 0x81, 0x07, 0x90, 0x04, 0x81, 0x06,
|
||||
0x88, 0x05, 0x84, 0x04, 0x81, 0x04, 0x88, 0x02, 0x88, 0x07, 0x84, 0x04,
|
||||
0x81, 0x0A, 0x81, 0x03, 0x81, 0x0E, 0x82, 0x08, 0x81, 0x0B, 0x84, 0x0A,
|
||||
0x84, 0x02, 0x81, 0x08, 0x84, 0x03, 0x90, 0x08, 0x88, 0x02, 0x81, 0x09,
|
||||
0x81, 0x0A, 0x88, 0x03, 0x84, 0x09, 0x90, 0x06, 0x81, 0x06, 0x81, 0x07,
|
||||
0x88, 0x03, 0x81, 0x07, 0x88, 0x05, 0x81, 0x0D, 0x81, 0x08, 0x88, 0x07,
|
||||
0x84, 0x07, 0x81, 0x06, 0x81, 0x01, 0x81, 0x0A, 0x88, 0x0E, 0x81, 0x03,
|
||||
0x81, 0x0C, 0x84, 0x02, 0x88, 0x0D, 0x81, 0x07, 0x84, 0x07, 0x82, 0x0A,
|
||||
0x90, 0x09, 0x81, 0x0C, 0x84, 0x06, 0x88, 0x04, 0x81, 0x06, 0x81, 0x02,
|
||||
0x81, 0x0E, 0x82, 0x02, 0x88, 0x0E, 0x81, 0x0B, 0x81, 0x06, 0x81, 0x02,
|
||||
0x81, 0x09, 0x81, 0x0D, 0x81, 0x05, 0x82, 0x0E, 0x88, 0x04, 0x90, 0x08,
|
||||
0x81, 0x0A, 0x81, 0x09, 0x84, 0x07, 0x81, 0x03, 0x81, 0x0A, 0x82, 0x09,
|
||||
0x88, 0x07, 0x90, 0x05, 0x81, 0x09, 0x88, 0x02, 0x82, 0x09, 0x82, 0x0D,
|
||||
0x81, 0x0C, 0x81, 0x01, 0x81, 0x07, 0x81, 0x0A, 0x84, 0x06, 0x90, 0x01,
|
||||
0x81, 0x08, 0x81, 0x08, 0x81, 0x0D, 0x81, 0x09, 0x90, 0x01, 0x90, 0x08,
|
||||
0x88, 0x04, 0x81, 0x02, 0x84, 0x04, 0x81, 0x0D, 0x84, 0x01, 0x88, 0x0A,
|
||||
0x82, 0x06, 0x81, 0x03, 0x88, 0x0E, 0x88, 0x03, 0x81, 0x0B, 0x81, 0x0D,
|
||||
0x82, 0x09, 0x88, 0x0C, 0x81, 0x07, 0x88, 0x0B, 0x81, 0x0B, 0x81, 0x09,
|
||||
0x82, 0x0B, 0x81, 0x03, 0x88, 0x09, 0x81, 0x08, 0x88, 0x0A, 0x81, 0x01,
|
||||
0x88, 0x05, 0x90, 0x08, 0x82, 0x02, 0x84, 0x05, 0x81, 0x0E, 0x90, 0x0A,
|
||||
0x81, 0x0B, 0x81, 0x0D, 0x82, 0x05, 0x81, 0x0C, 0x84, 0x07, 0x88, 0x0A,
|
||||
0x81, 0x09, 0x84, 0x05, 0x81, 0x02, 0x90, 0x0B, 0x82, 0x01, 0x81, 0x0D,
|
||||
0x81, 0x09, 0x81, 0x05, 0x81, 0x0C, 0x81, 0x0D, 0x84, 0x02, 0x90, 0x08,
|
||||
0x84, 0x0D, 0x90, 0x0C, 0x84, 0x01, 0x81, 0x0A, 0x82, 0x0E, 0x81, 0x0C,
|
||||
0x81, 0x0A, 0x81, 0x09, 0x81, 0x04, 0x81, 0x08,
|
||||
};
|
||||
|
||||
// level V -- 1400 ticks, 1 hook bytes, 10 fare stages, 256 pictures, 25 landings
|
||||
// regenerate: tools/stsim/recorder levels/level22.dat V 20261011 60 2000
|
||||
static const uint8_t kGateStreamV[320] = {
|
||||
0x88, 0x0D, 0x81, 0x02, 0x81, 0x09, 0x82, 0x0C, 0x90, 0x04, 0x81, 0x0B,
|
||||
0x81, 0x01, 0x84, 0x0A, 0x84, 0x06, 0x90, 0x0E, 0x88, 0x0B, 0x81, 0x01,
|
||||
0x90, 0x0C, 0x81, 0x03, 0x90, 0x02, 0x88, 0x0E, 0x90, 0x0E, 0x82, 0x0D,
|
||||
0x84, 0x0A, 0x81, 0x06, 0x90, 0x04, 0x82, 0x05, 0x82, 0x02, 0x81, 0x06,
|
||||
0x82, 0x0C, 0x84, 0x0B, 0x81, 0x05, 0x88, 0x09, 0x81, 0x02, 0x90, 0x0C,
|
||||
0x81, 0x01, 0x84, 0x08, 0x88, 0x0E, 0x88, 0x08, 0x81, 0x01, 0x81, 0x07,
|
||||
0x88, 0x07, 0x90, 0x04, 0x84, 0x0C, 0x90, 0x0B, 0x88, 0x0C, 0x88, 0x06,
|
||||
0x82, 0x03, 0x81, 0x07, 0x84, 0x0C, 0x90, 0x0C, 0x84, 0x0C, 0x88, 0x0B,
|
||||
0x84, 0x08, 0x81, 0x04, 0x90, 0x06, 0x88, 0x03, 0x82, 0x0D, 0x84, 0x04,
|
||||
0x88, 0x07, 0x84, 0x04, 0x81, 0x01, 0x90, 0x0E, 0x84, 0x02, 0x88, 0x07,
|
||||
0x81, 0x0A, 0x82, 0x02, 0x82, 0x09, 0x90, 0x0B, 0x84, 0x0B, 0x81, 0x06,
|
||||
0x90, 0x0D, 0x90, 0x03, 0x84, 0x02, 0x82, 0x08, 0x81, 0x07, 0x88, 0x03,
|
||||
0x88, 0x02, 0x81, 0x03, 0x90, 0x0C, 0x90, 0x06, 0x88, 0x08, 0x88, 0x0C,
|
||||
0x88, 0x08, 0x81, 0x09, 0x82, 0x06, 0x90, 0x0B, 0x88, 0x0D, 0x90, 0x02,
|
||||
0x81, 0x0C, 0x82, 0x0A, 0x84, 0x0B, 0x90, 0x0D, 0x81, 0x04, 0x90, 0x0C,
|
||||
0x81, 0x04, 0x88, 0x02, 0x82, 0x06, 0x81, 0x02, 0x88, 0x06, 0x81, 0x07,
|
||||
0x81, 0x0B, 0x81, 0x0E, 0x90, 0x07, 0x90, 0x06, 0x88, 0x0C, 0x81, 0x01,
|
||||
0x90, 0x07, 0x90, 0x03, 0x81, 0x04, 0x81, 0x02, 0x84, 0x0A, 0x84, 0x0D,
|
||||
0x84, 0x01, 0x84, 0x04, 0x88, 0x0B, 0x90, 0x09, 0x88, 0x0C, 0x90, 0x02,
|
||||
0x84, 0x03, 0x81, 0x01, 0x90, 0x07, 0x90, 0x0A, 0x81, 0x06, 0x90, 0x05,
|
||||
0x82, 0x08, 0x88, 0x0B, 0x81, 0x08, 0x88, 0x09, 0x84, 0x0C, 0x82, 0x05,
|
||||
0x90, 0x02, 0x88, 0x0D, 0x81, 0x0E, 0x81, 0x0D, 0x84, 0x04, 0x81, 0x06,
|
||||
0x82, 0x04, 0x90, 0x08, 0x84, 0x0E, 0x81, 0x01, 0x81, 0x05, 0x82, 0x07,
|
||||
0x90, 0x09, 0x81, 0x01, 0x90, 0x07, 0x88, 0x07, 0x81, 0x03, 0x90, 0x0E,
|
||||
0x88, 0x0C, 0x81, 0x01, 0x90, 0x05, 0x81, 0x06, 0x88, 0x01, 0x84, 0x04,
|
||||
0x84, 0x01, 0x84, 0x0E, 0x90, 0x06, 0x81, 0x02, 0x81, 0x03, 0x88, 0x03,
|
||||
0x82, 0x0A, 0x84, 0x02, 0x81, 0x04, 0x81, 0x0A,
|
||||
};
|
||||
|
||||
// level Y -- 1400 ticks, 14 hook bytes, 10 fare stages, 256 pictures, 15 landings
|
||||
// regenerate: tools/stsim/recorder levels/level25.dat Y 20261006 60 2000
|
||||
static const uint8_t kGateStreamY[320] = {
|
||||
0x88, 0x04, 0x88, 0x06, 0x84, 0x01, 0x90, 0x01, 0x88, 0x01, 0x84, 0x05,
|
||||
0x81, 0x03, 0x84, 0x0B, 0x81, 0x0E, 0x88, 0x07, 0x81, 0x0E, 0x82, 0x0D,
|
||||
0x81, 0x05, 0x81, 0x01, 0x82, 0x01, 0x88, 0x07, 0x84, 0x0A, 0x88, 0x0B,
|
||||
0x84, 0x07, 0x88, 0x0D, 0x81, 0x07, 0x81, 0x03, 0x84, 0x04, 0x84, 0x02,
|
||||
0x81, 0x06, 0x88, 0x08, 0x81, 0x0C, 0x82, 0x0E, 0x81, 0x0B, 0x81, 0x08,
|
||||
0x81, 0x01, 0x82, 0x0D, 0x81, 0x04, 0x90, 0x04, 0x84, 0x06, 0x84, 0x01,
|
||||
0x84, 0x0C, 0x81, 0x04, 0x81, 0x02, 0x90, 0x08, 0x88, 0x06, 0x81, 0x0B,
|
||||
0x88, 0x05, 0x88, 0x08, 0x82, 0x0D, 0x81, 0x08, 0x88, 0x02, 0x81, 0x0E,
|
||||
0x84, 0x07, 0x81, 0x04, 0x90, 0x0B, 0x88, 0x0E, 0x81, 0x05, 0x90, 0x0D,
|
||||
0x90, 0x0B, 0x81, 0x01, 0x81, 0x05, 0x81, 0x06, 0x90, 0x0A, 0x90, 0x0E,
|
||||
0x88, 0x01, 0x81, 0x0A, 0x82, 0x01, 0x88, 0x06, 0x90, 0x08, 0x81, 0x03,
|
||||
0x84, 0x0E, 0x82, 0x03, 0x88, 0x06, 0x84, 0x0E, 0x81, 0x07, 0x88, 0x01,
|
||||
0x90, 0x02, 0x90, 0x09, 0x81, 0x09, 0x88, 0x06, 0x81, 0x04, 0x88, 0x0B,
|
||||
0x84, 0x0D, 0x81, 0x08, 0x84, 0x04, 0x82, 0x0B, 0x81, 0x0E, 0x81, 0x0A,
|
||||
0x84, 0x01, 0x81, 0x0C, 0x81, 0x07, 0x81, 0x0A, 0x81, 0x07, 0x82, 0x0B,
|
||||
0x84, 0x08, 0x90, 0x0B, 0x88, 0x08, 0x84, 0x0C, 0x90, 0x0C, 0x81, 0x06,
|
||||
0x80, 0x0D, 0x84, 0x0E, 0x81, 0x05, 0x90, 0x0C, 0x81, 0x02, 0x90, 0x09,
|
||||
0x82, 0x0D, 0x90, 0x0E, 0x88, 0x03, 0x81, 0x09, 0x81, 0x0D, 0x88, 0x03,
|
||||
0x88, 0x0C, 0x90, 0x03, 0x81, 0x0E, 0x81, 0x03, 0x81, 0x04, 0x88, 0x05,
|
||||
0x88, 0x04, 0x88, 0x02, 0x88, 0x01, 0x81, 0x0C, 0x88, 0x09, 0x82, 0x0A,
|
||||
0x88, 0x09, 0x84, 0x0D, 0x90, 0x0C, 0x84, 0x03, 0x88, 0x07, 0x84, 0x0D,
|
||||
0x90, 0x02, 0x84, 0x09, 0x82, 0x04, 0x84, 0x0C, 0x81, 0x0E, 0x81, 0x0D,
|
||||
0x82, 0x0C, 0x81, 0x03, 0x90, 0x0D, 0x81, 0x08, 0x88, 0x0A, 0x81, 0x08,
|
||||
0x84, 0x0A, 0x90, 0x0D, 0x88, 0x0B, 0x81, 0x0D, 0x82, 0x08, 0x90, 0x0D,
|
||||
0x90, 0x0A, 0x90, 0x04, 0x81, 0x03, 0x84, 0x02, 0x88, 0x08, 0x90, 0x05,
|
||||
0x81, 0x0D, 0x81, 0x05, 0x88, 0x06, 0x84, 0x06, 0x90, 0x04, 0x82, 0x01,
|
||||
0x84, 0x0C, 0x82, 0x0D, 0x88, 0x08, 0x81, 0x0C,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
|
|
@ -441,19 +699,27 @@ typedef struct {
|
|||
} StGateStreamT;
|
||||
|
||||
static const StGateStreamT kGateStreams[] = {
|
||||
{ 'A', kGateStreamA, (uint16_t)sizeof(kGateStreamA) },
|
||||
{ 'B', kGateStreamB, (uint16_t)sizeof(kGateStreamB) },
|
||||
{ 'C', kGateStreamC, (uint16_t)sizeof(kGateStreamC) },
|
||||
{ 'D', kGateStreamD, (uint16_t)sizeof(kGateStreamD) },
|
||||
{ 'E', kGateStreamE, (uint16_t)sizeof(kGateStreamE) },
|
||||
{ 'F', kGateStreamF, (uint16_t)sizeof(kGateStreamF) },
|
||||
{ 'G', kGateStreamG, (uint16_t)sizeof(kGateStreamG) },
|
||||
{ 'I', kGateStreamI, (uint16_t)sizeof(kGateStreamI) },
|
||||
{ 'J', kGateStreamJ, (uint16_t)sizeof(kGateStreamJ) },
|
||||
{ 'K', kGateStreamK, (uint16_t)sizeof(kGateStreamK) },
|
||||
{ 'L', kGateStreamL, (uint16_t)sizeof(kGateStreamL) },
|
||||
{ 'M', kGateStreamM, (uint16_t)sizeof(kGateStreamM) },
|
||||
{ 'N', kGateStreamN, (uint16_t)sizeof(kGateStreamN) },
|
||||
{ 'O', kGateStreamO, (uint16_t)sizeof(kGateStreamO) },
|
||||
{ 'P', kGateStreamP, (uint16_t)sizeof(kGateStreamP) },
|
||||
{ 'Q', kGateStreamQ, (uint16_t)sizeof(kGateStreamQ) },
|
||||
{ 'R', kGateStreamR, (uint16_t)sizeof(kGateStreamR) },
|
||||
{ 'S', kGateStreamS, (uint16_t)sizeof(kGateStreamS) },
|
||||
{ 'U', kGateStreamU, (uint16_t)sizeof(kGateStreamU) },
|
||||
{ 'V', kGateStreamV, (uint16_t)sizeof(kGateStreamV) },
|
||||
{ 'E', kGateStreamE, (uint16_t)sizeof(kGateStreamE) },
|
||||
{ 'J', kGateStreamJ, (uint16_t)sizeof(kGateStreamJ) },
|
||||
{ 'L', kGateStreamL, (uint16_t)sizeof(kGateStreamL) },
|
||||
{ 'P', kGateStreamP, (uint16_t)sizeof(kGateStreamP) },
|
||||
{ 'S', kGateStreamS, (uint16_t)sizeof(kGateStreamS) },
|
||||
{ 'Y', kGateStreamY, (uint16_t)sizeof(kGateStreamY) },
|
||||
};
|
||||
#define ST_GATE_STREAMS (sizeof(kGateStreams) / sizeof(kGateStreams[0]))
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +1,17 @@
|
|||
// recorder -- search for a demo input stream that exercises a level.
|
||||
//
|
||||
// The C64 ships attract recordings for four screens (H, W, T, X). The
|
||||
// other levels have none, so a refactor of their hooks had nothing to be
|
||||
// checked against. This searches for a synthetic ride instead: random
|
||||
// restarts plus hill climbing over (joystick mask, duration) pairs,
|
||||
// scored on how long the cab survives and how much of the level it
|
||||
// reaches -- hook state bytes written, fare-machine stages entered,
|
||||
// distinct pictures drawn, pads landed on.
|
||||
// other levels have none, so this searches for a synthetic ride instead:
|
||||
// random restarts plus hill climbing over (joystick mask, duration)
|
||||
// pairs, scored on how long the cab survives its first life and how much
|
||||
// of the level it reaches -- hook state bytes written, fare-machine
|
||||
// stages entered, distinct pictures drawn, pads landed on.
|
||||
//
|
||||
// The result is a REGRESSION fixture, not a recording: it pins current
|
||||
// behaviour so a refactor that changes nothing observable leaves the
|
||||
// trace identical. It says nothing about matching the real C64.
|
||||
// The search runs on this port's simulation; what proves the ride is
|
||||
// the original game: gate.sh c64 rides the stream on the C64 in VICE and
|
||||
// the two traces must match byte for byte.
|
||||
//
|
||||
// Usage: recorder <level.dat> <letter> <seed> > stream.h
|
||||
// Usage: recorder <level.dat> <letter> <seed> [restarts] [iterations] > stream.h
|
||||
// Then assemble the per-level outputs into gateStreams.h.
|
||||
//
|
||||
// Determinism: a candidate is only useful if it replays identically from
|
||||
|
|
@ -41,8 +40,10 @@ void stAudioVoice1Freq(uint8_t v){(void)v;}
|
|||
#define PAIRS 160
|
||||
#define STREAMLEN (PAIRS * 2)
|
||||
#define MAXTICKS 1400
|
||||
#define RESTARTS 120
|
||||
#define ITERATIONS 3000
|
||||
|
||||
typedef struct { uint32_t score; uint32_t ticks; uint32_t cover; uint32_t lands; uint32_t stages; uint32_t scr; uint32_t near; } ResT;
|
||||
typedef struct { uint32_t score; uint32_t ticks; uint32_t cover; uint32_t lands; uint32_t stages; uint32_t scr; uint32_t near; uint32_t exited; } ResT;
|
||||
|
||||
static uint32_t rs = 1;
|
||||
static uint32_t rnd(void){ rs = rs*1664525u + 1013904223u; return rs>>8; }
|
||||
|
|
@ -59,6 +60,7 @@ static ResT evaluate(const uint8_t *stream, uint8_t *touchOut)
|
|||
ResT r;
|
||||
|
||||
memset(stageSeen, 0, sizeof(stageSeen));
|
||||
memset(&r, 0, sizeof(r));
|
||||
memset(touched, 0, sizeof(touched));
|
||||
// stSimNewGame deliberately PRESERVES flameParity, waveIdx and
|
||||
// walkParity (the C64 carries them from screen to screen), so a
|
||||
|
|
@ -67,14 +69,22 @@ static ResT evaluate(const uint8_t *stream, uint8_t *touchOut)
|
|||
// would then not reproduce from a cold start. Clear the whole struct
|
||||
// so every evaluation begins exactly where the gate will.
|
||||
memset(&gSim, 0, sizeof(gSim));
|
||||
// Exactly as stsim starts a gate ride.
|
||||
stSimNewGame(&gSim, 1u, true);
|
||||
stSimSeedDemoBuffer(&gSim, kDemoBufferInit, ST_DEMO_BUFFER_BYTES);
|
||||
(void)stSimAdvancePlayer(&gSim);
|
||||
gSim.levelState = (uint8_t)(gLevel.levelIndex + 1u);
|
||||
stSimEnterLevel(&gSim, &gLevel);
|
||||
stSimSetDemoStream(&gSim, stream, STREAMLEN);
|
||||
memcpy(base, &gSim.hookScratch, sizeof(base));
|
||||
for (t = 0; t < MAXTICKS; t++) {
|
||||
uint16_t k;
|
||||
if (stSimTick(&gSim) != ST_TICK_CONTINUE) { break; }
|
||||
StTickResultE res = stSimTick(&gSim);
|
||||
if (res != ST_TICK_CONTINUE) {
|
||||
/* Leaving through the hatch is a path of its own: reward it. */
|
||||
r.exited = (res == ST_TICK_LEVEL_EXIT) ? 1u : 0u;
|
||||
break;
|
||||
}
|
||||
{ const uint8_t *cur = (const uint8_t *)&gSim.hookScratch;
|
||||
for (k = 0; k < (uint16_t)sizeof(base); k++) {
|
||||
if (cur[k] != base[k]) { touched[k] = 1; } } }
|
||||
|
|
@ -116,7 +126,7 @@ static ResT evaluate(const uint8_t *stream, uint8_t *touchOut)
|
|||
r.ticks = t; r.cover = cover; r.lands = lands; r.stages = stages; r.scr = scrSeen;
|
||||
r.near = nearest;
|
||||
r.score = cover * 30000u + stages * 30000u + lands * 4000u + t * 100u + scrSeen * 200u
|
||||
+ (255u - nearest) * 60u;
|
||||
+ (255u - nearest) * 60u + r.exited * 300000u;
|
||||
return r;
|
||||
}
|
||||
|
||||
|
|
@ -143,20 +153,28 @@ int main(int argc, char **argv)
|
|||
ResT bestR, curR;
|
||||
FILE *fp;
|
||||
int restart, iter;
|
||||
int restarts = RESTARTS;
|
||||
int iterations = ITERATIONS;
|
||||
|
||||
if (argc < 4) { fprintf(stderr, "usage: recorder <level.dat> <name> <seed>\n"); return 2; }
|
||||
if (argc < 4) { fprintf(stderr, "usage: recorder <level.dat> <name> <seed> [restarts] [iterations]\n"); return 2; }
|
||||
fp = fopen(argv[1], "rb");
|
||||
if (!fp || !stLevelParse(&gLevel, fp)) { fprintf(stderr, "cannot load %s\n", argv[1]); return 1; }
|
||||
fclose(fp);
|
||||
rs = (uint32_t)strtoul(argv[3], NULL, 0);
|
||||
if (argc > 4) {
|
||||
restarts = (int)strtol(argv[4], NULL, 0);
|
||||
}
|
||||
if (argc > 5) {
|
||||
iterations = (int)strtol(argv[5], NULL, 0);
|
||||
}
|
||||
|
||||
randomise(best, 0);
|
||||
bestR = evaluate(best, NULL);
|
||||
for (restart = 0; restart < 120; restart++) {
|
||||
for (restart = 0; restart < restarts; restart++) {
|
||||
memcpy(cur, best, STREAMLEN);
|
||||
if (restart > 0) { randomise(cur, (int)(rnd() % PAIRS)); }
|
||||
curR = evaluate(cur, NULL);
|
||||
for (iter = 0; iter < 3000; iter++) {
|
||||
for (iter = 0; iter < iterations; iter++) {
|
||||
uint8_t trial[STREAMLEN];
|
||||
ResT tr;
|
||||
int j, nmut = 1 + (int)(rnd() % 3u);
|
||||
|
|
@ -182,10 +200,10 @@ int main(int argc, char **argv)
|
|||
if (curR.score > bestR.score) { memcpy(best, cur, STREAMLEN); bestR = curR; }
|
||||
}
|
||||
bestR = evaluate(best, touched);
|
||||
fprintf(stderr, "%s: ticks=%u hookBytes=%u stages=%u pictures=%u lands=%u nearPad=%u\n",
|
||||
argv[2], bestR.ticks, bestR.cover, bestR.stages, bestR.scr, bestR.lands, bestR.near);
|
||||
printf("// %s: %u ticks, %u hook bytes, %u fare stages, %u pictures, %u landings\n",
|
||||
argv[2], bestR.ticks, bestR.cover, bestR.stages, bestR.scr, bestR.lands);
|
||||
fprintf(stderr, "%s: ticks=%u hookBytes=%u stages=%u pictures=%u lands=%u nearPad=%u exited=%u\n",
|
||||
argv[2], bestR.ticks, bestR.cover, bestR.stages, bestR.scr, bestR.lands, bestR.near, bestR.exited);
|
||||
printf("// %s: %u ticks, %u hook bytes, %u fare stages, %u pictures, %u landings%s\n",
|
||||
argv[2], bestR.ticks, bestR.cover, bestR.stages, bestR.scr, bestR.lands, bestR.exited ? ", left through the hatch" : "");
|
||||
printf("static const uint8_t kGateStream%s[%d] = {", argv[2], STREAMLEN);
|
||||
{ int i; for (i = 0; i < STREAMLEN; i++) {
|
||||
if (i % 12 == 0) { printf("\n "); }
|
||||
|
|
|
|||
|
|
@ -1,14 +1,33 @@
|
|||
// stsim -- host-side driver for the Space Taxi simulation core.
|
||||
//
|
||||
// Loads an STL4 level, attaches a recorded demo input stream and runs
|
||||
// the game tick, printing one line of state per tick in the same shape
|
||||
// the VICE tracer records (see stuff/ and the compare script),
|
||||
// so the port's engine can be diffed against the real machine.
|
||||
// Loads an STL5 level, attaches a recorded demo input stream and runs
|
||||
// the game tick, printing one line of state per tick -- exactly the line
|
||||
// c64trace.py prints from the real game in VICE, so the two traces can
|
||||
// be compared byte for byte.
|
||||
//
|
||||
// stsim <level.dat> <level letter> <ticks>
|
||||
//
|
||||
// H, W, T and X use the C64's own attract recordings; every other
|
||||
// letter uses a synthesised gate fixture from gateStreams.h.
|
||||
//
|
||||
// A ride is the attract demo's input and random numbers on the screen as
|
||||
// the 24-hour shift reaches it ($7215 = the screen's place, A = 1 .. Y =
|
||||
// 25), on the game's crash path: a finished wreck costs a cab and the cab respawns
|
||||
// (stSimLoseCab, stSimRespawn -- c64trace.py sends the real machine down
|
||||
// $6B64 too), until the cabs run out, the cab leaves the screen, the
|
||||
// ticks are done, or the playback reaches the buffer's last pair -- past
|
||||
// that the C64 would read on into whatever follows $0B81.
|
||||
//
|
||||
// STSIM_DEMOBUF=1 prints the ride's whole playback buffer ($0902, the
|
||||
// boot-time image with the stream laid over it) as one hex line instead
|
||||
// of running it: c64trace.py loads exactly those bytes into the real
|
||||
// game, so both machines ride the same input.
|
||||
//
|
||||
// STSIM_RAW=<file> also appends each tick's picture to <file> -- screen
|
||||
// RAM, colour RAM and the effective charset, 1000 + 1000 + 2048 bytes a
|
||||
// tick -- so a hash mismatch can be traced to the cells and glyphs that
|
||||
// differ (c64trace.py writes the same layout from the real machine, and
|
||||
// compare.py --picture lists the differences).
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
|
@ -20,9 +39,33 @@
|
|||
#include "gateStreams.h"
|
||||
|
||||
|
||||
#define ST_CHARSET_BYTES (ST_CHARSET_CHARS * 8u)
|
||||
// The playback buffer's last (mask, count) pair: reading on from it, the
|
||||
// sim stops at the buffer's end while the C64 reads past $0B81.
|
||||
#define ST_DEMO_LAST_PAIR (ST_DEMO_BUFFER_BYTES - 2u)
|
||||
|
||||
|
||||
static void celCensusReport(const StLevelT *level);
|
||||
static void celCensusTick(const StSimT *sim);
|
||||
static uint32_t charsetHash(const StSimT *sim);
|
||||
static void charsetImage(const StSimT *sim, uint8_t *image);
|
||||
static uint32_t hashBytes(const uint8_t *p, size_t n);
|
||||
static void printDemoBuffer(const StSimT *sim);
|
||||
static void printState(const StSimT *sim, uint32_t tick);
|
||||
static void writeRaw(FILE *raw, const StSimT *sim);
|
||||
|
||||
|
||||
static StLevelT gLevel;
|
||||
static StSimT gSim;
|
||||
|
||||
// STSIM_CELS=1: every level cel (ptr below the baked set) a ride
|
||||
// displays, as (ptr, colour, multi, mc0, mc1), against what
|
||||
// stLevelCelList bakes for the level -- a cel shown but not listed is
|
||||
// built and drawn interpreted mid-ride on the real ports.
|
||||
#define CENSUS_MAX 64u
|
||||
static StCelDefT gCensus[CENSUS_MAX];
|
||||
static uint8_t gCensusCount;
|
||||
|
||||
|
||||
void stAudioSfx(const uint8_t *program9) { (void)program9; }
|
||||
void stAudioNoise(bool on) { (void)on; }
|
||||
|
|
@ -34,45 +77,24 @@ void stAudioVoice2(uint8_t freqLo, uint8_t freqHi, uint8_t ctrl) { (void)freqLo;
|
|||
void stAudioVoice1Freq(uint8_t value) { (void)value; }
|
||||
|
||||
|
||||
// FNV-1a over a byte span: the picture the player actually sees. The
|
||||
// hooks that animate the screen (O, R, V, W, X) change screen, colour or
|
||||
// charset bytes and nothing else this line used to capture.
|
||||
static uint32_t hashBytes(const uint8_t *p, size_t n) {
|
||||
uint32_t h = 2166136261u;
|
||||
size_t i;
|
||||
static void celCensusReport(const StLevelT *level) {
|
||||
StCelDefT listed[ST_LEVEL_CELS_MAX];
|
||||
uint8_t n = stLevelCelList(level, listed, ST_LEVEL_CELS_MAX);
|
||||
uint8_t k;
|
||||
uint8_t j;
|
||||
|
||||
for (i = 0u; i < n; i++) {
|
||||
h = (h ^ p[i]) * 16777619u;
|
||||
for (k = 0u; k < gCensusCount; k++) {
|
||||
for (j = 0u; j < n; j++) {
|
||||
if (memcmp(&listed[j], &gCensus[k], sizeof(StCelDefT)) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
fprintf(stderr, "CEL %s ptr=%02X color=%u multi=%u mc=%u/%u\n", (j < n) ? "listed " : "MISSING", gCensus[k].ptr, gCensus[k].color, gCensus[k].multi, gCensus[k].mc0, gCensus[k].mc1);
|
||||
}
|
||||
return h;
|
||||
fprintf(stderr, "CENSUS shown=%u listed=%u\n", gCensusCount, n);
|
||||
}
|
||||
|
||||
|
||||
// The charset the game would be showing. The sim no longer holds a
|
||||
// contiguous 2 KB copy -- edited glyphs live in an override table -- so
|
||||
// rebuild the effective image here. Hashing this keeps the trace value
|
||||
// identical to the pre-override captures, which is what lets the goldens
|
||||
// prove the change is behaviour-preserving.
|
||||
static uint32_t charsetHash(const StSimT *sim) {
|
||||
uint8_t image[ST_CHARSET_CHARS * 8u];
|
||||
uint16_t ch;
|
||||
|
||||
for (ch = 0u; ch < ST_CHARSET_CHARS; ch++) {
|
||||
memcpy(&image[ch * 8u], stSimGlyph(sim, (uint8_t)ch), 8u);
|
||||
}
|
||||
return hashBytes(image, sizeof(image));
|
||||
}
|
||||
|
||||
|
||||
// STSIM_CELS=1: every level cel (ptr below the baked set) a ride
|
||||
// displays, as (ptr, colour, multi, mc0, mc1), against what
|
||||
// stLevelCelList bakes for the level -- a cel shown but not listed is
|
||||
// built and drawn interpreted mid-ride on the real ports.
|
||||
#define CENSUS_MAX 64u
|
||||
static StCelDefT gCensus[CENSUS_MAX];
|
||||
static uint8_t gCensusCount;
|
||||
|
||||
|
||||
static void celCensusTick(const StSimT *sim) {
|
||||
uint8_t i;
|
||||
|
||||
|
|
@ -105,40 +127,75 @@ static void celCensusTick(const StSimT *sim) {
|
|||
}
|
||||
|
||||
|
||||
static void celCensusReport(const StLevelT *level) {
|
||||
StCelDefT listed[ST_LEVEL_CELS_MAX];
|
||||
uint8_t n = stLevelCelList(level, listed, ST_LEVEL_CELS_MAX);
|
||||
uint8_t k;
|
||||
uint8_t j;
|
||||
// The charset the game would be showing. The sim no longer holds a
|
||||
// contiguous 2 KB copy -- edited glyphs live in an override table -- so
|
||||
// rebuild the effective image here. Hashing this keeps the trace value
|
||||
// identical to the pre-override captures, which is what lets the goldens
|
||||
// prove the change is behaviour-preserving.
|
||||
static uint32_t charsetHash(const StSimT *sim) {
|
||||
uint8_t image[ST_CHARSET_BYTES];
|
||||
|
||||
for (k = 0u; k < gCensusCount; k++) {
|
||||
for (j = 0u; j < n; j++) {
|
||||
if (memcmp(&listed[j], &gCensus[k], sizeof(StCelDefT)) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
fprintf(stderr, "CEL %s ptr=%02X color=%u multi=%u mc=%u/%u\n", (j < n) ? "listed " : "MISSING", gCensus[k].ptr, gCensus[k].color, gCensus[k].multi, gCensus[k].mc0, gCensus[k].mc1);
|
||||
}
|
||||
fprintf(stderr, "CENSUS shown=%u listed=%u\n", gCensusCount, n);
|
||||
charsetImage(sim, image);
|
||||
return hashBytes(image, sizeof(image));
|
||||
}
|
||||
|
||||
|
||||
static void celCensusReport(const StLevelT *level);
|
||||
static void celCensusTick(const StSimT *sim);
|
||||
static void charsetImage(const StSimT *sim, uint8_t *image) {
|
||||
uint16_t ch;
|
||||
|
||||
for (ch = 0u; ch < ST_CHARSET_CHARS; ch++) {
|
||||
memcpy(&image[ch * 8u], stSimGlyph(sim, (uint8_t)ch), 8u);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// FNV-1a over a byte span: the picture the player actually sees. The
|
||||
// hooks that animate the screen (O, R, V, W, X) change screen, colour or
|
||||
// charset bytes and nothing else this line used to capture.
|
||||
static uint32_t hashBytes(const uint8_t *p, size_t n) {
|
||||
uint32_t h = 2166136261u;
|
||||
size_t i;
|
||||
|
||||
for (i = 0u; i < n; i++) {
|
||||
h = (h ^ p[i]) * 16777619u;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
|
||||
static void printDemoBuffer(const StSimT *sim) {
|
||||
size_t k;
|
||||
|
||||
printf("{\"demobuf\":\"");
|
||||
for (k = 0u; k < sizeof(sim->demoBuf); k++) {
|
||||
printf("%02X", sim->demoBuf[k]);
|
||||
}
|
||||
printf("\"}\n");
|
||||
}
|
||||
|
||||
|
||||
static void printState(const StSimT *sim, uint32_t tick) {
|
||||
bool shown1 = (sim->frame.enableMask & kStBit[1]) != 0u;
|
||||
uint8_t k;
|
||||
|
||||
printf("{\"tick\":%u,\"x\":\"%02X%02X.%02X\",\"y\":\"%02X.%02X\",\"vx\":\"%04X\",\"vy\":\"%04X\","
|
||||
"\"pad\":%u,\"st\":%u,\"ph\":%u,\"in\":\"%02X\",\"dir\":\"%02X\",\"ptr0\":\"%02X\",\"ptr1\":\"%02X\","
|
||||
"\"s1\":\"%02X%02X.%02X\",\"en\":\"%02X\",\"t4740\":\"%02X\",\"off\":%u,\"rng\":\"%02X%02X\","
|
||||
"\"fuel\":%u,\"slots\":%u,\"asi\":%u,\"score\":\"",
|
||||
"\"pad\":%u,\"st\":%u,\"ph\":%u,\"in\":\"%02X\",\"dir\":\"%02X\",\"ptr0\":\"%02X\",",
|
||||
tick, sim->posXmsb, sim->posXcol, sim->posXlo, sim->posYrow, sim->posYlo,
|
||||
(uint16_t)sim->velX, (uint16_t)sim->velY, sim->activePad, sim->stage, sim->collisionPhase,
|
||||
sim->inputMask, sim->dirMask, sim->spr[0].ptr, sim->spr[1].ptr,
|
||||
sim->spr[1].msb, sim->spr[1].col, sim->spr[1].row, sim->frame.enableMask,
|
||||
sim->demoTimer, sim->demoOff, sim->rngT1, sim->rngT2, sim->fuelCells, sim->fareSlotCount, sim->activeSpriteIdx);
|
||||
sim->inputMask, sim->dirMask, sim->spr[0].ptr);
|
||||
// A hidden sprite's registers are not part of the picture, and the
|
||||
// C64 leaves whatever the screen before set in them: sprite 1's
|
||||
// shadow and every hidden sprite's displayed position print as
|
||||
// dashes, on the real machine's trace too.
|
||||
if (shown1) {
|
||||
printf("\"ptr1\":\"%02X\",\"s1\":\"%02X%02X.%02X\",", sim->spr[1].ptr, sim->spr[1].msb, sim->spr[1].col, sim->spr[1].row);
|
||||
} else {
|
||||
printf("\"ptr1\":\"--\",\"s1\":\"--\",");
|
||||
}
|
||||
printf("\"en\":\"%02X\",\"t4740\":\"%02X\",\"off\":%u,\"rng\":\"%02X%02X\","
|
||||
"\"fuel\":%u,\"slots\":%u,\"asi\":%u,\"score\":\"",
|
||||
sim->frame.enableMask, sim->demoTimer, sim->demoOff, sim->rngT1, sim->rngT2,
|
||||
sim->fuelCells, sim->fareSlotCount, sim->activeSpriteIdx);
|
||||
for (k = 0u; k < ST_NUMBER_CHARS; k++) {
|
||||
printf("%02X", sim->screen[ST_CELL_SCORE + k]);
|
||||
}
|
||||
|
|
@ -152,7 +209,11 @@ static void printState(const StSimT *sim, uint32_t tick) {
|
|||
}
|
||||
printf("\",\"sprx\":\"");
|
||||
for (k = 0u; k < ST_HW_SPRITES; k++) {
|
||||
printf("%03X%02X", sim->frame.x[k], sim->frame.y[k]);
|
||||
if ((sim->frame.enableMask & kStBit[k]) != 0u) {
|
||||
printf("%03X%02X", sim->frame.x[k], sim->frame.y[k]);
|
||||
} else {
|
||||
printf("-----");
|
||||
}
|
||||
}
|
||||
printf("\",\"scr\":\"%08X\",\"col\":\"%08X\",\"chr\":\"%08X\",\"bd\":\"%02X\",\"bg\":\"%02X\"}\n",
|
||||
hashBytes(sim->screen, ST_SCREEN_CELLS),
|
||||
|
|
@ -162,8 +223,21 @@ static void printState(const StSimT *sim, uint32_t tick) {
|
|||
}
|
||||
|
||||
|
||||
// STSIM_RAW: one tick's picture (see the top of the file).
|
||||
static void writeRaw(FILE *raw, const StSimT *sim) {
|
||||
uint8_t image[ST_CHARSET_BYTES];
|
||||
|
||||
charsetImage(sim, image);
|
||||
fwrite(sim->screen, 1u, ST_SCREEN_CELLS, raw);
|
||||
fwrite(sim->color, 1u, ST_SCREEN_CELLS, raw);
|
||||
fwrite(image, 1u, sizeof(image), raw);
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
FILE *fp;
|
||||
FILE *raw = NULL;
|
||||
const char *rawPath;
|
||||
const uint8_t *stream = kDemoStreamH;
|
||||
uint16_t len = (uint16_t)sizeof(kDemoStreamH);
|
||||
uint8_t want;
|
||||
|
|
@ -205,34 +279,63 @@ int main(int argc, char **argv) {
|
|||
}
|
||||
}
|
||||
ticks = (uint32_t)strtoul(argv[3], NULL, 0);
|
||||
// A fresh one-player demo, the flame parity, wave index and walk
|
||||
// parity at zero (c64trace.py zeroes them on the real machine too).
|
||||
// The recorder starts its rides the same way.
|
||||
stSimNewGame(&gSim, 1u, true);
|
||||
stSimSeedDemoBuffer(&gSim, kDemoBufferInit, ST_DEMO_BUFFER_BYTES);
|
||||
if (argc > 4) {
|
||||
// carried-over state: flame parity, wave index, walk parity
|
||||
gSim.flameParity = (uint8_t)strtoul(argv[4], NULL, 16);
|
||||
}
|
||||
if (argc > 5) {
|
||||
gSim.waveIdx = (uint8_t)strtoul(argv[5], NULL, 16);
|
||||
}
|
||||
if (argc > 6) {
|
||||
gSim.walkParity = (uint8_t)strtoul(argv[6], NULL, 16);
|
||||
}
|
||||
// $61FB, as startDemoScreen runs it: the turn wraps to player 0 and
|
||||
// the screens-played count ($7215, this one included) is set before
|
||||
// the screen is loaded -- to the screen's place in the 24-hour shift
|
||||
// (A is the first, Y the 25th), so the rides cover the takeoff fare of
|
||||
// every screen after the second and the finished shift's HUD.
|
||||
(void)stSimAdvancePlayer(&gSim);
|
||||
gSim.levelState = (uint8_t)(gLevel.levelIndex + 1u);
|
||||
stSimEnterLevel(&gSim, &gLevel);
|
||||
stSimSetDemoStream(&gSim, stream, len);
|
||||
if (getenv("STSIM_DEMOBUF") != NULL) {
|
||||
printDemoBuffer(&gSim);
|
||||
return 0;
|
||||
}
|
||||
rawPath = getenv("STSIM_RAW");
|
||||
if (rawPath != NULL) {
|
||||
raw = fopen(rawPath, "wb");
|
||||
if (raw == NULL) {
|
||||
fprintf(stderr, "stsim: cannot write %s\n", rawPath);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
for (t = 0u; t < ticks; t++) {
|
||||
StTickResultE r;
|
||||
printState(&gSim, t);
|
||||
if (raw != NULL) {
|
||||
writeRaw(raw, &gSim);
|
||||
}
|
||||
r = stSimTick(&gSim);
|
||||
if (getenv("STSIM_CELS") != NULL) {
|
||||
celCensusTick(&gSim);
|
||||
}
|
||||
if (r == ST_TICK_CRASH_DONE) {
|
||||
r = stSimLoseCab(&gSim);
|
||||
if (r == ST_TICK_LIFE_LOST) {
|
||||
stSimRespawn(&gSim);
|
||||
r = ST_TICK_CONTINUE;
|
||||
}
|
||||
}
|
||||
if (r != ST_TICK_CONTINUE) {
|
||||
printf("{\"tick\":%u,\"end\":%d}\n", t, (int)r);
|
||||
break;
|
||||
}
|
||||
if (gSim.demoOff >= ST_DEMO_LAST_PAIR) {
|
||||
printf("{\"tick\":%u,\"end\":\"stream\"}\n", t);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (getenv("STSIM_CELS") != NULL) {
|
||||
celCensusReport(&gLevel);
|
||||
}
|
||||
if (raw != NULL) {
|
||||
fclose(raw);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue