Files
disasm/Pac-Man (USA) (Namco)/copilot/prg.s.preaudit
T
tmerritt 837007843d MUSCLE (U): decode the bytecode VM layer (Pass 3b)
The 42 token programs were the last undecoded part of MUSCLE. Each now carries a
token-by-token listing above its unchanged .byte data, generated by
_tools/bytecode_dis.py and wired into the build as 'make pass3'.

Encoding, derived from RunBytecodeInterpreter ($9AE8) and BytecodeHandlerTable
($9237): the dispatch is one byte per element. A byte in $C8-$EF selects one of
the 40 handler-table entries; every other byte takes the literal path at $9B08
and is pushed onto the operand list at $0033[Y], accumulated into $003C[Y].

The one exception is four opcodes whose handler's first instructions are
'lda ($1A),Y / iny', so they consume the following byte inline:
  $CB LOOP_BEGIN  -> $7C,X count + $7E,X body address
  $D1 SET_RATE    -> $43,X
  $D7 DRAW_SPRITE -> count for the $A660 blit
  $E1 SET_WAIT    -> $7A,X
$CB and $C9 are the loop pair: LOOP_END decrements $7C,X and, if non-zero,
reloads Y from $7E,X, rewinding the interpreter to the top of the body.

Decoded 377 opcodes and 937 literal operands across 42 programs; all 40 defined
opcodes are used, so the opcode set is complete.

Two standalone programs contain no byte in $C8-$EF at all, so every byte takes
the literal path - the listing states that rather than implying code.

Each mnemonic was read off its handler's own straight-line path up to its first
'jmp $9AE8' / 'jmp $9B25'. Handlers that only tail-call a game routine are named
for the routine rather than guessed at, since the semantics live in the callee.

bytecode_dis.py inserts comment lines only; .byte data is copied through
verbatim. Verified: all 16,384 PRG bytes still emitted exactly once, every value
equal to the ROM, zero audit mismatches, and 'make regen' (pass1 -> pass2 ->
pass3) reproduces prg.s byte-for-byte. Byte-identical ROM verified.

MUSCLE (U): trace the stage payload with py65 (Pass 3c)

The stage payload at $B0FB-$BEBF could not be decoded statically, and the
trace confirms why: across all 16 KB of code there is exactly ONE operand in
that range - $BD28, the literal pointer table itself. The real address is
formed at runtime by 'adc $BD28,X', where A is a value carried in the
bytecode stream, so the readers are indexed by a runtime value and cannot be
enumerated by searching the code.

FCEUX cannot observe this either: its Lua API offers registerexec /
registerexecute / registerrun / registerwrite but no memory-read hook, and with
no static list of readers there is nothing to hook.

_tools/trace_stage_payload.py therefore uses py65's
ObservableMemory.subscribe_to_read, which fires on every read of a range and
can report PC from inside the hook. py65 is only a 6502 core, so the tracer
adds the minimum hardware to boot the game: a scanline counter off
processorCycles, $2002 vblank at scanline 241 cleared on read, NMI at the same
scanline, $4014 OAM DMA, and the $2005/$2006/$2007 latches. Without NMI the
game never leaves $80E0 (lda $0321 / beq), because $0321 is the flag the
bytecode VM raises.

Results over 300 frames (26.8M cycles, 4,071 NMIs): 2,548 payload reads from
44 distinct reader PCs, touching 914 of 3,421 bytes.

Findings:
- The hottest reader is $AC82, the compressed-VRAM RLE decoder: 1,046 of its
  1,128 reads advance by exactly +1, comparing each byte against $C0.
- $9B50 is a backward copy (lda ($12),Y / sta ($10),Y / dey), stepping -1.
- 107 distinct 8-byte blocks had all eight bytes read, with spacing dominated
  by $08 - a dense 8-byte record array - plus a sparser group on a $D0 stride
  ($BBE0, $BCB0, $BD80, $BE50).
- Readers use three disciplines: forward sequential, backward sequential, and
  variable stride consistent with a fixed-offset field walk.

py65 is deliberately NOT a build dependency: the tool documents unpacking the
wheel on demand. 'make regen' is unaffected. Byte-identical ROM verified.

MUSCLE (U): fix the NMI/controller bugs in the payload tracer; correct the Pass 3c model

The Pass 3c tracer had two bugs that were suppressing all progress, both now
fixed in _tools/trace_stage_payload.py (previously scratch, now durable):

* NMI re-armed by $2002 reads. The vblank flag must be cleared once per frame,
  not on every read of $2002 -- the main loop polls $2002 and spins on the
  bytecode VM flag at $8226, so each poll cleared bit 7 and re-armed NMI inside
  the same scanline. That gave 40,900 NMIs across 3,000 frames and corrupted all
  game state. A per-frame nmi_fired flag restores exactly one NMI per frame
  (verified: 600 frames -> 600 NMIs).

* Input went to $4017. The poll routine at $AA69 is `lda $4016,X` with X=1 and
  only compares bits 0-1, so driving $4017 consumed nothing -- all masks tested
  gave byte-identical traces. The game consumes joy1 on $4016 and needs a real
  latch/shift register. With it, START matters: 47.7% coverage vs 39.0%.
  Match entry is two discrete START presses (title -> char select -> match).

Coverage 26.3% -> 40.9%. Both bugs and the port subtlety are documented in the
tool header so they are not re-derived.

Corrects the Pass 3c conclusion. $B0FB-$BEBF is NOT per-stage level data:

* $BD28 is an 8-entry per-ACTOR entry-offset table, not a stage table.
  $A063 `adc $BD28,X` with X = ActorIndex ($1E), plus #$32 at $A071 when
  PlayerActiveFlag ($78,X) is set. Observed actors are $00-$04.
  Forcing all 8 actor indices adds zero new bytes.

* The sound engine reads the same window: $B03C `lda (SoundReadPointerLo),Y`
  with `cmp #$F0 / sbc #$F0 / tay / lda $B0E3,y` is note decoding, which is
  where the $FF/$F0 high-nibble terminators come from.

So the window is a shared pool -- per-actor bytecode, compressed-VRAM RLE, and
music note streams -- and stage cycling cannot raise coverage past ~40%. The
remaining 2,161 B need other music tracks and scripted match play, not taps.

Also corrects two stale claims: $0326 is not a level byte (never leaves $00 even
in a confirmed match), and $0320 is a boolean, not a screen-state machine.

Build unaffected: byte-identical, and `make regen` still reproduces prg.s
byte-for-byte.

MUSCLE (U): promote to the top-level inventory as #16

git mv _wip/MUSCLE (U) -> MUSCLE (U). The relative symlink
copilot/rom.nes -> ../M.U.S.C.L.E. (U).nes survives the move untouched.

* copilot/Makefile: tool paths ../../../_tools/ -> ../../_tools/ for NESDIS,
  PASS2 and BYTECODE. `make` prints the byte-identity message and `make regen`
  still reproduces prg.s byte-for-byte, both verified after the move.
* MUSCLE (U)/OVERVIEW.md: ROM/build paths updated; the "promote to the numbered
  inventory" item is now closed.
* Root OVERVIEW.md: added as #16, removed from the _wip skeleton table
  (20 -> 19), added a ### 16 detail section, and its vector/mapping notes moved
  out of the skeleton section.

All 16 top-level projects verified byte-identical.

Add Pass 1 verification tool; teach nesdis.py the undocumented opcodes

_tools/verify_pass1.py verifies Pass 1 across every top-level project in two
independent directions:

  * Faithful  - does the source still rebuild its ROM byte-identical?
  * Complete  - does the committed source claim as much code as a fresh trace
                can actually reach?

The second is the one that catches real problems. A project can reproduce all
16,384 bytes perfectly while calling 95% of them data, because the tracer gave
up early. Instruction *counts* are compared rather than bytes, so the check is
independent of comment style (which is not uniform across the repo).

Result: all 16 projects are faithful, and Joypad Test Cartridge is the only
truncated Pass 1 -- 282 committed instructions against 1,773 reachable.

nesdis.py gains three fixes needed to make that verdict trustworthy:

* --undocumented (default OFF) decodes the 105 undocumented NMOS opcodes.
  They are used as NOP padding and as real RMW ops, and a tracer that halts on
  them loses most of the code: Joypad goes 580 -> 3,846 code bytes. Default off
  so MUSCLE's `make regen` still reproduces prg.s byte-for-byte.

* Undocumented opcodes are emitted as explicit `.byte` with the disassembly in
  the comment. ca65 V2.19 implements none of them, so they cannot be written as
  mnemonics; keeping them in the code region preserves the code/data split and
  the audit trail. Official 0xEA NOP still encodes normally.

* Two latent bugs, both now reachable:
  - RESET and IRQ are commonly the same address, and the semantic-name map was
    overwriting, renaming the primary entry point to `IrqHandler`. Now
    setdefault, in RESET/NMI/IRQ priority order.
  - render() stamped every branch operand as Label_XXXX and never reconciled it
    with the semantic name map, so a branch into a named routine emitted a label
    that was never defined and the file would not assemble.

Joypad Test Cartridge (U): redo Pass 1, complete Pass 2; merge _wip into the inventory

Pass 1 (redone). The committed split claimed 282 instructions / 580 bytes (3.5%).
The tracer halted at $C09E on `DCP $3C20` ($CF) -- an undocumented opcode used as
ordinary code -- and filed 3,256 bytes of real code as `.byte` data.

Now 1,773 instructions / 3,836 bytes (23.4%), a 6.6x increase. Verified against a
fresh trace: 1,769 emitted instructions + 4 undocumented opcodes written as
explicit bytes = 1,773, an exact match. Byte accounting stays exact at
3,836 + 12,548 = 16,384.

ca65 V2.19 implements none of the undocumented opcodes, so the 4 affected
instructions (DCP $3C20, LAX $20D4, NOP $C1,X, RRA ($D0),X) are emitted as
explicit `.byte` with the disassembly in the comment. They stay in the code
region, so the split and the audit trail are both preserved.

Pass 2 (complete). All 202 labels renamed; zero `Label_Cxxxx` remaining. Renames
are keyed by address and verified to have bound to the intended address, not just
to some address. 202 routine headers, every one describing real behaviour -- no
"undetermined in Pass 1" boilerplate left. `locals.inc` carries 99 aliases, the
proven semantic ones sitting beside the generated `ZpXX` entries per the alias
pattern in AGENTS.md.

Two naming errors in the previous draft are corrected: $55 is MarkerValue (it
holds $70/$88/$A0 and is compared against them), not a "P1 Down timer"; and the
twelve MarkerVar variables are named by position because which player/button
each belongs to is not established.

Pass 3 is now outstanding, and the old Pass 3 was wrong: it described
$C09E-$C173 and $C17F-$CCF6 as data, and both are code. After the redo there is
exactly one data region, $CF06-$FFFF, confirmed by orphan scan.

Documentation.

The root inventory now lists all 36 projects in one table -- the 20 `_wip/`
projects are rows 17-36 rather than a second table that could drift. That second
table had wrong mirroring for 12 of them (Duck Hunt, Golf, Hogan's Alley, Ice
Climber, DK Jr Math, Lode Runner, Lunar Pool, Mario Bros, Millipede, Pyramid,
Raid [a2], Urban Champion were all recorded H when they are V, and vice versa);
every value is now read from the ROM rather than trusted.

Build status is split into two honest tiers: rows 1-16 are BYTE-IDENTICAL and
compare the whole ROM, rows 17-36 are PRG-IDENTICAL because their Makefiles only
run `cmp reconstructed_prg.bin prg.bin`. Header and CHR are not proven for those.
All 36 build cleanly; the WIP count was also corrected from 19 to 20.

`_tools/verify_pass1.py` checks all 36 and reports none truncated.

Mario Bros (JU): Pass 1 disassembly, 14.6% -> 72.2% code

The skeleton prg.s was a 12-line .incbin of the whole PRG. It is now a real
disassembly: 5,735 instructions, 11,829 code bytes (72.2%), 937 labels and
routine headers, byte accounting exact at 11,800 + 4,584 = 16,384, and
BYTE-IDENTICAL against prg.bin.

Coverage came from four steps, not one:

  vectors + direct jsr/jmp/branch, official opcodes   1,185 instr   14.6%
  --undocumented (105 extra NMOS opcodes)            1,210 instr   14.9%
  --auto-seed (61 table-referenced entries)          4,187 instr   51.1%
  --extra-seed 0xC0B6,0xC5A3                         5,735 instr   72.2%

Almost everything sits behind computed dispatch. Three mechanisms are now
documented in the project OVERVIEW:

* $CD9E is a stack-based indirect call: it pops the table address with `pla`,
  indexes it by A*2 with `lda ($14),y`, then `jmp ($0014)`. Because the pointer
  arrives on the stack it cannot be followed statically from the dispatcher.
* $C5A3 is a 20-entry table of `JSR` thunks ending in `rts`. Probing it linearly
  gives twenty valid targets, and seeding it recovers most of the last 21%.
* $C0B6 is the NMI phase dispatcher, reachable only by computed dispatch.

Two overlapping-decode bugs in _tools/nesdis.py had to be fixed first:

* Control-flow targets must win over fall-through. `bne` at $C4BA targets $C528,
  but the fall-through path had decoded $C527 as a 2-byte `NOP #$A5` whose
  operand swallowed $C528. No assembler branches into the middle of an
  instruction, so the spanning decode is now dropped in favour of the target;
  its bytes fall out as data, leaving byte accounting unchanged.
* Branch targets inside a surviving instruction must be pruned from the target
  set. Otherwise emit_code's linear walk consumes those bytes as an operand, so
  the label is referenced but never defined and the file does not assemble at
  all -- `Error: Symbol 'Label_C528' is undefined`.

Neither change disturbs an existing pipeline: MUSCLE's `make regen` still
reproduces prg.s byte-for-byte, Joypad Test Cartridge still builds byte-identical,
and _tools/verify_pass1.py reports all 16 top-level projects faithful with none
truncated.

Mario Bros (JU): Pass 2, partial -- make the call graph readable

Two mechanical wins, in order of value:

* **477 jsr/jmp operands rewritten from raw $XXXX to labels.** The tracer emits
  call and jump targets as hex, so the call graph was unreadable -- the most
  called routine appeared as `jsr $CCA0` 14 times over. Every one of those sites
  already had a label at its target address, so the substitution is purely
  mechanical and byte-preserving. Zero raw hex call operands remain.

* **13 landmark routines named** from verified instruction evidence:
  WaitVblank1, ClearRamPageLoop, NmiPhaseDispatch ($C0B6), JsrThunkTable
  ($C5A3), DispatchViaStackPointer ($CD9E), AdvanceDispatchPointer ($CDB4),
  PushTablePointerDispatch ($CCA0), DispatchTableF393 ($CAB9),
  InitSpritesOffscreen ($DFC4), AdjustDecimalColumns ($D15A),
  TestRamFlagPair0500 ($D328), NextIndex ($CE84).

**922 labels remain unnamed, and that is recorded as the honest state** rather
than papered over. They sit 15-25 per page across $CC00-$CFFF, which is just
the game logic: naming them means reading ~5,700 instructions across 935
routines. Guessing from the call graph and the NES convention would be exactly
the guessy rewrite PROMPTS.md rules out.

The productive next step is more reach, not more naming. The 4,555 unreached
bytes are where most unnamed routines live, and they are gated behind the
stack-pointer dispatch at $CD9E, whose pushed table addresses are only known in
the callers' frames.

Still BYTE-IDENTICAL; all 16 top-level projects remain faithful.

Record audit-comment work for Excitebike and Pac-Man as a TODO

Both projects are hand-written sources with semantic labels but no
byte-identity audit comment, so a source line cannot be mapped back to exact
ROM bytes without re-assembling. Every other project in the repo carries one.

Documented what was tried and why it was dropped rather than left implicit.
Addresses from 'ca65 -l' work fine -- the listing offset indexes prg.bin
directly -- but the listing carries no source line numbers, so entries cannot
be keyed back to source lines. Byte values also have to come from prg.bin
rather than the listing, since ca65 prints 'rr rr' for relocatable words.

No tool was committed, because the one written does not work. Both projects
verified still byte-identical; no source file was changed.

Add TODO.md handoff; bring AGENTS.md back in line with the repo

AGENTS.md had drifted on facts that are now wrong, not just incomplete:

* "Eleven disassemblies" -> 36 (16 top-level, 20 in _wip). The index it points
  at had the same stale count.
* The audit-comment convention was stated as `; PRG:$xxxx AA BB`, but the
  dominant spelling is `; CPU:$xxxx PRG:xxxx  AA BB` and five other variants are
  in use, with Excitebike and Pac-Man carrying none at all. Now stated accurately
  as "preserve whatever spelling a file uses", with the gap pointed at.
* `_tools/` was described as holding splitnes.py and chr2gif.py. It holds six
  tools now, including the Pass 1 tracer and the verifier.
* ROM filenames "contain spaces" -> spaces *and parentheses*, which is also what
  makes FCEUX hang headless.

Added, because each one costs real time if rediscovered:

* The two build tiers. The 20 _wip projects only `cmp reconstructed_prg.bin
  prg.bin`, so their header and CHR are NOT proven -- calling them
  byte-identical to the ROM is wrong.
* A promotion procedure, since that has now been done twice.
* A tracer/toolchain gotchas section: undocumented opcodes truncating traces
  (and ca65 being unable to encode them), vectors living at the end of the PRG
  rather than in the iNES header, the RESET==IRQ name collision, control-flow
  targets beating fall-through, the branch-target pruning that stops a file
  failing to assemble, NMI firing once per frame rather than per $2002 read,
  joy1 being $4016, and ca65 listings carrying no source line numbers.

TODO.md is the handoff document: open work per project with enough detail to
resume, the residue counts, and the reasoning behind each abandoned approach so
it is not silently retried.

Nothing rebuilt or changed; all projects still verify.

Joypad Test Cartridge (U): Pass 3 complete — data region named and VRAM command stream decoded

The monolithic `.byte $CF06-$FFFF` block (12,538 bytes) has been broken into 20 named sub-blocks:

VRAM command streams ($CF06-$D4AD): three command streams separated by $FF terminators, read by VramWriteLoop via RetAddrUpdate. Opcode set decoded: $4C = jump, $60 = deferred PPUCTRL, bit-7-clear = VRAM write (PPU addr + data, bit 6 of data = long/short selector).

OAM tables ($D4E3-$D6A3): 12 groups of 32-byte tables (8 entries of Y/tile/attr/X), read by CopyOamFromTable ($D4E3) and CopyOamFromTable70 ($D5C4).

Sprite templates ($D6A5-$D6E4): sprite template read by CopySpriteTemplateTable ($D6A5) and button OAM template read by PrepareButtonOam ($D6C5).

Padding: $FF ROM padding ($D6E5-$FFF9) and vectors ($FFFA-$FFFF).

OVERVIEW.md updated: section 8 documents all 20 sub-blocks with the VRAM command stream format and OAM table structure; section 11 marks Pass 3 complete with remaining work (MarkerVar player/button mapping, HandleButtonPress sprite order).

Build verified byte-identical.

Root OVERVIEW.md: mark Joypad Pass 3 complete

Update summary table (row 14: Pass Pass 2 → Pass 3 complete), detail section
(section 14: Pass 2/3 → Pass 3 complete with VRAM command stream decoded),
methodology note table (Pass 2 complete → Pass 3 complete), and notable
observations (Joypad at Pass 2/3 → Pass 3 complete).

Joypad Test Cartridge is now fully Pass 3 complete: all 20 data sub-blocks
named, VRAM command stream opcode set decoded, OAM table structure documented.

TODO.md: mark Joycad Test Cartridge Pass 3 complete

Mario Bros (JU): Pass 2 substantial — 252 subroutine entry points named

All 252 jsr/jmp targets resolved from raw $XXXX to labels:
- 90 semantic names: audio/sound routines ($F8xx-$F9xx: AudioPulse1CtrlSetup,
  WritePulse2Freq, ProcessTriangleChannel, SetupAudioChannel, etc.), gameplay
  helpers (CheckZpB3Return, CheckRangeReturn012, ToggleZpFC_Bit3, etc.)
- 165 address-based names (SubCxxxx) for the remainder
- Zero raw $XXXX call operands remain

667 Label_Cxxxx internal branch targets remain — these are local control-flow
destinations within routines, not subroutine entry points, and do not need
semantic names for the call graph to be readable.

Build verified BYTE-IDENTICAL. Root OVERVIEW.md updated (row 26: Pass 0 skeleton
→ Pass 1 complete, Pass 2 substantial). TODO.md updated.

Lunar Pool (U): Pass 1 complete, 57.8% code; nesdis.py opcode/emitter fixes

The skeleton prg.s was a 12-line .incbin of the whole PRG. It is now a real
disassembly: 4,586 instructions, 9,475 code bytes (57.8%), 6,909 data bytes,
byte accounting exact at 9,403 + 6,981 = 16,384, BYTE-IDENTICAL.

Coverage: vectors + direct jsr/jmp/branch reached 1,259 instructions (15.6%).
--undocumented took it to 4,572 (57.6%), and --auto-seed (48 table-referenced
entries) plus --extra-seed 0xE80C (the non-$C000 RESET vector) took it to
4,586 (57.8%).

Three undocumented opcodes were missing from nesdis.py and are added:
* $72 RRA abs (the RRA family was missing the $72 absolute form)
* $42 NOP #imm (the NOP-immediate family was missing $42)
* $FA NOP abs,X (the NOP-abs,X family was missing $FA)

One emitter fix: ca65 silently rewrites an operand whose high byte is $00 in
the shorter zero-page form -- `sta $00FF,x` assembles as `sta $FF,x` ($95
instead of $9D). render() now emits the `a:` prefix for ABS/ABSX/ABSY/IND
operands with a high byte of $00, forcing the absolute form so the emitted
bytes match the ROM. operand_value() strips the prefix when parsing.

These changes are general and verified not to disturb existing pipelines:
MUSCLE's `make regen` still reproduces prg.s byte-for-byte.

Remaining gaps: 6,909 bytes (42.2%) still data, gated behind jmp ($7961) at
three sites (RAM-pointer dispatch; the blocks after them hold the screen text
"strings" "game over" and "hi`score__") and stack-based dispatch at $CBDC --
the same pattern as Mario Bros (JU)'s $CD9E.

TODO.md: add Lunar Pool as an open item

Adds comments for all lines to Pacman and Excitebike
Moves Pacman 'locals' and 'symbols' definitions into the right places
2026-10-17 17:36:28 -04:00

14333 lines
301 KiB
Plaintext

;----------------------------------------------------
; NES Specific
;----------------------------------------------------
; PPU registers
PPUCTRL = $2000
PPUMASK = $2001
PPUSTATUS = $2002
OAMADDR = $2003
OAMDATA = $2004
PPUSCROLL = $2005
PPUADDR = $2006
PPUDATA = $2007
OAMDMA = $4014
JOY1 = $4016
APUPC1BASE = $4000
APUPC1SWEEP = $4001
APUPC1TLOW = $4002
APUPC1THIGH = $4003
APUPC2BASE = $4004
APUPC2SWEEP = $4005
APUPC2LOW = $4006
APUPC2HIGH = $4007
APUTRIBASE = $4008
APUTRILOW = $400A
APUTRIHIGH = $400B
APUNOISEBASE = $400C
APUNOISELOOP = $400E
APUNOISELENGTH = $400F
APUDMABASE = $4010
APUDMALOAD = $4011
APUDMAADDR = $4012
APUDMALEN = $4013
APUCTRL = $4015
APUSTATUS = $4015
APUFRAMECNT = $4017
PAD_RIGHT=$01
PAD_LEFT=$02
PAD_DOWN=$04
PAD_UP=$08
PAD_START=$10
PAD_SELECT=$20
PAD_B=$40
PAD_A=$80
PAD_HORIZONTAL=PAD_LEFT|PAD_RIGHT
PAD_VERTICAL=PAD_UP|PAD_DOWN
PAD_DPAD=PAD_HORIZONTAL|PAD_VERTICAL
PAD_MENU_INPUT=PAD_SELECT|PAD_START|PAD_VERTICAL
;----------------------------------------------------
; Pacman Specific
;----------------------------------------------------
NmiHandlerOffset = $C10E
ResetOffset = $C033
IrqHandlerOffset = $C17E
;==============================================================================
; Zero Page
;==============================================================================
;------------------------------------------------------------------------------
; General work pointers / scratch
;------------------------------------------------------------------------------
workPtr0 = $00
workPtr1 = $02
workValue0 = $04
workValue1 = $05
workValue2 = $06
workValue3 = $07
workValue4 = $08
workValue5 = $09
;------------------------------------------------------------------------------
; Maze pathfinding / direction selection workspace
;------------------------------------------------------------------------------
pathCompareValue = $0A
pathFallbackValue = $0B
legalDirections = $0C
;------------------------------------------------------------------------------
; Indirect dispatcher vector
;------------------------------------------------------------------------------
dispatchVector = $10
;------------------------------------------------------------------------------
; 16-bit arithmetic workspace
;------------------------------------------------------------------------------
arithmeticValue = $12
arithmeticOperand = $14
;------------------------------------------------------------------------------
; Actor fixed-point/motion records
;
; Each actor record occupies four bytes.
;------------------------------------------------------------------------------
pacmanMotion = $1A
ghost1Motion = $1E
ghost2Motion = $22
ghost3Motion = $26
ghost4Motion = $2A
;------------------------------------------------------------------------------
; Pac-Man maze targeting
;------------------------------------------------------------------------------
pacmanTargetRow = $2E
pacmanTargetColumn = $30
;------------------------------------------------------------------------------
; Sprite/animation selection
;------------------------------------------------------------------------------
pacmanSpriteFrame = $32
ghostSpriteFrames = $33
;------------------------------------------------------------------------------
; Ghost direction/control state
;------------------------------------------------------------------------------
ghostDirectionFlags = $39
;------------------------------------------------------------------------------
; Main gameplay state
;------------------------------------------------------------------------------
gameState = $3F
;------------------------------------------------------------------------------
; PPU / player-screen state
;------------------------------------------------------------------------------
ppuCtrlShadow = $43
cocktailModeFlag = $46
activePlayerFlag = $47
gameplaySuspended = $48
frameCounter = $4B
stateTimer = $4C
;------------------------------------------------------------------------------
; Input / Pac-Man movement
;------------------------------------------------------------------------------
currentInput = $4D
newlyPressedInput = $4E
previousInput = $4F
pacmanRequestedDirection = $50
pacmanDirection = $51
;------------------------------------------------------------------------------
; Score / round / maze progression
;------------------------------------------------------------------------------
highScoreDigits = $61
roundNumber = $67
roundDisplayValue = $68
pelletsRemaining = $6A
extraLifeAwarded = $6B
actorStatusDisplay = $6C
playerScoreDigits = $70
;------------------------------------------------------------------------------
; Audio Engine
;------------------------------------------------------------------------------
audioRequestPtr = $F0
audioChannelTablePtr = $F2
audioSequenceTablePtr = $F4
audioWorkPtr = $F6
audioWorkValue = $F7
audioChannelFlags = $F8
audioRequestIndex = $FC
audioChannelOffset = $FD
audioIndirectPtr = $FE
.segment "PRG"
.org $C000
CopyrightText:
.byte $43,$4F,$50,$59,$20,$52,$49,$47
.byte $48,$54,$20,$31,$39,$38,$34,$20
.byte $31,$39,$39,$33,$20,$4E,$41,$4D
.byte $43,$4F,$20,$4C,$54,$44,$2E,$20
.byte $41,$4C,$4C,$20,$52,$49,$47,$48
.byte $54,$53,$20,$52,$45,$53,$45,$52
.byte $56,$45,$44
Reset:
ldx #$00
stx PPUCTRL
sei
cld
dex
txs
nop
nop
nop
ldx #$02
@WaitForPpu:
lda #$06
sta PPUMASK
lda PPUSTATUS
@WaitVBlank:
lda PPUSTATUS
bpl @WaitVBlank
lda #$00
sta PPUSCROLL
sta PPUSCROLL
dex
bne @WaitForPpu
lda #$00
tay
@ClearLowRam:
sta $0000,y
iny
cpy #$3E
bne @ClearLowRam
ldx #$08
ldy #$87
@ClearRamPages:
sta ($00),y
iny
bne @ClearRamPages
inc $01
cpx $01
bne @ClearRamPages
lda #$06
sta PPUMASK
lda #$00
sta PPUSCROLL
sta PPUSCROLL
sta $41
sta $42
sta PPUCTRL
sta PPUMASK
tay
@CheckRamSignature:
lda RamSignature,y
cmp $0052,y
bne @InitializeRamSignature
iny
cpy #$0F
bne @CheckRamSignature
beq @SignatureAlreadyPresent
@InitializeRamSignature:
lda #$00
tay
@ClearZeroPage:
sta $0000,y
iny
bne @ClearZeroPage
ldy #$00
@CopyRamSignature:
lda RamSignature,y
sta $0052,y
iny
cpy #$0F
bne @CopyRamSignature
lda #$01
sta $64
@SignatureAlreadyPresent:
ldy #$00
sty $3F
lda #$07
sta $45
lda #$01
cmp $47
bne @SkipSwap
cmp $46
bne @SkipSwap
@SwapLoop:
lda $77,y
sta workPtr0
lda $67,y
sta $77,y
lda workPtr0
sta $67,y
iny
cpy #$10
bne @SwapLoop
@SkipSwap:
lda #$1F
sta APUCTRL
lda #$C0
sta APUFRAMECNT
jsr InitializeAudioEngine
jsr $EEB4
lda #$88
sta $43
sta PPUCTRL
lda #$FF
sta $48
sta $023F
sta $024B
jmp MainEntry
RamSignature:
.byte "HIROKI AOYAGI"
.byte $AA, $55
;==============================================================================
; $C10E-$C17E: NMI Handler
;
; Local:
; Handles the per-frame NMI. Saves the CPU registers, configures the PPU,
; performs sprite OAM DMA, calls several frame-processing routines, restores
; the current scroll and PPU control state, polls both controllers, selects
; the active controller input, and conditionally performs additional
; per-frame processing.
;
; Global:
; Primary VBlank/frame interrupt entry point. This routine ties together
; rendering updates, sprite DMA, controller polling, and other work that
; needs to occur once per video frame.
;
; Notes:
; $41/$42 are used as the two PPUSCROLL writes.
; $45 supplies the source page for OAM DMA.
; $4D/$4E receive controller 1/2 input bits.
; $4F receives the controller state selected by $46/$47.
;
; The purposes of $40-$48 and the called routines at FlushGameplayPpuUpdates
; ReadNearbyNametableTiles, UpdatePalettes, and InitializeAudioChannels will
; be refined during later passes.
;==============================================================================
NmiHandler:
pha
txa
pha
tya
pha
lda #$1E
sta PPUMASK
lda #$00
sta OAMADDR
sta $40
lda $45
sta OAMDMA
jsr FlushGameplayPpuUpdates
jsr ReadNearbyNametableTiles
jsr UpdatePalettes
; Restore the two PPU scroll components.
lda $41
sta PPUSCROLL
lda $42
sta PPUSCROLL
; Construct the PPUCTRL value for this frame.
lda $47
and $46
and #$01
asl
ora $43
sta PPUCTRL
; Strobe the controller ports.
lda #$01
sta JOY1
lda #$00
sta JOY1
; Read eight serial bits from both controllers.
ldx #$08
@ReadControllers:
lda JOY1
and #$03
cmp #$01
ror $4D
lda APUFRAMECNT
and #$03
cmp #$01
ror $4E
dex
bne @ReadControllers
; Controller 1 is the default input source.
ldx currentInput
; Under the condition controlled by $46/$47,
; use controller 2 instead.
lda cocktailModeFlag
and activePlayerFlag
beq @ControllerSelected
ldx newlyPressedInput
@ControllerSelected:
stx previousInput
; Advance a per-frame counter.
inc frameCounter
; $48 gates an additional per-frame routine.
lda $48
bne @NmiDone
jsr InitializeAudioChannels
@NmiDone:
pla
tay
pla
tax
pla
IrqHandler:
rti
UpdatePalettes:
lda $0299
cmp #$0F
bne @CheckSecondPalette
lda PPUSTATUS
lda #$3F
sta PPUADDR
lda #$00
sta PPUADDR
ldy #$00
@CopyFirstPalette:
lda $0299,y
sta PPUDATA
iny
cpy #$10
bne @CopyFirstPalette
@CheckSecondPalette:
lda $02A9
cmp #$0F
bne @CheckPaletteState
lda PPUSTATUS
lda #$3F
sta PPUADDR
lda #$10
sta PPUADDR
ldy #$00
@CopySecondPalette:
lda $02A9,y
sta PPUDATA
iny
cpy #$10
bne @CopySecondPalette
@CheckPaletteState:
lda $0299
cmp #$0F
beq @ResetPpuAddress
lda $02A9
cmp #$0F
beq @ResetPpuAddress
rts
@ResetPpuAddress:
lda #$30
sta PPUADDR
lda #$00
sta PPUADDR
sta PPUADDR
sta PPUADDR
sta $0299
sta $02A9
rts
;==============================================================================
; $C1E7-$C266: Main Initialization and State Dispatch
;
; Local:
; Synchronizes with the NMI through $40, initializes PPU/OAM and several
; game-state variables, calls four initialization routines, initializes the
; two 16-byte palette buffers, then enters the main state-dispatch loop.
;
; The loop synchronizes with NMI again, uses $3F as an index into a table of
; five handler addresses, stores the selected address in $10/$11, and jumps
; to that handler indirectly.
;
; Global:
; This is the main program entry reached from Reset after hardware and RAM
; initialization. It transitions the game into its normal frame-driven state
; machine. NMI clears $40 each frame, allowing the foreground/main code to
; proceed through its next state-machine update.
;
; Notes:
; $40 clearly provides synchronization between foreground code and NMI.
; Writing $01 to $40 and spinning until NMI clears it forms a frame wait.
;
; $3F is used here as a state/dispatch index. Its exact semantic states will
; be named as the individual handlers are decoded.
;
; $10/$11 form a temporary indirect JMP pointer.
;
; $C267 begins the state-handler address table and is intentionally left for
; the next decoding pass.
;==============================================================================
MainEntry:
lda #$01
sta $40
@WaitForInitialNmi:
lda $40
bne @WaitForInitialNmi
lda #$08
sta PPUCTRL
sta ppuCtrlShadow
lda PPUSTATUS
lda #$00
sta PPUMASK
tax
@ClearOamPage:
sta $0700,x
inx
bne @ClearOamPage
sta cocktailModeFlag
jsr ClearGameplayNametables
jsr InitializeNametableAttributes
jsr InitializeNametableData
jsr $E419
ldy #$00
@InitializePaletteBuffers:
lda TitleScreenPalette,y
sta $0299,y
iny
cpy #$20
bne @InitializePaletteBuffers
lda #$2D
sta actorStatusDisplay
sta actorStatusDisplay+1
sta actorStatusDisplay+2
sta actorStatusDisplay+3
lda #$FF
sta $023F
sta gameplaySuspended
lda #$00
sta activePlayerFlag
sta cocktailModeFlag
sta $42
sta $41
sta stateTimer
sta $87
sta $88
lda gameState
bne @UseAlternatePpuCtrl
lda #$8A
bne @SetPpuCtrl
@UseAlternatePpuCtrl:
lda #$88
@SetPpuCtrl:
sta PPUCTRL
sta ppuCtrlShadow
MainDispatchLoop:
lda #$01
sta $40
@WaitForNmi:
lda $40
bne @WaitForNmi
ldy gameState
lda $C267,y
sta dispatchVector
lda $C268,y
sta dispatchVector+1
jmp ($0010)
;==============================================================================
; $C267-$C26E: Main State Dispatch Table
;
; Local:
; Four 16-bit addresses used by the indirect state dispatcher at the end of
; MainEntry. $3F is used directly as a byte offset into this table:
;
; lda MainStateHandlerTable,y
; sta $10
; lda MainStateHandlerTable+1,y
; sta $11
; jmp ($0010)
;
; This implies the normal state values in $3F are even byte offsets:
; $00, $02, $04, $06
;
; Global:
; Selects one of four major foreground/game-state handlers. This is the
; top-level state machine entered after Reset initialization and synchronized
; to the NMI-driven frame loop.
;
; Notes:
; The four targets are:
; InitialStateHandler
; PlayerSelectionState
; State04Handler
; TimedStateTransition
;
; Handler names remain provisional until each target is decoded.
;==============================================================================
MainStateHandlerTable:
.word InitialStateHandler
.word PlayerSelectionState
.word State04Handler
.word TimedStateTransition
;==============================================================================
; $C26F-$C279: Timed State Transition Handler
;
; Local:
; Decrements the counter at $0F once per frame. While the counter remains
; nonzero, control returns to the common NMI synchronization/dispatch loop
; at $C250.
;
; When the counter reaches zero, changes the main state offset at $3F to
; $02 and then returns to the same dispatch loop.
;
; Global:
; Implements the handler selected when the main state offset is $06.
; This appears to be a timed transitional state. Another state places a
; duration in $0F and selects state $06; this handler counts that duration
; down before advancing to state $02.
;
; Notes:
; $0F is clearly being used as a countdown timer here, but its broader use
; should be traced before assigning it a permanent global name.
;
; $3F is the byte offset into MainStateHandlerTable:
; $00 -> InitialStateHandler
; $02 -> PlayerSelectionState
; $04 -> State04Handler
; $06 -> this handler
;
; $C250 is inside MainEntry and is the common frame synchronization point.
;==============================================================================
TimedStateTransition:
dec $0F
bne MainDispatchLoop
lda #$02
sta gameState
jmp MainDispatchLoop
;==============================================================================
; $C27A-$C2A5: Initial/Input State Handler
;
; Local:
; Checks controller-1 state bits $0C. If either bit is set, selects state
; $06, initializes the countdown at $0F to $5A, and jumps back to MainEntry.
;
; Otherwise increments $42. When $42 reaches $F0, resets it to zero,
; changes the saved PPUCTRL value at $43 to $88, and selects state $06
; with the same $5A countdown.
;
; If $42 has not yet reached $F0, the initialization of state $06 is
; skipped and execution reaches the common JMP back to MainDispatchLoop.
;
; Global:
; Handler selected by main state offset $00. It appears to manage an early
; or attract/input-wait state, transitioning into the timed state either
; because of controller input or because the scrolling/progression value
; at $42 reaches $F0.
;
; Notes:
; $4D is controller-1 input assembled by NmiHandler.
; $42 is also used as the second PPUSCROLL write during NMI.
; Exact game-state semantics will be refined in later passes.
;==============================================================================
InitialStateHandler:
lda currentInput
and #$0C
beq @AdvanceScroll
lda #$06
sta gameState
lda #$5A
sta $0F
jmp MainEntry
@AdvanceScroll:
inc $42
lda #$F0
cmp $42
bne @ReturnToMainLoop
lda #$00
sta $42
lda #$88
sta ppuCtrlShadow
lda #$06
sta gameState
lda #$5A
sta $0F
@ReturnToMainLoop:
jmp MainDispatchLoop
;==============================================================================
; $C2A6-$C30A: Initialize Nametable Data
;
; Local:
; Performs two groups of PPU writes used during initial screen setup.
;
; First phase:
; - Begins at PPU address $20E5.
; - Copies $17 bytes from the ROM data at TitleScreenTileBlock
; - Repeats this for six rows.
; - Advances the destination PPU address by $20 after each row.
;
; Second phase:
; - Processes nine variable-length PPU update records beginning at $C3B0.
; - Each record supplies a two-byte PPU address followed by data bytes.
; - $FF terminates the data portion of each record.
;
; Global:
; Called from MainEntry during initial game setup. This routine constructs
; part of the initial nametable/display before normal state dispatch begins.
;
; Notes:
; $00/$01 are used here as a 16-bit PPU destination address.
; $02 counts the six repeated rows.
; $03 counts $17 bytes within each row.
;
; TitleScreenTileBlock is a repeated row of tile data.
; $C3B0 contains nine encoded PPU update records.
;
; These ROM data blocks will be named more precisely when their contents
; and resulting screen regions are decoded in later passes.
;==============================================================================
InitializeNametableData:
; Initial PPU destination = $20E5.
lda #$20
sta workPtr0
lda #$E5
sta workPtr0+1
; Write six rows.
lda #$06
sta workPtr1
ldy #$00
@WriteRow:
lda PPUSTATUS
lda workPtr0
sta PPUADDR
lda workPtr0+1
sta PPUADDR
; Each row contains $17 bytes.
lda #$17
sta workPtr1+1
@WriteRowBytes:
lda TitleScreenTileBlock,y
sta PPUDATA
iny
dec workPtr1+1
bne @WriteRowBytes
; Advance the PPU destination by $20.
lda workPtr0+1
clc
adc #$20
sta workPtr0+1
lda #$00
adc workPtr0
sta workPtr0
dec workPtr1
bne @WriteRow
; Process nine additional encoded PPU update records.
lda #$09
sta workPtr0
ldy #$00
@NextPpuRecord:
lda PPUSTATUS
; First two bytes of each record are the PPU destination address.
lda TitleScreenPpuRecords,y
sta PPUADDR
iny
lda TitleScreenPpuRecords,y
sta PPUADDR
iny
@WritePpuRecord:
lda TitleScreenPpuRecords,y
cmp #$FF
beq @EndPpuRecord
sta PPUDATA
iny
bne @WritePpuRecord
@EndPpuRecord:
iny
dec workPtr0
bne @NextPpuRecord
rts
;==============================================================================
; $C30B-$C325: Initialize Nametable Attributes
;
; Local:
; Writes $18 bytes from TitleScreenAttributeData to PPU address $23C8.
;
; Global:
; Initializes a portion of the nametable attribute table during startup.
;==============================================================================
InitializeNametableAttributes:
lda PPUSTATUS
lda #$23
sta PPUADDR
lda #$C8
sta PPUADDR
ldy #$00
@CopyAttributes:
lda TitleScreenAttributeData,y
sta PPUDATA
iny
cpy #$18
bne @CopyAttributes
rts
;==============================================================================
; $C326-$C3AF: Initial Nametable Tile Block
;
; Local:
; Contains six rows of $17 (23) tile indices. InitializeNametableData copies
; these rows sequentially to the PPU beginning at $20E5, advancing the PPU
; destination by $20 after each row.
;
; Destination layout:
; Row 0 -> $20E5-$20FB
; Row 1 -> $2105-$211B
; Row 2 -> $2125-$213B
; Row 3 -> $2145-$215B
; Row 4 -> $2165-$217B
; Row 5 -> $2185-$219B
;
; Global:
; Forms a rectangular portion of the initial nametable display constructed
; during MainEntry. The first and last rows consist primarily of border
; tiles, while the middle four rows contain the interior graphic/tile data.
;
; Notes:
; Each row is exactly $17 bytes.
;
; Tile values are left numeric during this pass. Their visual meanings can
; be determined later from the CHR-ROM and assigned symbolic tile names if
; useful.
;==============================================================================
TitleScreenTileBlock:
; Row 0 -> PPU $20E5-$20FB
.byte $E4,$E8,$E8,$E8,$E8,$E8,$E8,$E8
.byte $E8,$E8,$E8,$E8,$E8,$E8,$E8,$E8
.byte $E8,$E8,$E8,$E8,$E8,$E8,$E5
; Row 1 -> PPU $2105-$211B
.byte $EB,$88,$80,$81,$82,$83,$84,$85
.byte $86,$87,$88,$88,$89,$8A,$8B,$8C
.byte $8D,$8E,$8F,$90,$91,$A3,$E9
; Row 2 -> PPU $2125-$213B
.byte $EB,$88,$92,$93,$94,$95,$96,$97
.byte $98,$99,$9A,$9B,$9C,$9D,$9E,$9F
.byte $A0,$A1,$A2,$A3,$A4,$A3,$E9
; Row 3 -> PPU $2145-$215B
.byte $EB,$88,$92,$A5,$A6,$A7,$A8,$A9
.byte $AA,$AB,$AC,$AD,$AE,$A3,$AF,$D0
.byte $D1,$D2,$A3,$D3,$A4,$A3,$E9
; Row 4 -> PPU $2165-$217B
.byte $EB,$88,$D4,$D5,$D6,$D7,$D8,$D9
.byte $DA,$DB,$88,$88,$DC,$D7,$DD,$DE
.byte $DF,$E0,$E1,$E2,$E3,$A3,$E9
; Row 5 -> PPU $2185-$219B
.byte $E7,$EA,$EA,$EA,$EA,$EA,$EA,$EA
.byte $EA,$EA,$EA,$EA,$EA,$EA,$EA,$EA
.byte $EA,$EA,$EA,$EA,$EA,$EA,$E6
;==============================================================================
; $C3B0-$C43E: Initial Screen PPU Records
;
; Local:
; Nine variable-length PPU write records consumed by InitializeNametableData.
; Each record has the form:
;
; .byte PPU address high
; .byte PPU address low
; .byte tile data...
; .byte $FF
;
; InitializeNametableData processes exactly nine records. For each record it
; loads the two-byte destination into PPUADDR and copies tile values to
; PPUDATA until the $FF terminator is encountered.
;
; Global:
; Supplies additional initial-screen graphics and text after the main
; six-row tile block has been installed. These records include player-count
; selections and copyright/licensing text as represented by the ROM's
; nametable tile values.
;
; Notes:
; Many values correspond directly to ASCII character codes, but not all do.
; Preserve the original tile bytes rather than converting these records to
; assembler strings during this pass.
;
; $FF is structural and is not written to PPUDATA.
;==============================================================================
TitleScreenPpuRecords:
;------------------------------------------------------------------------------
; Record 0: $C3B0-$C3C7
; PPU destination: $2065
; Data length: $15 (21) tiles
;------------------------------------------------------------------------------
.byte $20,$65
.byte $B0,$B3,$B2,$20,$20,$20,$20
.byte $B4,$B5,$B6,$B7,$B8,$B9,$BA,$BB
.byte $20,$20,$20,$B1,$B3,$B2
.byte $FF
;------------------------------------------------------------------------------
; Record 1: $C3C8-$C3D2
; PPU destination: $220C
; Data length: $08
;
; Visible tile values correspond to:
; "1 PLAYER"
;------------------------------------------------------------------------------
.byte $22,$0C
.byte $31,$20,$50,$4C,$41,$59,$45,$52
.byte $FF
;------------------------------------------------------------------------------
; Record 2: $C3D3-$C3DE
; PPU destination: $224C
; Data length: $09
;
; Visible tile values correspond to:
; "2 PLAYERS"
;------------------------------------------------------------------------------
.byte $22,$4C
.byte $32,$20,$50,$4C,$41,$59,$45,$52,$53
.byte $FF
;------------------------------------------------------------------------------
; Record 3: $C3DF-$C3FC
; PPU destination: $22E3
; Data length: $1B (27)
;
; Mostly text-like tile data containing the original copyright/date/NAMCO
; display. Special tile values are retained numerically.
;------------------------------------------------------------------------------
.byte $22,$E3
.byte $54,$4D,$20,$CC,$20,$5D
.byte $20,$31,$39,$38,$30
.byte $20,$31,$39,$39,$33
.byte $20,$4E,$41,$4D,$43,$4F
.byte $20,$4C,$54,$44,$5B
.byte $FF
;------------------------------------------------------------------------------
; Record 4: $C3FD-$C411
; PPU destination: $2327
; Data length: $12 (18)
;
; Mostly text-like tile data containing:
; NAMCO HOMETEK ... INC
;
; Special tile values remain numeric.
;------------------------------------------------------------------------------
.byte $23,$27
.byte $4E,$41,$4D,$43,$4F,$20
.byte $48,$4F,$4D,$45,$54,$45,$4B
.byte $CD
.byte $49,$4E,$43
.byte $5B
.byte $FF
;------------------------------------------------------------------------------
; Record 5: $C412-$C428
; PPU destination: $2366
; Data length: $14 (20)
;
; Tile values correspond to:
; "LICENSED BY NINTENDO"
;------------------------------------------------------------------------------
.byte $23,$66
.byte $4C,$49,$43,$45,$4E,$53,$45,$44
.byte $20
.byte $42,$59
.byte $20
.byte $4E,$49,$4E,$54,$45,$4E,$44,$4F
.byte $FF
;------------------------------------------------------------------------------
; Record 6: $C429-$C42D
; PPU destination: $2293
; Data length: $02
;------------------------------------------------------------------------------
.byte $22,$93
.byte $CE,$CF
.byte $FF
;------------------------------------------------------------------------------
; Record 7: $C42E-$C439
; PPU destination: $22AC
; Data length: $09
;------------------------------------------------------------------------------
.byte $22,$AC
.byte $23,$24,$25,$26,$27,$28,$29,$2A,$2B
.byte $FF
;------------------------------------------------------------------------------
; Record 8: $C43A-$C43E
; PPU destination: $20FC
; Data length: $02
;------------------------------------------------------------------------------
.byte $20,$FC
.byte $0E,$0F
.byte $FF
;==============================================================================
; $C43F-$C45E: Initial Palette Data
;
; Local:
; Contains $20 bytes copied to RAM $0299-$02B8 during MainEntry. The first
; $10 bytes feed PPU palette $3F00-$3F0F and the second $10 bytes feed
; $3F10-$3F1F through UpdatePalettes.
;
; Global:
; Defines the initial background and sprite palette configuration.
;
; Notes:
; Palette values remain numeric during this pass.
;==============================================================================
TitleScreenPalette:
; Background palettes
.byte $0F,$20,$0F,$06
.byte $0F,$26,$20,$27
.byte $0F,$06,$0F,$26
.byte $0F,$06,$20,$26
; Sprite palettes
.byte $0F,$27,$20,$06
.byte $0F,$11,$20,$33
.byte $0F,$20,$20,$21
.byte $0F,$09,$20,$17
;==============================================================================
; $C45F-$C476: Initial Nametable Attribute Data
;
; Local:
; Contains $18 attribute bytes copied by InitializeNametableAttributes to
; PPU $23C8-$23DF.
;
; Global:
; Defines background-palette selections for part of the initial nametable
; display.
;
; Notes:
; Attribute bytes remain raw during this first pass.
;==============================================================================
TitleScreenAttributeData:
.byte $80,$A0,$A0,$A0,$A0,$A0,$A0,$00
.byte $00,$66,$55,$55,$55,$55,$DD,$00
.byte $08,$0A,$0A,$0A,$0A,$0A,$0A,$00
;==============================================================================
; $C477-$C506: Player Selection State Handler
;
; Local:
; Handles input and display updates for the player-count selection state.
;
; On first entry, identified by $87/$88 both being zero, copies a small
; display-data sequence from $C507 to RAM beginning at $024B and captures
; the current controller states for bits $04 and $08.
;
; $87/$88 form a 16-bit counter that advances once per frame. When its high
; byte reaches $02, the counter is reset and the main state changes to $04.
;
; During normal processing:
; - Detects changes to controller bit $04.
; - When that bit becomes active, toggles $47 between 0 and 1 and changes
; display data associated with the selected player count.
; - Detects changes to controller bit $08.
; - When bit $08 becomes active, clears $48, calls $EEB4, and jumps to
; GameModeEntry.
;
; Global:
; Handler selected by MainStateHandlerTable when $3F = $02. The surrounding PPU
; data and controller behavior strongly indicate that this state implements
; the "1 PLAYER" / "2 PLAYERS" selection screen.
;
; If no selection completes within the $87/$88 timeout, execution advances
; automatically to main state $04.
;
; Notes:
; $50 stores the previous masked state of controller bit $04.
; $51 stores the previous masked state of controller bit $08.
;
; $87/$88 act as a 16-bit frame counter in this state.
;
; $47 is toggled between 0 and 1 when the $04 input becomes active. This
; strongly suggests it represents the current 1-player/2-player selection,
; but the name should be confirmed through later consumers.
;
; $C507 begins an adjacent display-data table consumed during state entry.
;==============================================================================
PlayerSelectionState:
lda $87
ora $88
bne @AdvanceTimeout
ldy #$00
@InitializeDisplayData:
lda PlayerCountSelectionDisplayData,y
sta $024B,y
iny
cmp #$FF
bne @InitializeDisplayData
lda currentInput
and #$04
sta pacmanRequestedDirection
lda currentInput
and #$08
sta pacmanDirection
@AdvanceTimeout:
clc
lda #$01
adc $87
sta $87
lda $88
adc #$00
sta $88
cmp #$02
bne @CheckSelectionInput
lda #$00
sta $87
lda #$04
sta gameState
jmp MainDispatchLoop
@CheckSelectionInput:
lda currentInput
and #$04
cmp pacmanRequestedDirection
beq @CheckConfirmInput
sta pacmanRequestedDirection
lda #$00
sta $88
lda #$01
sta $87
lda pacmanRequestedDirection
beq @CheckConfirmInput
ldx #$00
inc activePlayerFlag
lda activePlayerFlag
and #$01
sta activePlayerFlag
beq @UseFirstSelectionData
inx
@UseFirstSelectionData:
lda $C50F,x
sta $024D
lda $C510,x
sta $0251
lda #$22
sta $024B
jmp MainDispatchLoop
@CheckConfirmInput:
lda currentInput
and #$08
cmp pacmanDirection
bne @ConfirmStateChanged
jmp MainDispatchLoop
@ConfirmStateChanged:
sta pacmanDirection
lda pacmanDirection
bne @ConfirmPressed
jmp MainDispatchLoop
@ConfirmPressed:
lda #$00
sta gameplaySuspended
jsr $EEB4
jmp GameModeEntry
;==============================================================================
; $C507-$C511: Player Selection Display Data
;
; Local:
; Contains display-update data used by PlayerSelectionState.
;
; The first eight bytes, $C507-$C50E, are copied to RAM beginning at $024B
; when the player-selection state is first entered. The copy terminates after
; the $FF byte has also been stored.
;
; The final three bytes, $C50F-$C511, form an overlapping pair table used
; when $47 toggles between its two selection values:
;
; X = 0 -> $5C, $20
; X = 1 -> $20, $5C
;
; These values are written to $024D and $0251, respectively.
;
; Global:
; Provides the display-update data for the "1 PLAYER" / "2 PLAYERS"
; selection state. The overlapping three-byte table moves a $5C tile
; between two display positions while placing $20 at the other position.
;
; Notes:
; $5C appears to be the selection-marker tile and $20 appears to clear the
; alternate position. Their exact visual appearance can be confirmed later
; against CHR-ROM.
;
; $FF terminates the initialization data copied to $024B.
;==============================================================================
PlayerCountSelectionDisplayData:
.byte $22,$0A,$5C,$00
.byte $22,$4A,$20,$FF
PlayerCountSelectionMarkerTiles:
.byte $5C,$20,$5C
;==============================================================================
; $C512-$C52D: State $04 Handler / Substate Dispatcher
;
; Local:
; Handles the top-level processing for main state $04.
;
; First checks controller-1 bits $0C. If either bit is active, changes the
; main state back to $02 and jumps to MainEntry, causing the main display
; initialization sequence to run again.
;
; Otherwise uses $87 as a byte offset into a table of 16-bit handler
; addresses beginning at $C52E. The selected address is placed in $10/$11
; and execution jumps to it indirectly.
;
; Global:
; This is the fourth major handler in MainStateHandlerTable. Unlike states $00,
; $02, and $06, state $04 contains its own secondary state machine driven
; by $87.
;
; $87 therefore has a different role depending on the current main state.
; In state $02 it participated in a 16-bit frame counter with $88. Here it
; acts as the byte offset into a secondary dispatch table.
;
; Notes:
; $87 appears to take even values because it indexes packed 16-bit addresses
; directly rather than multiplying a state number by two.
;
; The substate table begins immediately afterward at $C52E.
; We leave the individual substate handlers for following passes.
;==============================================================================
State04Handler:
lda currentInput
and #$0C
beq @DispatchSubstate
lda #$02
sta gameState
jmp MainEntry
@DispatchSubstate:
ldy $87
lda State04SubstateTable,y
sta dispatchVector
lda State04SubstateTable+1,y
sta dispatchVector+1
jmp ($0010)
;==============================================================================
; $C52E-$C545: State $04 Substate Dispatch Table
;
; Local:
; Twelve 16-bit handler addresses selected by State04Handler using $87 as
; a direct byte offset.
;
; Valid offsets observed from the table structure are:
; $00,$02,$04,$06,$08,$0A,$0C,$0E,$10,$12,$14,$16
;
; Global:
; Defines the secondary state machine used while the top-level game state
; is $04. Several intermediate substates deliberately share the same handler
; at $C573.
;
; Notes:
; Later code advances $87 using two INC instructions, confirming that $87
; is a byte offset into this table rather than an ordinal state number.
;==============================================================================
State04SubstateTable:
.word State04Initialize ; $00
.word State04TimedSubstate ; $02
.word State04TimedSubstate ; $04
.word State04TimedSubstate ; $06
.word State04TimedSubstate ; $08
.word State04TimedSubstate ; $0A
.word State04TimedSubstate ; $0C
.word State04TimedSubstate ; $0E
.word State04TimedSubstate ; $10
.word State04TimedSubstate ; $12
.word State04LongDelay ; $14
.word State04FinalPhase ; $16
;==============================================================================
; $C546-$C572: State $04 Initialization Substate
;
; Local:
; Performs the one-time initialization for state $04 when substate offset
; $87 is $00.
;
; First synchronizes with NMI using $40. It then sets PPUCTRL/$43 to $08,
; disables rendering through PPUMASK, clears $88, and calls three setup
; routines:
;
; $C5D8
; $C608
; SetSelectionFlagAndContinue
;
; After setup completes, advances $87 by two, selecting substate $02 on the
; following frame. PPUCTRL/$43 are then changed to $88 before returning to
; MainDispatchLoop.
;
; Global:
; Entry/initialization substate for the secondary state machine used by
; main state $04. It prepares the display and state-specific data before
; handing execution to the shared $C573 substate handler.
;
; Notes:
; $40 continues to act as the foreground/NMI synchronization flag.
;
; $87 is a byte offset into State04SubstateTable. The two INC instructions
; deliberately advance it from $00 to $02.
;
; The three called routines remain structurally named by address until they
; are decoded. $C5D8 and $C608 appear to perform direct PPU initialization,
; while SetSelectionFlagAndContinue operates on state/display data.
;==============================================================================
State04Initialize:
lda #$01
sta $40
@WaitForNmi:
lda $40
bne @WaitForNmi
lda #$08
sta PPUCTRL
sta ppuCtrlShadow
lda #$00
sta PPUMASK
sta $88
jsr $C5D8
jsr InitializeState04PpuData
jsr LoadState04DisplayData
inc $87
inc $87
lda #$88
sta PPUCTRL
sta ppuCtrlShadow
jmp MainDispatchLoop
;==============================================================================
; $C573-$C588: State $04 Shared Timed Substate Handler
;
; Local:
; Advances $88 once per frame. At $30 frames, clears $88, invokes the
; state-$04 display-data helper, and advances $87 by two to the next
; secondary state.
;
; Global:
; Shared timing handler used by state-$04 substates $02-$12.
;
; Notes:
; LoadState04DisplayData is kept as a raw address temporarily to guarantee
; byte identity while the overlapping $C5A6-$C5D8 region is being resolved.
;==============================================================================
State04TimedSubstate:
inc $88
lda $88
cmp #$30
bne @ReturnToMainLoop
lda #$00
sta $88
jsr $C5A6
inc $87
inc $87
@ReturnToMainLoop:
jmp MainDispatchLoop
;==============================================================================
; $C589-$C59F: State $04 Long Delay Substate
;
; Local:
; Increments $88 once per frame and waits until it reaches $80.
;
; While $88 is not $80, returns to MainDispatchLoop.
;
; When $88 reaches $80:
; - Advances $87 by two, selecting substate $16.
; - Clears $88.
; - Returns to MainDispatchLoop.
;
; Global:
; Implements substate $14 of the state $04 secondary state machine. It
; follows the series of $30-frame substates handled by
; State04TimedSubstate and introduces a longer delay before entering the
; final known substate at $16.
;
; Notes:
; $88 is the frame counter for this substate.
; $87 is the byte offset into State04SubstateTable.
;
; Unlike substates $02-$12, reaching the timeout here does not call
; SetSelectionFlagAndContinue. It simply advances from substate $14 to $16.
;==============================================================================
State04LongDelay:
inc $88
lda $88
cmp #$80
beq @AdvanceSubstate
jmp MainDispatchLoop
@AdvanceSubstate:
inc $87
inc $87
lda #$00
sta $88
jmp MainDispatchLoop
;==============================================================================
; $C59F-$C5A5: Select Alternate Player Mode and Continue
;
; Local:
; Sets $47 to $01 and tail-jumps to the routine at GameModeEntry.
;
; Global:
; Forces the same $47 flag that PlayerSelectionState toggles between $00
; and $01, then transfers control to GameModeEntry. This strongly links the routine
; to the player-count/player-mode selection logic.
;
; The exact global purpose of GameModeEntry has not yet been decoded, so the routine
; and $47 remain conservatively named during this pass.
;
; Notes:
; There is no RTS. Control is permanently transferred to GameModeEntry.
;
; $47 is known to be constrained to 0/1 by PlayerSelectionState and is also
; consulted by NmiHandler when selecting controller input and constructing
; PPUCTRL. We should trace GameModeEntry before assigning $47 a final semantic name.
;==============================================================================
SetSelectionFlagAndContinue:
lda #$01
sta activePlayerFlag
jmp GameModeEntry
;==============================================================================
; $C5A6-$C5D7: State $04 Display Data Loader
;
; Local:
; Uses $87 as a byte offset into a pointer table at $C687. The selected
; $FF-terminated stream is copied through $00/$01 into RAM beginning at
; $024B, including the $FF terminator.
;
; The byte following the terminator controls an optional secondary copy:
; nonzero -> return
; zero -> copy 16 bytes using a substate-derived offset
;
; Global:
; Supplies update/display data as the state-$04 secondary state machine
; progresses.
;
; Notes:
; $00/$01 form the temporary source pointer.
;==============================================================================
LoadState04DisplayData:
ldy $87
lda CharacterIntroDisplayDataTable,y
sta workPtr0
lda CharacterIntroDisplayDataTable+1,y
sta workPtr0+1
ldy #$00
@CopyDisplayData:
lda (workPtr0),y
sta $024B,y
iny
cmp #$FF
bne @CopyDisplayData
lda (workPtr0),y
beq @SecondaryCopy
rts
@SecondaryCopy:
lda $87
sec
sbc #$02
asl
asl
tay
ldx #$10
@CopySecondaryData:
lda CharacterIntroSpriteData,y
sta $0760,y
iny
dex
bne @CopySecondaryData
rts
;==============================================================================
; $C5D8-$C607: Initialize Playfield Area
;
; Local:
; Sets the PPU destination to $20C0 and fills 24 complete 32-tile nametable
; rows.
;
; Each row consists of:
; 2 x $2D
; 28 x $20
; 2 x $2D
;
; The inner count at $01 is reinitialized to $1C at the beginning of every
; row. $00 begins at $17 and counts rows down through zero.
;
; Global:
; Called during State04Initialize with rendering disabled. Builds the large
; rectangular background area used by state $04.
;
; Notes:
; The BPL at $C605 intentionally returns to $C5E6, not directly to the first
; tile write. This reloads $01 with $1C for every row and must be preserved
; for byte identity.
;==============================================================================
InitializePlayfieldArea:
lda #$20
sta PPUADDR
lda #$C0
sta PPUADDR
lda #$17
sta workPtr0
@NextRow:
lda #$1C
sta workPtr0+1
lda #$2D
sta PPUDATA
sta PPUDATA
lda #$20
@ClearRowInterior:
sta PPUDATA
dec workPtr0+1
bne @ClearRowInterior
lda #$2D
sta PPUDATA
sta PPUDATA
dec workPtr0
bpl @NextRow
rts
;==============================================================================
; $C608-$C666: Initialize State $04 PPU Data
;
; Local:
; Performs three groups of PPU initialization:
;
; 1. Writes $24 (36) bytes from $C76E to PPUDATA beginning at PPU $23C8.
;
; 2. Processes a sequence of variable-length PPU write records beginning
; at $C69B. Each record contains a two-byte PPU destination followed by
; data bytes terminated by $FF.
;
; 3. Writes a full $20-byte palette table from CharacterIntroPalette to PPU palette RAM
; beginning at $3F00.
;
; After the palette transfer, resets PPUADDR away from palette space and
; returns.
;
; Global:
; Called by State04Initialize while rendering is disabled. Together with
; InitializePlayfieldArea, this routine constructs the PPU state needed by
; the state-$04 display sequence.
;
; Notes:
; Unlike InitializeNametableData, the PPU-record loop at $C61D does not
; contain an explicit record count. The encoded data and Y wrap behavior
; determine when processing terminates. We should decode $C69B before
; assigning a more precise description to that stream.
;
; CharacterIntroPalette begins the 32-byte palette table.
; $C76E is the source of the 36-byte attribute-data transfer.
;==============================================================================
InitializeState04PpuData:
; Set PPU destination to $23C8.
lda #$23
sta PPUADDR
lda #$C8
sta PPUADDR
; Copy $24 bytes of attribute data.
ldy #$00
@CopyAttributeData:
lda $C76E,y
sta PPUDATA
iny
cpy #$24
bne @CopyAttributeData
; Process encoded PPU write data from $C69B.
ldy #$00
@NextPpuRecord:
lda CharacterNicknameHeaderData,y
sta PPUADDR
iny
lda CharacterNicknameHeaderData,y
sta PPUADDR
iny
@CopyPpuRecord:
lda CharacterNicknameHeaderData,y
cmp #$FF
beq @EndPpuRecord
sta PPUDATA
iny
bne @CopyPpuRecord
@EndPpuRecord:
lda PPUSTATUS
; Upload the complete 32-byte palette directly.
lda #$3F
sta PPUADDR
lda #$00
sta PPUADDR
ldy #$00
@CopyPalette:
lda CharacterIntroPalette,y
sta PPUDATA
iny
cpy #$20
bne @CopyPalette
; Reset the PPU address following palette access.
lda #$3F
sta PPUADDR
lda #$00
sta PPUADDR
sta PPUADDR
sta PPUADDR
rts
;==============================================================================
; $C667-$C686: State $04 Palette Data
;
; Local:
; Complete 32-byte palette uploaded directly to PPU $3F00-$3F1F by
; InitializeState04PpuData.
;
; Global:
; Defines the background and sprite colors used by the state-$04 display.
;
; Notes:
; First 16 bytes are the four background palettes.
; Second 16 bytes are the four sprite palettes.
;==============================================================================
CharacterIntroPalette:
; Background palettes
.byte $0F,$20,$0F,$06
.byte $0F,$06,$0F,$33
.byte $0F,$33,$0F,$27
.byte $0F,$17,$0F,$21
; Sprite palettes
.byte $0F,$27,$20,$06
.byte $0F,$11,$20,$33
.byte $0F,$20,$20,$21
.byte $0F,$09,$20,$17
;==============================================================================
; $C687-$C69A: State $04 Display Data Pointer Table
;
; Local:
; Contains ten 16-bit pointers to display/update data streams. The helper at
; $C5A6 uses $87 directly as a byte offset into this table:
;
; ldy $87
; lda CharacterIntroDisplayDataTable,y
; sta $00
; lda CharacterIntroDisplayDataTable+1,y
; sta $01
;
; The valid table offsets are therefore:
; $00,$02,$04,$06,$08,$0A,$0C,$0E,$10,$12
;
; Global:
; Selects the display data used as the state-$04 secondary state machine
; progresses. State04Initialize uses offset $00, while the successive timed
; substates use offsets $02 through $12.
;
; Notes:
; Each target begins a $FF-terminated display/update stream copied into RAM
; at $024B by LoadState04DisplayData.
;
; Several streams contain an additional control byte following $FF. The
; helper uses that byte to decide whether to perform its secondary copy.
;==============================================================================
CharacterIntroDisplayDataTable:
.word CharacterNicknameHeaderData ; $87 = $00 Character; NICKNAME
.word ShadowNameData ; $87 = $02 : SHADOW
.word BlinkyNameData ; $87 = $04 _BLINKY_
.word SpeedyNameData ; $87 = $06 : SPEEDY
.word PinkyNameData ; $87 = $08 _PINKY_
.word CharacterIntroGraphicData0 ; $87 = $0A
.word CharacterIntroGraphicData1 ; $87 = $0C
.word PokeyNameData ; $87 = $0E
.word ClydeNameData ; $87 = $10
.word ScoringLegendData ; $87 = $12
;==============================================================================
; $C69B-$C72D: State $04 Display Update Streams
;
; Local:
; Contains ten display/update streams selected through
; CharacterIntroDisplayDataTable according to the state-$04 substate offset $87.
;
; LoadState04DisplayData copies the selected stream into RAM beginning at
; $024B until it encounters $FF. The $FF byte itself is also copied.
;
; For several streams, the byte immediately following $FF is $00. The
; loader examines this byte and uses it to trigger its optional secondary
; data copy.
;
; Global:
; Supplies the sequence of text and display changes shown during the timed
; state-$04 sequence. The data reveals that this portion of the attract/
; information display introduces game characters and later installs
; scoring/instruction-related display data.
;
; Notes:
; Many tile values correspond directly to ASCII-like character tiles, but
; special tile values are retained numerically.
;
; $00 bytes appearing after $FF belong to the control mechanism used by
; LoadState04DisplayData. They are not part of the $FF-terminated portion
; copied to $024B.
;
; The final stream at $C707 is structurally different: it contains multiple
; apparent PPU/update command groups before the final $FF.
;==============================================================================
;------------------------------------------------------------------------------
; $C69B-$C6B2
; State04 substate offset: $00
;
; Destination prefix: $20E6
;
; Tile data corresponds approximately to:
; "CHARACTER ; NICKNAME"
;
; No explicit $00 control byte follows this stream before the next record.
;------------------------------------------------------------------------------
CharacterNicknameHeaderData:
.byte $20,$E6
.byte $43,$48,$41,$52,$41,$43,$54,$45,$52
.byte $20,$3B,$20,$20
.byte $4E,$49,$43,$4B,$4E,$41,$4D,$45
.byte $FF
;------------------------------------------------------------------------------
; $C6B3-$C6BD
; State04 substate offset: $02
;
; Destination prefix: $2148
;
; Text-like tile data:
; ":SHADOW"
;
; $00 after $FF selects the loader's secondary-copy path.
;------------------------------------------------------------------------------
ShadowNameData:
.byte $21,$48
.byte $3A,$53,$48,$41,$44,$4F,$57
.byte $FF
.byte $00
;------------------------------------------------------------------------------
; $C6BE-$C6C8
; State04 substate offset: $04
;
; Destination prefix: $2153
;
; Text-like tile data:
; "_BLINKY_"
;
; No trailing $00 before the next stream.
;------------------------------------------------------------------------------
BlinkyNameData:
.byte $21,$53
.byte $5F,$42,$4C,$49,$4E,$4B,$59,$5F
.byte $FF
;------------------------------------------------------------------------------
; $C6C9-$C6D3
; State04 substate offset: $06
;
; Destination prefix: $21A8
;
; Text-like tile data:
; ":SPEEDY"
;
; $00 after $FF selects the loader's secondary-copy path.
;------------------------------------------------------------------------------
SpeedyNameData:
.byte $21,$A8
.byte $3A,$53,$50,$45,$45,$44,$59
.byte $FF
.byte $00
;------------------------------------------------------------------------------
; $C6D4-$C6DD
; State04 substate offset: $08
;
; Destination prefix: $21B3
;
; Text-like tile data:
; "_PINKY_"
;------------------------------------------------------------------------------
PinkyNameData:
.byte $21,$B3
.byte $5F,$50,$49,$4E,$4B,$59,$5F
.byte $FF
;------------------------------------------------------------------------------
; $C6DE-$C6E9
; State04 substate offset: $0A
;
; Destination prefix: $2208
;
; Contains graphical/special tile values rather than plain character codes.
; $00 after $FF selects the loader's secondary-copy path.
;------------------------------------------------------------------------------
CharacterIntroGraphicData0:
.byte $22,$08
.byte $B6,$C1,$C3,$B7,$B4,$C2,$B3,$C4
.byte $FF
.byte $00
;------------------------------------------------------------------------------
; $C6EA-$C6F2
; State04 substate offset: $0C
;
; Destination prefix: $2213
;
; Contains another set of special graphical tile values.
;------------------------------------------------------------------------------
CharacterIntroGraphicData1:
.byte $22,$13
.byte $C8,$B5,$C5,$C0,$C6,$C8
.byte $FF
;------------------------------------------------------------------------------
; $C6F3-$C6FC
; State04 substate offset: $0E
;
; Destination prefix: $2268
;
; Text-like tile data:
; ":POKEY"
;
; $00 after $FF selects the loader's secondary-copy path.
;------------------------------------------------------------------------------
PokeyNameData:
.byte $22,$68
.byte $3A,$50,$4F,$4B,$45,$59
.byte $FF
.byte $00
;------------------------------------------------------------------------------
; $C6FD-$C706
; State04 substate offset: $10
;
; Destination prefix: $2273
;
; Text-like tile data:
; "_CLYDE_"
;------------------------------------------------------------------------------
ClydeNameData:
.byte $22,$73
.byte $5F,$43,$4C,$59,$44,$45,$5F
.byte $FF
;------------------------------------------------------------------------------
; $C707-$C72D
; State04 substate offset: $12
;
; Final composite display stream. Unlike the earlier simple records, this
; contains several apparent update groups separated by $00 bytes before the
; final $FF.
;
; Visible character-like portions include:
; "10 PTS"
; "50 PTS"
;
; The special tile values $CE,$CF and $23-$2B are preserved numerically.
;------------------------------------------------------------------------------
ScoringLegendData:
.byte $22,$AD
.byte $03,$20,$31,$30,$20,$50,$54,$53
.byte $00
.byte $22,$ED
.byte $01,$20,$35,$30,$20,$50,$54,$53
.byte $00
.byte $23,$34
.byte $CE,$CF
.byte $00
.byte $23,$4D
.byte $23,$24,$25,$26,$27,$28,$29,$2A,$2B
.byte $FF
;==============================================================================
; $C72E-$C76D: State $04 Secondary Object/Sprite Data
;
; Local:
; Contains 64 bytes of secondary display data used by
; LoadState04DisplayData.
;
; When a state-$04 display stream requests the secondary-copy path, the
; loader calculates:
;
; Y = ($87 - $02) * 4
;
; It then copies $10 (16) bytes from:
;
; CharacterIntroSpriteData,Y
;
; to:
;
; $0760,Y
;
; Because state-$04 substates advance by two, Y advances by eight bytes:
;
; $87 = $02 -> Y = $00
; $87 = $04 -> Y = $08
; $87 = $06 -> Y = $10
; $87 = $08 -> Y = $18
; ...
;
; The 16-byte transfers therefore intentionally overlap by eight bytes.
;
; Global:
; Supplies progressively updated object/sprite-related state during the
; state-$04 timed display sequence. The destination lies in RAM page $07,
; which is also used as an OAM DMA source elsewhere in the program.
;
; The repeated four-byte patterns strongly suggest sprite/OAM-style records,
; but exact field semantics should be confirmed against the code that
; consumes $0760 and the active OAM page before assigning field names.
;
; Notes:
; The table is safest to represent as 16 four-byte records during this pass.
; This preserves its natural repeated structure while allowing the loader's
; overlapping 16-byte windows to remain obvious.
;==============================================================================
CharacterIntroSpriteData:
; $C72E - record $00
.byte $48,$1C,$40,$26
; $C732 - record $01
.byte $48,$1B,$40,$2E
; $C736 - record $02
.byte $50,$1F,$40,$26
; $C73A - record $03
.byte $50,$1D,$40,$2E
; $C73E - record $04
.byte $60,$1C,$41,$26
; $C742 - record $05
.byte $60,$1B,$41,$2E
; $C746 - record $06
.byte $68,$1F,$41,$26
; $C74A - record $07
.byte $68,$1D,$41,$2E
; $C74E - record $08
.byte $78,$1C,$42,$26
; $C752 - record $09
.byte $78,$1B,$42,$2E
; $C756 - record $0A
.byte $80,$1F,$42,$26
; $C75A - record $0B
.byte $80,$1D,$42,$2E
; $C75E - record $0C
.byte $90,$1C,$43,$26
; $C762 - record $0D
.byte $90,$1B,$43,$2E
; $C766 - record $0E
.byte $98,$1F,$43,$26
; $C76A - record $0F
.byte $98,$1D,$43,$2E
;==============================================================================
; $C76E-$C791: State $04 Nametable Attribute Data
;
; Local:
; Contains $24 (36) bytes copied directly to PPUDATA by
; InitializeState04PpuData.
;
; The destination begins at PPU $23C8, so the complete transfer covers:
;
; $23C8-$23EB
;
; Global:
; Defines background palette selection attributes used by the state-$04
; display. The highly regular $00/$55/$AA/$FF pattern assigns repeating
; palette selections across large regions of the nametable.
;
; Notes:
; These are NES attribute-table bytes rather than tile indices.
;
; Each attribute byte contains four 2-bit palette selections. Values such
; as $00, $55, $AA and $FF therefore select the same palette throughout all
; four quadrants covered by that attribute byte:
;
; $00 -> palette 0 in all four quadrants
; $55 -> palette 1 in all four quadrants
; $AA -> palette 2 in all four quadrants
; $FF -> palette 3 in all four quadrants
;
; The final $22 mixes palette selections within its attribute regions.
;==============================================================================
CharacterIntroAttributeData:
.byte $00,$00,$00,$00,$00,$00,$00,$00
.byte $55,$55,$55,$55,$55,$55,$55,$55
.byte $AA,$AA,$AA,$AA,$AA,$AA,$AA,$AA
.byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
.byte $AA,$AA,$AA,$22
;==============================================================================
; $C792-$C7A0: State $04 Final-Phase Dispatcher
;
; Local:
; Uses $88 as a direct byte offset into a four-entry table of 16-bit
; addresses beginning at $C7A1.
;
; The selected address is stored in the temporary pointer $10/$11 and
; execution is transferred to it using an indirect JMP.
;
; Global:
; Implements state-$04 substate offset $16. Rather than performing the work
; directly, this substate introduces another secondary state machine.
;
; At this stage:
;
; $3F = $04 main state
; $87 = $16 state-$04 substate
; $88 final-phase dispatch offset
;
; This provides another level of progression within the final state-$04
; phase.
;
; Notes:
; $88 is used directly as a byte offset, so normal values are expected to
; advance by two:
;
; $00, $02, $04, $06
;
; The individual target handlers will be named as they are decoded.
;==============================================================================
State04FinalPhase:
ldy $88
lda State04FinalPhaseTable,y
sta dispatchVector
lda State04FinalPhaseTable+1,y
sta dispatchVector+1
jmp ($0010)
;==============================================================================
; $C7A1-$C7A6: State $04 Final-Phase Dispatch Table
;
; Local:
; Three 16-bit handler addresses indexed by $88.
;
; Global:
; Selects the processing stage used during State04FinalPhase.
;
; Notes:
; Valid byte offsets established by the table are $00, $02 and $04.
; $C7A7 is executable code and is not part of the pointer table.
;==============================================================================
State04FinalPhaseTable:
.word State04FinalPhaseInitialize ; $88 = $00
.word State04FinalPhaseActive ; $88 = $02
.word State04FinalPhaseComplete ; $88 = $04
;==============================================================================
; $C7A7-$C7F1: State $04 Final-Phase Initialization
;
; Local:
; Initializes the first stage of State04FinalPhase.
;
; Copies a $FF-terminated display/update stream from State04FinalDisplayData
; into RAM at $024B, including the terminator.
;
; Initializes several state variables and object/display values, clears
; $001E-$0031 and $0278-$028B, calls the helper at $DAF1, then advances
; $88 by two to select final-phase offset $02.
;
; Global:
; First handler selected by State04FinalPhase when $88 = $00. It prepares
; the RAM and display state required by the next final-phase handler at
; $C7F2.
;
; Notes:
; $1A is initialized to $FF.
; $1B, $89 and $8A are initialized to $00.
;
; $0274 = $F4
; $0276 = $A8
; $028C = $01
; $0292 = $20
;
; The exact semantics of these locations will be refined after tracing
; their consumers.
;
; State04FinalDisplayData is a display/update stream in the same general RAM
; command area used elsewhere through $024B.
;==============================================================================
State04FinalPhaseInitialize:
ldy #$00
@CopyDisplayData:
lda State04FinalDisplayData,y
sta $024B,y
iny
cmp #$FF
bne @CopyDisplayData
lda #$FF
sta pacmanMotion
lda #$00
sta $1B
sta $89
sta $8A
lda #$F4
sta $0274
lda #$A8
sta $0276
lda #$01
sta $028C
lda #$20
sta $0292
lda #$00
tay
@ClearLowRamBlock:
sta $001E,y
iny
cpy #$14
bne @ClearLowRamBlock
tay
@ClearObjectRamBlock:
sta $0278,y
iny
cpy #$14
bne @ClearObjectRamBlock
jsr $DAF1
inc $88
inc $88
jmp MainDispatchLoop
;==============================================================================
; $C7F2-$C856: State $04 Final-Phase Active Handler
;
; Local:
; Calls two per-frame helpers at UpdateState04Objects and
; AnimateState04DisplayPattern, then examines $0274.
;
; When $0274 equals one of:
;
; $E0
; $D1
; $C2
; $B3
;
; the routine calls InitializeState04ObjectSlot.
;
; Processing otherwise continues until $0274 reaches $40. While it has not
; reached $40, control returns to MainDispatchLoop.
;
; When $0274 reaches $40, the routine:
;
; - Initializes four 4-byte structures beginning at $001E.
; - Sets $1A = $01 and $1B = $50.
; - Sets $0293-$0296 to $01.
; - Clears $89.
; - Writes a short display/update sequence at $024B/$024D.
; - Advances $88 by two, selecting final-phase offset $04.
;
; Global:
; Active middle stage of State04FinalPhase. The handler appears to drive
; an animated sequence by calling UpdateState04Objects/AnimateState04DisplayPattern
; each frame and reacting when $0274 reaches several specific values.
;
; The milestone values $E0,$D1,$C2,$B3 invoke InitializeState04ObjectSlot. The exact meaning of
; those milestones should be assigned after the movement/update helpers are
; decoded.
;
; Notes:
; $0274 was initialized to $F4 by State04FinalPhaseInitialize, so the
; descending milestone tests strongly suggest $0274 is modified by one of
; the per-frame helper routines.
;
; The four low-RAM structures are $001E/$0022/$0026/$002A. Each receives
; $00 in its first byte and $C0 in its second byte. The remaining two bytes
; are not changed by this loop.
;==============================================================================
State04FinalPhaseActive:
jsr UpdateState04Objects
jsr AnimateState04DisplayPattern
lda $0274
cmp #$E0
beq @RunMilestoneUpdate
cmp #$D1
beq @RunMilestoneUpdate
cmp #$C2
beq @RunMilestoneUpdate
cmp #$B3
bne @CheckForCompletion
@RunMilestoneUpdate:
jsr InitializeState04ObjectSlot
@CheckForCompletion:
lda $0274
cmp #$40
beq @CompletePhase
jmp MainDispatchLoop
@CompletePhase:
ldy #$00
@InitializeStructures:
lda #$00
sta ghost1Motion,y
lda #$C0
sta ghost1Motion+1,y
iny
iny
iny
iny
cpy #$10
bne @InitializeStructures
lda #$01
sta pacmanMotion
lda #$50
sta pacmanMotion+1
lda #$01
sta $0293
sta $0294
sta $0295
sta $0296
lda #$00
sta $89
lda #$22
sta $024B
lda #$20
sta $024D
inc $88
inc $88
jmp MainDispatchLoop
;==============================================================================
; $C857-$C8A7: State $04 Final-Phase Completion Handler
;
; Local:
; Final handler for final-phase offset $88 = $04.
;
; If $8A is nonzero, calls RotateState04ObjectSlots and decrements the countdown. When the
; countdown reaches zero, scans status entries beginning at $028D until an
; entry with one or more upper-three status bits set is found.
;
; The matching entry and associated state are cleared. If $89 has reached
; $04, the routine sets $47 to $01 and transfers control to GameModeEntry.
;
; When $8A is already zero on entry, calls $C8DC and returns to the common
; main dispatch loop.
;
; Global:
; Final processing stage of the nested state-$04 sequence:
;
; $3F = $04
; $87 = $16
; $88 = $04
;
; The eventual transfer to GameModeEntry appears to leave this attract/display
; sequence and enter another major portion of the program.
;
; Notes:
; $8A acts as a countdown here.
; $89 is tested against $04 for completion.
;
; The scan at $028D looks for the first entry satisfying:
;
; ($028D,Y & $E0) != 0
;
; Exact object/state semantics remain for a later pass.
;==============================================================================
State04FinalPhaseComplete:
lda $8A
beq @RunRegularUpdate
jsr RotateState04ObjectSlots
dec $8A
beq @CountdownComplete
jmp MainDispatchLoop
@CountdownComplete:
ldy #$00
@FindEntry:
lda $028D,y
and #$E0
bne @EntryFound
iny
bne @FindEntry
@EntryFound:
lda #$00
sta $028D,y
lda #$00
sta $0293,y
tya
asl
asl
tay
lda #$00
sta $0278,y
sta $027A,y
sta ghost1Motion,y
sta ghost1Motion+1,y
lda #$A8
sta $0276
lda $89
cmp #$04
beq @FinishSequence
jmp MainDispatchLoop
@FinishSequence:
lda #$01
sta activePlayerFlag
jmp GameModeEntry
@RunRegularUpdate:
jsr UpdateState04Objects
jmp MainDispatchLoop
;==============================================================================
; $C8A8-$C8DB: Allocate/Initialize State $04 Object Slot
;
; Local:
; Searches four 4-byte structures beginning at $0278 for the first slot
; whose first byte is zero.
;
; During the search, $00 tracks the logical slot number while Y advances
; by four bytes per structure.
;
; Once a free slot is found, initializes that structure:
;
; +0 = $F4
; +2 = $A8
;
; It also initializes corresponding four-byte-strided zero-page state:
;
; $001E+Y = $FF
; $001F+Y = $00
;
; The logical slot number saved in $00 is then used to index two separate
; per-slot arrays:
;
; $0293+slot = current $89 value
; $028D+slot = $0C
;
; Finally increments $89 and returns.
;
; Global:
; Called by State04FinalPhaseActive whenever $0274 reaches the milestone
; values $E0, $D1, $C2, or $B3.
;
; This strongly suggests that the routine activates one additional object
; at each milestone during the state-$04 final sequence. Four slots are
; available, matching the four milestone values.
;
; Notes:
; $0278 appears to be the first field of a four-byte structure array.
; $027A is the third field of the same structure.
;
; $001E/$001F are parallel four-byte-strided state fields.
;
; $028D and $0293 are indexed by logical slot number rather than the
; four-byte structure offset.
;
; Exact object semantics remain deliberately unnamed until UpdateState04Objects
; and RotateState04ObjectSlots reveal how these structures are updated.
;==============================================================================
InitializeState04ObjectSlot:
ldy #$00
sty workPtr0
@FindFreeSlot:
lda $0278,y
beq @InitializeSlot
inc workPtr0
iny
iny
iny
iny
bne @FindFreeSlot
@InitializeSlot:
lda #$F4
sta $0278,y
lda #$A8
sta $027A,y
lda #$FF
sta ghost1Motion,y
lda #$00
sta $001F,y
ldy workPtr0
lda $89
sta $0293,y
lda #$0C
sta $028D,y
inc $89
rts
;==============================================================================
; $C8DC-$C8EA: State $04 Per-Frame Object Update Coordinator
;
; Local:
; Runs four update stages in sequence:
;
; UpdateTransitionObjectPositions
; RotateState04ObjectSlots
; UpdateState04ObjectStatus
; CheckState04ObjectTrigger
;
; After those return, tail-jumps to $DAF1 rather than returning directly.
;
; Global:
; Called once per frame during portions of the state-$04 final sequence.
; This routine coordinates the lower-level movement/object/display work
; associated with that sequence.
;
; It is called from both State04FinalPhaseActive and
; State04FinalPhaseComplete.
;
; Notes:
; The exact responsibilities of $EA19, $C92E, $C9FA, CheckState04ObjectTrigger
; and $DAF1 will be refined as those routines are decoded.
;
; The JMP to $DAF1 is a tail call: $DAF1 ultimately returns directly to
; the original caller of this routine.
;==============================================================================
UpdateState04Objects:
jsr UpdateTransitionObjectPositions
jsr RotateState04ObjectSlots
jsr UpdateState04ObjectStatus
jsr CheckState04ObjectTrigger
jmp BuildEntitySpriteBuffer
;==============================================================================
; $C8EB-$C92D: Check State $04 Object Trigger
;
; Local:
; Returns immediately when $1A is negative.
;
; Otherwise computes:
;
; threshold = $0274 + $08
;
; and scans four 4-byte structures beginning at $0278.
;
; For each active structure, compares its first byte against the threshold.
; When a qualifying structure is found, uses $89 as an index into a ROM
; table at $C9EE, installs the resulting value into a per-slot status array,
; clears a related slot value, initializes $8A to $40, and clears $028C and
; $0276.
;
; Global:
; Part of UpdateState04Objects. Appears to detect when one of the active
; state-$04 objects crosses a position threshold relative to the primary
; value at $0274, then initiates a $40-frame/countdown event through $8A.
;
; Notes:
; $00 holds the comparison threshold ($0274 + $08).
; $01 tracks the logical object slot while Y steps through four-byte
; structures.
;
; $89 is incremented as part of the event setup.
;
; $C9EE is a small ROM lookup table that remains to be decoded.
;==============================================================================
CheckState04ObjectTrigger:
lda pacmanMotion
bpl @Continue
rts
@Continue:
lda $0274
clc
adc #$08
sta workPtr0
ldy #$00
sty workPtr0+1
@ScanObjects:
lda $0278,y
beq @NextObject
cmp workPtr0
bcc @TriggerEvent
@NextObject:
inc workPtr0+1
iny
iny
iny
iny
cpy #$10
bne @ScanObjects
rts
@TriggerEvent:
ldy $89
inc $89
lda #$40
sta $8A
lda $C9EE,y
ldy workPtr0+1
sta $028D,y
lda #$00
sta $0293,y
lda #$00
sta $028C
sta $0276
rts
;==============================================================================
; $C92E-$C9B7: Rotate State $04 Object Slots
;
; Local:
; Rotates four related object/state entries across several parallel arrays.
;
; The routine operates on:
;
; $001E/$001F four-byte-strided zero-page structures
; $0278-$0287 four 4-byte object structures
; $028D-$0290 four object/status bytes
; $0293-$0296 four object/index bytes
;
; For each array, the first entry is saved temporarily, entries 1-3 are
; shifted toward entry 0, and the saved entry 0 is moved to entry 3.
;
; Global:
; Called by UpdateState04Objects every frame, and also directly from
; State04FinalPhaseComplete while its $8A countdown is active.
;
; This appears to cycle the four state-$04 object slots through a fixed
; ordering while keeping their parallel state/status arrays synchronized.
;
; Notes:
; $00-$03 are temporary storage.
;
; The zero-page structures use a four-byte stride but only the first two
; bytes of each structure are moved here.
;
; The $0278 array moves all four bytes of each structure.
;
; $028D-$0290 and $0293-$0296 are simple four-entry byte arrays.
;==============================================================================
RotateState04ObjectSlots:
; -------------------------------------------------------------------------
; Rotate the first two bytes of the four-byte-strided zero-page structures.
;
; Entries:
; 0 -> $001E/$001F
; 1 -> $0022/$0023
; 2 -> $0026/$0027
; 3 -> $002A/$002B
; -------------------------------------------------------------------------
lda ghost1Motion
sta workPtr0
lda ghost1Motion+1
sta workPtr0+1
ldy #$00
@ShiftLowRamStructures:
lda ghost2Motion,y
sta ghost1Motion,y
lda ghost2Motion+1,y
sta ghost1Motion+1,y
iny
iny
iny
iny
cpy #$0C
bne @ShiftLowRamStructures
lda workPtr0
sta ghost4Motion
lda workPtr0+1
sta ghost4Motion+1
; -------------------------------------------------------------------------
; Rotate four complete 4-byte structures at $0278-$0287.
; -------------------------------------------------------------------------
lda $0278
sta workPtr0
lda $0279
sta workPtr0+1
lda $027A
sta workPtr1
lda $027B
sta workPtr1+1
ldy #$00
@ShiftObjectStructures:
lda $027C,y
sta $0278,y
iny
cpy #$0C
bne @ShiftObjectStructures
lda workPtr0
sta $0284
lda workPtr0+1
sta $0285
lda workPtr1
sta $0286
lda workPtr1+1
sta $0287
; -------------------------------------------------------------------------
; Rotate the four status bytes at $028D-$0290.
; -------------------------------------------------------------------------
lda $028D
sta workPtr0
ldy #$00
@ShiftStatusArray:
lda $028E,y
sta $028D,y
iny
cpy #$03
bne @ShiftStatusArray
lda workPtr0
sta $0290
; -------------------------------------------------------------------------
; Rotate the four associated index/state bytes at $0293-$0296.
; -------------------------------------------------------------------------
lda $0293
sta workPtr0
ldy #$00
@ShiftIndexArray:
lda $0294,y
sta $0293,y
iny
cpy #$03
bne @ShiftIndexArray
lda workPtr0
sta $0296
rts
;==============================================================================
; $C9B8-$C9D7: Animate State $04 Display Pattern
;
; Local:
; Uses the frame counter at $4B to periodically copy one of two four-byte
; patterns into RAM $024B-$024E.
;
; The routine runs only when the low three bits of $4B are zero:
;
; ($4B & $07) == 0
;
; so the display data is updated once every eight frames.
;
; Bit 3 of $4B selects between two adjacent four-byte patterns beginning
; at $C9F2:
;
; bit 3 = 0 -> source $C9F2-$C9F5
; bit 3 = 1 -> source $C9F6-$C9F9
;
; Global:
; Called by State04FinalPhaseActive once per frame. Provides a simple
; two-frame animation/blink effect by updating the command/display buffer
; at $024B according to the NMI-driven frame counter $4B.
;
; Notes:
; $4B is incremented by NmiHandler once per frame.
;
; The source data at $C9F2 overlaps the tail of nearby ROM data. We should
; decode the surrounding $C9D8-$C9F9 region before assigning a more
; specific visual meaning to the two animation frames.
;==============================================================================
AnimateState04DisplayPattern:
lda frameCounter
and #$07
beq @UpdatePattern
rts
@UpdatePattern:
ldx #$00
ldy #$00
lda frameCounter
and #$08
beq @CopyPattern
ldy #$04
@CopyPattern:
lda State04AnimationFrames,y
sta $024B,x
inx
iny
cpx #$04
bne @CopyPattern
rts
;==============================================================================
; $C9D8-$C9ED: State $04 Final Display Data
;==============================================================================
State04FinalDisplayData:
.byte $22,$AD
.byte $2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D
.byte $00
.byte $22,$ED
.byte $2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D
.byte $FF
;------------------------------------------------------------------------------
; $C9EE-$C9F1: Adjacent data, purpose not yet resolved.
;------------------------------------------------------------------------------
State04UnknownData:
.byte $2D,$2F,$32,$34
;==============================================================================
; $C9F2-$C9F9: State $04 Animation Frames
;
; Two four-byte animation/update frames.
;
; $4B bit 3 = 0 -> frame 0
; $4B bit 3 = 1 -> frame 1
;==============================================================================
State04AnimationFrames:
.byte $22,$C7,$20,$FF
.byte $22,$C7,$01,$FF
;==============================================================================
; $C9FA-$CA3F: Update State $04 Object Status Animation
;
; Local:
; Selects an animation/status value from a ROM table at State04ObjectStatusTable and writes
; the value into active entries of $028D-$0290.
;
; The selected table range depends on $1A:
;
; $1A negative -> base offset $00
; $1A nonnegative -> base offset $0A
;
; $B7 is incremented each call and its low three bits select an entry from
; the first lookup range. The resulting value is written to $028C.
;
; The routine then updates the four status entries $028D-$0290 only once
; every eight frames. Bit 3 of $4B selects between two animation phases.
;
; Global:
; Called by UpdateState04Objects during the active state-$04 final sequence.
; Provides periodic animation/status changes for the four object slots
; maintained in $028D-$0290.
;
; Notes:
; $B7 behaves as an additional animation counter here.
;
; $4B is the NMI-driven frame counter.
;
; $028C is updated on every call, while $028D-$0290 are changed only when
; ($4B & $07) == 0.
;
; The table beginning at State04ObjectStatusTable follows this routine immediately and can be
; decoded separately.
;==============================================================================
UpdateState04ObjectStatus:
ldx #$00
lda $1A
bmi @UseTableBase
ldx #$0A
@UseTableBase:
stx workPtr0
inc $B7
lda $B7
and #$07
clc
adc workPtr0
tay
lda State04ObjectStatusTable,y
sta $028C
; Only update the four object entries once every eight frames.
lda $4B
and #$07
beq @UpdateObjectEntries
rts
@UpdateObjectEntries:
lda frameCounter
and #$08
beq @SelectTableValue
lda #$01
@SelectTableValue:
clc
adc workPtr0
adc #$08
tay
lda State04ObjectStatusTable,y
sta workPtr0
ldy #$00
@UpdateStatusEntries:
lda $028D,y
beq @NextStatusEntry
lda workPtr0
sta $028D,y
@NextStatusEntry:
iny
cpy #$04
bne @UpdateStatusEntries
rts
;==============================================================================
; $CA40-$CA53: State $04 Object Status/Animation Lookup
;
; Local:
; Twenty-byte lookup table used by UpdateState04ObjectStatus.
;
; The routine selects one of two 10-byte groups:
;
; $1A negative -> group at offset $00
; $1A nonnegative -> group at offset $0A
;
; Within each group:
;
; bytes 0-7 are selected by ($B7 & $07)
; bytes 8-9 are selected by the slower frame-animation state
;
; Global:
; Supplies animation/status values for the four objects used during the
; final state-$04 sequence.
;
; Notes:
; Exact meanings of the individual values remain intentionally unnamed
; until the consumers of $028C and $028D-$0290 are better understood.
;==============================================================================
State04ObjectStatusTable:
; Group 0, selected while $1A is negative.
.byte $04,$04,$04,$05,$05,$04,$01,$01
.byte $0C,$0D
; Group 1, selected while $1A is nonnegative.
.byte $08,$08,$08,$09,$09,$08,$01,$01
.byte $1E,$1F
;==============================================================================
; $CA54-$CAD6: Main Game/Mode Initialization and Dispatch Loop
;
; Local:
; Reconfigures the PPU, waits for VBlank, disables rendering, clears several
; state variables, and invokes three initialization helpers.
;
; Depending on $48, initializes or preserves parts of two parallel RAM
; regions beginning at $67 and $77. It then initializes additional state,
; re-enables NMI through PPUCTRL=$88, and synchronizes with NMI via $40.
;
; After initialization, enters a persistent foreground loop that:
;
; - optionally watches controller bits $0C and returns to MainEntry,
; - services the countdown at $4C,
; - uses $3F as a byte offset into an eight-entry dispatch table at $CAD7,
; - indirect-jumps through $10/$11 to the selected handler.
;
; Global:
; Multiple earlier state-machine paths converge here, making this a major
; transition point out of the initial/state-$04 sequence and into another
; top-level game-mode state machine.
;
; The routine contains both one-time initialization and the common dispatch
; loop used by the handlers selected from $CAD7.
;
; Notes:
; $3F is reused as a byte offset into this new dispatch table.
;
; $40 continues to provide foreground/NMI synchronization.
;
; $4C acts as a countdown/delay gate in the dispatch loop.
;
; The exact semantics of $48, $46, $67-$86 and the new dispatch states will
; be refined as their handlers are decoded.
;==============================================================================
GameModeEntry:
lda #$08
sta PPUCTRL
sta ppuCtrlShadow
@WaitForVBlank:
lda PPUSTATUS
bpl @WaitForVBlank
lda #$00
sta PPUMASK
sta gameState
sta cocktailModeFlag
sta $69
sta $4A
jsr ClearGameplayNametables
jsr DrawGameplayHudLabels
jsr DrawGameplayScoreFields
lda gameplaySuspended
beq @ClearPlayerState
lda #$01
sta roundNumber
bne @InitializeCommonState
@ClearPlayerState:
lda #$00
tay
@ClearPlayerStateLoop:
sta roundNumber,y
iny
cpy #$20
bne @ClearPlayerStateLoop
lda #$03
sta roundNumber
sta $77
sta $0600
sta $0601
@InitializeCommonState:
lda #$FF
sta roundDisplayValue
sta $78
sta $0245
lda #$88
sta ppuCtrlShadow
sta PPUCTRL
GameModeFrameSync:
@WaitForNextFrame:
lda #$01
sta $40
@WaitForNmi:
lda $40
bne @WaitForNmi
@DispatchLoop:
lda gameplaySuspended
beq @ProcessDelay
lda currentInput
and #$0C
beq @ProcessDelay
lda #$02 ; TODO: what is state $02?
sta gameState
jmp MainEntry
@ProcessDelay:
lda stateTimer
beq @DispatchState
dec stateTimer
bne @WaitForNextFrame
@DispatchState:
ldy gameState
lda GameModeStateTable,y
sta dispatchVector
lda GameModeStateTable+1,y
sta dispatchVector+1
jmp ($0010)
;==============================================================================
; $CAD7-$CAE6: Game Mode State Dispatch Table
;
; Local:
; Eight 16-bit handler addresses selected by GameModeEntry using $3F as a
; direct byte offset.
;
; Global:
; Defines the next major foreground state machine entered through GameModeEntry.
;
; Notes:
; Since $3F indexes raw bytes rather than words, expected state offsets are:
;
; $00,$02,$04,$06,$08,$0A,$0C,$0E
;
; Handler names remain unresolved until each target is decoded.
;==============================================================================
GameModeStateTable:
.word GameModeState00 ; $3F = $00
.word GameModeState02 ; $3F = $02
.word GameModeState04 ; $3F = $04
.word GameModeState06 ; $3F = $06
.word GameModeState08 ; $3F = $08
.word GameModeState0A ; $3F = $0A
.word GameModeState0C ; $3F = $0C
.word InitializeGameplayTransitionState
; $3F = $0E
;==============================================================================
; $CAE7-$CAE8: Game-Mode Auxiliary Pointer
;
; Local:
; Two-byte little-endian ROM value containing $E7BF.
;
; Global:
; Sits immediately between GameModeStateTable and the $3F=$04 handler.
; Its consumer has not yet been established during this pass.
;
; Notes:
; Keep this as data rather than attempting to decode it as 6502 code.
;==============================================================================
GameModeAuxPointer:
.word $E7BF
;==============================================================================
; $CAE9-$CB5A: Game-Mode State $04 / Pause and Per-Frame Processing
;
; Local:
; First checks $060F. If nonzero, immediately returns to the common
; NMI/frame synchronization point at $CAA7.
;
; Otherwise performs edge detection on controller bit $08 using $49 as
; the stored previous state.
;
; On a new active $08 input:
; - increments $4A,
; - installs a short update command at $024B onward,
; - selects between two command variants according to $47 & $46,
; - uses bit 0 of $4A to alternate the displayed data,
; - optionally clears $060F.
;
; When no new pause/input edge is being handled, and bit 0 of $4A is clear,
; calls a sequence of gameplay/update helpers before returning to the common
; frame synchronization point.
;
; Global:
; Handler selected when GameModeStateTable offset $3F = $04.
;
; The adjacent data beginning at $CB5B contains the tile values for the word
; "PAUSE", strongly indicating that this handler manages pause-state input
; and display updates while also running the normal per-frame gameplay
; helper chain when not paused.
;
; Notes:
; $49 stores the previous masked state of controller bit $08.
; $4A is incremented on each newly detected active edge and bit 0 is used
; here as a pause/run toggle.
;
; $060F is consulted as another gate associated with this handler.
;
; Helper routines remain address-named during this pass.
;==============================================================================
GameModeState04:
lda $060F
beq @CheckPauseInput
jmp GameModeFrameSync
@CheckPauseInput:
lda currentInput
and #$08
cmp $49
beq @ProcessCurrentMode
sta $49
lda $49
beq @ProcessCurrentMode
inc $4A
ldx #$22
lda activePlayerFlag
and cocktailModeFlag
beq @SetCommandAddress
ldx #$2A
@SetCommandAddress:
stx $024B
lda #$37
sta $024C
ldx #$06
ldy #$00
lda $4A
and #$01
beq @CopyPauseData
ldx #$00
sta $060F
@CopyPauseData:
lda PauseDisplayData,x
sta $024D,y
inx
iny
cpy #$06
bne @CopyPauseData
jmp GameModeFrameSync
@ProcessCurrentMode:
lda $4A
and #$01
beq @RunFrameUpdates
jmp GameModeFrameSync
@RunFrameUpdates:
jsr UpdateGameplayTimers
jsr UpdateGameplayTileState
jsr UpdatePlayerMovement
jsr UpdatePlayerDirectionAnimation
jsr UpdateGameplayEntities
jsr UpdateEntitySpriteStates
jsr UpdateEntityStatusFlags
jsr UpdatePeriodicFourEntryState
jsr CheckGameplayProximity
jsr CheckEntityCollisions
jmp GameModeFrameSync
;==============================================================================
; $CB5B-$CB66: Pause Display Data
;
; Local:
; Two six-byte display strings selected by GameModeState04.
;
; Offset $00:
; "PAUSE",$FF
;
; Offset $06:
; five $2D tiles followed by $FF, used to replace/clear the pause text.
;
; Global:
; Provides the visible pause-state update controlled by $4A bit 0.
;==============================================================================
PauseDisplayData:
.byte $50,$41,$55,$53,$45,$FF
.byte $2D,$2D,$2D,$2D,$2D,$FF
;==============================================================================
; $CB67-$CC9B: Game-Mode State $02 Handler
;
; Local:
; Implements a multi-phase initialization/progression state.
;
; When $87 is zero, constructs structured data in RAM page $07 using several
; ROM lookup tables. The construction is influenced by $48, $47, $46,
; $68 and $69.
;
; Once $87 is nonzero, it advances once per frame. At $87=$78, initializes
; a new group of RAM state and prepares display/update data in $024B-$0254.
;
; At $87=$C0, checks $0600/$0601. If both are zero, clears a large area of
; page $07 and advances the main game-mode state to $04.
;
; Global:
; Handler selected by GameModeStateTable when $3F=$02. This appears to be
; a substantial setup/transition state between game-mode phases, preparing
; object/OAM-related RAM, display command data, and other game state before
; handing control to GameModeState04.
;
; Notes:
; $87 serves as the phase timer/progression value.
;
; $00/$01 initially form a pointer to $0760 during the page-$07 build.
; The same locations are reused later as temporary display-address values.
;
; ROM tables $CC9C-$CCD8 immediately follow this routine and should be
; decoded next.
;==============================================================================
GameModeState02:
lda $87
beq @BuildInitialData
jmp @AdvancePhase
@BuildInitialData:
lda #$60
sta workPtr0
lda #$07
sta workPtr0+1
lda $48
bne @AdvancePhase
lda #$02
sta workValue0
ldy #$00
sty workValue1
sty workValue2
sty workValue3
@BuildOuterLoop:
ldx workValue2
lda State02PositionTable,x
sta workPtr1
lda State02PositionTable + 1,x
sta workPtr1+1
ldx workValue3
lda State02EntryCounts,x
sta workValue4
@BuildInnerLoop:
lda workPtr1+1
sta ($00),y
ldx workValue1
lda State02ValueTableA,x
iny
sta (workPtr0),y
lda State02ValueTableB,x
iny
sta (workPtr0),y
lda workPtr1
iny
sta ($00),y
lda workPtr1
clc
adc #$08
sta workPtr1
iny
inc workValue1
dec workValue4
bne @BuildInnerLoop
inc workValue2
inc workValue2
inc workValue3
lda $69
bne @ContinueBuild
lda roundDisplayValue
bne @AdvancePhase
@ContinueBuild:
dec workValue0
beq @CheckPlayerMode
bpl @BuildOuterLoop
bmi @AdvancePhase
@CheckPlayerMode:
lda activePlayerFlag
beq @AdvancePhase
lda cocktailModeFlag
beq @BuildOuterLoop
inc workValue1
inc workValue1
inc workValue1
bne @BuildOuterLoop
;------------------------------------------------------------------------------
; $CBE4
; Frame-based progression portion of the same state.
;------------------------------------------------------------------------------
@AdvancePhase:
lda $87
cmp #$C0
bne @IncrementPhase
jmp $CC7C
@IncrementPhase:
inc $87
lda $87
cmp #$78
beq @InitializeMidPhase
jmp $CC99
@InitializeMidPhase:
lda #$A8
sta $1C
lda #$60
sta pacmanMotion
sta ghost1Motion
sta ghost2Motion
lda #$58
sta ghost3Motion
sta $20
lda #$68
sta ghost4Motion
lda #$70
sta $24
sta $28
sta $2C
lda #$00
tay
@ClearOamRegion:
sta $0778,y
iny
cpy #$24
bne @ClearOamRegion
lda activePlayerFlag
and cocktailModeFlag
beq @NoPlayerOffset
lda #$08
@NoPlayerOffset:
sta $00
lda $67
sec
sbc #$01
asl
tay
lda State02DisplayAddressTable,y
clc
adc workPtr0
sta workPtr0
lda State02DisplayAddressTable+1,y
sta workPtr0+1
lda workPtr0
sta $024B
lda workPtr0+1
sta $024C
lda #$2D
sta $024D
sta $024E
lda #$00
sta $024F
lda $00
sta $0250
lda $01
clc
adc #$20
sta $0251
lda #$2D
sta $0252
sta $0253
lda #$FF
sta $0254
jsr UpdatePlayerDirectionAnimation
jsr UpdateEntitySpriteStates
jsr $DA40
jmp $CC99
@CheckCompletion:
lda $0600
ora $0601
bne $CC99
lda $48
bne @AdvanceMainState
lda #$00
sta $69
tay
@ClearPage07Region:
sta $0760,y
iny
cpy #$A0
bne @ClearPage07Region
@AdvanceMainState:
lda #$04
sta $3F
@ReturnToFrameLoop:
jmp GameModeFrameSync
;==============================================================================
; $CC9C-$CCD8: Game-Mode State $02 Lookup Tables
;
; Local:
; Collection of lookup tables consumed by GameModeState02 while constructing
; page-$07 object/display data and preparing the update command at $024B.
;
; Global:
; Supplies the tile/status/position/count/address parameters used by the
; state-$02 setup sequence.
;
; Notes:
; The tables are separated according to the exact addresses referenced by
; GameModeState02. Names remain structural during this first pass.
;==============================================================================
;------------------------------------------------------------------------------
; $CC9C-$CCAD: State $02 tile/value table
;
; Indexed through $05:
;
; lda State02ValueTableA,x
;
; Written as the second byte of each 4-byte structure generated at $0760+.
; Length: $12 (18) bytes.
;------------------------------------------------------------------------------
State02ValueTableA:
.byte $C6,$C3,$C1,$BA,$C7,$BB
.byte $B0,$B1,$B2,$B3,$B4,$B5
.byte $B6,$B7,$B4,$B8,$B9,$B6
;------------------------------------------------------------------------------
; $CCAE-$CCBF: State $02 secondary value table
;
; Indexed through the same $05 value as State02ValueTableA.
;
; Written as the third byte of each generated 4-byte structure.
; Length: $12 (18) bytes.
;------------------------------------------------------------------------------
State02ValueTableB:
.byte $00,$00,$00,$00,$00,$00
.byte $02,$02,$02,$02,$02,$02
.byte $02,$02,$02,$02,$02,$02
;------------------------------------------------------------------------------
; $CCC0-$CCC5: State $02 position/parameter pairs
;
; GameModeState02 accesses this table as overlapping little groups:
;
; lda State02PositionTable,x
; lda State02PositionTable+1,x
;
; $06 advances by 2 each outer pass.
;
; Therefore the natural structure is three 2-byte pairs.
;------------------------------------------------------------------------------
State02PositionTable:
.byte $44,$88
.byte $44,$60
.byte $50,$70
;------------------------------------------------------------------------------
; $CCC6-$CCC8: State $02 entry-count table
;
; Indexed by $07. Determines how many four-byte structures are emitted by
; each inner construction loop.
;------------------------------------------------------------------------------
State02EntryCounts:
.byte $06,$06,$03
;------------------------------------------------------------------------------
; $CCC9-$CCD0: Additional state-$02 values
;
; No direct reference has yet been established in the portion decoded so far.
; Keep this data structurally separated until its consumer is found.
;------------------------------------------------------------------------------
State02UnknownData:
.byte $C0,$C1,$C2,$C3,$C4,$C5,$C3,$C6
;------------------------------------------------------------------------------
; $CCD1-$CCD8: Display-address table
;
; Indexed by:
;
; ($67 - 1) * 2
;
; and read as two-byte values:
;
; lda State02DisplayAddressTable,y
; lda State02DisplayAddressTable+1,y
;
; The bytes form four PPU-style address pairs:
;
; $2317
; $2319
; $231B
; $2357
;
; When $47 & $46 is nonzero, $08 is added to the low byte before the command
; is placed into $024B/$024C.
;------------------------------------------------------------------------------
State02DisplayAddressTable:
.byte $23,$17
.byte $23,$19
.byte $23,$1B
.byte $23,$57
;==============================================================================
; $CCD9-$CD05: Game-Mode State $06 Handler
;
; Local:
; Increments $DA each frame. Normally falls through to a group of four
; per-frame update routines.
;
; When $DA reaches $28, resets $DA to $02, changes the main game-mode state
; to $04, finds an even-indexed entry in $B8+X containing $08, and changes
; that entry to $06 before continuing through the same update routines.
;
; Global:
; Handler selected by GameModeStateTable when $3F = $06. Implements a timed
; transition back to game-mode state $04 while continuing normal frame
; processing throughout the delay.
;
; Notes:
; $DA is used as a frame/progression counter.
; The $B8 array is searched only at even offsets.
;==============================================================================
GameModeState06:
inc $DA
lda #$28
cmp $DA
bne @RunFrameUpdates
lda #$02
sta $DA
lda #$04
sta $3F
lda #$08
ldx #$FE
@FindStateEntry:
inx
inx
cmp $B8,x
bne @FindStateEntry
lda #$06
sta $B8,x
@RunFrameUpdates:
jsr UpdatePeriodicFourEntryState
jsr UpdateEntityMovementStates
jsr UpdateEntitySpriteStates
jsr $DA40
jmp GameModeFrameSync
;==============================================================================
; $CD06-$CDAF: Game-Mode State $08 Handler
;
; Local:
; Multi-phase timed state using $87, $DB, and $32.
;
; During the initial phase, $DB counts down. When it expires, $87 advances,
; $DB and $0603 are initialized to $0A, and $001E-$0031 are cleared.
;
; During the active phase, $DB periodically advances $32 until $32 reaches
; $1E. The completion logic then updates the two parallel 16-byte state
; blocks at $67-$76 and $77-$86 and selects the next game-mode state.
;
; Global:
; Handler selected by GameModeStateTable when $3F = $08.
;
; Notes:
; $DB acts as a short-period timer.
; $87 distinguishes the initial and active phases.
; $67-$76 and $77-$86 are interchangeable 16-byte state structures.
;
; A few cross-flow destinations are kept as raw addresses during this pass
; to preserve the original control flow exactly.
;==============================================================================
GameModeState08:
lda $87
bne @ActivePhase
dec $DB
bne @InitialFrameUpdates
inc $87
lda #$0A
sta $DB
sta $0603
lda #$00
tax
@ClearLowRamState:
sta $1E,x
inx
cpx #$14
bne @ClearLowRamState
@InitialFrameUpdates:
jsr UpdatePeriodicFourEntryState
jsr UpdateEntitySpriteStates
jsr $DA40
jmp GameModeFrameSync
@ActivePhase:
dec $DB
bpl @ActiveFrameUpdates
lda #$0A
sta $DB
lda $32
clc
adc #$01
sta $32
cmp #$1E
beq @PhaseComplete
@ActiveFrameUpdates:
jsr UpdatePeriodicFourEntryState
jsr $DA40
jmp GameModeFrameSync
@PhaseComplete:
lda $48
bne @SinglePlayerRestart
dec $67
bne @ContinuePlayerHandling
lda #$00
sta $87
sta $69
lda #$0A
sta $3F
jmp GameModeFrameSync
@ContinuePlayerHandling:
lda #$00
sta $3F
lda #$01
sta $69
lda $47
beq @CheckPrimaryBlock
lda $77
bne @SwapBlocksAndToggle
@CheckPrimaryBlock:
lda $67
bne $CDAD
sta $69
lda $46
beq @SinglePlayerRestart
ldx #$0F
@SwapBlocksForMainEntry:
lda $67,x
sta $00
lda $77,x
sta $67,x
lda $00
sta $77,x
dex
bpl @SwapBlocksForMainEntry
@SinglePlayerRestart:
lda #$00
sta $46
lda #$00
sta $3F
jmp MainEntry
@SwapBlocksAndToggle:
ldx #$0F
@SwapBlocks:
lda $67,x
sta $00
lda $77,x
sta $67,x
lda $00
sta $77,x
dex
bpl @SwapBlocks
inc $46
lda $46
and #$01
sta $46
jmp GameModeFrameSync
;==============================================================================
; $CDB0-$CE1E: Game-Mode State $0C Handler
;
; Local:
; Uses $87 as a frame/progression counter.
;
; On first entry ($87 == 0):
; - Clears $001E-$0031.
; - Increments $87.
; - Initializes $32 to $01.
;
; On subsequent frames, processing is throttled by the low three bits of
; the NMI-driven frame counter $4B. Only once every eight frames does the
; handler:
;
; - Alternate a value written to $029E according to bit 0 of $87.
; - Set $0299 to $0F.
; - Increment $87.
;
; When $87 reaches $10, the routine clears $1A/$1C/$87 and selects a new
; main game-mode state according to the value at $68.
;
; Global:
; Handler selected by GameModeStateTable when $3F = $0C.
;
; This appears to implement a short visual/timed transition. The write of
; $0F to $0299 interacts directly with the palette-update mechanism decoded
; earlier, while $029E lies inside that first 16-byte palette/update buffer.
;
; Notes:
; $4B is the NMI-driven frame counter.
;
; $87 advances only once every eight frames after initialization.
;
; $68 controls which state is selected when the sequence completes.
;==============================================================================
GameModeState0C:
lda $87
bne @TimedPhase
ldx #$00
@ClearLowRam:
sta $1E,x
inx
cpx #$14
bne @ClearLowRam
inc $87
lda #$01
sta $32
bne @RunFrameUpdates
@TimedPhase:
lda $4B
and #$07
bne @RunFrameUpdates
ldx #$11
lda $87
and #$01
bne @SetPaletteValue
ldx #$20
@SetPaletteValue:
stx $029E
lda #$0F
sta $0299
inc $87
lda #$10
cmp $87
bne @RunFrameUpdates
lda #$00
sta $1A
sta $1C
sta $87
lda $68
cmp #$01
beq @SelectState0E
inc $87
inc $87
cmp #$04
beq @SelectState0E
inc $87
inc $87
cmp #$08
beq @SelectState0E
cmp #$0C
beq @SelectState0E
cmp #$10
bne @SelectState00
@SelectState0E:
lda #$0E
sta $3F
jmp @RunFrameUpdates
@SelectState00:
lda #$00
sta $3F
@RunFrameUpdates:
jsr UpdatePeriodicFourEntryState
jsr $DA40
jmp GameModeFrameSync
;==============================================================================
; $CE1F-$CEBE: Game-Mode State $0A Handler
;
; Local:
; Uses $87 to distinguish between initialization and subsequent processing.
;
; On initial entry:
; - Fills $0700-$075F with $FF.
; - Initializes $00/$01 as a pointer to $0760.
; - Builds structured four-byte records in page $07 from several ROM
; lookup tables beginning at $CEBF.
;
; The builder uses:
;
; $00/$01 destination pointer ($0760)
; $02/$03 per-group values
; $04 outer pass counter, initially $03
; $05 shared table index
; $06 index into the value-pair table
; $07 index into the entry-count table
; $08 inner record counter
;
; Player/mode variables $47, $77, and $46 influence whether passes repeat
; and whether the lookup index is advanced.
;
; After the construction phase, $87 is incremented. A later entry either
; clears $0760-$07FF and transitions to the code at $CD5E, or simply
; returns to the common frame loop.
;
; Global:
; Handler selected by GameModeStateTable when $3F = $0A.
;
; Structurally this is closely related to GameModeState02, but uses a
; different set of ROM tables and first clears $0700-$075F to $FF. Both
; handlers construct arrays of four-byte records in page $07.
;
; Notes:
; The tables at $CEBF-$CEF2 are decoded immediately after this routine.
; Semantic object names are deferred until the page-$07 consumers are
; understood more completely.
;==============================================================================
GameModeState0A:
lda $87
beq @Initialize
jmp $CEAA
@Initialize:
ldy #$00
lda #$FF
@FillPage07Prefix:
sta $0700,y
iny
cpy #$60
bne @FillPage07Prefix
lda #$60
sta $00
lda #$07
sta $01
lda #$03
sta $04
ldy #$00
sty $05
sty $06
sty $07
@BuildOuterLoop:
ldx $06
lda State0AParameterPairs,x
sta $02
lda State0AParameterPairs+1,x
sta $03
ldx $07
lda State0AEntryCounts,x
sta $08
@BuildInnerLoop:
lda $03
sta ($00),y
ldx $05
lda State0AValueTableA,x
iny
sta ($00),y
lda State0AValueTableB,x
iny
sta ($00),y
lda $02
iny
sta ($00),y
lda $02
clc
adc #$08
sta $02
iny
inc $05
dec $08
bne @BuildInnerLoop
inc $06
inc $06
inc $07
lda $47
beq @ContinueOuterPass
lda $77
bne @ContinueOuterPass
lda $04
cmp #$02
beq $CEAA
@ContinueOuterPass:
dec $04
beq @CheckPlayerMode
bpl @BuildOuterLoop
bmi @FinishInitialization
@CheckPlayerMode:
lda $47
beq @FinishInitialization
lda $46
beq @BuildOuterLoop
@AdjustTableIndex:
inc $05
inc $05
inc $05
bne @BuildOuterLoop
@FinishInitialization:
inc $87
bne @ReturnToFrameLoop
@PostInitialization:
lda #$00
tay
@ClearPage07Region:
sta $0760,y
iny
cpy #$A0
bne @ClearPage07Region
jmp $CD5E
@ReturnToFrameLoop:
jmp GameModeFrameSync
;==============================================================================
; $CEBF-$CEF2: Game-Mode State $0A Lookup Tables
;
; Local:
; Lookup data used by GameModeState0A to construct four-byte records in
; page $07.
;
; Global:
; Provides the object/tile/value and layout parameters for state $0A.
;==============================================================================
;------------------------------------------------------------------------------
; $CEBF-$CED2: Primary value table
; Length: $14 (20) bytes
;------------------------------------------------------------------------------
State0AValueTableA:
.byte $BC,$B2,$BD,$B4,$B6,$BE,$B4,$B5
.byte $B0,$B1,$B2,$B3,$B4,$B5,$B6,$B7
.byte $B4,$B8,$B9,$B6
;------------------------------------------------------------------------------
; $CED3-$CEE6: Secondary value table
; Length: $14 (20) bytes
;------------------------------------------------------------------------------
State0AValueTableB:
.byte $00,$00,$00,$00,$00,$00,$00,$00
.byte $02,$02,$02,$02,$02,$02,$02,$02
.byte $02,$02,$02,$02
;------------------------------------------------------------------------------
; $CEE7-$CEEE: Parameter-pair table
;
; $06 advances by two, so represent this as four 2-byte pairs.
;------------------------------------------------------------------------------
State0AParameterPairs:
.byte $38,$88
.byte $60,$88
.byte $44,$60
.byte $50,$70
;------------------------------------------------------------------------------
; $CEEF-$CEF2: Inner-loop entry counts
;------------------------------------------------------------------------------
State0AEntryCounts:
.byte $04,$04,$06,$03
;==============================================================================
; $CEF3-$D0AA: Game-Mode State $00 Initialization
;
; Local:
; Performs the main initialization for the game-mode state machine:
;
; - Synchronizes with NMI and disables rendering.
; - Clears working RAM $87-$EF.
; - Clears page $0700-$07FF.
; - Copies a 32-byte palette table from GameplayPaletteData to $0299-$02B8.
; - Selects game-mode state $02.
; - Initializes gameplay/state variables and invokes several setup helpers.
; - Loads numerous parameters from ROM lookup tables.
; - Initializes arrays and display/update data.
; - Re-enables NMI with PPUCTRL=$88.
; - Returns to GameModeFrameSync.
;
; Global:
; Primary initialization handler for GameModeStateTable offset $00.
; Establishes the working state used by the later $02/$04/etc. handlers.
;
; Notes:
; Many ROM tables referenced from $EBxx/$ECxx are intentionally left raw
; during this pass and will be named when those regions are reached.
;==============================================================================
GameModeState00:
lda #$01
sta $40
@WaitForNmi:
lda $40
bne @WaitForNmi
lda #$08
sta PPUCTRL
sta $43
lda #$00
sta PPUMASK
ldx #$00
@ClearWorkingRam:
sta $87,x
inx
cpx #$69
bne @ClearWorkingRam
tax
@ClearPage07:
sta $0700,x
inx
bne @ClearPage07
ldy #$00
@LoadPalette:
lda GameplayPaletteData,y
sta $0299,y
iny
cpy #$20
bne @LoadPalette
lda #$02
sta $3F
lda $69
bne $CF4A
sta $8B
lda #$01
sta $6C
sta $6D
sta $6E
sta $6F
lda #$C0
sta $6A
lda $68
cmp #$16
beq @SkipIncrement68
inc $68
@SkipIncrement68:
jsr InitializeGameplayAttributes
jsr DrawMazeNametable
jsr DrawGameplayScoreFields
jsr InitializeGameNametableRegion
jsr DrawLevelProgressMarkers
jsr $E5C1
jsr DrawGameplayHudLabels
@ContinueSetup:
ldx $68
lda #$00
clc
@MultiplyBy6:
dex
bmi @MultiplyBy6Done
adc #$06
bne @MultiplyBy6
@MultiplyBy6Done:
sta $00
tax
lda $EBB6,x
tax
lda #$00
clc
@MultiplyBy22:
dex
bmi @MultiplyBy22Done
adc #$16
bne @MultiplyBy22
@MultiplyBy22Done:
tay
ldx #$00
@CopyGameParameters:
lda $EC40,y
sta $9F,x
inx
iny
cpx #$16
bne @CopyGameParameters
ldx $00
lda $EBB6,x
asl
asl
asl
tax
ldy #$00
sty $D0
; Save the first selected table value separately.
lda $ECAE,x
sta $CF
@CopyTableECae:
lda $ECAE,x
sta $0097,y
inx
iny
cpy #$08
bne @CopyTableECae
inc $00
ldx $00
lda $EBB6,x
sta $8C
inc $00
ldx $00
lda $EBB6,x
asl
tax
lda $ECCE,x
sta $8D
inx
lda $ECCE,x
sta $8E
inc $00
lda $69
beq @UseAlternateData
ldx #$0C
ldy #$00
lda $ECDC,x
sta $008F,y
iny
inx
lda #$C0
sec
sbc $6A
sta $01
@CopyOffsetData:
lda $01
clc
adc $ECDC,x
sta $008F,y
iny
inx
cpy #$04
bne @CopyOffsetData
beq @InitializeArray
@UseAlternateData:
ldx $00
lda $EBB6,x
asl
asl
tax
ldy #$00
@CopyAlternateData:
lda $ECDC,x
sta $008F,y
inx
iny
cpy #$04
bne @CopyAlternateData
@InitializeArray:
lda #$04
sta $B8
lda #$01
sta $B9
lda $8F
sta $01
ldx #$00
@InitializeArrayLoop:
dec $01
beq @InitializeArrayTail
lda #$02
sta $BA,x
lda #$00
sta $BB,x
inx
inx
bne @InitializeArrayLoop
@InitializeArrayTail:
lda #$00
@ClearRemainingArray:
cpx #$06
beq @ContinueFinalSetup
sta $BA,x
sta $BB,x
inx
inx
bne @ClearRemainingArray
@ContinueFinalSetup:
lda $90
sta $D3
inc $00
ldx $00
lda $EBB6,x
sta $93
inc $00
ldx $00
lda $EBB6,x
sta $96
lda $A5
sta $B5
lda $A6
sta $B6
lda $AF
sta $C4
sta $C6
sta $C8
sta $CA
lda $B0
sta $C5
sta $C7
sta $C9
sta $CB
lda #$01
sta $50
sta $51
ldy #$00
ldx #$00
lda $46
and $47
beq @CopyDisplayInit
ldy #$09
@CopyDisplayInit:
lda GameDisplayInitialData,y
sta $026B,x
iny
inx
cpx #$09
bne @CopyDisplayInit
ldy #$00
@CopyStateData:
lda GameStateInitialValues,y
sta $0032,y
iny
cpy #$0C
bne @CopyStateData
ldy #$00
@NormalizeValues:
lda $006C,y
cmp #$02
bne @NextNormalizeValue
lda #$01
sta $006C,y
@NextNormalizeValue:
iny
cpy #$04
bne @NormalizeValues
sty $C0
lda #$FF
sta $89
lda #$88
sta PPUCTRL
sta $43
jmp GameModeFrameSync
;==============================================================================
; $D0AB-$D0F2: Initialize/Clear Repeated Nametable Region
;
; Local:
; Selects an initial PPU destination based on ($46 & $47):
;
; zero -> $2256
; nonzero -> $2A56
;
; It then performs 10 iterations. For each iteration:
;
; - Sets PPUADDR to the current destination.
; - Writes six $20 tiles.
; - Writes two $2D tiles.
; - Advances the destination address by $20.
;
; Global:
; Called from GameModeState00 during game/round initialization while
; rendering is disabled. This prepares the same rectangular display region
; in one of two nametable locations depending on the current player/mode
; state represented by $46/$47.
;
; Notes:
; $00/$01 form the current 16-bit PPU destination address.
; $02 counts 10 rows.
; $03 counts the six $20 tile writes in each row.
;
; $20 and $2D are retained as raw tile IDs during this pass.
;==============================================================================
InitializeGameNametableRegion:
ldy #$22
lda $46
and $47
beq @SetDestination
ldy #$2A
@SetDestination:
sty $00
lda #$56
sta $01
lda #$0A
sta $02
@NextRow:
lda PPUSTATUS
lda $00
sta PPUADDR
lda $01
sta PPUADDR
lda #$06
sta $03
lda #$20
@WriteInterior:
sta PPUDATA
dec $03
bne @WriteInterior
lda #$2D
sta PPUDATA
sta PPUDATA
; Advance the destination by one full nametable row ($20 bytes).
lda $01
clc
adc #$20
sta $01
lda $00
adc #$00
sta $00
dec $02
bne @NextRow
rts
;==============================================================================
; $D0F3-$D130: Game-Mode State $00 Initialization Data
;
; Local:
; Contains three adjacent data blocks consumed during GameModeState00:
;
; $D0F3-$D0FE 12-byte state initialization table
; $D0FF-$D110 18-byte display/update table
; $D111-$D130 32-byte palette table
;
; Global:
; Supplies initial state, display-command, and palette data used when
; GameModeState00 prepares a new game/round state.
;
; Notes:
; The display table is intentionally kept as raw tile/command bytes during
; this pass. GameModeState00 selects either offset $00 or $09 from this
; table and copies nine bytes to $026B-$0273.
;
; The palette table is copied in full to $0299-$02B8.
;==============================================================================
;==============================================================================
; $D0F3-$D0FE: Game State Initialization Values
;
; Local:
; 12 bytes copied directly to zero-page RAM $32-$3D:
;
; ROM $D0F3-$D0FE -> RAM $0032-$003D
;
; Global:
; Establishes a compact block of initial game-state values during
; GameModeState00.
;
; Notes:
; Individual fields remain unnamed until their consumers are traced.
;==============================================================================
GameStateInitialValues:
.byte $04,$0C,$0A,$0A
.byte $0A,$00,$00,$00
.byte $01,$02,$03,$00
;==============================================================================
; $D0FF-$D110: Game Display Initialization Data
;
; Local:
; Contains two 9-byte records. GameModeState00 selects the first record when
; ($46 & $47) is zero and the second record when it is nonzero.
;
; The selected nine bytes are copied to RAM $026B-$0273.
;
; Global:
; Provides alternate display/update initialization according to the active
; player/mode state.
;
; Notes:
; These bytes appear to mix PPU/update-command values and tile IDs.
; Preserve them numerically until the consumer of $026B is decoded.
;==============================================================================
GameDisplayInitialData:
; Record 0, selected when ($46 & $47) == 0.
; $D0FF-$D107
.byte $21,$36,$20,$F7,$B0,$B3,$B2,$20,$B6
; Record 1, selected when ($46 & $47) != 0.
; $D108-$D110
.byte $29,$B6,$29,$77,$B1,$B3,$B2,$28,$B6
;==============================================================================
; $D111-$D130: Initial Gameplay Palette
;
; Local:
; Complete 32-byte palette copied to RAM $0299-$02B8 by GameModeState00.
; The NMI palette-update mechanism later transfers these buffers into PPU
; palette RAM.
;
; Global:
; Defines the initial background and sprite color palettes used for normal
; gameplay/game-mode initialization.
;
; Notes:
; $D111-$D120 contains four background palettes.
; $D121-$D130 contains four sprite palettes.
;
; Palette values are retained as NES color indices.
;==============================================================================
GameplayPaletteData:
; Background palette 0
.byte $0F,$20,$0F,$06
; Background palette 1
.byte $0F,$11,$0F,$27
; Background palette 2
.byte $0F,$16,$26,$06
; Background palette 3
.byte $0F,$19,$17,$12
; Sprite palette 0
.byte $0F,$27,$20,$06
; Sprite palette 1
.byte $0F,$11,$20,$33
; Sprite palette 2
.byte $0F,$21,$20,$21
; Sprite palette 3
.byte $0F,$09,$20,$17
;==============================================================================
; $D131-$D15F: Initialize Gameplay Nametable Attributes
;
; Local:
; Copies the 64-byte attribute table at $D160 to two PPU destinations:
;
; pass 1 -> PPU $23C0-$23FF
; pass 2 -> PPU $2BC0-$2BFF
;
; The first pass uses X=$01. After copying all $40 bytes, X is decremented.
; Since X becomes zero, execution falls through into the setup for the
; second destination at $2BC0.
;
; After the second copy, X decrements from $00 to $FF and the routine
; returns.
;
; Global:
; Called by GameModeState00 during game/round initialization. Initializes
; complete attribute tables for two nametables using identical palette-
; selection data.
;
; Notes:
; The branch at $D15E intentionally jumps back into the shared copy loop
; after selecting PPU address $2BC0.
;
; $D160 begins the 64-byte attribute source table and is not executable.
;==============================================================================
InitializeGameplayAttributes:
lda PPUSTATUS
lda #$23
sta PPUADDR
lda #$C0
sta PPUADDR
ldx #$01
@CopyAttributeTable:
ldy #$00
@CopyAttributeBytes:
lda GameplayAttributeData,y
sta PPUDATA
iny
cpy #$40
bne @CopyAttributeBytes
dex
beq @SetupSecondNametable
rts
@SetupSecondNametable:
lda PPUSTATUS
lda #$2B
sta PPUADDR
lda #$C0
sta PPUADDR
bne @CopyAttributeTable
;==============================================================================
; $D160-$D19F: Gameplay Nametable Attribute Data
;
; Local:
; Complete 64-byte NES attribute table copied to both PPU $23C0-$23FF and
; $2BC0-$2BFF by InitializeGameplayAttributes.
;
; Global:
; Defines background-palette selections for the gameplay nametables.
;
; Notes:
; Common values:
;
; $00 -> palette 0 in all four attribute quadrants
; $55 -> palette 1 in all four quadrants
;
; Values such as $11, $51, $50 and $05 combine different palette selections
; within the four quadrants of an attribute byte.
;==============================================================================
GameplayAttributeData:
.byte $55,$55,$55,$55,$55,$11,$00,$00
.byte $55,$55,$55,$55,$55,$11,$00,$00
.byte $55,$55,$55,$55,$55,$11,$00,$00
.byte $55,$55,$55,$55,$55,$51,$50,$50
.byte $55,$55,$55,$55,$55,$11,$05,$05
.byte $55,$55,$55,$55,$55,$11,$00,$00
.byte $55,$55,$55,$55,$55,$55,$55,$55
.byte $55,$55,$55,$55,$55,$55,$55,$55
;==============================================================================
; $D1A0-$D289: Gameplay Timers and State Progression
;
; Local:
; Updates several gameplay timers, palette-animation values, progression
; counters, and state variables.
;
; Global:
; Called once per active gameplay frame from GameModeState04. Coordinates
; several independent timing and progression systems.
;
; Notes:
; Address-based local labels are used during this pass to make the original
; branch destinations explicit and simplify byte-identical verification.
;==============================================================================
UpdateGameplayTimers:
lda $89
bmi @D1E1
inc $8A
lda #$3C
cmp $8A
bne @D1C9
lda #$00
sta $8A
dec $89
bpl @D1C9
sta $88
ldx #$00
@D1B7:
lda $39,x
and #$FC
sta $00
txa
ora $00
sta $39,x
inx
cpx #$04
bne @D1B7
@D1C9:
lda $89
cmp #$02
bcs @D1E1
ldx #$11
lda $8A
and #$08
bne @D1D9
ldx #$20
@D1D9:
stx $02AE
lda #$0F
sta $02A9
@D1E1:
lda $88
bne @D213
lda $CF
cmp #$FF
beq @D213
inc $D1
lda $D1
cmp #$3C
bne @D213
lda #$00
sta $D1
dec $CF
bpl @D213
inc $D0
lda $D0
and #$01
beq @D209
jsr $E08E
lda #$0F
bne @D20B
@D209:
lda $D2
@D20B:
sta $87
ldx $D0
lda $97,x
sta $CF
@D213:
lda $D3
beq @D236
clc
adc $6A
cmp #$C0
bne @D236
jsr ActivateNextStateEntry
lda $D3
ldx #$00
@D225:
cmp $8F,x
bne @D231
inx
lda $8F,x
sta $D3
jmp @D236
@D231:
inx
cpx #$04
bne @D225
@D236:
inc $D6
lda #$60
cmp $D6
bne @D251
lda #$00
sta $D6
inc $D5
lda $D5
cmp $96
bne @D251
lda #$00
sta $D5
jsr ActivateNextStateEntry
@D251:
ldx $D4
cpx #$02
beq @D26E
lda $8D,x
cmp $6A
bne @D26E
inc $D4
lda #$01
sta $D2
txa
asl
tax
lda $AB,x
sta $CA
lda $AC,x
sta $CB
@D26E:
ora $D7
ora $D8
beq @D289
dec $D8
bne @D289
lda $D7
beq @D283
dec $D7
lda #$3C
sta $D8
rts
@D283:
sta $2E
sta $30
sta $8B
@D289:
rts
;==============================================================================
; $D28A-$D29B: Activate First Available State Entry
;
; Local:
; Searches the even-indexed entries at $BA, $BC, and $BE for the first
; zero value.
;
; When a zero entry is found, replaces it with $02 and returns.
;
; If all three entries are already nonzero, returns without modifying them.
;
; Global:
; Called by UpdateGameplayTimers when certain progression timers expire.
; This appears to activate the next available entry in a three-element
; state array.
;
; Notes:
; Only the even addresses $BA/$BC/$BE are examined. The interleaved bytes
; $BB/$BD/$BF form a parallel field initialized elsewhere.
;
; The meaning of state value $02 will be refined when the consumers of
; $BA-$BF are decoded.
;==============================================================================
ActivateNextStateEntry:
ldx #$00
@FindAvailableEntry:
lda $BA,x
bne @NextEntry
lda #$02
sta $BA,x
rts
@NextEntry:
inx
inx
cpx #$06
bne @FindAvailableEntry
rts
;==============================================================================
; $D29C-$D2A3: Sequential Tile/Value Table
;
; Local:
; Eight sequential values from $24 through $2B.
;
; Global:
; Consumer has not yet been decoded during this pass. Given the sequential
; values and the surrounding graphics-oriented code, this may represent
; tile IDs or animation values.
;
; Notes:
; Keep the table generically named until a direct reference establishes its
; exact purpose.
;==============================================================================
SequentialValueTableD29C:
.byte $24,$25,$26,$27,$28,$29,$2A,$2B
;==============================================================================
; $D2A4-$D36B: Gameplay Proximity / State Transition Handler
;
; Local:
; Returns immediately when $6A is zero.
;
; Otherwise scans even-indexed state entries beginning at $B8. Entries whose
; state value is $04 undergo two proximity tests using the four-byte
; structures beginning at $001E.
;
; $02 is shifted once for each scanned entry and acts as a candidate bitmask.
; When a qualifying entry is found, the routine selects one of several state
; transition paths based on X and ($02 & $88).
;
; Global:
; Called during normal gameplay processing from GameModeState04. This
; routine appears to detect proximity/collision conditions and initiate
; corresponding gameplay-state transitions.
;
; Notes:
; $00/$01 form a temporary pointer, initially $001E.
; $02 is a shifting bitmask.
; $03 is temporary storage for distance/index calculations.
;
; Lookup tables begin at ProximityCandidateValues and will be decoded separately.
;==============================================================================
CheckGameplayProximity:
lda $6A
bne @Start
rts
@Start:
lda #$1E
sta $00
lda #$00
sta $01
lda #$01
sta $02
ldx #$00
@ScanEntries:
lda $B8,x
cmp #$04
bne @NextEntry
ldy #$00
lda $1A
cmp ($00),y
bcs @FirstAxisReverse
lda ($00),y
sec
sbc $1A
bcs @CheckFirstAxisDistance
@FirstAxisReverse:
sbc ($00),y
@CheckFirstAxisDistance:
cmp #$0A
bcs @NextEntry
sta $03
ldy #$02
lda $1C
cmp ($00),y
bcs @SecondAxisReverse
lda ($00),y
sec
sbc $1C
bcs @CheckSecondAxisDistance
@SecondAxisReverse:
sbc ($00),y
@CheckSecondAxisDistance:
cmp #$0A
bcs @NextEntry
adc $03
cmp #$05
bcc @CandidateFound
@NextEntry:
inx
inx
lda $00
clc
adc #$04
sta $00
asl $02
cpx #$0A
bne @ScanEntries
rts
@CandidateFound:
cpx #$08
beq @ProcessSecondaryState
lda $02
and $88
beq @SpecialCandidate
txa
lsr
sta $03
ldy $D9
lda ProximityCandidateValues,y
ldy $03
sta $0033,y
lda #$00
sta $32
ldy $D9
lda ProximityDDValues,y
sta $DD
lda ProximityDEValues,y
sta $DE
inc $D9
lda #$08
sta $B8,x
sta $0607
lda #$06
sta $3F
jmp $E0E6
;------------------------------------------------------------------------------
; $D337
; Candidate path selected when ($02 & $88) == 0.
;------------------------------------------------------------------------------
@SpecialCandidate:
lda #$08
sta $3F
lda #$12
sta $32
lda #$80
sta $DB
lda #$00
sta $87
rts
;------------------------------------------------------------------------------
; $D348
; Special path selected when X == $08.
;------------------------------------------------------------------------------
@ProcessSecondaryState:
lda $8B
beq @InitializeSecondaryState
rts
;------------------------------------------------------------------------------
; $D34D
; Initialize secondary state.
;------------------------------------------------------------------------------
@InitializeSecondaryState:
sta $D7
lda #$80
sta $D8
sta $8B
sta $0606
ldy $93
lda SecondaryStateDDValues,y
sta $DD
lda SecondaryStateDEValues,y
sta $DE
lda SecondaryState37Values,y
sta $37
jmp $E0E6
;==============================================================================
; $D36C-$D38F: Gameplay Proximity / Transition Lookup Data
;
; Local:
; Lookup data used by CheckGameplayProximity when a qualifying proximity
; condition triggers a gameplay-state transition.
;
; Global:
; Supplies values written to $33+, $37, $DD and $DE during the different
; transition paths handled by CheckGameplayProximity.
;
; Notes:
; $D370 is intentionally a label inside the table beginning at $D36C.
; No bytes are duplicated.
;==============================================================================
ProximityCandidateValues:
; $D36C-$D36F
.byte $2D,$2F,$32,$34
SecondaryState37Values:
; $D370-$D373
; Also forms bytes 4-7 of ProximityCandidateValues.
.byte $2C,$2E,$30,$31
;------------------------------------------------------------------------------
; $D374-$D377
; Consumer not yet established.
;------------------------------------------------------------------------------
ProximityUnknownTableD374:
.byte $33,$35,$36,$37
;------------------------------------------------------------------------------
; $D378-$D37B
; Indexed by $D9 and loaded into $DE.
;------------------------------------------------------------------------------
ProximityDEValues:
.byte $00,$00,$00,$01
;------------------------------------------------------------------------------
; $D37C-$D37F
; Indexed by $D9 and loaded into $DD.
;------------------------------------------------------------------------------
ProximityDDValues:
.byte $02,$04,$08,$06
;------------------------------------------------------------------------------
; $D380-$D387
; Indexed by $93 and loaded into $DD.
;------------------------------------------------------------------------------
SecondaryStateDDValues:
.byte $01,$03,$05,$07
.byte $00,$00,$00,$00
;------------------------------------------------------------------------------
; $D388-$D38F
; Indexed by $93 and loaded into $DE.
;------------------------------------------------------------------------------
SecondaryStateDEValues:
.byte $00,$00,$00,$00
.byte $01,$02,$03,$05
;==============================================================================
; $D390-...: Gameplay Direction / Movement Processing
;
; Local:
; Processes directional controller input, validates a requested direction
; against the current map/tile state, and selects movement parameters.
;
; Global:
; Called during normal gameplay processing from GameModeState04.
;
; Notes:
; Address-derived labels are retained during this pass to make branch
; targets explicit while maintaining byte identity.
;==============================================================================
UpdatePlayerMovement:
lda $48
beq @D397
jmp $D506
@D397:
lda $4F
and #$F0
beq @D3D2
ldx #$FF
@D39F:
inx
asl
bcc @D39F
lda InputDirectionTable,x
sta $50
clc
adc #$02
and #$03
cmp $51
bne @D3C9
lda $1A
ora $1C
and #$07
bne @D3C2
ldy $50
lda $022B,y
and #$F0
bne @D3C9
@D3C2:
lda $50
sta $51
jmp @D3D6
@D3C9:
ldx $50
lda $022B,x
and #$F0
beq @D3D6
@D3D2:
lda $51
sta $50
@D3D6:
ldx #$04
lda $88
bne @D3DE
ldx #$0A
@D3DE:
lda $022A
cmp #$01
beq @D3F4
cmp #$02
beq @D3F4
dex
dex
cmp #$03
beq @D3F4
cmp #$09
beq @D3F4
dex
dex
@D3F4:
lda $9F,x
sta $B5
lda $A0,x
sta $B6
lda $51
asl
tay
lda MovementArithmeticTable,y
sta $10
lda MovementArithmeticTable+1,y
sta $11
jmp ($0010)
;==============================================================================
; $D40E-$D415: Movement Arithmetic Dispatch Table
;
; Local:
; Four 16-bit handlers selected using the internal direction value in $51.
;
; Global:
; Dispatches into direction-specific coordinate arithmetic used by
; UpdatePlayerMovement.
;
; Notes:
; Direction semantics remain unnamed during this pass.
;==============================================================================
MovementArithmeticTable:
.word $D416
.word $D41A
.word $D435
.word $D431
;==============================================================================
; $D416-$D443: Direction-Specific Movement Arithmetic
;
; Local:
; Four entry points selected by MovementArithmeticTable converge into two
; shared arithmetic paths.
;
; $D416/$D41A select the coordinate component and enter subtraction.
; $D431/$D435 select the coordinate component and enter addition.
;
; Global:
; Computes the fixed-point movement result used by the second direction
; dispatch beginning at MovementApplyResult.
;
; Notes:
; Shared labels are deliberately non-local because multiple entry-point
; labels must branch into the same code.
;==============================================================================
MovementArithmeticD416:
ldx #$02
bne MovementSubtract
MovementArithmeticD41A:
ldx #$00
MovementSubtract:
lda $1B,x
sec
sbc $B5
sta $1B,x
lda #$00
bcs MovementSubtractNoBorrow
lda #$01
MovementSubtractNoBorrow:
clc
adc $B6
sta $CC
jmp MovementApplyResult
MovementArithmeticD431:
ldx #$00
beq MovementAdd
MovementArithmeticD435:
ldx #$02
MovementAdd:
lda $1B,x
clc
adc $B5
sta $1B,x
lda #$00
adc $B6
sta $CC
;==============================================================================
; $D444-$D459: Apply Movement Result and Dispatch Direction Handler
;
; Local:
; Decrements the movement accumulator $CC. If it becomes negative, returns
; immediately.
;
; Otherwise uses the current direction value at $51 as a byte offset into a
; four-entry handler table beginning at $D45A.
;
; Global:
; Second stage of UpdatePlayerMovement. After the fixed-point arithmetic
; performed by the $D416-$D443 handlers, this dispatches to the actual
; direction-specific coordinate/tile movement code.
;
; Notes:
; $51 is multiplied by two before indexing the pointer table.
;==============================================================================
MovementApplyResult:
dec $CC
bpl @D449
rts
@D449:
lda $51
asl
tay
lda MovementDirectionTable,y
sta $10
lda MovementDirectionTable+1,y
sta $11
jmp ($0010)
;==============================================================================
; $D45A-$D461: Direction-Specific Movement Dispatch Table
;
; Local:
; Four handlers selected according to the internal direction value in $51.
;
; Notes:
; Handler names remain address-based until coordinate effects are fully
; mapped to semantic direction names.
;==============================================================================
MovementDirectionTable:
.word MovementDirectionD49A
.word MovementDirectionD490
.word MovementDirectionD46C
.word MovementDirectionD462
;==============================================================================
; $D462-$D501: Direction-Specific Movement Handlers
;
; Local:
; Four entry points selected by MovementDirectionTable.
;
; $D462 and $D46C enter the increment path.
; $D490 and $D49A enter the decrement path.
;
; X selects which coordinate component at $1A/$1C is modified. $00 and $01
; preserve working indexes used for collision/tile checks against $022B.
;
; After movement, the shared tail handles coordinate wrapping/state flags
; and, when aligned to an 8-unit boundary, commits a requested direction
; change from $50 to $51.
;
; Global:
; Final stage of UpdatePlayerMovement. Applies movement to the player's
; coordinate state and reconciles requested direction changes.
;
; Notes:
; Entry points intentionally share code. Use non-local labels for shared
; targets so ca65 scope rules do not change the branch structure.
;==============================================================================
;------------------------------------------------------------------------------
; $D462
; Increment coordinate component X=0.
;------------------------------------------------------------------------------
MovementDirectionD462:
ldx #$00
stx $00
lda #$03
sta $01
bne MovementIncrement
;------------------------------------------------------------------------------
; $D46C
; Increment coordinate component X=2.
;------------------------------------------------------------------------------
MovementDirectionD46C:
ldx #$02
stx $00
stx $01
;------------------------------------------------------------------------------
; $D472: Shared increment path.
;------------------------------------------------------------------------------
MovementIncrement:
lda $1A,x
clc
adc #$01
sta $1A,x
and #$04
bne MovementSharedTail
ldx $01
lda $022B,x
and #$F0
beq MovementSharedTail
ldx $00
lda $1A,x
and #$FC
sta $1A,x
bne MovementSharedTail
;------------------------------------------------------------------------------
; $D490
; Decrement coordinate component X=0.
;------------------------------------------------------------------------------
MovementDirectionD490:
ldx #$00
stx $00
lda #$01
sta $01
bne MovementDecrement
;------------------------------------------------------------------------------
; $D49A
; Decrement coordinate component X=2.
;------------------------------------------------------------------------------
MovementDirectionD49A:
ldx #$02
stx $00
lda #$00
sta $01
;------------------------------------------------------------------------------
; $D4A2: Shared decrement path.
;------------------------------------------------------------------------------
MovementDecrement:
lda $1A,x
sec
sbc #$01
sta $1A,x
and #$04
beq MovementSharedTail
ldx $01
lda $022B,x
and #$F0
beq MovementSharedTail
ldx $00
lda $1A,x
clc
adc #$04
and #$FC
sta $1A,x
;------------------------------------------------------------------------------
; $D4C1: Shared movement/wrap tail.
;------------------------------------------------------------------------------
MovementSharedTail:
lda $1A
cmp #$18
bcc MovementCheckLeftWrap
cmp #$A9
bcs MovementCheckLeftWrap
lda $38
and #$DF
bcc MovementStoreWrapState
MovementCheckLeftWrap:
lda #$20
ora $38
MovementStoreWrapState:
sta $38
lda $1A
cmp #$0B
bcs MovementCheckRightWrap
lda #$BF
sta $1A
bne MovementCheckAlignment
MovementCheckRightWrap:
cmp #$C0
bcc MovementCheckAlignment
lda #$0B
sta $1A
MovementCheckAlignment:
lda $1A
ora $1C
and #$07
bne MovementReturnToArithmetic
lda $50
cmp $51
beq MovementReturnToArithmetic
sta $51
inc $CC
inc $CC
MovementReturnToArithmetic:
jmp MovementApplyResult
;==============================================================================
; $D502-$D505: Controller Direction Mapping
;==============================================================================
InputDirectionTable:
.byte $03,$01,$02,$00
;==============================================================================
; $D506-$D51E: Scripted Direction/Input Update
;
; Local:
; Uses $E3 as a countdown and $E4 as an offset into the table beginning at
; $D51F.
;
; Each time $E3 reaches zero:
; - Advances $E4 by two.
; - Loads a new duration from $D51F+$E4 into $E3.
;
; It then takes the following table byte, masks it to two bits, and
; tail-jumps into UpdatePlayerMovement at $D3A6 with that value in A.
;
; Global:
; Entered from UpdatePlayerMovement when $48 is nonzero. This provides a
; scripted/autonomous direction source in place of normal controller input.
;
; Notes:
; $E4 indexes two-byte table records:
;
; byte 0 = duration/count
; byte 1 = direction/control value
;
; The second byte is masked with #$03 before entering movement processing.
;==============================================================================
UpdateScriptedDirection:
ldy $E4
dec $E3
bne @UseCurrentEntry
inc $E4
inc $E4
ldy $E4
lda ScriptedDirectionTable,y
sta $E3
@UseCurrentEntry:
lda ScriptedDirectionTable+1,y
and #$03
jmp $D3A6
;==============================================================================
; $D51F-...: Scripted Direction Table
;
; Local:
; Two-byte records used by UpdateScriptedDirection:
;
; byte 0 = duration
; byte 1 = direction/control number
;
; Global:
; Drives autonomous/scripted movement when $48 selects this path instead of
; normal player controller processing.
;==============================================================================
ScriptedDirectionTable:
.byte $00,$01
.byte $D0,$00
.byte $28,$01
.byte $28,$02
.byte $90,$03
.byte $38,$00
.byte $20,$03
.byte $18,$01
.byte $60,$02
.byte $30,$01
.byte $10,$02
.byte $48,$01
.byte $18,$02
.byte $40,$01
.byte $20,$02
.byte $18,$00
.byte $20,$03
.byte $40,$02
.byte $38,$01
.byte $18,$02
.byte $E0,$03
.byte $30,$00
.byte $28,$01
.byte $28,$02
.byte $20,$02
.byte $80,$03
.byte $40,$00
.byte $40,$01
;==============================================================================
; $D557-$D586: Update Four Gameplay Entities
;
; Local:
; Iterates four gameplay entries using X offsets:
;
; $00, $02, $04, $06
;
; Initializes several temporary values before the loop:
;
; $00 = $2F
; $01 = $02
; $02 = $1E
; $03 = $00
; $04 = $01
;
; For each entry, calls the dispatcher at $D587. After each call:
;
; $00 += 4
; $02 += 4
; $04 <<= 1
; X += 2
;
; Processing ends after X reaches $08.
;
; Global:
; Called every active gameplay frame from GameModeState04. Coordinates
; per-entry updates for four gameplay entities/state slots.
;
; Notes:
; $00 and $02 appear to advance through parallel four-byte-strided RAM
; structures.
;
; $04 becomes a per-entry bitmask:
;
; $01,$02,$04,$08
;
; The exact roles of the temporary values will become clearer as the
; dispatcher at $D587 and its handlers are decoded.
;==============================================================================
UpdateGameplayEntities:
ldx #$00
lda #$2F
sta $00
lda #$02
sta $01
lda #$1E
sta $02
lda #$00
sta $03
lda #$01
sta $04
@UpdateNextEntity:
jsr $D587
lda $00
clc
adc #$04
sta $00
lda $02
clc
adc #$04
sta $02
asl $04
inx
inx
cpx #$08
bne @UpdateNextEntity
rts
;==============================================================================
; $D587-$D595: Gameplay Entity State Dispatcher
;
; Local:
; Reads the state value for the current entity from $B8,X and uses that
; value directly as a byte offset into EntityStateTable.
;
; The selected 16-bit handler address is copied to $10/$11 and execution
; transfers to it with an indirect JMP.
;
; Global:
; Called by UpdateGameplayEntities once for each of four gameplay entity
; slots. This provides the per-entity state machine used by those entries.
;
; Notes:
; X identifies the current entry using even offsets:
;
; $00,$02,$04,$06
;
; $B8,X is a byte offset into the pointer table, so valid state values are
; even:
;
; $00,$02,$04,$06,$08
;==============================================================================
DispatchGameplayEntityState:
ldy $B8,x
lda EntityStateTable,y
sta $10
lda EntityStateTable+1,y
sta $11
jmp ($0010)
;==============================================================================
; $D596-$D59F: Gameplay Entity State Table
;
; Local:
; Five 16-bit handlers indexed directly by the even state value stored in
; $B8,X.
;
; Global:
; Defines the per-entity state machine used by
; DispatchGameplayEntityState.
;
; Notes:
; States $04 and $06 deliberately share the same handler at $D5A1.
; State $08 targets $D5A0, one byte before that shared handler, suggesting
; another intentional overlapping entry point.
;==============================================================================
EntityStateTable:
.word $D8D5 ; state $00
.word $D885 ; state $02
.word $D5A1 ; state $04
.word $D5A1 ; state $06
.word EntityState08 ; state $08
;==============================================================================
; $D5A0: Entity State $08 Handler
;
; Local:
; Returns immediately.
;
; Global:
; The entity-state table uses this as the handler for state $08, effectively
; disabling further processing for that entity state.
;==============================================================================
EntityState08:
rts
;==============================================================================
; $D5A1-$D5B4: Entity State $04/$06 Secondary Dispatcher
;
; Local:
; Called for entity states $04 and $06 from EntityStateTable.
;
; First invokes the common entity helper at $D821. It then reads the
; secondary state value at $B9,X, doubles it, and uses the result as a byte
; offset into a four-entry pointer table at $D5B5.
;
; The selected address is stored in $10/$11 and execution transfers through
; an indirect JMP.
;
; Global:
; Provides a second-level state machine for gameplay entities whose primary
; state at $B8,X is $04 or $06.
;
; Notes:
; $B9,X behaves as an ordinal secondary state here. Unlike several earlier
; state tables, the value is explicitly multiplied by two before indexing
; the pointer table.
;==============================================================================
DispatchEntitySecondaryState:
jsr $D821
lda $B9,x
asl
tay
lda EntitySecondaryStateTable,y
sta $10
lda EntitySecondaryStateTable+1,y
sta $11
jmp ($0010)
;==============================================================================
; $D5B5-$D5BC: Entity Secondary-State Dispatch Table
;
; Local:
; Four handler addresses selected using ($B9,X * 2).
;
; Global:
; Defines the second-level state machine used by primary entity states
; $04 and $06.
;==============================================================================
EntitySecondaryStateTable:
.word EntitySecondaryState0 ; secondary state 0
.word EntitySecondaryState1 ; secondary state 1
.word EntitySecondaryState2 ; secondary state 2
.word EntitySecondaryState3 ; secondary state 3
;==============================================================================
; $D5BD-$D5D2: Secondary-State Coordinate Adjustment
;
; Local:
; Secondary state 2 enters at $D5BD and selects Y=$03.
; Secondary state 3 enters at $D5C1 and selects Y=$01.
;
; Both then read through the indirect pointer at $02/$03, add the
; entity-specific values at $C2,X/$C3,X, write the low-byte result back
; through the pointer, and store the high-byte result in $CC.
;
; Control then tail-jumps to $D5EB.
;
; Global:
; Shared movement/update arithmetic for two secondary entity states.
;
; Notes:
; $02/$03 were initialized by UpdateGameplayEntities and advance by four
; bytes per entity, so they identify the current entity's four-byte state
; structure.
;==============================================================================
EntitySecondaryState2:
ldy #$03
bne EntitySecondaryAdjust
EntitySecondaryState3:
ldy #$01
EntitySecondaryAdjust:
lda ($02),y
clc
adc $C2,x
sta ($02),y
lda #$00
adc $C3,x
sta $CC
jmp $D5EB
;==============================================================================
; $D5D3-$D5EE: Entity Secondary States $00/$01 Movement Arithmetic
;
; Local:
; Secondary state 0 enters at $D5D3 and selects Y=$03.
; Secondary state 1 enters at $D5D7 and selects Y=$01.
;
; Both subtract the entity-specific movement value at $C2,X from the
; structure addressed by $02/$03. Borrow is converted into a high-byte
; adjustment using $C3,X and stored in $CC.
;
; Afterward, $CC is decremented. A negative result returns immediately;
; otherwise execution continues into another secondary dispatcher at $D5EF.
;
; Global:
; Complements the addition paths at $D5BD/$D5C1. Together, the four
; secondary states provide signed movement along two coordinate components.
;
; Notes:
; The shared labels are non-local because multiple handler entry points
; converge into them.
;==============================================================================
EntitySecondaryState0:
ldy #$03
bne EntitySecondarySubtract
EntitySecondaryState1:
ldy #$01
EntitySecondarySubtract:
lda ($02),y
sec
sbc $C2,x
sta ($02),y
lda #$00
bcs EntitySecondarySubtractNoBorrow
lda #$01
EntitySecondarySubtractNoBorrow:
clc
adc $C3,x
sta $CC
;==============================================================================
; $D5EB-$D600: Apply Entity Movement and Dispatch Direction Substate
;
; Local:
; Shared continuation reached by both the addition and subtraction
; secondary-state handlers.
;
; Decrements $CC. If it becomes negative, returns. Otherwise reads the
; current entity secondary state from $B9,X, doubles it, and selects a
; handler from the table at $D601.
;
; Global:
; Final arithmetic stage for entity movement before dispatching into the
; direction/substate-specific update handlers.
;==============================================================================
EntitySecondaryApplyResult:
dec $CC
bpl EntityDirectionDispatch
rts
EntityDirectionDispatch:
lda $B9,x
asl
tay
lda EntityDirectionStateTable,y
sta $10
lda EntityDirectionStateTable+1,y
sta $11
jmp ($0010)
;==============================================================================
; $D601-$D608: Entity Direction-State Dispatch Table
;
; Local:
; Four handlers selected from the entity's secondary state at $B9,X.
;
; Notes:
; The pointer order deliberately differs from the physical order of the
; handlers in ROM.
;==============================================================================
EntityDirectionStateTable:
.word $D609
.word $D61D
.word $D60F
.word $D617
;==============================================================================
; $D609-$D622: Entity Direction-State Handlers
;
; Local:
; Four handlers selected by EntityDirectionStateTable.
;
; Each handler configures:
;
; $05 = +1 or -1
; Y = $00 or $02
;
; before entering the shared coordinate-update code at $D623.
;
; Global:
; Provides the four direction-specific movement modes for the per-entity
; state machine.
;
; Notes:
; The four states form two coordinate axes and two signed directions:
;
; $D609 -> $05=$FF, Y=$02
; $D60F -> $05=$01, Y=$02
; $D617 -> $05=$01, Y=$00
; $D61D -> $05=$FF, Y=$00
;
; Concrete direction names remain deferred until the coordinate axes are
; mapped to screen X/Y.
;==============================================================================
EntityDirectionD609:
lda #$FF
sta $05
bne EntityDirectionAxis2
EntityDirectionD60F:
lda #$01
sta $05
EntityDirectionAxis2:
ldy #$02
bne EntityDirectionMove
EntityDirectionD617:
lda #$01
sta $05
bne EntityDirectionAxis0
EntityDirectionD61D:
lda #$FF
sta $05
EntityDirectionAxis0:
ldy #$00
;==============================================================================
; $D623-$D69A: Shared Entity Direction Movement
;
; Local:
; Applies the signed step in $05 to either byte $00 or $02 of the entity
; structure referenced by $02/$03.
;
; Performs coordinate wrapping/bounds handling, updates a per-entity flag
; stored in $39+, checks tile/grid alignment, and when the entity reaches a
; specific location ($60,$70) changes the entity primary state to $02.
;
; Global:
; Shared movement implementation for the four handlers above.
;
; Notes:
; This section eventually rejoins the earlier movement arithmetic at $D5EB
; when the entity is not aligned to the grid.
;==============================================================================
EntityDirectionMove:
lda ($02),y
clc
adc $05
sta ($02),y
ldy #$00
lda ($02),y
cmp #$0A
bne @D637
lda #$BF
sta ($02),y
bne @D64B
@D637:
cmp #$C0
bne @D641
lda #$0B
sta ($02),y
bne @D64B
@D641:
cmp #$18
bcc @D64B
cmp #$A9
bcc @D64F
@D64B:
lda #$20
bne @D651
@D64F:
lda #$00
@D651:
sta $05
txa
lsr
tay
lda $39,y
and #$DF
ora $05
sta $39,y
ldy #$00
lda ($02),y
ldy #$02
ora ($02),y
and #$07
beq @D66E
jmp EntitySecondaryApplyResult
@D66E:
ldy #$00
lda ($02),y
cmp #$60
bne $D69B
ldy #$02
lda ($02),y
cmp #$70
bne $D69B
lda #$02
sta $B8,x
lda #$00
clc
sbc $04
and $88
sta $88
txa
lsr
tay
sta $05
lda $39,y
and #$FC
ora $05
sta $39,y
rts
;==============================================================================
; $D69B-$D6C6: Entity Movement Continuation / Target Selection
;
; Local:
; Continues entity processing after direction movement.
;
; Primary entity state $06 selects the fixed value $6F60 in $CD/$CE.
; Otherwise entity bitmasks in $04 are tested against $88 and $87.
;
; ($04 & $88) != 0 transfers to $D70A.
; ($04 & $87) != 0 transfers to $D6CF.
;
; If neither mask matches, the current entity index X selects a 16-bit value
; from EntityTargetTable and processing continues at $D778.
;
; Global:
; Chooses the next entity target/path-processing mode based on entity state
; and per-entity bitmasks.
;
; Notes:
; Cross-region branch targets remain raw until the destination blocks are
; decoded.
;==============================================================================
EntityMovementContinuation:
lda $B8,x
cmp #$06
bne @CheckEntityMasks
lda #$60
sta $CD
lda #$6F
sta $CE
jmp $D778
@CheckEntityMasks:
lda $04
and $88
bne $D70A
lda $04
and $87
bne $D6CF
txa
tay
lda EntityTargetTable,y
sta $CD
lda EntityTargetTable+1,y
sta $CE
jmp $D778
;==============================================================================
; $D6C7-$D6CE: Entity Target Table
;
; Local:
; Four 16-bit values indexed directly by the even entity offset X.
;
; Global:
; Supplies the $CD/$CE working target used by EntityMovementContinuation.
;
; Notes:
; Values are retained numerically until the consumer at $D778 is decoded.
;==============================================================================
EntityTargetTable:
.word $08A8
.word $0818
.word $D0A8
.word $D018
;==============================================================================
; $D6CF-$D6DD: Entity Target-Selection Dispatcher
;
; Local:
; Uses the current even entity index X directly as a byte offset into the
; four-entry pointer table at $D6DE.
;
; The selected handler address is stored in $10/$11 and execution transfers
; through an indirect JMP.
;
; Global:
; Entered from EntityMovementContinuation when the current entity's bit in
; $87 is set. Selects entity-specific target/path behavior.
;
; Notes:
; X is already constrained by the calling code to the even entity offsets:
;
; $00,$02,$04,$06
;
; Therefore no multiply-by-two is required here.
;==============================================================================
DispatchEntityTargetBehavior:
txa
tay
lda EntityTargetBehaviorTable,y
sta $10
lda EntityTargetBehaviorTable+1,y
sta $11
jmp ($0010)
;==============================================================================
; $D6DE-$D6E5: Entity Target-Behavior Dispatch Table
;
; Local:
; Four 16-bit handlers indexed directly by the even entity index X.
;
; Global:
; Selects different target/path calculations for each of the four gameplay
; entities.
;
; Notes:
; The fourth entry deliberately points backward to $D6E6, immediately after
; this table.
;==============================================================================
EntityTargetBehaviorTable:
.word EntityTargetBehaviorD73B ; X = $00
.word EntityTargetBehaviorD745 ; X = $02
.word EntityTargetBehaviorD764 ; X = $04
.word EntityTargetBehaviorD6E6 ; X = $06
;==============================================================================
; $D6E6-$D73A: Entity Target Behavior - Proximity/Direction Selection
;
; Local:
; Computes absolute differences between $1A/$2A and $1C/$2C.
; If either difference is at least $20, transfers to EntityTargetBehaviorD73B.
;
; Otherwise attempts up to five candidate directions. Each candidate is
; checked against the data addressed by $00/$01 and against the current
; entity direction/state at $B9,X.
;
; The selected value is stored into $B9,X before returning to the common
; entity movement result handler at $D5EB.
;
; Global:
; Fourth entry in EntityTargetBehaviorTable, selected for entity X=$06.
;
; Notes:
; Cross-block target $D73B remains raw until that handler is decoded.
;==============================================================================
EntityTargetBehaviorD6E6:
lda $1A
sec
sbc $2A
bcs @D6F4
sta $05
lda #$00
sec
sbc $05
@D6F4:
cmp #$20
bcs $D73B
lda $1C
sec
sbc $2C
bcs @D703
sta $05
lda #$00
sec
sbc $05
@D703:
cmp #$20
bcs $D73B
lda $4B
sta $05
lda #$05
sta $06
@D712:
dec $06
bne @D71A
lda #$02
bne @D736
@D71A:
inc $05
lda $05
and #$03
tay
lda ($00),y
and #$F8
bne @D712
lda $05
clc
adc #$02
and #$03
cmp $B9,x
beq @D712
lda $05
and #$03
@D736:
sta $B9,x
jmp EntitySecondaryApplyResult
;==============================================================================
; $D73B-$D777: Entity Target Calculation Handlers
;
; Local:
; Three handlers selected by EntityTargetBehaviorTable.
;
; $D73B:
; Uses the current $1A/$1C coordinate pair directly as the target.
;
; $D745:
; Uses $51 to select a signed coordinate offset from the table at $D75C,
; then adds that offset to $1A/$1C.
;
; $D764:
; Reflects/extrapolates the current coordinate pair relative to
; $1E/$20.
;
; All three produce the target coordinate in $CD/$CE and converge on the
; still-undecoded common processing at $D778.
;
; Global:
; Three of the four target strategies selected by
; EntityTargetBehaviorTable.
;
; Notes:
; Cross-block destination $D778 remains raw until the common handler is
; decoded separately.
;==============================================================================
;------------------------------------------------------------------------------
; $D73B: direct target
;------------------------------------------------------------------------------
EntityTargetBehaviorD73B:
lda $1A
sta $CD
lda $1C
sta $CE
bne $D778
;------------------------------------------------------------------------------
; $D745: direction-offset target
;------------------------------------------------------------------------------
EntityTargetBehaviorD745:
lda $51
asl
tay
lda EntityDirectionTargetOffsets,y
clc
adc $1A
sta $CD
lda EntityDirectionTargetOffsets+1,y
clc
adc $1C
sta $CE
jmp $D778
;==============================================================================
; $D75C-$D763: Direction Target Offset Table
;
; Local:
; Four signed coordinate offset pairs selected by ($51 * 2).
;
; As signed 8-bit values:
;
; 0 -> ( 0, -24 )
; 1 -> (-24, 0 )
; 2 -> ( 0, +24 )
; 3 -> (+24, 0 )
;
; Global:
; Used by EntityTargetBehaviorD745 to project a target position in the
; entity's current direction.
;==============================================================================
EntityDirectionTargetOffsets:
.byte $00,$E8
.byte $E8,$00
.byte $00,$18
.byte $18,$00
;------------------------------------------------------------------------------
; $D764: relative/reflected target
;
; Computes:
;
; $CD = $1A + ($1A - $1E)
; $CE = $1C + ($1C - $20)
;
; which algebraically corresponds to:
;
; $CD = 2*$1A - $1E
; $CE = 2*$1C - $20
;
; using 8-bit arithmetic.
;------------------------------------------------------------------------------
EntityTargetBehaviorD764:
lda $1A
sec
sbc $1E
clc
adc $1A
sta $CD
lda $1C
sec
sbc $20
clc
adc $1C
sta $CE
;==============================================================================
; $D778-$D79F: Common Entity Target Processing
;
; Local:
; Shared continuation used after an entity target coordinate has been
; calculated in $CD/$CE.
;
; Uses the entity primary state at $B8,X, minus $04, as a byte offset into
; a packed pointer table beginning at $D881. The selected pointer is stored
; in $0A/$0B and passed to BuildLegalDirectionList.
;
; The results returned in $0C/$0D determine the entity's next secondary
; state at $B9,X:
;
; $0C == $FF
; -> force state $02
;
; $0C != $FF and $0D == $FF
; -> store $0C into $B9,X and return
;
; $0C != $FF and $0D != $FF
; -> continue at $D7A0 for additional selection logic
;
; Global:
; Common target-evaluation stage reached by the entity target strategies
; at $D73B, $D745 and $D764, as well as fixed-target paths from earlier
; entity processing.
;
; Notes:
; $D881 remains undecoded and is therefore referenced by address.
;
; The branch to $D7A0 is intentionally kept raw because $D7A0 belongs to
; the next block and is an explicit continuation rather than an RTS.
;==============================================================================
EntityTargetCommon:
lda $B8,x
sec
sbc #$04
tay
lda $D881,y
sta $0A
lda $D882,y
sta $0B
jsr BuildLegalDirectionList
lda $0C
cmp #$FF
bne @D795
lda #$02
bne @D79D
@D795:
lda $0D
cmp #$FF
bne $D7A0
lda $0C
@D79D:
sta $B9,x
rts
;==============================================================================
; $D7A0-$D820: Select Entity Direction Toward Target
;
; Local:
; Entered from EntityTargetCommon when both $0C and $0D returned by
; BuildLegalDirectionList are valid.
;
; Computes absolute coordinate distances between target $CD/$CE and the
; current entity coordinates addressed through $02/$03.
;
; $05 accumulates direction-order information according to:
; - sign of the horizontal/vertical difference
; - which axis has the larger absolute distance
;
; The result is used to index a direction-priority table at $D871.
; Candidate directions are compared against the four values at $0C-$0F.
; When a candidate matches, execution branches back to the common direction
; store at $D79D.
;
; If the preferred candidates fail, the routine tests directions adjacent
; to the current secondary state $B9,X before eventually returning.
;
; Global:
; Alternate continuation of EntityTargetCommon. Selects the next movement
; direction for an entity based on its current position and target.
;
; Notes:
; $05 is a temporary direction-priority index.
; $06/$07 hold absolute coordinate differences.
; $08 is temporary storage during absolute-value calculations.
;
; $D871 is not decoded yet and remains a raw table reference.
;==============================================================================
SelectEntityDirectionTowardTarget:
ldy #$00
sty $05
lda $CD
sec
sbc ($02),y
bcs @D7B9
sta $08
lda #$00
sec
sbc $08
inc $05
inc $05
inc $05
inc $05
@D7B9:
sta $06
lda $CE
ldy #$02
sec
sbc ($02),y
bcs @D7D2
sta $08
lda #$00
sec
sbc $08
inc $05
inc $05
@D7D2:
sta $07
lda $06
cmp $07
bcs @D7DB
inc $05
@D7DB:
lda $05
asl
sta $05
lda #$02
sta $06
@D7E3:
ldy $05
lda $D871,y
ldy #$00
@D7EA:
cmp $000C,y
beq $D79D
iny
cpy #$04
bne @D7EA
inc $05
dec $06
bne @D7E3
lda $B9,x
sec
sbc #$01
and #$03
ldy #$00
@D802:
cmp $000C,y
beq $D79D
iny
cpy #$04
bne @D802
lda $B9,x
clc
adc #$01
and #$03
ldy #$00
@D814:
cmp $000C,y
beq $D79D
iny
cpy #$04
bne @D814
rts
;==============================================================================
; $D821-$D870: Select Entity Movement Parameters
;
; Local:
; Chooses the two-byte movement/step value stored at:
;
; $C2,X
; $C3,X
;
; for the current gameplay entity.
;
; Selection depends on:
;
; - primary entity state $B8,X
; - current entity coordinates through ($02)
; - entity bitmask $04 against $88
; - whether X is zero or identifies another entity
;
; Parameter pairs are copied from several global values:
;
; $B3/$B4
; $CA/$CB
; $AF/$B0
; $B1/$B2
;
; Primary state $06 instead installs the fixed pair:
;
; $00,$02
;
; Global:
; Called before the $B9,X secondary-state dispatcher at $D5A1. This chooses
; the movement increment used by the entity movement arithmetic.
;
; Notes:
; The coordinate pointer $02/$03 identifies the current entity's structure.
; X is the even entity slot index.
;==============================================================================
SelectEntityMovementSpeed:
lda $B8,x
cmp #$06
beq @D864
ldy #$02
lda ($02),y
cmp #$70
bne @D843
ldy #$00
lda ($02),y
cmp #$90
bcs @D83D
cmp #$30
bcs @D843
@D83D:
lda $B3
sta $C2,x
lda $B4
sta $C3,x
rts
@D843:
lda $04
and $88
bne @D85A
txa
bne @D854
lda $CA
sta $C2
lda $CB
sta $C3
rts
@D854:
lda $AF
sta $C2,x
lda $B0
sta $C3,x
rts
@D85A:
lda $B1
sta $C2,x
lda $B2
sta $C3,x
rts
@D864:
lda #$00
sta $C2,x
lda #$02
sta $C3,x
rts
;==============================================================================
; $D871-$D880: Entity Direction Priority Table
;
; Local:
; Sixteen direction values used by SelectEntityDirectionTowardTarget.
;
; $05 is derived from:
; - the sign of each target-coordinate difference,
; - which axis has the greater absolute distance.
;
; $05 is doubled before indexing this table, and the routine may examine
; the following entry as its second preferred candidate.
;
; Global:
; Defines the preferred entity movement directions for the different
; relative target-position quadrants/axis priorities.
;
; Notes:
; Values are internal direction numbers 0-3. Concrete direction names remain
; deferred until the internal direction numbering is fully mapped.
;
; The natural layout is eight pairs of preferred directions.
;==============================================================================
EntityDirectionPriorityTable:
.byte $03,$02
.byte $02,$03
.byte $03,$00
.byte $00,$03
.byte $01,$02
.byte $02,$01
.byte $01,$00
.byte $00,$01
;==============================================================================
; $D881-$D884: Entity Path/Map Pointer Table
;
; Local:
; Two 16-bit values selected according to entity primary state:
;
; state $04 -> $0000
; state $06 -> $082C
;
; The selected value is placed in $0A/$0B and passed to the helper at
; BuildLegalDirectionList.
;
; Global:
; Supplies state-dependent path/map parameters used by EntityTargetCommon.
;
; Notes:
; Keep the entries as literal 16-bit values until BuildLegalDirectionList
; is decoded and the semantic meaning of $0A/$0B is established.
;==============================================================================
EntityPathParameterTable:
.word $0000
.word $082C
;==============================================================================
; $D885-$D8D4: Entity Primary State $02 Handler
;==============================================================================
UpdateEntityState02:
lda #$00
tay
sta $05
lda #$80
sta $06
lda ($02),y
cmp #$60
beq UpdateEntityState02SecondaryPhase
bcc @D89C
dec $05
lda #$01
bne @D89E
@D89C:
lda #$03
@D89E:
sta $B9,x
ldy #$01
lda ($02),y
clc
adc $06
sta ($02),y
ldy #$00
lda ($02),y
adc $05
sta ($02),y
rts
;==============================================================================
; $D8B2-$D8D4: Entity State $02 Secondary Movement Phase
;==============================================================================
UpdateEntityState02SecondaryPhase:
lda #$00
sta $B9,x
ldy #$03
lda ($02),y
sec
sbc #$80
sta ($02),y
ldy #$02
lda ($02),y
sbc #$00
sta ($02),y
cmp #$58
beq @D8CC
rts
@D8CC:
lda #$01
sta $B9,x
lda #$04
sta $B8,x
rts
;==============================================================================
; $D8D5-$D913: Entity Primary State $00 Handler
;
; Local:
; Initializes working movement values:
;
; $05 = $00
; $06 = $80
;
; Then uses the entity secondary state at $B9,X as a byte offset into a
; two-entry table at $D8EC.
;
; The selected subhandler adjusts bytes 3/2 of the current entity structure
; through $02/$03. When the resulting high byte crosses defined thresholds,
; $B9,X advances to secondary state $02 or resets to $00.
;
; Global:
; Handler selected by EntityStateTable for primary entity state $00.
;
; Notes:
; Unlike the $D5A1 secondary dispatcher, $B9,X is already treated as a byte
; offset here. Valid values are $00 and $02.
;==============================================================================
EntityState00:
lda #$00
sta $05
lda #$80
sta $06
ldy $B9,x
lda EntityState00SubstateTable,y
sta $10
lda EntityState00SubstateTable+1,y
sta $11
jmp ($0010)
;==============================================================================
; $D8EC-$D8EF: Entity State $00 Secondary Table
;==============================================================================
EntityState00SubstateTable:
.word UpdateEntityState00MovingNegative
.word UpdateEntityState00MovingPositive
;------------------------------------------------------------------------------
; $D8F0: secondary state $00
;------------------------------------------------------------------------------
UpdateEntityState00MovingNegative:
dec $05
;------------------------------------------------------------------------------
; $D8F2: secondary state $02
;------------------------------------------------------------------------------
UpdateEntityState00MovingPositive:
ldy #$03
lda ($02),y
clc
adc $06
sta ($02),y
ldy #$02
lda ($02),y
adc $05
sta ($02),y
cmp #$69
bcs @D90B
lda #$02
bne @D911
@D90B:
cmp #$70
bcc @D913
lda #$00
@D911:
sta $B9,x
@D913:
rts
;==============================================================================
; $D914-$D943: Update Four Gameplay Entity States
;
; Local:
; Iterates the four gameplay entity slots using even X offsets:
;
; $00,$02,$04,$06
;
; Initializes the same per-entity working values used by the other entity
; update coordinator:
;
; $00 = $2F
; $01 = $02
; $02 = $1E
; $03 = $00
; $04 = $01
;
; Calls the entity dispatcher at $D944 for each slot.
;
; After each call:
;
; $00 += 4
; $02 += 4
; $04 <<= 1
; X += 2
;
; Global:
; Called from GameModeState06 during its per-frame update sequence.
; Processes a second entity-state machine for all four gameplay entities.
;
; Notes:
; The layout closely parallels UpdateGameplayEntities at $D557, but uses
; a different dispatcher and therefore a different behavior set.
;==============================================================================
UpdateEntityMovementStates:
ldx #$00
lda #$2F
sta $00
lda #$02
sta $01
lda #$1E
sta $02
lda #$00
sta $03
lda #$01
sta $04
@D92A:
jsr $D944
lda $00
clc
adc #$04
sta $00
lda $02
clc
adc #$04
sta $02
asl $04
inx
inx
cpx #$08
bne @D92A
rts
;==============================================================================
; $D944-$D952: Gameplay Entity Transition-State Dispatcher
;
; Local:
; Reads the current entity primary state from $B8,X and uses it directly as
; a byte offset into the packed pointer table at $D953.
;
; The selected 16-bit address is stored in $10/$11 and execution transfers
; to it through an indirect JMP.
;
; Global:
; Called by UpdateEntityMovementStates once for each of the four
; gameplay entity slots.
;
; Notes:
; X identifies the entity using even offsets:
;
; $00,$02,$04,$06
;
; $B8,X is itself an even byte offset into the pointer table:
;
; $00,$02,$04,$06,$08
;==============================================================================
DispatchEntityMovementState:
ldy $B8,x
lda EntityMovementStateHandlerTable,y
sta $10
lda EntityMovementStateHandlerTable+1,y
sta $11
jmp ($0010)
;==============================================================================
; $D953-$D95C: Gameplay Entity Transition-State Table
;
; Local:
; Five 16-bit handlers indexed directly by the entity primary state stored
; in $B8,X.
;
; Global:
; Defines the per-entity behavior used by
; DispatchEntityMovementState.
;
; Notes:
; States $00, $02, $04 and $08 all use the same handler at $D95D.
;
; State $06 reuses DispatchEntitySecondaryState at $D5A1.
;==============================================================================
EntityMovementStateHandlerTable:
.word EntityMovementStateNoOp ; state $00
.word EntityMovementStateNoOp ; state $02
.word EntityMovementStateNoOp ; state $04
.word DispatchEntitySecondaryState ; state $06
.word EntityMovementStateNoOp ; state $08
;==============================================================================
; $D95D: No-Op Entity Transition Handler
;
; Local:
; Returns immediately.
;
; Global:
; Used by EntityMovementStateHandlerTable for primary entity states
; $00, $02, $04 and $08.
;==============================================================================
EntityMovementStateNoOp:
rts
;==============================================================================
; $D95E-$D979: Check Entity Transition State
;
; Local:
; Scans the four primary entity-state entries at:
;
; $B8,$BA,$BC,$BE
;
; If any entity is in primary state $06, stores the current A value
; ($06 from the CMP) into $0608 and returns.
;
; If no entity is in state $06, tests $88. When $88 is nonzero, stores that
; value into $0609 before returning.
;
; Global:
; Called during normal gameplay processing. Exports entity/state conditions
; to RAM $0608/$0609 for use by other gameplay systems.
;
; Notes:
; $00 is initialized but not subsequently used inside this routine in the
; decoded path. Preserve the write because another overlapping entry or
; external expectation may depend on it.
;==============================================================================
UpdateEntityStatusFlags:
ldx #$00
stx $00
@D962:
lda $B8,x
cmp #$06
bne @D96C
sta $0608
rts
@D96C:
inx
inx
cpx #$08
bne @D962
lda $88
beq UpdatePlayerVerticalRegionFlag
sta $0609
rts
;==============================================================================
; $D97A-$D98D: Update Gameplay Range Flag
;==============================================================================
UpdatePlayerVerticalRegionFlag:
ldy #$00
lda $6A
cmp #$88
bcs @D988
iny
cmp #$42
bcs @D988
iny
@D988:
lda #$01
sta $060A,y
rts
;==============================================================================
; $D98E-$D9C5: Update Direction-Dependent Gameplay Value
;
; Local:
; Builds a base value from the current direction $51, then selects an
; additional offset based on coordinate alignment, tile state, and $B7.
;
; The resulting value is stored in $32.
;
; Notes:
; Address-derived labels preserve the original compact control flow.
;==============================================================================
UpdatePlayerDirectionAnimation:
lda $51
asl
clc
adc #$02
sta $00
lda $1A
ora $1C
and #$07
bne @D9AE
ldy $51
lda $022B,y
and #$F0
beq @D9AE
lda $B7
and #$03
jmp @D9B2
@D9AE:
inc $B7
lda $B7
@D9B2:
and #$07
cmp #$06
bcc @D9BC
lda #$01
bne @D9C3
@D9BC:
tay
lda PlayerDirectionAnimationOffsets,y
clc
adc $00
@D9C3:
sta $32
rts
;==============================================================================
; $D9C6-$D9CB: Direction-Value Offset Table
;
; Local:
; Six values indexed by $B7 & $07 when that result is less than six.
;
; Global:
; Provides the small offset added to the direction-derived base before the
; result is stored in $32.
;==============================================================================
PlayerDirectionAnimationOffsets:
.byte $00,$00,$00,$01,$01,$00
;==============================================================================
; $D9CC-$D9EA: Update Four Entity Display/State Values
;
; Local:
; Initializes:
;
; X = $00
; $00 = $00 or $01 depending on frame-counter bit 3
; $01 = $01
;
; Then iterates the four entity slots:
;
; X = $00,$02,$04,$06
;
; For each entity:
; - Calls the dispatcher at $D9EB.
; - Shifts $01 left once, producing per-entity masks:
;
; $01,$02,$04,$08
;
; - Advances X by two.
;
; Global:
; Called from several game-mode states and during normal gameplay. Provides
; per-frame updates for all four gameplay entities, with an alternating
; phase selected from frame-counter bit 3.
;
; Notes:
; $4B is the NMI-driven frame counter.
; $00 is a two-phase animation/control value.
; $01 is the current entity bitmask.
;==============================================================================
UpdateEntitySpriteStates:
lda #$00
tax
sta $00
lda $4B
and #$08
beq @D9DC
lda #$01
sta $00
@D9DC:
lda #$01
sta $01
@D9E0:
jsr $D9EB
asl $01
inx
inx
cpx #$08
bne @D9E0
rts
;==============================================================================
; $D9EB-$D9F9: Per-Entity Display/State Dispatcher
;==============================================================================
DispatchEntitySpriteState:
ldy $B8,x
lda EntitySpriteStateHandlerTable,y
sta $10
lda EntitySpriteStateHandlerTable+1,y
sta $11
jmp ($0010)
;==============================================================================
; $D9FA-$DA03: Entity Display/State Table
;==============================================================================
EntitySpriteStateHandlerTable:
.word UpdateNormalEntitySprite ; state $00
.word UpdateNormalEntitySprite ; state $02
.word UpdateNormalEntitySprite ; state $04
.word UpdateSpecialEntitySprite ; state $06
.word $D9EA ; state $08 -> shared RTS
;==============================================================================
; $DA04-$DA3F: Entity Display/State Value Handlers
;
; Local:
; Three entry points used by EntitySpriteStateHandlerTable:
;
; $DA21 primary states $00/$02/$04
; $DA2D primary state $06
;
; $DA04 is shared calculation code entered by those handlers.
;
; Each path computes a value from the entity secondary state $B9,X, combines
; it with a base value, and stores the final result into one of $33-$36
; according to the current entity index X.
;
; Global:
; Produces per-entity display/state values during UpdateEntitySpriteStates.
;
; Notes:
; X is an even entity index ($00,$02,$04,$06). TXA/LSR converts this into
; the ordinal entity index 0-3 for the final store at $33,Y.
;==============================================================================
;==============================================================================
; $DA04-$DA3F: Entity Display/State Value Handlers
;
; Local:
; Shared calculations used by the per-entity display/state dispatcher.
;
; Global:
; UpdateNormalEntitySprite handles primary states $00/$02/$04.
; UpdateSpecialEntitySprite handles primary state $06.
;
; Notes:
; Non-local labels are intentional because multiple handler entry points
; branch backward into shared code.
;==============================================================================
;------------------------------------------------------------------------------
; $DA04
;------------------------------------------------------------------------------
CalculateEntitySpriteValue:
lda $B9,x
sta $03
lda #$0A
;------------------------------------------------------------------------------
; $DA0A
;------------------------------------------------------------------------------
CalculateEntitySpriteValueLoop:
dec $03
bmi AddEntitySpriteBase
clc
adc #$02
bne CalculateEntitySpriteValueLoop
;------------------------------------------------------------------------------
; $DA13
;------------------------------------------------------------------------------
AddEntitySpriteBase:
clc
adc $00
sta $02
;------------------------------------------------------------------------------
; $DA18
;------------------------------------------------------------------------------
StoreEntitySpriteValue:
txa
lsr
tay
lda $02
sta $0033,y
rts
;------------------------------------------------------------------------------
; $DA21
; Primary entity states $00/$02/$04.
;------------------------------------------------------------------------------
UpdateNormalEntitySprite:
lda $01
and $88
beq CalculateEntitySpriteValue
lda #$1E
sta $02
bne AddEntitySpriteBase
;------------------------------------------------------------------------------
; $DA2D
; Primary entity state $06.
;------------------------------------------------------------------------------
UpdateSpecialEntitySprite:
lda $B9,x
sta $03
lda #$20
;------------------------------------------------------------------------------
; $DA33
;------------------------------------------------------------------------------
UpdateSpecialEntitySpriteLoop:
dec $03
bmi UpdateSpecialEntitySpriteDone
clc
adc #$01
bne UpdateSpecialEntitySpriteLoop
;------------------------------------------------------------------------------
; $DA3C
;------------------------------------------------------------------------------
UpdateSpecialEntitySpriteDone:
sta $02
bmi StoreEntitySpriteValue
;==============================================================================
; $DA40-$DAF0: Gameplay Entity Collision / Slot Exchange
;
; Local:
; Copies $24 bytes from zero-page $1A-$3D to RAM $0274-$0297.
;
; Then initializes an indirect pointer to $0284 and scans entity structures
; while testing them against the primary coordinate pair at $0274/$0276.
;
; For each enabled entity, computes absolute coordinate differences. If both
; axes are within $19 and their combined distance is below $10, exchanges
; coordinate and associated state fields between the primary slot and the
; selected entity slot.
;
; If no qualifying overlap is found, advances the structure pointer backward
; by four bytes, shifts the per-entity mask, and continues.
;
; Global:
; Called in several game-mode states and during normal gameplay. Maintains
; the mirrored gameplay/object state in $0274+ and handles close-overlap
; exchanges between entity slots.
;
; Notes:
; $00/$01 form a temporary pointer, initially $0284.
; $02 counts entity slots.
; $03 is a bitmask, initially $08.
; $04-$06 are temporary coordinate/state values during a swap.
;
; $DAF1 is a separate routine and is not included in this block.
;==============================================================================
CheckEntityCollisions:
ldx #$23
@CopyWorkingState:
lda $1A,x
sta $0274,x
dex
bpl @CopyWorkingState
lda #$84
sta $00
lda #$02
sta $01
lda #$08
sta $03
lda #$03
sta $02
@CheckNextEntity:
lda $03
and $88
bne @AdvanceEntity
ldy #$00
lda $0274
cmp ($00),y
bcs @FirstAxisReverse
lda ($00),y
sec
sbc $0274
bcs @CheckFirstAxis
@FirstAxisReverse:
sbc ($00),y
@CheckFirstAxis:
cmp #$19
bcs @AdvanceEntity
sta $04
ldy #$02
lda $0276
cmp ($00),y
bcs @SecondAxisReverse
lda ($00),y
sec
sbc $0276
bcs @CheckSecondAxis
@SecondAxisReverse:
sbc ($00),y
@CheckSecondAxis:
cmp #$19
bcs @AdvanceEntity
adc $04
cmp #$10
bcs @AdvanceEntity
; Save the primary slot coordinates/state.
lda $0274
sta $03
lda $0276
sta $04
lda $028C
sta $05
lda $0292
sta $06
; Copy selected entity coordinates into the primary slot.
ldy #$00
lda ($00),y
sta $0274
lda $03
sta ($00),y
ldy #$02
lda ($00),y
sta $0276
lda $04
sta ($00),y
; Exchange the parallel status values.
ldx $02
lda $028D,x
sta $028C
lda $05
sta $028D,x
lda $0293,x
sta $0292
lda $06
sta $0293,x
jmp $DAEB
@AdvanceEntity:
lda $00
sec
sbc #$04
sta $00
lsr $03
dec $02
bmi @NoCollision
jmp @CheckNextEntity
@NoCollision:
jsr $E1DA
jmp $DAF1
;==============================================================================
; $DAF1-$DBEE: Rebuild Gameplay Entity Render Data
;
; Local:
; Builds four-byte render/object records in page $07 from mirrored gameplay
; state beginning at $0274.
;
; Working variables:
;
; $00/$01 = source pointer, initially $0274
; $02/$03 = destination pointer, initially $0700
; $04 = source-group index
; $05 = record/lookup index
; $06 = temporary lookup index
;
; X advances through even entity offsets $00,$02,$04,$06.
;
; Encoded state values at $028C+ determine which lookup-table path is used
; to construct bytes 1 and 2 of each four-byte output record.
;
; Global:
; Called by several gameplay paths to rebuild the page-$07 representation
; used by the rendering/object pipeline.
;
; Notes:
; Several ROM tables referenced here remain undecoded:
;
; $DBEE
; $DCEE
; $DD22
; EntitySpriteAttributeTable0
; EntitySpriteCoordinateOffsets
; $DE57
;
; $DBEE is particularly interesting because it is also the RTS terminating
; this routine. The ROM intentionally uses that address as both executable
; code and the base of a later indexed lookup.
;==============================================================================
BuildEntitySpriteBuffer:
lda #$74
sta $00
lda #$02
sta $01
lda #$00
sta $02
lda #$07
sta $03
lda #$00
sta $04
;------------------------------------------------------------------------------
; $DB05
; Start/restart one source group.
;------------------------------------------------------------------------------
BuildSpriteBufferNextGroup:
lda #$00
sta $05
tax
;------------------------------------------------------------------------------
; $DB0A
; Build one four-byte render record.
;------------------------------------------------------------------------------
BuildSpriteBufferNextSprite:
ldy #$02
lda ($00),y
bne AdjustSpriteY
lda #$FF
bne StoreSpriteY
;------------------------------------------------------------------------------
; $DB14
;------------------------------------------------------------------------------
AdjustSpriteY:
clc
adc EntitySpriteCoordinateOffsets,x
;------------------------------------------------------------------------------
; $DB18
;------------------------------------------------------------------------------
StoreSpriteY:
ldy #$00
sta ($02),y
ldy $04
lda $028C,y
asl
rol
rol
and #$03
beq BuildSpriteClass0
cmp #$02
beq BuildSpriteClass2
bcc BuildSpriteClass1
;------------------------------------------------------------------------------
; $DB2E
; Status-class path.
;------------------------------------------------------------------------------
lda $028C,y
asl
asl
clc
adc $05
tay
sta $06
lda $DD22,y
ldy #$01
sta ($02),y
ldy $04
lda $0292,y
ldy $06
ora EntitySpriteCoordinateOffsets,y
ldy #$02
sta ($02),y
jmp BuildSpriteX
;------------------------------------------------------------------------------
; $DB51
;------------------------------------------------------------------------------
BuildSpriteClass0:
lda $028C,y
asl
asl
clc
adc $05
tay
sta $06
lda $DBEE,y
ldy #$01
sta ($02),y
ldy $04
lda $0292,y
ldy $06
ora $DD22,y
ldy #$02
sta ($02),y
jmp BuildSpriteX
;------------------------------------------------------------------------------
; $DB74
;------------------------------------------------------------------------------
BuildSpriteClass1:
lda $028C,y
asl
asl
clc
adc $05
tay
sta $06
lda $DCEE,y
ldy #$01
sta ($02),y
ldy $04
lda $0292,y
ldy $06
ora $DE22,y
ldy #$02
sta ($02),y
jmp BuildSpriteX
;------------------------------------------------------------------------------
; $DB97
;------------------------------------------------------------------------------
BuildSpriteClass2:
lda $028C,y
asl
asl
clc
adc $05
tay
sta $06
lda $DD22,y
ldy #$01
sta ($02),y
ldy $04
lda $0292,y
ldy $06
ora EntitySpriteCoordinateOffsets,y
ldy #$02
sta ($02),y
;------------------------------------------------------------------------------
; $DBB7
; Construct byte 3 of the render record.
;------------------------------------------------------------------------------
BuildSpriteX:
ldy #$00
lda ($00),y
bne AdjustSpriteX
lda #$FF
bne StoreSpriteX
;------------------------------------------------------------------------------
; $DBC0
;------------------------------------------------------------------------------
AdjustSpriteX:
clc
adc EntitySpriteCoordinateOffsets+1,x
;------------------------------------------------------------------------------
; $DBC4
;------------------------------------------------------------------------------
StoreSpriteX:
ldy #$03
sta ($02),y
; Advance destination by one 4-byte record.
lda $02
clc
adc #$04
sta $02
inc $05
inx
inx
cpx #$08
beq BuildSpriteBufferNextSource
jmp BuildSpriteBufferNextSprite
BuildSpriteBufferNextSource:
lda $00
clc
adc #$04
sta $00
inc $04
lda $04
cmp #$06
beq BuildSpriteBufferDone
jmp BuildSpriteBufferNextGroup
BuildSpriteBufferDone:
rts
;==============================================================================
; $DBEE-$DC3F: Gameplay Entity Render Lookup Data
;
; Local:
; Lookup data used by BuildEntitySpriteBuffer.
;
; The routine constructs an index from:
;
; ($028C,Y * 4) + $05
;
; and uses that value to index this region for render-facing object data.
;
; Global:
; Supplies tile/state values used when rebuilding the four-byte page-$07
; entity records.
;
; Notes:
; The region contains strongly patterned groups of repeated values, but the
; complete set of consumers has not yet been decoded. Keep this as one
; contiguous table during the first pass rather than assigning speculative
; semantic subdivisions.
;==============================================================================
EntitySpriteTileTable0:
; $DBEE-$DBFD
.byte $4C,$4C,$4C,$4C
.byte $00,$00,$00,$00
.byte $04,$04,$03,$03
.byte $08,$08,$07,$07
; $DBFE-$DC0D
.byte $02,$01,$02,$01
.byte $06,$05,$06,$05
.byte $03,$03,$04,$04
.byte $07,$07,$08,$08
; $DC0E-$DC1D
.byte $01,$02,$01,$02
.byte $05,$06,$05,$06
.byte $18,$18,$19,$19
.byte $18,$18,$1A,$1A
; $DC1E-$DC2D
.byte $1B,$1C,$1D,$1F
.byte $1B,$1C,$1E,$20
.byte $21,$21,$22,$22
.byte $21,$21,$23,$23
; $DC2E-$DC3D
.byte $1C,$1B,$1F,$1D
.byte $1C,$1B,$20,$1E
.byte $00,$00,$00,$00
.byte $09,$09,$0A,$0A
; $DC3E-$DC3F
.byte $0B,$0B
;==============================================================================
; $DC40-$DCED: Gameplay Entity Render Lookup Data, Continued
;
; Local:
; Additional render/tile lookup values associated with the table beginning
; at $DBEE.
;
; The data is strongly grouped into repeated pairs and four-byte patterns,
; consistent with direction/animation variants used by the four-byte entity
; render records.
;
; Global:
; Part of the graphics-facing lookup data used when gameplay state is
; converted into page-$07 render records.
;
; Notes:
; Keep these bytes as a contiguous data block for now. $DCEE is separately
; referenced by BuildEntitySpriteBuffer and therefore marks the next
; known lookup-table boundary.
;==============================================================================
EntityRenderLookupDC40:
; $DC40-$DC4F
.byte $0C,$0C,$4C,$4C
.byte $0D,$0D,$4C,$4C
.byte $0E,$0E,$4C,$4C
.byte $0F,$0F,$4C,$4C
; $DC50-$DC5F
.byte $10,$10,$4C,$4C
.byte $11,$11,$4C,$4C
.byte $12,$12,$4C,$4C
.byte $13,$13,$14,$15
; $DC60-$DC6F
.byte $16,$17,$4C,$4C
.byte $4C,$4C,$24,$24
.byte $25,$25,$24,$24
.byte $26,$26,$27,$27
; $DC70-$DC7F
.byte $4C,$4C,$28,$29
.byte $2A,$2B,$2C,$2C
.byte $2D,$2D,$29,$28
.byte $2B,$2A,$90,$91
; $DC80-$DC8F
.byte $92,$93,$94,$95
.byte $96,$97,$98,$99
.byte $9A,$9B,$9C,$9D
.byte $9E,$9F,$A0,$A1
; $DC90-$DC9F
.byte $A2,$A3,$A4,$A5
.byte $A6,$A7,$A8,$A9
.byte $AA,$AB,$AC,$AD
.byte $AE,$AF,$2E,$2F
; $DCA0-$DCAF
.byte $30,$31,$32,$2F
.byte $33,$31,$34,$2F
.byte $35,$31,$36,$2F
.byte $37,$31,$38,$2F
; $DCB0-$DCBF
.byte $39,$31,$3A,$2F
.byte $3B,$31,$3C,$2F
.byte $3D,$31,$3E,$3F
.byte $40,$41,$42,$3F
; $DCC0-$DCCF
.byte $43,$41,$44,$45
.byte $46,$47,$48,$45
.byte $49,$47,$4A,$45
.byte $4B,$47,$4D,$4E
; $DCD0-$DCDF
.byte $4F,$50,$4E,$4D
.byte $50,$4F,$4F,$50
.byte $4D,$4E,$50,$4F
.byte $4E,$4D,$4D,$4E
; $DCE0-$DCED
.byte $4F,$51,$4E,$52
.byte $53,$54,$4F,$51
.byte $4D,$4E,$53,$54
.byte $4E,$52
;==============================================================================
; $DCEE-$DD21: Gameplay Entity Render Lookup Table
;
; Local:
; Lookup values used by BuildEntitySpriteBuffer in the status-class
; path beginning at $DB74.
;
; The index is constructed from:
;
; ($028C,Y * 4) + $05
;
; and the selected byte is written as byte 1 of the generated page-$07
; render record.
;
; Global:
; Supplies graphics/tile values for one class of gameplay entity render
; states.
;
; Notes:
; The repeated $4C values and four-byte groupings strongly suggest
; animation/direction variants. Keep the table as raw values during this
; first pass.
;
; $DD22 is separately referenced by the render-data builder and therefore
; marks the next lookup-table boundary.
;==============================================================================
EntitySpriteTileTable2:
; $DCEE-$DCFD
.byte $55,$4C,$56,$4C
.byte $56,$4C,$55,$4C
.byte $4C,$57,$4C,$58
.byte $4C,$59,$4C,$5A
; $DCFE-$DD0D
.byte $4C,$5B,$4C,$5C
.byte $4C,$4C,$4C,$5D
.byte $18,$18,$19,$5E
.byte $60,$61,$19,$5E
; $DD0E-$DD1D
.byte $1B,$1C,$1D,$62
.byte $1B,$1C,$1E,$63
.byte $64,$65,$66,$67
.byte $64,$65,$68,$69
; $DD1E-$DD21
.byte $6A,$6B,$4C,$4C
;==============================================================================
; $DD22-$DE21: Gameplay Entity Render Attribute Lookup
;
; Local:
; 256-byte lookup table used by BuildEntitySpriteBuffer.
;
; Entries are predominantly:
;
; $00
; $40
; $80
; $C0
;
; These values occupy bits 6-7 and are ORed with the corresponding value
; from $0292+ before being written as byte 2 of a page-$07 four-byte render
; record.
;
; Global:
; Supplies upper-bit render attributes for gameplay entities across their
; various states, animation phases and directional variants.
;
; Notes:
; The table is exactly $100 bytes long:
;
; $DD22-$DE21
;
; $DE22 is independently referenced by another render-data path and therefore
; marks the next table boundary.
;==============================================================================
EntitySpriteAttributeTable0:
; $DD22-$DD31
.byte $00,$00,$00,$00
.byte $00,$40,$80,$C0
.byte $80,$C0,$80,$C0
.byte $80,$C0,$80,$C0
; $DD32-$DD41
.byte $00,$00,$80,$80
.byte $00,$00,$80,$80
.byte $00,$40,$00,$40
.byte $00,$40,$00,$40
; $DD42-$DD51
.byte $40,$40,$C0,$C0
.byte $40,$40,$C0,$C0
.byte $00,$40,$00,$40
.byte $00,$40,$00,$40
; $DD52-$DD61
.byte $00,$00,$00,$00
.byte $00,$00,$00,$00
.byte $00,$40,$00,$40
.byte $00,$40,$00,$40
; $DD62-$DD71
.byte $40,$40,$40,$40
.byte $40,$40,$40,$40
.byte $00,$40,$80,$C0
.byte $00,$40,$00,$40
; $DD72-$DD81
.byte $00,$40,$00,$40
.byte $00,$00,$00,$40
.byte $00,$00,$00,$40
.byte $00,$00,$00,$40
; $DD82-$DD91
.byte $00,$00,$00,$40
.byte $00,$00,$00,$40
.byte $00,$00,$00,$40
.byte $00,$00,$00,$40
; $DD92-$DDA1
.byte $00,$00,$00,$00
.byte $00,$00,$00,$00
.byte $00,$40,$00,$40
.byte $00,$40,$00,$40
; $DDA2-$DDB1
.byte $00,$40,$00,$00
.byte $00,$00,$00,$00
.byte $00,$40,$00,$40
.byte $40,$40,$40,$40
; $DDB2-$DDC1
.byte $00,$00,$00,$00
.byte $00,$00,$00,$00
.byte $00,$00,$00,$00
.byte $00,$00,$00,$00
; $DDC2-$DDD1
.byte $00,$00,$00,$00
.byte $00,$00,$00,$00
.byte $00,$00,$00,$00
.byte $00,$00,$00,$00
; $DDD2-$DDE1
.byte $00,$00,$00,$00
.byte $00,$00,$00,$00
.byte $00,$00,$00,$00
.byte $00,$00,$00,$00
; $DDE2-$DDF1
.byte $00,$00,$00,$00
.byte $00,$00,$00,$00
.byte $00,$00,$00,$00
.byte $00,$00,$00,$00
; $DDF2-$DE01
.byte $00,$00,$00,$00
.byte $00,$00,$00,$00
.byte $00,$00,$00,$00
.byte $00,$00,$00,$00
; $DE02-$DE11
.byte $00,$00,$00,$00
.byte $40,$40,$00,$40
.byte $80,$00,$80,$80
.byte $00,$C0,$C0,$C0
; $DE12-$DE21
.byte $00,$00,$00,$00
.byte $40,$00,$00,$00
.byte $80,$80,$80,$80
.byte $80,$80,$C0,$80
;==============================================================================
; $DE22-$DE55: Secondary Gameplay Entity Render Attribute Lookup
;
; Local:
; Lookup data used by BuildEntitySpriteBuffer in the path beginning
; at $DB74.
;
; Values from this table are ORed with the corresponding state value from
; $0292+ and written as byte 2 of a four-byte page-$07 render record.
;
; Global:
; Supplies upper-bit render attributes for one class of gameplay entity
; render states.
;
; Notes:
; As with the larger table at $DD22, the significant nonzero values occupy
; the upper bits, primarily $40 and $80.
;
; EntitySpriteCoordinateOffsets is separately referenced as another
; lookup-table base and therefore marks the next known boundary.
;==============================================================================
EntitySpriteAttributeTable1:
; $DE22-$DE31
.byte $00,$00,$00,$00
.byte $80,$00,$80,$00
.byte $00,$00,$00,$00
.byte $00,$00,$00,$00
; $DE32-$DE41
.byte $00,$00,$00,$00
.byte $00,$00,$00,$00
.byte $00,$40,$00,$00
.byte $00,$00,$00,$00
; $DE42-$DE51
.byte $00,$00,$00,$00
.byte $00,$00,$00,$00
.byte $00,$00,$00,$00
.byte $00,$00,$00,$00
; $DE52-$DE55
.byte $00,$00,$00,$00
;==============================================================================
; $DE56-$DE5D: Entity Render Coordinate Adjustment Table
;
; Local:
; Eight coordinate-adjustment bytes used by
; BuildEntitySpriteBuffer.
;
; X takes even values $00,$02,$04,$06, and the routine reads:
;
; EntitySpriteCoordinateOffsets,X
; EntitySpriteCoordinateOffsets+1,X
;
; giving four byte pairs.
;
; Global:
; Adjusts source coordinates when building page-$07 four-byte render
; records.
;==============================================================================
EntitySpriteCoordinateOffsets:
.byte $03,$F4
.byte $03,$FC
.byte $0B,$F4
.byte $0B,$FC
;==============================================================================
; $DE5E-$DE7D: Periodic Four-Entry State Update
;
; Local:
; Once every 16 frames, updates the four entries at $6C-$6F:
;
; $07 -> remains $07
; $01 -> becomes $02
; anything else -> becomes $01
;==============================================================================
UpdatePeriodicFourEntryState:
lda $4B
and #$0F
beq @DE65
rts
@DE65:
tax
@DE66:
lda $6C,x
cmp #$07
beq @DE76
cmp #$01
bne @DE74
lda #$02
bne @DE76
@DE74:
lda #$01
@DE76:
sta $6C,x
inx
cpx #$04
bne @DE66
rts
;==============================================================================
; $DE7E-$DF6B: Gameplay PPU / Display Update Processor
;
; Local:
; Processes several gameplay display-update buffers and writes their data
; directly to the PPU.
;
; Handles:
; - Six-byte update block at $023F using destination $026B/$026C.
; - Six-byte update block at $0245 using destination $0272/$0273.
; - Three-byte periodic update at $026F-$0271.
; - Four state values at $6C-$6F written to PPU addresses in the table
; beginning at GameplayStatusPpuAddressTable.
; - Generic PPU command stream beginning at $024B.
;
; Global:
; Central gameplay display-update routine.
;
; Notes:
; UpdateAnimatedGameplayPpuFields at $DF15 is a shared entry point reached from
; several earlier control-flow paths and is therefore a global label rather
; than a ca65 cheap-local label.
;==============================================================================
FlushGameplayPpuUpdates:
lda $48
beq @DE85
jmp UpdateAnimatedGameplayPpuFields
;------------------------------------------------------------------------------
; $DE85
; Process the update block beginning at $023F.
;------------------------------------------------------------------------------
@DE85:
lda $023F
cmp #$FF
beq @DEB5
lda PPUSTATUS
lda $026B
sta PPUADDR
lda $026C
sta PPUADDR
ldy #$00
@DE9B:
lda $023F,y
sta PPUDATA
iny
cpy #$06
bne @DE9B
lda #$30
sta PPUDATA
lda #$FF
sta $023F
bne @DEE2
;------------------------------------------------------------------------------
; $DEB5
; Process the update block beginning at $0245.
;------------------------------------------------------------------------------
@DEB5:
lda $0245
cmp #$FF
beq @DEE2
lda PPUSTATUS
lda $0272
sta PPUADDR
lda $0273
sta PPUADDR
ldy #$00
@DECB:
lda $0245,y
sta PPUDATA
iny
cpy #$06
bne @DECB
lda #$30
sta PPUDATA
lda #$FF
sta $0245
;------------------------------------------------------------------------------
; $DEE2
; Periodic three-byte display update.
;------------------------------------------------------------------------------
@DEE2:
lda $4B
and #$07
bne UpdateAnimatedGameplayPpuFields
lda PPUSTATUS
lda $026D
sta PPUADDR
lda $026E
sta PPUADDR
ldx #$00
lda $4B
and #$18
beq @DF05
@DEFB:
lda $026F,x
sta PPUDATA
inx
cpx #$03
bne @DEFB
beq UpdateAnimatedGameplayPpuFields
@DF05:
lda #$20
@DF07:
sta PPUDATA
inx
cpx #$03
bne @DF07
;==============================================================================
; $DF15: Shared frame-dependent display update entry
;==============================================================================
UpdateAnimatedGameplayPpuFields:
lda $4B
and #$01
beq @DF42
ldy #$00
ldx #$00
lda $46
and $47
beq @DF27
ldy #$08
@DF27:
lda PPUSTATUS
lda GameplayStatusPpuAddressTable,y
sta PPUADDR
lda GameplayStatusPpuAddressTable+1,y
sta PPUADDR
lda $6C,x
sta PPUDATA
iny
iny
inx
cpx #$04
bne @DF27
;------------------------------------------------------------------------------
; $DF42
; Process the $024B command stream.
;------------------------------------------------------------------------------
@DF42:
lda PPUSTATUS
ldy #$FF
@DF46:
iny
lda $024B,y
cmp #$FF
beq @DF66
sta PPUADDR
iny
lda $024B,y
sta PPUADDR
@DF55:
iny
lda $024B,y
beq @DF46
cmp #$FF
beq @DF66
sta PPUDATA
bne @DF55
@DF66:
sta $024B
rts
;==============================================================================
; $DF6C-$DF7B: Gameplay PPU Address Table
;
; Local:
; Eight PPU addresses stored in high-byte/low-byte order.
;
; When ($46 & $47) == 0, the first four addresses are used.
; Otherwise the second four addresses are used.
;
; Values from $6C-$6F are written to the selected addresses.
;
; Notes:
; These must remain .byte pairs. Using .word would reverse each address
; because ca65 emits words little-endian.
;==============================================================================
GameplayStatusPpuAddressTable:
.byte $20,$B4
.byte $20,$A2
.byte $22,$D4
.byte $22,$C2
.byte $28,$B4
.byte $28,$A2
.byte $2A,$D4
.byte $2A,$C2
;==============================================================================
; $DF7C-$E042: Gameplay Tile / State Update
;
; Local:
; Processes selected values of $022A, updates $6C-$6F, emits a command into
; the $024B display-command buffer, updates $6A and related gameplay state,
; and may transfer control to $E0E6.
;
; Global:
; Called during the normal gameplay frame-update chain.
;
; Notes:
; $E043 and $E044 are lookup-table bases used near the end of this routine.
; Those bytes are data and begin immediately after the final JMP at $E040.
;==============================================================================
UpdateGameplayTileState:
ldx #$00
lda $022A
cmp #$09
beq $DFBB
inx
inx
cmp #$03
beq $DFBB
cmp #$01
beq @DF94
cmp #$02
beq @DF94
rts
@DF94:
jsr $E063
lda $1A
sta $02
lda $1C
sta $03
jsr $E263
ldx #$00
lda $03
and #$07
beq @DFAD
inx
inx
@DFAD:
lda $02
and #$0F
cmp #$04
beq @DFB6
inx
@DFB6:
lda #$07
sta $6C,x
@DFBB:
ldx #$04
lda #$00
sta $D5
sta $D6
lda $1A
sta $02
lda $1C
sta $03
jsr $E263
ldy #$FF
@DFCE:
iny
lda $024B,y
cmp #$FF
bne @DFCE
tya
beq @DFDC
lda #$00
sta $024B,y
iny
@DFDC:
lda $03
sta $024B,y
iny
lda $02
sta $024B,y
iny
lda $E043,x
sta $024B,y
iny
lda #$FF
sta $024B,y
lda #$07
sta $022A
lda $E044,x
sta $DC
dec $6A
bne @E012
lda #$0C
sta $3F
lda #$00
sta $87
lda #$48
sta $4C
@E012:
lda $6A
cmp #$37
beq @E01C
cmp #$86
bne $E036
@E01C:
lda #$0A
sta $D7
lda #$3C
sta $D8
ldy $93
lda GameplayStateValueLookup,y
sta $37
lda #$03
sta $3D
lda #$60
sta $2E
lda #$80
sta $30
lda $6A
and #$01
tay
lda #$01
sta $0604,y
jmp $E0E6
;==============================================================================
; $E043-$E048: Gameplay Tile-State Lookup
;
; Local:
; Used by UpdateGameplayTileState through overlapping indexed reads at
; $E043,X and $E044,X.
;==============================================================================
TileEventDisplayLookup:
.byte $08,$01,$07,$01,$07,$05
;==============================================================================
; $E049-$E05A: Activate First Available State Slot
;
; Local:
; Scans the three even-indexed entries:
;
; $BA,$BC,$BE
;
; for the first zero value. If one is found, changes it to $02 and returns.
; If all three are already active/nonzero, returns without modification.
;==============================================================================
ActivateNextStateSlotE049:
ldx #$00
@E04B:
lda $BA,x
bne @E054
lda #$02
sta $BA,x
rts
@E054:
inx
inx
cpx #$06
bne @E04B
rts
;==============================================================================
; $E05B-$E062: Gameplay State Value Lookup
;
; Local:
; Eight sequential values indexed by $93 and copied into $37 during
; gameplay-state transitions.
;==============================================================================
GameplayStateValueLookup:
.byte $24,$25,$26,$27,$28,$29,$2A,$2B
;==============================================================================
; $E063-$E0E5: Initialize / Adjust Entity Direction State
;
; Local:
; Initializes several gameplay variables and scans all four entity slots.
;
; Initialization:
;
; if $8C == 0:
; $8A = $1E
;
; $89 = $00
; $88 = $0F
; $D9 = $00
;
; The low two bits of $39-$3C are normalized to $01, preserving the upper
; six bits.
;
; Palette/update values are installed at:
;
; $02AE = $11
; $02A9 = $0F
;
; Then the four entity structures are examined. For eligible entities in
; primary state $04, a candidate secondary direction is calculated from
; $B9,X and accepted when coordinate alignment and path-map checks permit it.
;
; Global:
; Called from UpdateGameplayTileState before that routine derives its
; direction/tile update.
;
; Notes:
; $00/$01 and $02/$03 are parallel indirect pointers.
; X advances through entity offsets $00,$02,$04,$06.
; $04 temporarily holds a candidate direction value.
;==============================================================================
InitializeEntityDirectionState:
lda $8C
bne @E06D
lda #$1E
sta $8A
lda #$00
@E06D:
sta $89
lda #$0F
sta $88
ldx #$03
@E075:
lda $39,x
and #$FC
ora #$01
sta $39,x
dex
bpl @E075
lda #$11
sta $02AE
lda #$0F
sta $02A9
lda #$00
sta $D9
ldx #$00
lda #$2F
sta $00
lda #$02
sta $01
lda #$1E
sta $02
lda #$00
sta $03
@E09E:
lda #$0F
cmp $88
beq @E0AA
lda $D2
bne @E0D1
@E0AA:
lda $B8,x
cmp #$04
bne @E0D1
lda $B9,x
clc
adc #$02
and #$03
sta $04
ldy #$00
lda ($02),y
ldy #$02
ora ($02),y
and #$07
bne @E0CD
ldy $04
lda ($00),y
and #$F8
bne @E0D1
@E0CD:
lda $04
sta $B9,x
@E0D1:
lda $00
clc
adc #$04
sta $00
lda $02
clc
adc #$04
sta $02
inx
inx
cpx #$08
bne @E0AA
rts
;==============================================================================
; $E0E6-$E1BF: Score / State Display Update
;
; Updates the six decimal-like values at $70-$75 using increments at $DC-$E1,
; converts the result into display characters at $023F, handles progression
; state associated with $6B/$73/$67, appends display commands to $024B, then
; compares and updates the saved values at $61-$66.
;==============================================================================
UpdateScoreDisplay:
lda $48
beq @E0EB
rts
;------------------------------------------------------------------------------
; $E0EB
; Add $DC-$E1 into the six values at $70-$75 using decimal carry handling.
;------------------------------------------------------------------------------
@E0EB:
ldy #$00
lda #$06
sta $00
clc
;------------------------------------------------------------------------------
; $E0F2
;------------------------------------------------------------------------------
@E0F2:
lda $70,y
adc $DC,y
sta $70,y
cmp #$0A
bcc @E105
sbc #$0A
sta $70,y
sec
;------------------------------------------------------------------------------
; $E105
; Clear the consumed increment and continue through all six entries.
;------------------------------------------------------------------------------
@E105:
lda #$00
sta $DC,y
iny
dec $00
bne @E0F2
;------------------------------------------------------------------------------
; $E10F
; Convert the six numeric values into display/tile values.
;------------------------------------------------------------------------------
ldx #$00
dey
;------------------------------------------------------------------------------
; $E112
; Suppress leading zero-style values by replacing $30 with $20.
;------------------------------------------------------------------------------
@E112:
lda $70,y
jsr $E1CE
cmp #$30
bne @E12B
lda #$20
sta $023F,x
inx
dey
bne @E112
;------------------------------------------------------------------------------
; $E125
; Convert remaining digits without leading-space suppression.
;------------------------------------------------------------------------------
@E125:
lda $70,y
jsr $E1CE
;------------------------------------------------------------------------------
; $E12B
;------------------------------------------------------------------------------
@E12B:
sta $023F,x
inx
dey
bpl @E125
;------------------------------------------------------------------------------
; $E132
; Progression check.
;------------------------------------------------------------------------------
lda $6B
bne @E196
lda #$01
cmp $73
bne @E196
lda #$01
sta $6B
sta $0602
inc $67
lda $67
sec
sbc #$02
asl
sta $00
;------------------------------------------------------------------------------
; Locate the $FF terminator of the $024B display-command stream.
;------------------------------------------------------------------------------
ldy #$FF
@E14D:
iny
lda $024B,y
cmp #$FF
bne @E14D
tya
beq @E15D
lda #$00
sta $024B,y
iny
;------------------------------------------------------------------------------
; $E15D
; Select a display offset according to $46 & $47.
;------------------------------------------------------------------------------
@E15D:
lda $46
and $47
beq @E165
lda #$08
@E165:
sta $01
ldx #$00
;------------------------------------------------------------------------------
; $E169
; Append two five-byte command templates from $E1C0.
;------------------------------------------------------------------------------
@E169:
lda ScorePpuCommandTemplates,x
clc
adc $01
sta $024B,y
iny
inx
lda ScorePpuCommandTemplates,x
clc
adc $00
sta $024B,y
lda #$03
sta $02
@E182:
iny
inx
lda ScorePpuCommandTemplates,x
sta $024B,y
dec $02
bne @E182
iny
inx
cpx #$0A
bne @E169
;------------------------------------------------------------------------------
; $E196
; Compare saved values $61-$66 with current values $70-$75.
;------------------------------------------------------------------------------
@E196:
ldy #$05
@E198:
lda $61,y
cmp $70,y
beq @E1A3
bcc @E1A7
rts
@E1A3:
dey
bpl @E198
rts
;------------------------------------------------------------------------------
; $E1A7
; Copy display buffer $023F-$0244 to $0245-$024A.
;------------------------------------------------------------------------------
@E1A7:
ldx #$00
@E1A9:
lda $023F,x
sta $0245,x
inx
cpx #$06
bne @E1A9
;------------------------------------------------------------------------------
; Copy current values $70-$75 into saved values $61-$66.
;------------------------------------------------------------------------------
ldx #$00
@E1B4:
lda $70,x
sta $61,x
inx
cpx #$06
bne @E1B4
rts
;==============================================================================
; $E1C0-$E1C9: Score Display Command Table
;==============================================================================
ScorePpuCommandTemplates:
.byte $23,$17,$3C,$3D,$00
.byte $23,$37,$3E,$3F,$FF
;==============================================================================
; $E1CA-$E1D9: Convert High Nibble to Display Character
;
; Input:
; A = packed numeric value
;
; Output:
; A = display character code
;
; Behavior:
; Uses the original high nibble:
;
; $0-$9 -> $30-$39
; $A-$F -> $40-$45
;
; Global:
; Called by UpdateScoreDisplay while building the display buffer at
; $023F.
;==============================================================================
ConvertNibbleToDigitTile:
lsr
lsr
lsr
lsr
and #$0F
cmp #$0A
bcs @HexDigit
adc #$30
rts
@HexDigit:
adc #$36
rts
;==============================================================================
; $E1DA-$E262: Rebuild Gameplay Coordinate/Map Buffer
;
; Local:
; Converts the primary coordinate pair $1A/$1C through the helper at $E263
; and stores the result at $0200/$0201.
;
; Then iterates through gameplay coordinate structures beginning at $1A,
; advancing X by four each iteration.
;
; For each iteration:
; - Converts the current $1A+X/$1C+X pair through $E263.
; - Initializes working 16-bit values in $12-$15.
; - Calls helpers at $E2D4 and $E2C6.
; - Emits two groups of four bytes into the buffer at $0202+.
;
; The output loop terminates when Y reaches $28.
;
; Global:
; Called after gameplay collision/entity-state changes to rebuild a
; 40-byte coordinate/map-facing buffer in page $02.
;
; Notes:
; $E263 is the next known helper and is already called elsewhere.
;==============================================================================
BuildNearbyTileAddressBuffer:
lda $1A
sta $02
lda $1C
sta $03
jsr $E263
lda $03
sta $0200
lda $02
sta $0201
ldx #$00
ldy #$00
@E1F2:
lda $1A,x
sta $02
lda $1C,x
sta $03
jsr $E263
lda #$20
sta $14
lda #$00
sta $15
lda $02
sta $12
lda $03
sta $13
jsr $E2D4
lda $13
sta $0202,y
lda $12
iny
sta $0202,y
lda $03
iny
sta $0202,y
lda $02
sec
sbc #$01
iny
sta $0202,y
lda #$20
sta $14
lda #$00
sta $15
lda $02
sta $12
lda $03
sta $13
jsr $E2C6
lda $13
iny
sta $0202,y
lda $12
iny
sta $0202,y
lda $03
iny
sta $0202,y
lda $02
clc
adc #$01
iny
sta $0202,y
inx
inx
inx
inx
iny
cpy #$28
bne @E1F2
rts
;==============================================================================
; $E263-$E2A1: Convert Gameplay Coordinates to Map/PPU Address
;
; Input:
; $02 = first gameplay coordinate
; $03 = second gameplay coordinate
;
; Output:
; $02/$03 = converted 16-bit map/PPU-style address
;
; Local:
; Derives a nametable-style address from the coordinate pair.
;
; The second coordinate is adjusted by -4, aligned to an 8-unit boundary,
; then multiplied/shifted while carries are accumulated in $05.
;
; The first coordinate is adjusted by -4 and divided by eight before being
; added to the low address byte.
;
; The resulting address receives a base of $20xx.
;
; When ($46 & $47) is nonzero, $08 is added to the high/result byte at $03,
; selecting the corresponding alternate nametable region.
;
; Global:
; Called from several gameplay routines whenever object coordinates need to
; be converted into map/PPU addresses.
;
; Notes:
; $04/$05 are temporary low/high address components.
;==============================================================================
ConvertPixelToNametableAddress:
lda #$00
sta $05
lda $03
sec
sbc #$04
and #$F8
asl
rol $05
asl
rol $05
clc
adc #$40
sta $04
lda #$00
adc $05
sta $05
lda $02
sec
sbc #$04
lsr
lsr
lsr
clc
adc $04
sta $02
lda $05
clc
adc #$20
sta $03
lda $46
and $47
bne @E299
rts
@E299:
lda $03
clc
adc #$08
sta $03
rts
;==============================================================================
; $E2A2-$E2C5: Read PPU Values for Coordinate Address Buffer
;
; Reads 21 PPU addresses stored at $0200-$0229 and stores the corresponding
; PPU data bytes at $022A-$023E.
;
; PPUSTATUS is read once before processing the address list.
;==============================================================================
ReadNearbyNametableTiles:
ldx #$00
ldy #$00
lda PPUSTATUS
@E2A9:
lda $0200,x
sta PPUADDR
inx
lda $0200,x
sta PPUADDR
; Buffered PPU read.
lda PPUDATA
; Actual requested value.
lda PPUDATA
sta $022A,y
inx
iny
cpx #$2A
bne @E2A9
rts
;==============================================================================
; $E2C6-$E2D3: Add 16-Bit Working Values
;
; Input:
; $12/$13 = low/high first operand
; $14/$15 = low/high second operand
;
; Output:
; $12/$13 = 16-bit sum
;==============================================================================
Add16BitWorkingValue:
clc
lda $12
adc $14
sta $12
lda $13
adc $15
sta $13
rts
;==============================================================================
; $E2D4-$E2E1: Subtract 16-Bit Working Values
;
; Input:
; $12/$13 = low/high first operand
; $14/$15 = low/high second operand
;
; Output:
; $12/$13 = first operand - second operand
;==============================================================================
Subtract16BitWorkingValue:
sec
lda $12
sbc $14
sta $12
lda $13
sbc $15
sta $13
rts
;==============================================================================
; $E2E2-$E381: Draw Gameplay Nametable
;
; Local:
; Expands compressed gameplay map data directly into VRAM.
;
; The primary destination begins at:
;
; $2040 when ($47 & $46) == 0
; $2840 when ($47 & $46) != 0
;
; The compressed source pointer is loaded from $FFF6/$FFF7.
;
; After the primary map is written, a small decoration pattern from
; MazeNametableDecorationTiles is written to additional PPU locations.
;
; Global:
; Called from GameModeState00 while initializing gameplay.
;
; Notes:
; $00/$01 = compressed source pointer during the first phase
; $02/$03 = PPU destination during the first phase
; $04 = number of PPU bytes remaining in current row
; $05 = repeat count extracted from the source byte
; X = row counter
; Y = compressed source index
;==============================================================================
DrawMazeNametable:
lda #$20
sta $02
lda #$40
sta $03
lda $47
and $46
beq @E2F4
lda #$28
sta $02
;------------------------------------------------------------------------------
; $E2F4
; Load compressed-map source pointer.
;------------------------------------------------------------------------------
@E2F4:
lda MazeDataPointerBytes+1
sta $01
lda MazeDataPointerBytes
sta $00
ldx #$1B
ldy #$00
;------------------------------------------------------------------------------
; $E302
; Set PPU destination for the next row.
;------------------------------------------------------------------------------
@E302:
lda PPUSTATUS
lda $02
sta PPUADDR
lda $03
sta PPUADDR
lda #$16
sta $04
;------------------------------------------------------------------------------
; $E313
; Start decoding the next compressed source byte.
;------------------------------------------------------------------------------
@E313:
lda #$00
sta $05
lda ($00),y
asl
rol $05
asl
rol $05
lda ($00),y
and #$3F
;------------------------------------------------------------------------------
; $E321
; Repeat the decoded value according to the repeat count in $05.
;------------------------------------------------------------------------------
@E321:
sta PPUDATA
dec $04
dec $05
bpl @E321
iny
bne @E32F
inc $01
;------------------------------------------------------------------------------
; $E32F
; Continue until the current row contains $16 output bytes.
;------------------------------------------------------------------------------
@E32F:
lda $04
bne @E313
lda $03
clc
adc #$20
sta $03
lda $02
adc #$00
sta $02
dex
bne @E302
;==============================================================================
; $E345-$E381: Draw additional nametable decoration strips.
;==============================================================================
ldy #$02
lda #$21
sta $00
lda #$D6
sta $01
lda $47
and $46
beq @E359
lda #$29
sta $00
;------------------------------------------------------------------------------
; $E359
; Start one decoration strip.
;------------------------------------------------------------------------------
@E359:
ldx #$07
lda PPUSTATUS
lda $00
sta PPUADDR
lda $01
sta PPUADDR
lda MazeNametableDecorationTiles,y
;------------------------------------------------------------------------------
; $E36B
; Write the selected table value eight times.
;------------------------------------------------------------------------------
@E36B:
sta PPUDATA
dex
bpl @E36B
lda $01
clc
adc #$20
sta $01
lda #$00
adc $00
sta $00
dey
bpl @E359
rts
;==============================================================================
; $E382-$E385: Gameplay Nametable Decoration Table
;==============================================================================
MazeNametableDecorationTiles:
.byte $2D,$04,$2D
;==============================================================================
; $E385-$E3FE: Initialize Gameplay Nametable Regions
;
; Fills gameplay nametable regions with tile $2D, then clears the corresponding
; attribute-table regions.
;
; Address-based global labels are used for shared entry points to avoid ca65
; cheap-local scope issues and collisions with previously defined symbols.
;==============================================================================
ClearGameplayNametables:
lda PPUSTATUS
lda #$20
sta PPUADDR
lda #$00
sta PPUADDR
lda #$01
sta $02
;------------------------------------------------------------------------------
; $E396
; Shared entry used for another nametable pass.
;------------------------------------------------------------------------------
ClearGameplayNametablesSecondPass:
lda #$01
sta $03
;------------------------------------------------------------------------------
; $E39A
; Initialize fill counters.
;------------------------------------------------------------------------------
InitializeNametableFillCounters:
lda #$03
sta $00
lda #$C0
sta $01
lda #$2D
;------------------------------------------------------------------------------
; $E3A4
; Shared fill loop.
;------------------------------------------------------------------------------
FillNametableByte:
sta PPUDATA
dec $01
bne FillNametableByte
dec $00
bpl FillNametableByte
dec $03
bne AdvanceNametableFillPass
lda #$40
sta $01
lda #$00
beq FillNametableByte
;------------------------------------------------------------------------------
; $E3BB
; Advance to alternate nametable / next phase.
;------------------------------------------------------------------------------
AdvanceNametableFillPass:
dec $02
bne ClearGameplayAttributeTables
lda PPUSTATUS
lda #$28
sta PPUADDR
lda #$00
sta PPUADDR
jmp ClearGameplayNametablesSecondPass
;------------------------------------------------------------------------------
; $E3CF
; Begin clearing attribute-table regions.
;------------------------------------------------------------------------------
ClearGameplayAttributeTables:
lda #$01
sta $00
lda PPUSTATUS
lda #$23
sta PPUADDR
lda #$C0
sta PPUADDR
;------------------------------------------------------------------------------
; $E3E0
; Shared attribute-clear setup.
;------------------------------------------------------------------------------
BeginAttributeTableClear:
lda #$00
tay
;------------------------------------------------------------------------------
; $E3E3
; Write $20 zero bytes to PPUDATA.
;------------------------------------------------------------------------------
ClearAttributeTableLoop:
sta PPUDATA
iny
cpy #$20
bne ClearAttributeTableLoop
dec $00
bne ClearSecondAttributeTable
rts
;------------------------------------------------------------------------------
; $E3F0
; Set destination to the second attribute table, $2BC0.
;------------------------------------------------------------------------------
ClearSecondAttributeTable:
lda PPUSTATUS
lda #$2B
sta PPUADDR
lda #$C0
sta PPUADDR
bne BeginAttributeTableClear
;==============================================================================
; $E3FF-$E473: Draw Gameplay Numeric / Status Fields
;
; Local:
; Configures a PPU destination and repeatedly draws numeric fields through
; the helper at DrawSixDigitScore.
;
; Initial destination/configuration:
;
; $00/$01 = PPU destination
; $04 = destination stride
; X = index into a pointer table at ScoreFieldSourcePointerTable
;
; The destination and table index vary depending on $47 & $46.
;
; Three fields are drawn per pass. The routine may perform another pass
; according to $47 and the current field counter.
;
; Global:
; Called by GameModeState00 as part of gameplay initialization.
;
; Notes:
; DrawSixDigitScore renders one numeric field using the source pointer
; loaded from the table at ScoreFieldSourcePointerTable.
;==============================================================================
DrawGameplayScoreFields:
lda #$20
sta $00
lda #$B6
sta $01
lda #$80
sta $04
ldx #$00
lda $47
and $46
beq @E429
lda #$28
sta $00
bne @E429
;------------------------------------------------------------------------------
; Alternate setup path.
;------------------------------------------------------------------------------
lda #$20
sta $00
lda #$83
sta $01
lda #$09
sta $04
lda #$01
sta $47
ldx #$0C
;------------------------------------------------------------------------------
; $E429
; Adjust lookup-table index for alternate player/mode state.
;------------------------------------------------------------------------------
@E429:
lda $47
and $46
beq @E436
txa
clc
adc #$06
tax
@E436:
lda #$03
sta $05
;------------------------------------------------------------------------------
; $E43A: render one status field.
;------------------------------------------------------------------------------
@E43A:
lda PPUSTATUS
lda $00
sta PPUADDR
lda $01
sta PPUADDR
lda ScoreFieldSourcePointerTable,x
sta $02
lda ScoreFieldSourcePointerTable+1,x
sta $03
jsr DrawSixDigitScore
inx
inx
lda $01
clc
adc $04
sta $01
lda #$00
adc $00
sta $00
dec $05
lda $05
cmp #$01
bne @E46D
lda $47
beq @E473
@E46D:
lda $05
bne @E43A
@E473:
rts
;==============================================================================
; $E474-$E49E: Draw Six-Digit Numeric Field
;
; Input:
; $02/$03 = pointer to packed numeric values
;
; Local:
; Processes six source values from index 5 down to 0.
;
; Leading values whose low nibble is zero are rendered as spaces ($20).
; Once a nonzero low nibble is encountered, remaining values are converted
; through the shared conversion entry at $E1CE and written to PPUDATA.
;
; The final value at Y=0 is always converted, after which a trailing $30
; character is written.
;
; Notes:
; The conversion calls MUST target $E1CE, not $E1CA. $E1CE is the shared
; entry that begins with AND #$0F, while $E1CA additionally shifts A right
; four times.
;==============================================================================
DrawSixDigitScore:
ldy #$05
SkipLeadingZeroDigits:
lda ($02),y
and #$0F
bne ConvertCurrentScoreDigit
lda #$20
sta PPUDATA
dey
bne SkipLeadingZeroDigits
beq DrawFinalScoreDigit
LoadNextScoreDigit:
lda ($02),y
ConvertCurrentScoreDigit:
jsr $E1CE
sta PPUDATA
dey
bne LoadNextScoreDigit
DrawFinalScoreDigit:
lda ($02),y
jsr $E1CE
sta PPUDATA
lda #$30
sta PPUDATA
rts
;==============================================================================
; $E49F-$E4B0: Gameplay Status Field Source Pointer Table
;
; Local:
; Nine little-endian zero-page/RAM pointers used by the gameplay status-field
; drawing routine beginning at $E3FF.
;
; X indexes this table in steps of two. Each selected address is loaded into
; $02/$03 and passed to DrawSixDigitScore.
;
; Global:
; Selects the numeric/state arrays rendered into the different gameplay
; status display fields.
;==============================================================================
ScoreFieldSourcePointerTable:
.word $0061
.word $0070
.word $0080
.word $0061
.word $0080
.word $0070
.word $0070
.word $0061
.word $0080
;==============================================================================
; $E4B1-$E501: Build Entity Direction Candidate List
;
; Input:
; $00/$01 = pointer to direction/path data
; $0A/$0B = comparison values prepared by the caller
; X = current entity slot index
;
; Output:
; $0C-$0F = candidate direction values, with invalid entries marked $FF
;
; Local:
; First clears all four candidate entries to $FF.
;
; Then examines four values through ($00),Y. A candidate is accepted when
; either:
;
; value == $0A
;
; or:
;
; (value & $F8) != 0
; and value == $0B
;
; Accepted entries store their Y index into $0C-$0F.
;
; The entity's reverse/current-excluded direction is then calculated from
; $B9,X and explicitly removed from the candidate list.
;
; Finally performs up to three passes compacting $FF holes by shifting the
; following entry left.
;
; Global:
; Called by EntityTargetCommon at $D778 before entity direction selection.
;
; Notes:
; $0C-$0F therefore form a filtered list of legal direction indices.
;==============================================================================
BuildLegalDirectionList:
ldy #$FF
sty $0C
sty $0D
sty $0E
sty $0F
iny
ScanDirectionCandidate:
lda ($00),y
cmp $0A
beq AcceptDirectionCandidate
and #$F8
beq AcceptDirectionCandidate
cmp $0B
bne NextDirectionCandidate
AcceptDirectionCandidate:
tya
sta $000C,y
NextDirectionCandidate:
iny
cpy #$04
bne ScanDirectionCandidate
lda $B9,x
clc
adc #$02
and #$03
tay
lda #$FF
sta $000C,y
lda #$03
sta $0B
CompactDirectionListPass:
ldy #$00
CompactDirectionListEntry:
lda $000C,y
cmp #$FF
bne NextDirectionListEntry
lda $000D,y
sta $000C,y
lda #$FF
sta $000D,y
NextDirectionListEntry:
iny
cpy #$03
bne CompactDirectionListEntry
dec $0B
bne CompactDirectionListPass
rts
;==============================================================================
; $E502-$E53B: Draw Gameplay Initialization Data
;
; Local:
; Reads compact PPU command data beginning at $E53C and writes the payloads
; directly to PPUDATA.
;
; X selects between two passes/variants.
; Y indexes the command stream.
; $00 holds the number of bytes remaining in the current payload.
;
; Global:
; Called from GameModeState00 during gameplay initialization.
;==============================================================================
DrawGameplayHudLabels:
ldx #$01
;------------------------------------------------------------------------------
; $E504
; Reset command-stream index for a pass.
;------------------------------------------------------------------------------
InitData_E504:
ldy #$00
;------------------------------------------------------------------------------
; $E506
; Start one command record.
;------------------------------------------------------------------------------
InitData_E506:
lda $E53C,y
cpx #$00
bne InitData_E50F
adc #$07
;------------------------------------------------------------------------------
; $E50F
; Write PPU destination address.
;------------------------------------------------------------------------------
InitData_E50F:
sta PPUADDR
iny
lda $E53C,y
sta PPUADDR
iny
lda $E53C,y
sta $00
;------------------------------------------------------------------------------
; $E51F
; Write record payload.
;------------------------------------------------------------------------------
InitData_E51F:
iny
lda $E53C,y
sta PPUDATA
dec $00
bne InitData_E51F
iny
cpy #$11
bne InitData_E534
lda $47
bne InitData_E534
rts
;------------------------------------------------------------------------------
; $E534
; Continue through the first/second stream section.
;------------------------------------------------------------------------------
InitData_E534:
cpy #$17
bne InitData_E506
dex
beq InitData_E504
rts
;==============================================================================
; $E53C-$E552: Gameplay Initialization PPU Command Data
;
; Record format:
; byte 0 = PPU address high
; byte 1 = PPU address low
; byte 2 = payload length
; remaining bytes = payload written to PPUDATA
;==============================================================================
GameplayHudInitializationData:
; $E53C: PPU $2076, 8-byte payload
.byte $20,$76,$08
.byte $B4,$B5,$B6,$B7,$B8,$B9,$BA,$BB
; $E547: PPU $20F7, 3-byte payload
.byte $20,$F7,$03
.byte $B0,$B3,$B2
; $E54D: PPU $2177, 3-byte payload
.byte $21,$77,$03
.byte $B1,$B3,$B2
;==============================================================================
; $E553-$E5C0: Draw Progression / Status Markers
;
; Local:
; Returns immediately when $67 is zero.
;
; Otherwise computes:
;
; count = min($67 + 1, $07)
;
; and stores that in $02.
;
; Initializes:
;
; $03 = $04
; $00/$01 = $2317
;
; If ($46 & $47) != 0, the high destination byte is adjusted by $08.
;
; The routine then performs nested loops, calling the helper at $E59A with
; Y=$3C. The helper writes a 2x2-style pattern at the current PPU position.
;
; Global:
; Called during gameplay initialization to draw a progression/status display.
;
; Notes:
; $E59A is a valid internal helper entry point within this routine.
;==============================================================================
DrawLevelProgressMarkers:
lda $67
bne Progression_E558
rts
Progression_E558:
clc
adc #$01
cmp #$07
bcc Progression_E55F
lda #$07
Progression_E55F:
sta $02
lda #$04
sta $03
lda #$23
sta $00
lda #$17
sta $01
lda $46
and $47
beq Progression_E57C
lda $00
clc
adc #$08
sta $00
Progression_E57C:
dec $02
bne Progression_E581
rts
Progression_E581:
dec $03
bne Progression_E58A
lda $01
clc
adc #$3A
sta $01
Progression_E58A:
ldy #$3C
jsr Draw2x2Marker
lda $01
clc
adc #$02
sta $01
bne Progression_E57C
;==============================================================================
; $E59A-$E5C0: Draw 2x2 Progression Marker
;
; Input:
; $00/$01 = first PPU destination
; Y = first tile value
;
; Effect:
; Writes Y and Y+1 horizontally, then Y+2 and Y+3 one row ($20 bytes)
; below the initial destination.
;==============================================================================
Draw2x2Marker:
lda $00
sta PPUADDR
lda $01
sta PPUADDR
sty PPUDATA
iny
sty PPUDATA
lda $00
sta PPUADDR
lda $01
clc
adc #$20
sta PPUADDR
iny
sty PPUDATA
iny
sty PPUDATA
rts
;==============================================================================
; $-$E699: Initialize Gameplay State / Markers
;
; Local:
; Builds several gameplay marker/display values using the current value at
; $68 and a collection of lookup tables beginning at LevelProgressMarkerIndexTable
;
; The routine:
; - Initializes working values at $0A-$0F.
; - Derives an index/count from $68.
; - Selects PPU coordinates according to $46/$47.
; - Draws repeated 2x2 marker blocks using Draw2x2Marker.
; - Combines several lookup-table values into $0A+ working state.
; - Writes the resulting values directly into PPU attribute/display areas.
; - Selects final values for $02B6 and $02A9.
;
; Global:
; Called from GameModeState00 during initial gameplay setup.
;
; Notes:
; Address-oriented labels are used for the larger loops to preserve the
; original compact branch structure.
;==============================================================================
InitializeLevelProgressDisplay:
lda #$00
sta $03
sta $0E
sta $0F
lda #$15
sta $0A
lda #$11
sta $0D
lda #$05
sta $0B
sta $0C
lda $68
sta $02
sec
sbc #$07
bcc UseFirstLevelProgressIndex
cmp #$0C
bcc ClampLevelProgressIndex
lda #$0C
;------------------------------------------------------------------------------
; $E5E9
;------------------------------------------------------------------------------
ClampLevelProgressIndex:
tax
lda #$07
sta $02
bne InitializeLevelProgressPosition
;------------------------------------------------------------------------------
; $E5F0
;------------------------------------------------------------------------------
UseFirstLevelProgressIndex:
ldx #$00
;------------------------------------------------------------------------------
; $E5F2
; Initial PPU destination.
;------------------------------------------------------------------------------
InitializeLevelProgressPosition:
lda #$22
sta $00
lda #$56
sta $01
lda $46
and $47
beq InitializeLevelProgressRowCounter
lda $00
clc
adc #$08
sta $00
;------------------------------------------------------------------------------
; $E605
;------------------------------------------------------------------------------
InitializeLevelProgressRowCounter:
lda #$05
sta $04
;------------------------------------------------------------------------------
; $E609
; Main marker loop.
;------------------------------------------------------------------------------
DrawNextLevelProgressMarker:
dec $04
bne SelectLevelProgressMarker
lda $01
clc
adc #$38
sta $01
;------------------------------------------------------------------------------
; $E612
; Select marker information.
;------------------------------------------------------------------------------
SelectLevelProgressMarker:
lda LevelProgressMarkerIndexTable,x
sta $05
asl
asl
adc #$60
tay
jsr Draw2x2Marker
ldy $03
lda LevelProgressAttributeIndexTable,y
sta $06
lda LevelProgressAttributeIndexTable+1,y
sta $07
ldy $05
lda LevelProgressAttributeBits,y
;------------------------------------------------------------------------------
; $E633
; Shift selected marker bits.
;------------------------------------------------------------------------------
ShiftLevelProgressAttributeBits:
dec $07
bmi StoreLevelProgressAttributeBits
asl
asl
bcc ShiftLevelProgressAttributeBits
;------------------------------------------------------------------------------
; $E63B
; Combine marker bits into working values.
;------------------------------------------------------------------------------
StoreLevelProgressAttributeBits:
ldy $06
ora $0A,y
sta $0A,y
lda $01
clc
adc #$02
sta $01
inc $03
inc $03
inx
dec $02
bpl DrawNextLevelProgressMarker
;==============================================================================
; Write generated marker-state values into PPU memory.
;==============================================================================
ldx #$23
lda $47
and $46
beq SetLevelProgressAttributeAddress
ldx #$2B
;------------------------------------------------------------------------------
; $E65D
; Set first PPU destination.
;------------------------------------------------------------------------------
SetLevelProgressAttributeAddress:
stx PPUADDR
stx $00
lda #$E5
sta PPUADDR
ldx #$00
;------------------------------------------------------------------------------
; $E667
; Reset count for one three-byte PPU group.
;------------------------------------------------------------------------------
ResetAttributeWriteCount:
lda #$03
sta $01
;------------------------------------------------------------------------------
; $E66B
; Write three values from $0A+.
;------------------------------------------------------------------------------
WriteLevelProgressAttribute:
lda $0A,x
sta PPUDATA
inx
dec $01
bne WriteLevelProgressAttribute
inc $02
bne SelectLevelProgressPalette
lda $00
sta PPUADDR
lda #$ED
sta PPUADDR
bne ResetAttributeWriteCount
;------------------------------------------------------------------------------
; $E685
; Select final marker/display value.
;------------------------------------------------------------------------------
SelectLevelProgressPalette:
lda $68
cmp #$10
bcc StoreLevelProgressPalette
lda #$0F
;------------------------------------------------------------------------------
; $E68D
;------------------------------------------------------------------------------
StoreLevelProgressPalette:
tay
lda LevelProgressPaletteTable,y
sta $02B6
lda #$0F
sta $02A9
rts
;==============================================================================
; $E69A-$E6AD: Gameplay Marker Level Index Table
;==============================================================================
LevelProgressMarkerIndexTable:
.byte $00,$01,$02,$02
.byte $03,$03,$04,$04
.byte $05,$05,$06,$06
.byte $07,$07,$07,$07
.byte $07,$07,$07,$07
;==============================================================================
; $E6AE-$E6BD: Gameplay Marker Working-Value Table
;
; Used as byte pairs through $03.
;==============================================================================
LevelProgressAttributeIndexTable:
.byte $00,$03
.byte $01,$02
.byte $01,$03
.byte $02,$02
.byte $03,$01
.byte $04,$00
.byte $04,$01
.byte $05,$00
;==============================================================================
; $E6BE-$E6C5: Gameplay Marker Bit/Pattern Table
;==============================================================================
LevelProgressAttributeBits:
.byte $02,$02,$02,$02
.byte $03,$03,$03,$03
;==============================================================================
; $E6C6-$E6D5: Gameplay Marker Final Value Table
;
; Indexed by min($68,$0F). Selected value is stored at $02B6.
;==============================================================================
LevelProgressPaletteTable:
.byte $16,$16,$26,$26
.byte $06,$06,$19,$19
.byte $17,$17,$17,$17
.byte $12,$12,$12,$12
;==============================================================================
; $E6D6-$E737: Initialize Gameplay Transition State
;==============================================================================
InitializeGameplayTransitionState:
lda #$08
sta $43
sta PPUCTRL
TransitionInit_E6DD:
lda PPUSTATUS
bpl TransitionInit_E6DD
lda #$01
sta $060D
sta $060E
lda #$00
sta PPUMASK
jsr ClearPlayfieldNametable
lda #$01
sta $028D
lda #$01
sta $0293
lda #$F7
sta $0278
lda #$7C
sta $027A
lda #$FF
sta $1E
lda #$00
sta $1F
lda #$00
sta $88
ldy #$00
TransitionInit_CopyPalette:
lda GameplayTransitionPalette,y
sta $02A9,y
iny
cpy #$10
bne TransitionInit_CopyPalette
lda #$20
sta $026F
sta $0270
sta $0271
lda #$10
sta $3F
lda #$88
sta PPUCTRL
sta $43
jmp $CAA7
;==============================================================================
; $E738-$E7AE: Initialize Gameplay PPU Region
;
; Selects $2000/$2800 according to $47 & $46, performs a patterned fill,
; clears $40 PPU bytes, and conditionally writes tile $5E at $2230/$2A30.
;
; Several branches deliberately re-enter the counter setup at $E751.
;==============================================================================
ClearPlayfieldNametable:
ldx #$20
lda $47
and $46
beq SetPlayfieldPpuAddress
ldx #$28
;------------------------------------------------------------------------------
; $E742
; Initialize selected nametable address.
;------------------------------------------------------------------------------
SetPlayfieldPpuAddress:
lda PPUSTATUS
stx PPUADDR
lda #$00
sta PPUADDR
lda #$1C
sta $00
;------------------------------------------------------------------------------
; $E751
; Shared counter-setup entry.
;
; The outer $00 loop deliberately jumps back here rather than rebuilding $00.
;------------------------------------------------------------------------------
ResetPlayfieldFillCounters:
lda #$02
sta $01
lda #$1C
sta $02
lda #$02
sta $03
lda #$2D
;------------------------------------------------------------------------------
; $E75F
; Shared PPUDATA write point.
;------------------------------------------------------------------------------
WritePlayfieldFillByte:
sta PPUDATA
ldx $01
beq AdvancePlayfieldInnerRun
dec $01
bne WritePlayfieldFillByte
lda #$20
bne WritePlayfieldFillByte
;------------------------------------------------------------------------------
; $E76E
; Process the second run counter.
;------------------------------------------------------------------------------
AdvancePlayfieldInnerRun:
ldx $02
beq AdvancePlayfieldOuterRun
dec $02
bne WritePlayfieldFillByte
lda #$2D
bne WritePlayfieldFillByte
;------------------------------------------------------------------------------
; $E77A
; Process outer fill counters.
;------------------------------------------------------------------------------
AdvancePlayfieldOuterRun:
dec $03
bne WritePlayfieldFillByte
dec $00
bne ResetPlayfieldFillCounters
;------------------------------------------------------------------------------
; $E782
; Clear $40 PPU bytes.
;------------------------------------------------------------------------------
lda #$00
tax
ClearPlayfieldTail:
sta PPUDATA
inx
cpx #$40
bne ClearPlayfieldTail
lda $87
cmp #$02
beq DrawPlayfieldSpecialTile
rts
;------------------------------------------------------------------------------
; $E794
; Optional special tile at PPU $2230 or $2A30.
;------------------------------------------------------------------------------
DrawPlayfieldSpecialTile:
ldx #$22
lda $47
and $46
beq SetSpecialTilePpuAddress
ldx #$2A
SetSpecialTilePpuAddress:
lda PPUSTATUS
stx PPUADDR
lda #$30
sta PPUADDR
lda #$5E
sta PPUDATA
rts
;==============================================================================
; $E7AF-$E7BE: Gameplay Transition Palette
;==============================================================================
GameplayTransitionPalette:
.byte $0F,$36,$20,$06
.byte $0F,$27,$20,$06
.byte $0F,$11,$20,$33
.byte $0F,$36,$20,$11
;==============================================================================
; $E7BF-$E7CD: Gameplay Transition Update Coordinator
;
; Runs several already-known gameplay/render helpers, then dispatches through
; the state machine at $E7CE before returning to the main game-mode dispatcher.
;==============================================================================
UpdateIntermissionTransition:
jsr $EA19
jsr $EA94
jsr BuildEntitySpriteBuffer
jsr DispatchIntermissionState
jmp $CAA7
;==============================================================================
; $E7CE-$E7DC: Transition state dispatcher
;
; $87 is an even state value used directly as a byte offset into the table.
;==============================================================================
DispatchIntermissionState:
ldy $87
lda IntermissionStateHandlerTable,y
sta $10
lda IntermissionStateHandlerTable+1,y
sta $11
jmp ($0010)
;==============================================================================
; $E7DD-$E7E2: Transition state pointer table
;==============================================================================
IntermissionStateHandlerTable:
.word DispatchIntermissionPhase
.word DispatchIntermissionState2Phase
.word DispatchIntermissionState4Phase
;==============================================================================
; $E7E3-$E7F1: Secondary transition-state dispatcher
;
; $88 is used directly as a byte offset into another pointer table.
;==============================================================================
DispatchIntermissionPhase:
ldy $88
lda IntermissionPhaseHandlerTable,y
sta $10
lda IntermissionPhaseHandlerTable+1,y
sta $11
jmp ($0010)
;==============================================================================
; $E7F2-$E7F9: Transition substate pointer table
;==============================================================================
IntermissionPhaseHandlerTable:
.word UpdateIntermissionPhase0
.word UpdateIntermissionPhase1
.word UpdateIntermissionPhase2
.word UpdateIntermissionPhase3
;==============================================================================
; $E7FA-$E822: Transition substate 0
;
; Waits until $0278 reaches at least $D0, then advances $88 by two and
; initializes several mirrored object/state values.
;==============================================================================
UpdateIntermissionPhase0:
lda $0278
cmp #$D0
bcc TransitionSubstate_E802
rts
TransitionSubstate_E802:
inc $88
inc $88
lda #$0C
sta $028C
lda #$20
sta $0292
lda #$F7
sta $0274
lda #$7C
sta $0276
lda #$FE
sta $1A
lda #$E0
sta $1B
rts
;==============================================================================
; $E823-$E84D: Transition substate 1
;
; Waits for $0274 to become zero, then advances $88 and initializes a
; different object/state configuration.
;==============================================================================
UpdateIntermissionPhase1:
lda $0274
beq TransitionSubstate_E829
rts
TransitionSubstate_E829:
inc $88
inc $88
lda #$40
sta $4C
lda #$1E
sta $028C
lda #$22
sta $0292
lda #$09
sta $0274
lda #$7C
sta $0276
lda #$01
sta $1A
lda #$00
sta $1B
rts
;==============================================================================
; $E84E-$E8B9: Transition substate 2
;
; Waits until $0274 reaches at least $80, then advances $88 and initializes
; four related coordinate/state structures and their mirrored render values.
;==============================================================================
UpdateIntermissionPhase2:
lda #$01
sta $060D
sta $060E
lda $0274
cmp #$80
bcs TransitionSubstate_E85D
rts
TransitionSubstate_E85D:
inc $88
inc $88
lda #$01
sta $1E
sta $22
sta $26
sta $2A
lda #$C0
sta $1F
sta $23
sta $27
sta $2B
clc
lda #$38
sta $028D
adc #$01
sta $028E
adc #$01
sta $028F
adc #$01
sta $0290
lda #$21
sta $0293
sta $0294
sta $0295
sta $0296
lda #$09
sta $0278
sta $0280
lda #$19
sta $027C
sta $0284
lda #$6C
sta $027A
sta $027E
lda #$7C
sta $0282
sta $0286
rts
;==============================================================================
; $E8BA-$E8C6: Transition substate 3
;
; Waits for $0278 to become zero, then primes delay $4C and transfers to the
; routine at $EA14.
;==============================================================================
UpdateIntermissionPhase3:
lda $0278
beq TransitionSubstate_E8C0
rts
TransitionSubstate_E8C0:
lda #$40
sta $4C
jmp $EA14
;==============================================================================
; $E8C7-$E8D5: Transition State $02 Substate Dispatcher
;
; Local:
; Uses $88 as an even byte offset into IntermissionState2PhaseTable.
;==============================================================================
DispatchIntermissionState2Phase:
ldy $88
lda IntermissionState2PhaseTable,y
sta $10
lda IntermissionState2PhaseTable+1,y
sta $11
jmp ($0010)
;==============================================================================
; $E8D6-$E8DB: Transition phase table
;==============================================================================
IntermissionState2PhaseTable:
.word UpdateIntermissionState2Phase0
.word UpdateIntermissionState2Phase1
.word UpdateIntermissionState2Phase2
;==============================================================================
; $E8DC-$E904: Transition phase 0
;
; Waits until $0278 reaches $A0, then advances the $88 state and initializes
; the primary mirrored object/state values.
;==============================================================================
UpdateIntermissionState2Phase0:
lda $0278
cmp #$A0
bcc TransitionPhase_E8E4
rts
TransitionPhase_E8E4:
inc $88
inc $88
lda #$0C
sta $028C
lda #$20
sta $0292
lda #$F7
sta $0274
lda #$7C
sta $0276
lda #$FF
sta $1A
lda #$00
sta $1B
rts
;==============================================================================
; $E905-$E93E: Transition phase 1
;
; Watches $0274. At $80 it installs one object configuration. At $78 it
; clears the position pair and advances to the next phase.
;==============================================================================
UpdateIntermissionState2Phase1:
lda $0274
cmp #$80
bne TransitionPhase_E92D
lda #$FF
sta $1A
lda #$C0
sta $1B
lda #$42
sta $028E
lda #$00
sta $0294
lda #$80
sta $027C
lda #$7C
sta $027E
lda #$00
sta $22
sta $23
rts
TransitionPhase_E92D:
cmp #$78
beq TransitionPhase_E933
rts
TransitionPhase_E933:
lda #$00
sta $1A
sta $1B
inc $88
inc $88
rts
;==============================================================================
; $E93F-$E96D: Transition phase 2
;
; Uses bit $40 of $028C and timer/state value $89 to alternate the object
; state between values $46 and $47. Eventually transfers to $EA14.
;==============================================================================
UpdateIntermissionState2Phase2:
lda $028C
and #$40
bne TransitionPhase_E953
lda #$46
sta $028C
lda #$45
sta $028E
lda #$40
sta $89
rts
TransitionPhase_E953:
dec $89
beq TransitionPhase_E958
rts
TransitionPhase_E958:
lda $028C
cmp #$47
bne TransitionPhase_E963
jmp $EA14
TransitionPhase_E963:
lda #$47
sta $028C
lda #$40
sta $89
rts
;==============================================================================
; $E96E-$E97C: Transition State $04 Dispatcher
;
; $88 is an even byte offset into the four-entry phase table.
;==============================================================================
DispatchIntermissionState4Phase:
ldy $88
lda IntermissionState4PhaseTable,y
sta $10
lda IntermissionState4PhaseTable+1,y
sta $11
jmp ($0010)
;==============================================================================
; $E97D-$E984: Transition State $04 Phase Table
;==============================================================================
IntermissionState4PhaseTable:
.word UpdateIntermissionState4Phase0
.word UpdateIntermissionState4Phase1
.word UpdateIntermissionState4Phase2
.word UpdateIntermissionState4Phase3
;==============================================================================
; $E985-$E9AD: Phase 0
;
; Waits until $0278 reaches $D8, advances $88, then installs the initial
; mirrored object/state configuration.
;==============================================================================
UpdateIntermissionState4Phase0:
lda $0278
cmp #$D8
bcc TransitionPhase_E98D
rts
TransitionPhase_E98D:
inc $88
inc $88
lda #$4C
sta $028C
lda #$20
sta $0292
lda #$F7
sta $0274
lda #$7C
sta $0276
lda #$FF
sta $1A
lda #$00
sta $1B
rts
;==============================================================================
; $E9AE-$E9D8: Phase 1
;
; Waits for $0274 to reach zero, advances $88 and installs the next object
; configuration.
;==============================================================================
UpdateIntermissionState4Phase1:
lda $0274
beq TransitionPhase_E9B4
rts
TransitionPhase_E9B4:
inc $88
inc $88
lda #$28
sta $4C
lda #$4A
sta $028C
lda #$23
sta $0292
lda #$09
sta $0274
lda #$7C
sta $0276
lda #$01
sta $1A
lda #$00
sta $1B
rts
;==============================================================================
; $E9D9-$EA09: Phase 2
;
; Enables transition flags, waits until $0274 reaches $19, then advances $88
; and sets the next mirrored-state values.
;==============================================================================
UpdateIntermissionState4Phase2:
lda #$01
sta $060D
sta $060E
lda $0274
cmp #$19
bcs TransitionPhase_E9E8
rts
TransitionPhase_E9E8:
inc $88
inc $88
lda #$4C
sta $028D
lda #$21
sta $0293
lda #$11
sta $0278
lda #$84
sta $027A
lda #$01
sta $1E
lda #$00
sta $1F
rts
;==============================================================================
; $EA0A-$EA18: Phase 3
;
; Waits for $0278 to reach zero, then primes the next game-mode/state
; transition.
;==============================================================================
UpdateIntermissionState4Phase3:
lda $0278
beq TransitionPhase_EA10
rts
TransitionPhase_EA10:
lda #$40
sta $4C
lda #$00
sta $3F
rts
;==============================================================================
; $EA19-$EA93: Update Mirrored Gameplay Object Positions
;
; Local:
; Walks five four-byte structures in parallel:
;
; $00/$01 -> source gameplay structures beginning at $001A
; $02/$03 -> mirrored structures beginning at $0274
;
; Movement increments from the source structures are added into mirrored
; coordinates. Attribute bit $20 in $0292+ is adjusted according to the
; resulting coordinate, and coordinate reset/wrap handling is performed.
;
; Global:
; Called from gameplay transition processing before render data is rebuilt.
;
; Notes:
; Several branches deliberately re-enter shared code, so global address-style
; labels are used throughout.
;==============================================================================
UpdateTransitionObjectPositions:
lda #$1A
sta $00
lda #$00
sta $01
lda #$74
sta $02
lda #$02
sta $03
ldx #$00
;------------------------------------------------------------------------------
; $EA2B
; Process one structure.
;------------------------------------------------------------------------------
UpdateNextTransitionObject:
ldy #$00
lda ($02),y
beq AdvanceTransitionObjectPointers
iny
lda ($00),y
clc
adc ($02),y
sta ($02),y
dey
lda ($00),y
adc ($02),y
sta ($02),y
cmp #$C0
bcc UpdateTransitionObjectAttribute
;------------------------------------------------------------------------------
; $EA44
; Set attribute bit $20.
;------------------------------------------------------------------------------
SetTransitionObjectAttributeBit:
lda #$20
ora $0292,x
sta $0292,x
bne CheckTransitionObjectWrap
;------------------------------------------------------------------------------
; $EA4E
; Clear/set attribute according to coordinate range.
;
; If value < $40, branch backward to $EA44 and set bit $20.
; Otherwise clear bit $20 with mask $DF.
;------------------------------------------------------------------------------
UpdateTransitionObjectAttribute:
cmp #$40
bcc SetTransitionObjectAttributeBit
lda #$DF
and $0292,x
sta $0292,x
;------------------------------------------------------------------------------
; $EA5A
; Coordinate wrap/reset test.
;------------------------------------------------------------------------------
CheckTransitionObjectWrap:
ldy #$00
lda ($02),y
cmp #$FC
bcc CheckTransitionObjectLowBoundary
;------------------------------------------------------------------------------
; $EA62
; Reset coordinate/movement state.
;------------------------------------------------------------------------------
ResetTransitionObjectPosition:
lda #$00
sta ($02),y
sta ($00),y
iny
sta ($00),y
iny
sta ($02),y
bne AdvanceTransitionObjectPointers
;------------------------------------------------------------------------------
; $EA70
; Values below $04 also take the reset path.
;------------------------------------------------------------------------------
CheckTransitionObjectLowBoundary:
cmp #$04
bcc ResetTransitionObjectPosition
;------------------------------------------------------------------------------
; $EA74
; Advance both source and mirrored pointers.
;------------------------------------------------------------------------------
AdvanceTransitionObjectPointers:
lda $00
clc
adc #$04
sta $00
lda $01
adc #$00
sta $01
lda $02
clc
adc #$04
sta $02
lda $03
adc #$00
sta $03
inx
cpx #$05
bne UpdateNextTransitionObject
rts
;==============================================================================
; $EA94-$EAA2: Gameplay Animation State Dispatcher
;
; $87 is an even state value used directly as an offset into the pointer table
; at $EAA3.
;==============================================================================
DispatchTransitionAnimation:
ldy $87
lda TransitionAnimationHandlerTable,y
sta $10
lda TransitionAnimationHandlerTable+1,y
sta $11
jmp ($0010)
;==============================================================================
; $EAA3-$EAA8: Gameplay Animation State Table
;==============================================================================
TransitionAnimationHandlerTable:
.word DispatchTransitionAnimationPhase
.word DispatchTransitionAnimationState2
.word DispatchTransitionAnimationState4
;==============================================================================
; $EAA9-$EAB7: Animation Substate Dispatcher
;
; $88 is an even state value used directly as an offset into this table.
;==============================================================================
DispatchTransitionAnimationPhase:
ldy $88
lda TransitionAnimationPhaseTable,y
sta $10
lda TransitionAnimationPhaseTable+1,y
sta $11
jmp ($0010)
;==============================================================================
; $EAB8-$EABF: Animation Substate Table
;==============================================================================
TransitionAnimationPhaseTable:
.word UpdateTransitionAnimationFrame
.word UpdateTransitionAnimationType1
.word UpdateTransitionAnimationType2
.word UpdateTransitionAnimationPattern
;==============================================================================
; $EAC0-$EACD: Animation Substate 0
;
; Advances $B7 modulo eight and uses it to select a value for $028D.
;==============================================================================
UpdateTransitionAnimationFrame:
inc $B7
lda $B7
and #$07
tay
lda TransitionAnimationFrameTable,y
sta $028D
rts
;==============================================================================
; $EACE-$EAD5: Animation frame table
;==============================================================================
TransitionAnimationFrameTable:
.byte $04,$04,$04,$05
.byte $05,$04,$01,$01
;==============================================================================
; $EAD6-$EAEC: Animation Substate 1
;
; Runs only when ($4B & 7) == 0. Alternates $028C between $0C and $0D
; according to frame-counter bit 3, then falls back into the common animation
; update path beginning at $EAC0.
;==============================================================================
UpdateTransitionAnimationType1:
lda $4B
and #$07
bne UpdateTransitionAnimationFrame
lda $4B
and #$08
bne AnimationSubstate_EAE7
lda #$0C
bne AnimationSubstate_EAE9
AnimationSubstate_EAE7:
lda #$0D
AnimationSubstate_EAE9:
sta $028C
bne UpdateTransitionAnimationFrame
;==============================================================================
; $EAED-$EB03: Animation Substate 2
;
; Updates only once every eight frames and alternates $028C between $1E/$1F
; according to frame-counter bit 3.
;==============================================================================
UpdateTransitionAnimationType2:
lda $4B
and #$07
beq AnimationSubstate_EAF5
rts
AnimationSubstate_EAF5:
lda $4B
and #$08
bne AnimationSubstate_EB00
lda #$1E
bne AnimationSubstate_EB02
AnimationSubstate_EB00:
lda #$1F
AnimationSubstate_EB02:
sta $028C
rts
;==============================================================================
; $EB04-$EB28: Animation Substate 3
;
; Advances $B7 modulo eight, maps the result through a small index table, and
; uses the selected entry to load four consecutive animation values into
; $028D-$0290.
;
; The final BNE intentionally branches backward into the common update path.
;==============================================================================
UpdateTransitionAnimationPattern:
inc $B7
lda $B7
and #$07
tay
lda TransitionAnimationPatternIndex,y
tay
lda TransitionAnimationPatternTiles,y
sta $028D
lda TransitionAnimationPatternTiles+1,y
sta $028E
lda TransitionAnimationPatternTiles+2,y
sta $028F
lda TransitionAnimationPatternTiles+3,y
sta $0290
bne UpdateTransitionAnimationType2
;==============================================================================
; $EB29-$EB30: Animation pattern index table
;==============================================================================
TransitionAnimationPatternIndex:
.byte $00,$00,$00,$04
.byte $04,$00,$08,$08
;==============================================================================
; $EB31-$EB3C: Animation pattern data
;
; Indexed using offsets 0, 4 and 8. The final logical group continues beyond
; this initial range, so preserve the next code/data boundary carefully.
;==============================================================================
TransitionAnimationPatternTiles:
.byte $3C,$3D,$3E,$3F
.byte $3C,$40,$3E,$41
.byte $38,$39,$3A,$3B
;==============================================================================
; $EB3D-$EB4B: Gameplay Animation State $02 Dispatcher
;==============================================================================
DispatchTransitionAnimationState2:
ldy $88
lda TransitionAnimationState2Table,y
sta $10
lda TransitionAnimationState2Table+1,y
sta $11
jmp ($0010)
;==============================================================================
; $EB4C-$EB51: Gameplay Animation State $02 Pointer Table
;==============================================================================
TransitionAnimationState2Table:
.word UpdateTransitionAnimationFrame
.word UpdateTransitionAnimationState2
.word UpdateTransitionAnimationFrame
;==============================================================================
; $EB52-$EB6C: Animation State $02 Handler
;==============================================================================
UpdateTransitionAnimationState2:
jsr UpdateTransitionAnimationType1
lda $0274
cmp #$7D
bne AnimationState2_EB62
lda #$43
sta $028E
rts
AnimationState2_EB62:
cmp #$7B
beq AnimationState2_EB68
rts
AnimationState2_EB68:
lda #$44
sta $028E
rts
;==============================================================================
; $EB6D-$EB7B: Gameplay Animation State $04 Dispatcher
;==============================================================================
DispatchTransitionAnimationState4:
ldy $88
lda TransitionAnimationState4Table,y
sta $10
lda TransitionAnimationState4Table+1,y
sta $11
jmp ($0010)
;==============================================================================
; $EB7C-$EB83: Gameplay Animation State $04 Pointer Table
;==============================================================================
TransitionAnimationState4Table:
.word UpdateTransitionAnimationFrame
.word UpdateTransitionAnimationState4Type1
.word UpdateTransitionAnimationState4Type2
.word UpdateTransitionAnimationState4Type2
;==============================================================================
; $EB84-$EB9E: Animation State $04 Handler 1
;
; Alternates $028C between $48/$49 every eight frames, while non-update
; frames tail-jump into the common $EAC0 animation updater.
;==============================================================================
UpdateTransitionAnimationState4Type1:
lda $4B
and #$07
beq AnimationState4_EB8E
jmp UpdateTransitionAnimationFrame
AnimationState4_EB8E:
lda $4B
and #$08
bne AnimationState4_EB97
lda #$48
bne AnimationState4_EB99
AnimationState4_EB97:
lda #$49
AnimationState4_EB99:
sta $028C
jmp UpdateTransitionAnimationFrame
;==============================================================================
; $EB9F-$EBB5: Animation State $04 Handler 2/3
;
; Alternates $028C between $4A and $4B every eight frames.
;==============================================================================
UpdateTransitionAnimationState4Type2:
lda $4B
and #$07
beq AnimationState4_EBA6
rts
AnimationState4_EBA6:
lda $4B
and #$08
bne AnimationState4_EBB0
lda #$4A
bne AnimationState4_EBB2
AnimationState4_EBB0:
lda #$4B
AnimationState4_EBB2:
sta $028C
rts
;==============================================================================
; $EBB6-$EC3F: Gameplay Lookup Data
;
; Raw lookup data preserved byte-for-byte.
; Semantic subdivision is deferred until the consuming routines are decoded.
;==============================================================================
MazeGameplayData_EBB6:
; $EBB6-$EBC5
.byte $00,$07,$00,$00,$00,$04,$01,$06
.byte $01,$01,$01,$04,$01,$04,$02,$02
; $EBC6-$EBD5
.byte $02,$03,$02,$03,$02,$02,$02,$03
.byte $02,$02,$02,$02,$03,$03,$03,$05
; $EBD6-$EBE5
.byte $03,$02,$03,$03,$03,$02,$03,$02
.byte $04,$03,$03,$02,$03,$02,$04,$03
; $EBE6-$EBF5
.byte $03,$01,$04,$02,$05,$03,$03,$05
.byte $04,$02,$05,$03,$03,$02,$04,$02
; $EBF6-$EC05
.byte $06,$03,$03,$01,$05,$02,$06,$03
.byte $03,$01,$05,$02,$07,$03,$03,$03
; $EC06-$EC15
.byte $05,$02,$07,$03,$03,$01,$06,$02
.byte $07,$03,$03,$01,$06,$02,$07,$03
; $EC16-$EC25
.byte $03,$00,$06,$02,$07,$03,$03,$01
.byte $06,$02,$07,$03,$03,$00,$06,$02
; $EC26-$EC35
.byte $07,$03,$03,$00,$06,$02,$07,$03
.byte $03,$00,$06,$02,$07,$03,$04,$00
; $EC36-$EC3F
.byte $06,$02,$07,$03,$04,$00,$06,$02
.byte $07,$03
;==============================================================================
; $EC40-$ECAB: Gameplay Parameter Lookup Data
;
; Raw lookup data preserved byte-for-byte.
;
; The byte pattern strongly resembles little-endian 16-bit quantities, but
; semantic grouping is intentionally deferred until the consuming routines
; have been identified.
;==============================================================================
MazeGameplayData_EC40:
; $EC40-$EC4F
.byte $D0,$00,$C0,$00,$A0,$00,$B0,$00
.byte $A0,$00,$80,$00,$A0,$00,$B0,$00
; $EC50-$EC5F
.byte $90,$00,$50,$00,$50,$00,$D0,$00
.byte $C0,$00,$A0,$00,$B0,$00,$A0,$00
; $EC60-$EC6F
.byte $80,$00,$B0,$00,$C0,$00,$A0,$00
.byte $50,$00,$50,$00,$F0,$00,$E0,$00
; $EC70-$EC7F
.byte $C0,$00,$E0,$00,$D0,$00,$B0,$00
.byte $E0,$00,$F0,$00,$D0,$00,$70,$00
; $EC80-$EC8F
.byte $70,$00,$00,$01,$F0,$00,$D0,$00
.byte $00,$01,$F0,$00,$D0,$00,$00,$01
; $EC90-$EC9F
.byte $10,$01,$F0,$00,$80,$00,$80,$00
.byte $00,$01,$F0,$00,$D0,$00,$00,$01
; $ECA0-$ECAB
.byte $F0,$00,$D0,$00,$20,$01,$30,$01
.byte $10,$01,$80,$00
;==============================================================================
; $ECAC-$ECFF: Gameplay Lookup / Parameter Data
;
; Raw data preserved byte-for-byte. Semantic subdivision is intentionally
; deferred until the consuming routines are decoded.
;==============================================================================
MazeGameplayData_ECAC:
; $ECAC-$ECBB
.byte $80,$00,$07,$14,$07,$14,$05,$14
.byte $05,$FF,$07,$14,$07,$14,$05,$FF
; $ECBC-$ECCB
.byte $00,$00,$05,$14,$05,$14,$05,$FF
.byte $00,$00,$05,$14,$05,$14,$05,$FF
; $ECCC-$ECDB
.byte $00,$00,$14,$0A,$1E,$0F,$28,$14
.byte $32,$19,$3C,$1E,$50,$28,$64,$32
; $ECDC-$ECEB
.byte $02,$1E,$5A,$00,$03,$32,$00,$00
.byte $04,$00,$00,$00,$01,$07,$11,$20
; $ECEC-$ECFB
.byte $2D,$1F,$D0,$D0,$10,$13,$D0,$D0
.byte $10,$1D,$2D,$11,$C3,$C3,$03,$11
; $ECFC-$ECFF
.byte $C3,$C3,$03,$11
;==============================================================================
; $ED00-$EDFF: Gameplay Lookup / Pattern Data
;
; Raw data preserved byte-for-byte. This region contains highly structured
; repeated values and bit-pattern-like fields, but semantic subdivisions are
; deferred until the routines that consume this region are identified.
;==============================================================================
MazeLayoutData_ED00:
; $ED00-$ED0F
.byte $2D,$11,$03,$1F,$10,$1D,$03,$1F
.byte $10,$1D,$03,$11,$03,$1F,$10,$1D
; $ED10-$ED1F
.byte $03,$1F,$10,$1D,$03,$11,$2D,$11
.byte $01,$11,$20,$11,$03,$11,$20,$11
; $ED20-$ED2F
.byte $03,$11,$03,$11,$20,$11,$03,$11
.byte $20,$11,$01,$11,$2D,$11,$03,$1E
; $ED30-$ED3F
.byte $10,$1C,$03,$1E,$10,$1C,$03,$1A
.byte $03,$1E,$10,$1C,$03,$1E,$10,$1C
; $ED40-$ED4F
.byte $03,$11,$2D,$11,$C3,$C3,$C3,$C3
.byte $83,$11,$2D,$11,$03,$1F,$10,$1D
; $ED50-$ED5F
.byte $03,$1B,$03,$1F,$10,$10,$10,$1D
.byte $03,$1B,$03,$1F,$10,$1D,$03,$11
; $ED60-$ED6F
.byte $2D,$11,$03,$1E,$10,$1C,$03,$11
.byte $03,$1E,$10,$13,$10,$1C,$03,$11
; $ED70-$ED7F
.byte $03,$1E,$10,$1C,$03,$11,$2D,$11
.byte $C3,$03,$11,$83,$11,$83,$11,$C3
; $ED80-$ED8F
.byte $03,$11,$2D,$1E,$90,$1D,$03,$15
.byte $10,$18,$08,$1A,$08,$19,$10,$14
; $ED90-$ED9F
.byte $03,$1F,$90,$1C,$2D,$E0,$11,$03
.byte $11,$C0,$80,$11,$03,$11,$E0,$2D
; $EDA0-$EDAF
.byte $E0,$11,$03,$11,$00,$1F,$17,$2C
.byte $16,$1D,$00,$11,$03,$11,$E0,$2D
; $EDB0-$EDBF
.byte $22,$90,$1C,$03,$1A,$00,$11,$80
.byte $11,$00,$1A,$03,$1E,$90,$21,$04
; $EDC0-$EDCF
.byte $06,$C0,$03,$40,$11,$80,$11,$40
.byte $03,$C0,$05,$2D,$22,$90,$1D,$03
; $EDD0-$EDDF
.byte $1B,$00,$1E,$90,$1C,$00,$1B,$03
.byte $1F,$90,$21,$2D,$E0,$11,$03,$11
; $EDE0-$EDEF
.byte $C0,$80,$11,$03,$11,$E0,$2D,$E0
.byte $11,$03,$11,$00,$1F,$90,$1D,$00
; $EDF0-$EDFF
.byte $11,$03,$11,$E0,$2D,$1F,$90,$1C
.byte $03,$1A,$00,$1E,$10,$13,$10,$1C
;==============================================================================
; $EE00-$EE8B: Gameplay Lookup / Pattern Data
;
; Raw data preserved byte-for-byte.
;==============================================================================
MazeLayoutData_EE00:
; $EE00-$EE0F
.byte $00,$1A,$03,$1E,$90,$1D,$2D,$11
.byte $C3,$C3,$03,$11,$C3,$C3,$03,$11
; $EE10-$EE1F
.byte $2D,$11,$03,$19,$10,$1D,$03,$19
.byte $10,$18,$09,$1A,$09,$19,$10,$18
; $EE20-$EE2F
.byte $03,$1F,$10,$18,$03,$11,$2D,$11
.byte $01,$43,$11,$C3,$03,$00,$C3,$03
; $EE30-$EE3F
.byte $11,$43,$01,$11,$2D,$15,$10,$1D
.byte $03,$11,$03,$1B,$03,$1F,$90,$1D
; $EE40-$EE4F
.byte $03,$1B,$03,$11,$03,$1F,$10,$14
.byte $2D,$15,$10,$1C,$03,$1A,$03,$11
; $EE50-$EE5F
.byte $03,$1E,$10,$13,$10,$1C,$03,$11
.byte $03,$1A,$03,$1E,$10,$14,$2D,$11
; $EE60-$EE6F
.byte $C3,$03,$11,$83,$11,$83,$11,$C3
.byte $03,$11,$2D,$11,$03,$19,$90,$12
; $EE70-$EE7F
.byte $10,$18,$03,$1A,$03,$19,$10,$12
.byte $90,$18,$03,$11,$2D,$11,$C3,$C3
; $EE80-$EE8B
.byte $C3,$C3,$83,$11,$2D,$1E,$D0,$D0
.byte $D0,$D0,$90,$1C
;==============================================================================
; $EE8C-$EECF: Initialize Runtime Work Buffers
;
; Initializes zero-page runtime pointers and hardware state, clears $0600-$060F,
; then clears sixteen locations at eight-byte intervals beginning at $0620.
;==============================================================================
InitializeAudioEngine:
lda #$00
sta audioRequestPtr
lda #$06
sta audioRequestPtr+1
lda #$20
sta audioChannelTablePtr
lda #$06
sta audioChannelTablePtr+1
lda $F100
sta audioSequenceTablePtr
lda $F101
sta audioSequenceTablePtr+1
lda #$40
sta audioWorkValue
lda #$1F
sta APUSTATUS
lda #$C0
sta APUFRAMECNT
ldy #$00
lda #$00
;------------------------------------------------------------------------------
; $EEB8
; Clear sixteen contiguous bytes through ($F0).
;------------------------------------------------------------------------------
RuntimeInit_Clear0600:
sta (audioRequestPtr),y
iny
cpy #$10
bne RuntimeInit_Clear0600
ldy #$00
ldx #$10
;------------------------------------------------------------------------------
; $EEC3
; Clear sixteen entries at an eight-byte stride.
;
; LDA #$00 is intentionally executed on every iteration.
;------------------------------------------------------------------------------
RuntimeInit_Clear0620Stride:
lda #$00
sta (audioChannelTablePtr),y
tya
clc
adc #$08
tay
dex
bne RuntimeInit_Clear0620Stride
rts
;==============================================================================
; $EED0-$EF2C: Initialize and Scan Runtime Command Slots
;
; Clears runtime work values, then scans sixteen 8-byte entries addressed
; through $F2/$F3. Occupied entries update flags through $FE/$FF, while newly
; activated entries copy four bytes through the pointer selected in $F6.
;
; Execution falls through at $EF2D into the next processing phase.
;==============================================================================
InitializeAudioChannels:
lda #$00
sta audioChannelFlags
sta $F9
sta $FA
sta $FB
sta audioChannelOffset
sta $FF
lda #$F8
sta audioIndirectPtr
;------------------------------------------------------------------------------
; $EEE2
; Examine one eight-byte slot.
;------------------------------------------------------------------------------
ScanAudioChannelSlot:
ldy $FD
lda (audioChannelTablePtr),y
beq AdvanceAudioChannelSlot
cmp #$05
bcc ActivateAudioChannelSlot
sec
sbc #$05
tay
lda #$01
sta ($FE),y
jmp AdvanceAudioChannelSlot
;------------------------------------------------------------------------------
; $EEF5
; Process slot values $01-$04.
;------------------------------------------------------------------------------
ActivateAudioChannelSlot:
sec
sbc #$01
tay
lda ($FE),y
bne AdvanceAudioChannelSlot
lda #$01
sta ($FE),y
tya
tax
adc #$04
ldy $FD
sta ($F2),y
txa
asl
asl
sta $F6
ldx #$00
;------------------------------------------------------------------------------
; $EF12
; Set initial four-byte copy count.
;------------------------------------------------------------------------------
BeginAudioChannelParameterCopy:
lda #$04
;------------------------------------------------------------------------------
; $EF14
; Push the current copy count.
;
; The loop at $EF20 deliberately returns here, so PHA is repeated on every
; iteration.
;------------------------------------------------------------------------------
CopyAudioChannelParameter:
pha
iny
lda ($F2),y
sta ($F6,x)
inc $F6
pla
sec
sbc #$01
bne CopyAudioChannelParameter
;------------------------------------------------------------------------------
; $EF22
; Advance to the next eight-byte slot.
;------------------------------------------------------------------------------
AdvanceAudioChannelSlot:
lda $FD
clc
adc #$08
sta $FD
cmp #$80
bcc ScanAudioChannelSlot
; Fall through to $EF2D.
;==============================================================================
; $EF2D-$F006: Process Runtime Command Entries
;
; Continuation of the runtime command-slot scan.
;
; $FC indexes source commands through ($F0).
; $FD indexes eight-byte runtime records through ($F2).
;
; Empty runtime records are initialized from tables through $F4/$F5 and
; command bytes fetched through the helper at $F0C3. Active records update
; countdowns and command parameters before both indices advance.
;==============================================================================
UpdateAudioChannels:
ldy #$00
sty $FC
sty $FD
;------------------------------------------------------------------------------
; $EF33
; Process one source entry.
;------------------------------------------------------------------------------
UpdateNextAudioSequence:
ldy $FC
lda ($F0),y
bne CheckAudioChannelActive
jmp AdvanceAudioSequence
;------------------------------------------------------------------------------
; $EF3B
; Check the corresponding runtime record.
;------------------------------------------------------------------------------
CheckAudioChannelActive:
ldy $FD
lda ($F2),y
bne TickAudioChannelDelay
lda $FC
asl
tay
lda ($F4),y
pha
lda $FD
adc #$05
tay
pla
sta ($F2),y
lda $FC
asl
adc #$01
tay
lda ($F4),y
pha
lda $FD
adc #$06
tay
pla
sta ($F2),y
jsr $F0C3
ldy $FD
sta ($F2),y
jsr $F0C3
tax
lda $FD
clc
adc #$01
tay
txa
sta ($F2),y
jsr $F0C3
tax
lda $FD
clc
adc #$02
tay
txa
sta ($F2),y
jsr $F0C3
tax
lda $FD
clc
adc #$04
tay
txa
sta ($F2),y
jmp DecodeAudioCommand
;------------------------------------------------------------------------------
; $EF94
; Decrement the active record countdown at offset 7.
;------------------------------------------------------------------------------
TickAudioChannelDelay:
lda $FD
clc
adc #$07
tay
lda ($F2),y
sec
sbc #$01
sta ($F2),y
bne AdvanceAudioSequence
;------------------------------------------------------------------------------
; $EFA2
; Fetch and decode the next runtime command byte.
;------------------------------------------------------------------------------
DecodeAudioCommand:
jsr $F0C3
cmp #$F0
bcs $F00D
cmp #$C0
bcs LoadAudioCommandDelay
pha
and #$F0
lsr
lsr
lsr
tax
lda AudioFrequencyLookupTable,x
sta $FE
lda AudioFrequencyLookupTable+1,x
sta $FF
pla
and #$0F
beq ApplyAudioFrequencyMask
tax
;------------------------------------------------------------------------------
; $EFC4
; Shift the selected mask by the command low nibble.
;------------------------------------------------------------------------------
ShiftAudioFrequencyMask:
lsr $FE
ror $FF
dex
bne ShiftAudioFrequencyMask
;------------------------------------------------------------------------------
; $EFCB
; Merge the generated mask into the runtime record.
;------------------------------------------------------------------------------
ApplyAudioFrequencyMask:
lda $FD
clc
adc #$04
tay
lda ($F2),y
and #$F8
ora $FE
sta ($F2),y
lda $FF
dey
sta ($F2),y
ldy $FD
lda ($F2),y
cmp #$05
bcc LoadAudioCommandDelay
sec
sbc #$04
sta ($F2),y
;------------------------------------------------------------------------------
; $EFEB
; Load the new runtime countdown.
;------------------------------------------------------------------------------
LoadAudioCommandDelay:
jsr $F0C3
pha
lda $FD
clc
adc #$07
tay
pla
sta ($F2),y
;------------------------------------------------------------------------------
; $EFF8
; Advance to the next runtime/source entry.
;------------------------------------------------------------------------------
AdvanceAudioSequence:
lda $FD
clc
adc #$08
sta $FD
lda $FC
adc #$01
sta $FC
cmp #$10
; Fall through at $F007 to the next control-flow block.
;==============================================================================
; $F007-$F00C: Runtime command-loop termination / continuation
;
; Entry is the fall-through from the CMP #$10 at $F005.
;==============================================================================
FinishAudioSequenceScan:
bcs AudioSequenceUpdateDone
jmp UpdateNextAudioSequence
AudioSequenceUpdateDone:
rts
;==============================================================================
; $F00D-$F01D: Runtime Command Dispatcher
;
; Input:
; A = command byte >= $F0
;
; Uses the low nibble to select a handler through the pointer table at $F01E.
;==============================================================================
DispatchAudioCommand:
and #$0F
asl
tax
lda AudioCommandHandlerTable,x
sta $FE
lda AudioCommandHandlerTable+1,x
sta $FF
jmp ($00FE)
;==============================================================================
; $F01E-$F03D: Runtime Command Handler Table
;
; Sixteen 16-bit handlers. Unsupported command values use the no-op handler
; at $F03E.
;==============================================================================
AudioCommandHandlerTable:
.word StopAudioSequence ; $F0
.word AudioCommandF1 ; $F1
.word AudioCommandF2 ; $F2
.word AudioCommandF3 ; $F3
.word AudioCommandF4 ; $F4
.word AudioCommandF5 ; $F5
.word AudioCommandF6 ; $F6
.word StopAudioSequence ; $F7
.word StopAudioSequence ; $F8
.word StopAudioSequence ; $F9
.word StopAudioSequence ; $FA
.word StopAudioSequence ; $FB
.word StopAudioSequence ; $FC
.word StopAudioSequence ; $FD
.word StopAudioSequence ; $FE
.word StopAudioSequence ; $FF
;==============================================================================
; $F03E-$F04A: Runtime command no-op/terminate handler
;
; Clears the current source entry and current runtime record, then advances
; through the common continuation at $EFF8.
;==============================================================================
StopAudioSequence:
ldy $FC
lda #$00
sta ($F0),y
ldy $FD
sta ($F2),y
jmp AdvanceAudioSequence
;==============================================================================
; $F04B-$F062: Runtime command $F1
;
; Fetches one command byte and merges its bits into runtime-record byte 0/1.
;==============================================================================
AudioCommandF1:
jsr FetchAudioSequenceByte
pha
lda $FD
clc
adc #$01
tay
lda ($F2),y
and #$3F
sta ($F2),y
pla
ora ($F2),y
sta ($F2),y
jmp DecodeAudioCommand
;==============================================================================
; $F063-$F07A: Runtime command $F2
;==============================================================================
AudioCommandF2:
jsr FetchAudioSequenceByte
pha
lda $FD
clc
adc #$01
tay
lda ($F2),y
and #$CF
sta ($F2),y
pla
ora ($F2),y
sta ($F2),y
jmp DecodeAudioCommand
;==============================================================================
; $F07B-$F092: Runtime command $F3
;==============================================================================
AudioCommandF3:
jsr FetchAudioSequenceByte
pha
lda $FD
clc
adc #$01
tay
lda ($F2),y
and #$F0
sta ($F2),y
pla
ora ($F2),y
sta ($F2),y
jmp DecodeAudioCommand
;==============================================================================
; $F093-$F0A2: Runtime command $F4
;==============================================================================
AudioCommandF4:
jsr FetchAudioSequenceByte
pha
lda $FD
clc
adc #$02
tay
pla
sta ($F2),y
jmp DecodeAudioCommand
;==============================================================================
; $F0A3-$F0B2: Runtime command $F5
;==============================================================================
AudioCommandF5:
jsr FetchAudioSequenceByte
pha
lda $FD
clc
adc #$04
tay
pla
sta ($F2),y
jmp DecodeAudioCommand
;==============================================================================
; $F0B3-$F0C2: Runtime command $F6
;==============================================================================
AudioCommandF6:
jsr FetchAudioSequenceByte
pha
lda $FD
clc
adc #$01
tay
pla
sta ($F2),y
jmp DecodeAudioCommand
;==============================================================================
; $F0C3-$F0E7: Fetch Runtime Command Byte
;
; Uses the 16-bit source pointer stored in runtime-record bytes +5/+6.
;
; Output:
; A = byte read from the command stream
;
; Side effect:
; Advances the source pointer by one and writes it back into bytes +5/+6.
;==============================================================================
FetchAudioSequenceByte:
lda $FD
clc
adc #$05
tay
lda ($F2),y
sta $FE
iny
lda ($F2),y
sta $FF
ldx #$00
lda ($FE,x)
pha
lda $FE
dey
clc
adc #$01
sta ($F2),y
lda $FF
adc #$00
iny
sta ($F2),y
pla
rts
;==============================================================================
; $F0E8-$F14F: Runtime / Audio Lookup Data
;
; Raw data preserved byte-for-byte.
;
; The first portion is referenced by UpdateAudioChannels through
; $F0E8,X / $F0E9,X as paired mask values. Later portions appear to contain
; additional pointers and command/parameter data.
;
; Semantic subdivision is deferred until all consumers are decoded.
;==============================================================================
AudioFrequencyLookupTable:
; $F0E8-$F0F7
.byte $03,$F9,$03,$C0,$03,$8A,$03,$57
.byte $03,$27,$02,$FA,$02,$CF,$02,$A7
; $F0F8-$F107
.byte $02,$81,$02,$5D,$02,$3B,$02,$1B
.byte $02,$F1,$CB,$F3,$14,$F4,$22,$F1
; $F108-$F117
.byte $3C,$F3,$3F,$F4,$50,$F4,$37,$F1
.byte $E4,$F1,$1D,$F3,$CF,$F1,$62,$F1
; $F118-$F127
.byte $8D,$F1,$B0,$F1,$07,$F2,$7A,$F2
.byte $61,$F4,$01,$03,$7F,$08,$A2,$0C
; $F128-$F137
.byte $A2,$0C,$A2,$0C,$A2,$0C,$A2,$0C
.byte $A2,$0C,$A2,$0C,$A2,$0C,$F0,$03
; $F138-$F147
.byte $04,$7F,$08,$33,$01,$23,$01,$03
.byte $01,$A2,$01,$92,$01,$72,$01,$52
; $F148-$F14F
.byte $01,$32,$01,$22,$01,$02,$01,$A1
;==============================================================================
; $F150-$F24F: Runtime / Audio Command Data
;
; Raw data preserved byte-for-byte.
;
; The region contains strongly patterned pairs and command-like values used
; by the late-PRG runtime/audio interpreter. Semantic subdivision is deferred
; until its individual consumers are mapped.
;==============================================================================
AudioSequenceData_F150:
; $F150-$F15F
.byte $01,$91,$01,$C0,$08,$91,$01,$A1
.byte $01,$02,$01,$22,$01,$32,$01,$52
; $F160-$F16F
.byte $01,$F0,$01,$96,$7F,$28,$82,$01
.byte $92,$01,$A2,$01,$B2,$01,$03,$01
; $F170-$F17F
.byte $13,$01,$23,$01,$33,$01,$43,$01
.byte $53,$01,$33,$01,$23,$01,$13,$01
; $F180-$F18F
.byte $03,$01,$B2,$01,$A2,$01,$92,$01
.byte $82,$01,$72,$01,$F0,$01,$96,$7F
; $F190-$F19F
.byte $28,$A2,$01,$B2,$01,$03,$01,$13
.byte $01,$23,$01,$33,$01,$43,$01,$53
; $F1A0-$F1AF
.byte $01,$33,$01,$23,$01,$13,$01,$03
.byte $01,$B2,$01,$A2,$01,$92,$01,$F0
; $F1B0-$F1BF
.byte $01,$96,$7F,$28,$B2,$01,$03,$01
.byte $13,$01,$23,$01,$33,$01,$43,$01
; $F1C0-$F1CF
.byte $53,$01,$33,$01,$23,$01,$13,$01
.byte $03,$01,$B2,$01,$A2,$01,$F0,$02
; $F1D0-$F1DF
.byte $9C,$7F,$28,$31,$01,$71,$01,$A1
.byte $01,$12,$01,$32,$01,$72,$01,$A2
; $F1E0-$F1EF
.byte $01,$13,$01,$F0,$02,$00,$7F,$10
.byte $20,$03,$50,$03,$80,$02,$A0,$02
; $F1F0-$F1FF
.byte $21,$02,$51,$02,$81,$01,$A1,$01
.byte $02,$01,$22,$01,$32,$01,$52,$01
; $F200-$F20F
.byte $F5,$18,$73,$01,$C0,$08,$F0,$01
.byte $03,$7F,$08,$72,$02,$82,$08,$72
; $F210-$F21F
.byte $02,$82,$08,$72,$02,$82,$08,$42
.byte $02,$52,$04,$32,$04,$82,$06,$82
; $F220-$F22F
.byte $08,$B2,$02,$03,$10,$C0,$08,$72
.byte $02,$82,$08,$72,$02,$82,$08,$72
; $F230-$F23F
.byte $02,$82,$08,$42,$02,$52,$04,$32
.byte $04,$82,$06,$82,$08,$42,$02,$52
; $F240-$F24F
.byte $10,$C0,$08,$72,$02,$82,$08,$72
.byte $02,$82,$08,$72,$02,$82,$08,$42
;==============================================================================
; $F250-$F34F: Runtime / Audio Command Data
;
; Raw data preserved byte-for-byte.
; Semantic subdivision is deferred until the individual stream consumers are
; fully mapped.
;==============================================================================
AudioSequenceData_F250:
; $F250-$F25F
.byte $02,$52,$04,$32,$04,$82,$06,$82
.byte $08,$A2,$02,$B2,$08,$03,$02,$13
; $F260-$F26F
.byte $0A,$13,$02,$23,$0C,$03,$02,$13
.byte $08,$A2,$02,$B2,$08,$82,$0A,$A2
; $F270-$F27F
.byte $02,$B2,$0A,$72,$02,$82,$10,$C0
.byte $08,$F0,$02,$81,$7F,$40,$80,$08
; $F280-$F28F
.byte $C0,$06,$80,$04,$C0,$02,$80,$08
.byte $C0,$06,$80,$04,$C0,$02,$80,$08
; $F290-$F29F
.byte $C0,$06,$80,$04,$C0,$02,$01,$04
.byte $C0,$01,$11,$04,$C0,$01,$21,$04
; $F2A0-$F2AF
.byte $C0,$01,$31,$04,$C0,$01,$80,$08
.byte $C0,$06,$80,$04,$C0,$02,$80,$08
; $F2B0-$F2BF
.byte $C0,$06,$80,$04,$C0,$02,$80,$08
.byte $C0,$06,$80,$04,$C0,$02,$01,$04
; $F2C0-$F2CF
.byte $C0,$01,$11,$04,$C0,$01,$21,$04
.byte $C0,$01,$31,$04,$C0,$01,$80,$08
; $F2D0-$F2DF
.byte $C0,$06,$80,$04,$C0,$02,$80,$08
.byte $C0,$06,$80,$04,$C0,$02,$80,$08
; $F2E0-$F2EF
.byte $C0,$06,$80,$04,$C0,$02,$01,$04
.byte $C0,$01,$11,$04,$C0,$01,$21,$04
; $F2F0-$F2FF
.byte $C0,$01,$31,$04,$C0,$01,$81,$0A
.byte $31,$04,$C0,$01,$21,$04,$C0,$01
; $F300-$F30F
.byte $11,$04,$C0,$01,$B0,$04,$C0,$01
.byte $80,$04,$C0,$01,$70,$04,$C0,$01
; $F310-$F31F
.byte $60,$08,$C0,$02,$70,$08,$C0,$02
.byte $80,$0C,$C0,$06,$F0,$02,$98,$7F
; $F320-$F32F
.byte $28,$33,$01,$23,$01,$13,$01,$03
.byte $01,$A2,$01,$82,$01,$72,$01,$52
; $F330-$F33F
.byte $01,$32,$01,$12,$01,$A1,$01,$71
.byte $01,$31,$01,$F0,$01,$42,$7F,$38
; $F340-$F34F
.byte $23,$02,$33,$02,$43,$02,$53,$02
.byte $43,$02,$33,$02,$23,$02,$13,$02
;==============================================================================
; $F350-$F44F: Runtime / Audio Command Data
;
; Raw command/parameter stream preserved byte-for-byte.
; Semantic subdivision is deferred until all stream consumers are mapped.
;==============================================================================
AudioSequenceData_F350:
; $F350-$F35F
.byte $23,$02,$33,$02,$43,$02,$33,$02
.byte $23,$02,$13,$02,$03,$02,$13,$02
; $F360-$F36F
.byte $23,$02,$33,$02,$23,$02,$13,$02
.byte $03,$02,$B2,$02,$03,$02,$13,$02
; $F370-$F37F
.byte $23,$02,$13,$02,$03,$02,$B2,$02
.byte $A2,$02,$B2,$02,$03,$02,$23,$02
; $F380-$F38F
.byte $03,$02,$B2,$02,$A2,$02,$92,$02
.byte $A2,$02,$B2,$02,$03,$02,$B2,$02
; $F390-$F39F
.byte $A2,$02,$92,$02,$82,$02,$92,$02
.byte $A2,$02,$03,$02,$A2,$02,$92,$02
; $F3A0-$F3AF
.byte $82,$02,$F2,$00,$F3,$00,$F5,$18
.byte $31,$01,$71,$01,$B1,$01,$32,$01
; $F3B0-$F3BF
.byte $72,$01,$B2,$01,$33,$01,$73,$01
.byte $C0,$02,$31,$01,$71,$01,$B1,$01
; $F3C0-$F3CF
.byte $32,$01,$72,$01,$B2,$01,$33,$01
.byte $73,$01,$F0,$01,$81,$7F,$40,$31
; $F3D0-$F3DF
.byte $08,$32,$08,$A1,$08,$71,$08,$32
.byte $07,$A1,$07,$71,$0C,$C0,$06,$41
; $F3E0-$F3EF
.byte $08,$42,$08,$B1,$08,$81,$08,$42
.byte $07,$B1,$07,$81,$0C,$C0,$06,$31
; $F3F0-$F3FF
.byte $08,$32,$08,$A1,$08,$71,$08,$32
.byte $07,$A1,$07,$71,$0C,$C0,$06,$61
; $F400-$F40F
.byte $05,$71,$05,$81,$05,$81,$05,$91
.byte $05,$A1,$05,$A1,$05,$B1,$05,$02
; $F410-$F41F
.byte $05,$32,$0C,$F0,$02,$82,$7F,$40
.byte $30,$18,$A0,$08,$30,$0E,$A0,$0C
; $F420-$F42F
.byte $C0,$06,$40,$18,$B0,$08,$40,$0E
.byte $B0,$0C,$C0,$06,$30,$18,$A0,$08
; $F430-$F43F
.byte $30,$0E,$A0,$0C,$C0,$06,$A0,$0F
.byte $01,$0F,$21,$0F,$31,$0C,$F0,$01
; $F440-$F44F
.byte $9F,$7F,$28,$01,$01,$41,$01,$71
.byte $01,$A1,$01,$02,$01,$F5,$18,$F0
;==============================================================================
; $F450-$F49A: Runtime / Audio Command Data
;
; Raw command data preserved byte-for-byte.
;==============================================================================
AudioSequenceData_F450:
; $F450-$F45F
.byte $01,$9F,$7F,$28,$02,$01,$A1,$01
.byte $71,$01,$41,$01,$F5,$18,$01,$01
; $F460-$F46F
.byte $F0,$01,$9F,$7F,$28,$52,$01,$92
.byte $01,$03,$01,$33,$01,$F5,$18,$53
; $F470-$F47F
.byte $03,$F5,$28,$53,$01,$33,$01,$03
.byte $01,$92,$01,$F5,$18,$52,$03,$92
; $F480-$F48F
.byte $01,$13,$01,$43,$01,$73,$01,$F5
.byte $18,$93,$0A,$F5,$28,$93,$01,$73
; $F490-$F49A
.byte $01,$43,$01,$13,$01,$F5,$18,$92
.byte $03,$F0,$FF
;==============================================================================
; $F49B-$F64F: PRG Padding
;==============================================================================
.res $0B5B, $FF
;==============================================================================
; $FFF6-$FFF9: Final ROM Data
;
; Notes:
; $FFF6/$FFF7 are read directly by DrawMazeNametable:
;
; lda $FFF7
; sta $01
; lda $FFF6
; sta $00
;
; Therefore the first two bytes form the little-endian value $ECEC.
;
; $FFF8/$FFF9 form the little-endian value $C10E.
;==============================================================================
MazeDataPointerBytes:
.byte $EC,$EC
.byte $0E,$C1
;==============================================================================
; $FFFA-$FFFF: Interrupt Vectors
;==============================================================================
.org $FFFA
CpuInterruptVectors:
.word NmiHandler ; NMI
.word Reset ; Reset
.word IrqHandler ; IRQ/BRK