# SegaSonic The Hedgehog (MAME set 'sonic', Japan rev. C) - V60 program symbols
#
# Addresses and names only: no game data lives in this file.
# Format:  ADDRESS  KIND  NAME  [LAYOUT]   [# comment]
#   KIND: func = function entry, code = code label, data = data label
# Addresses use the copy of each ROM chip the code refers to:
#   000000-01FFFF (ic17), 080000-09FFFF (ic8), 100000-17FFFF (ic18 + ic9)
# Mirrors are accepted and folded (e.g. FFFFF0 -> 09FFF0).
#
# LAYOUT, for data tables that hold code addresses the analysis cannot see the code load:
#   record=N        the table is an array of N-byte records
#   count=N         ... of N records, or
#   count=prefix    ... of as many records as the 16-bit word before them says, or
#   end=XXXX        ... closed by this 16-bit word where the next record would start
#   code=A,B        every record holds a 32-bit code address at these offsets (0 = no code);
#                   A:BASE is a 16-bit offset from BASE instead
#   lists=N         the table is N 16-bit offsets, from its own address, to such arrays
#   pointers=N      the table is N 32-bit addresses of such arrays
# Sizes, offsets and counts are decimal; end and BASE are hexadecimal, like the data and addresses.
#
# FILE lines lay the functions out in the files of src/ (tools/decompile.py source_files):
#   ADDRESS  file  PATH   # what the stretch holds
# the functions from ADDRESS up to the next file line go to src/PATH.c (PATH has a folder: player/states).
# A file may take several stretches; its functions go in the order of their addresses, and the comment
# of its first line is its description (the Description of the file's header).

# ======== source files: the files of src/ and the stretches of the program each takes (decompile.source_files) ========
# The files by subject: system/, video/, engine/, stage/, player/, objects/ (one a stage), scenes/. A
# stretch is a run of functions the original's linker laid out together; where a module of the
# original ends (its tables after the code, or a 00 byte that aligns the next routine: the *_padding
# data lines), a stretch ends. A routine the linker left among another subject's has its own stretch.
000000  file  system/main              # the machine's main program: power-on and boot, the frame loop, the attract mode and the system modes, the frame wait and the controls, the interrupt handlers, the reset vector
000652  file  stage/game_modes         # the game modes (run_game_mode) and what a stage frame runs: starting a stage, layer blending, the stage clear check, the player summary, stage slots and events, restart points, the player objects, the GAME OVER banner and the demo prompt
0018B6  file  engine/contacts          # contacts between objects and players: the zones, the hitter, touchable, rider, platform and wall lists, and the checks that pair them
00196E  file  stage/game_modes         # the stage clear check, the player summary, stage slots and events, restart points, the player objects
001DDD  file  engine/contacts          # the contact lists and their checks
002342  file  stage/game_modes         # the GAME OVER banner and the demo prompt
0027F2  file  player/player            # the player: starting, joining, resetting and placing a player, player_update, the movement (screen edges, trackball, turning, jumps, walls), the shadow, the floor checks, the restart point, the checks of every frame, and check_player_contacts (what touching each kind of object does)
002DEC  file  player/states            # the player's states in play: standing, walking, running, falling and jumping (player_move, player_animate), teetering on a ledge and going over it, dropping, falling into a pit, climbing a wall, skidding, hopping, dying, launched, hurt, bouncing
005E24  file  player/scene_states      # the player's states in scenes and stage events: out of the game, continue, entering and leaving a stage, tumbling, blown away, the intro and opening scenes, stunned, choosing a character, YOU FAILED, held, stuck
007C0A  file  player/carried_states    # the player's states while the stage carries or holds them: thrown, carried off, trapped, riding, the final stage, tripped, swept away, climbing an object, pulled, held still, stuck on an object, caught, sinking, knocked off
00A608  file  engine/objects           # the object system: the object list and its two pools, allocating, linking and deleting objects, spawning them from the stage lists (by type, by handler, in groups led by a leader), and what the stage objects share
00A669  file  player/effects           # around the players: the skid dust, the HUD (gauge, portrait, ring count, start prompt, messages), the ride of game mode 0x1C, joining with a character select, the result bonus, the climb zone splash, the intro marker
00AD91  file  player/player            # the movement
00AFB3  file  stage/camera             # the stage view, the scroll and the camera: the view set-up and the tile sets around it, the scroll modes and the screen edges the players push, the camera's speeds, shake and path along the sections, the fades between stages, the start section of the demos and the unused scroll skip panel
00B173  file  player/player            # the movement: trackball, steering, jumps, walls; the shadow
00C409  file  engine/geometry          # between the map and the screen: projecting objects, the floor under a point, unprojecting a screen point; square roots, angles and BCD
00C920  file  player/player            # the player's steps against the floor and walls
00CB15  file  objects/shared           # objects several stages use: the signs, the scenery sprites, the walls players and objects bounce off, the zones behind the chasers, the pacer that keeps two players together, the hold on a group leader; object types 4 and 13; the crawler, which nothing spawns
00CB58  file  engine/sprites           # animation and the sprite list: animation steps (frames, flips, moves, offsets, multi-part sprites), part palette cycles, the depth buckets, build_sprite_list and the buffer swap at VBLANK
00CBAC  file  player/player            # the player placed at the restart point
00CC75  file  engine/geometry          # a screen point unprojected to the ground
00CCB0  file  player/player            # aiming at the screen centre, the hurt flash, the checks of every frame
00DE4F  file  engine/sprites           # animation steps
00E149  file  player/effects           # the ride, the join, the bonus, the climb zone splash, the intro marker
00E488  file  player/player            # check_player_contacts
00EC9E  file  objects/stage_04         # stage 4's objects: the vines, the catapults, arrow boards, pebbles, the propped board, boulders, the door wall at the start and the rollers behind it, the fireball emitter and the bouncing fireballs, the electric fence and the flame machine at the end
00F087  file  objects/stage_03         # stage 3's objects: the spiked panels and the electric poles, the crate pushed to a post, the breakable panel, the spiked roller that chases the players (also in stage E) and the floor that crumbles at the finale, the machine at 8300 with its fire and fireballs, the fire vents, the end pillar
00F77D  file  objects/intro            # the scenes that start a game: stage 6's opening (the rushing ground, the giant machine head) and stage 7's how to play (the panel, the hands on the button and the trackball, the metal wall that rises and breaks)
00FBE4  file  objects/stage_04         # object types 7-9: the flame machine
0108D4  file  objects/stage_08         # stage 8's objects, the lava: the treadmill that raises a gate, the flame jets, the rock wall, the collapsing log bridge, the burning boulders and embers, the carriers and the seesaw, the metal platform and the fire wall, the fire trenches, the lava flow under the hanging bridge, the rock slabs and the lava columns
0111AF  file  objects/stage_03         # object type 12: the pushed crate
01140F  file  objects/shared           # object type 13: a sign from the type list
011437  file  objects/stage_09         # stage 9's objects, the ice: the handle rod between two posts, the spear traps, the ice cube launcher, the padded wall, the snow mound, the ice blocks and the moving ice platform, the crumbling ice floor, the icicles
012086  file  objects/stage_0a         # stage A's objects, the sand: the cactus, the centipede, the wall pieces, the shaking blocks, the sand rock, the sand arch, the stepped wall, the tornado and the whirlwinds it throws, quicksand, sinkholes, the collapsing sand blocks
01274C  file  objects/shared           # object type 21: the crawler
012B24  file  objects/eggman           # Eggman: his booth that slides in during his speeches, the speech scripts (stages 3, 8, A), his scene with the button box in stage B and the sliding door, his teleport (never spawned)
012C80  file  objects/shared           # object type 4
012C81  file  objects/stage_0a         # object types 22-24: the wall pieces, the shaking blocks, the sand rock
012FB9  file  objects/stage_09         # object type 25: the snow mound
013207  file  objects/stage_08         # object type 26: the rock wall
013429  file  objects/eggman           # object type 27: Eggman's teleport
013637  file  objects/stage_0a         # object type 41: the sand arch
0138C6  file  objects/stage_04         # object type 1: the electric fence
013AAD  file  objects/stage_10         # stage 10's objects, the river: the mines and the bubble column, the lid trap, the turtle, the eel and its den, the rafts, the geysers, the waterfall and the river water, the propeller machine that chases the players, the barred gate, the rope bridge
014054  file  objects/eggman           # object types 31 and 32: Eggman's scene, the sliding door
01453E  file  objects/stage_10         # the lid trap, the turtle, the eel
01503C  file  objects/stage_03         # object type 36: the breakable panel
0151DD  file  objects/stages_0b_0f     # the objects of stages B to F: the chasers (the brick wall that bursts, the biting head, the fire front) and the flames that spread ahead of it, the spike gate, the falling grates, the rising saws and the steps they cut, the puffs, the checkered pad, the static triangle, the railing
0156F4  file  objects/stage_10         # object type 43: the eel den
01586E  file  objects/stage_0a         # object type 44: the stepped wall
0158E2  file  stage/camera             # the stage view, the scroll, the fades, the camera
0178AB  file  stage/layers             # the stage's background: the palettes animated per stage, the palette sets and the backdrop, and the tilemap layers NBG0-NBG3 (pictures, mixing, per-stage scroll)
01A6E1  file  stage/events             # the scripted game modes run_game_mode jumps into and what they spawn: platforms, rocks, lightning, message boxes, the escape alarm and countdown
01A798  file  stage/camera             # the start section and the scroll skip panel
01AD30  file  stage/layers             # the animated palettes and the tilemap layers
01BCC6  file  scenes/ending            # stage 0x11, the ending: the runners, the tower and the explosions, the flash to white, the credits (portraits, name plates, scrolling blocks), Eggman flying away and the three running across
01E37E  file  objects/rings            # rings: the stage rows, spinning, carried and scattered rings, sparkles; the unused gauge ring and template spawner
01EEC2  file  engine/objects           # next_random, child shadows, off-screen tests, objects made outside the object loop
080000  file  engine/objects           # spawn_objects_by_handler
080072  file  objects/stage_03         # the spiked roller
080E75  file  objects/stage_04         # the catapults, the props, the bouncing fireballs
082E82  file  objects/shared           # the zones behind the chasers
08343E  file  objects/stage_03         # the machine at 8300
083CE8  file  objects/intro            # the opening and the how to play
0843C0  file  objects/shared           # the player pacer
084558  file  objects/stage_05         # stage 5's objects: the exit door, the floor patch, the bubbles, the spiked press, the cage
084B91  file  objects/shared           # the signs
084E83  file  objects/stages_0b_0f     # the spreading flames
08535E  file  objects/stage_03         # the end pillar
08574A  file  objects/eggman           # Eggman's speech scripts
0857E5  file  objects/shared           # the scenery sprites
085925  file  objects/stage_03         # the fire vents
085B75  file  objects/shared           # the hold on a group leader
085F80  file  objects/stage_08         # stage 8
087D88  file  objects/stage_09         # stage 9
0890DC  file  objects/stage_0a         # stage A
08A85E  file  objects/stages_0b_0f     # stages B to F
08C148  file  objects/stage_10         # stage 10
08DC36  file  engine/objects           # spawning by type, making objects, what the stage objects share
08E8A6  file  video/text               # text: the text layer's font, printing and erasing text, lines, fractions and digits, and the strings drawn with sprites
08EAC9  file  scenes/ranking           # the ranking: entering initials at game over, loading and resetting the table, the TOP15 display
08F998  file  scenes/character_select  # choosing a character: the PLAYER SELECT chooser at a new game and the chooser of a player joining
090CFA  file  objects/shared           # the walls and the unused wall editor
091220  file  engine/objects           # spawn_object_groups
091939  file  objects/stage_08         # the rock slabs and the lava columns
09383A  file  engine/objects           # spawn_object_groups_2
093ADE  file  objects/stage_10         # the rope bridge
094606  file  stage/events             # the scripted game modes
095976  file  scenes/map               # the map screen between stages (game mode 4): the wipe, the island map, the trail, the markers, Eggman
096E24  file  scenes/title             # the title screen: the SEGA logo, the emblem, the land, the craft, Sonic's loop, the clouds and the sky
0983FE  file  scenes/ring_tally        # the ring tally after a stage (game mode 0x1D): the total and the players' panels
098EC6  file  system/main              # the frame wait and the controls
099049  file  engine/geometry          # square roots, angles, BCD
0991B3  file  engine/objects           # the object list and its pools
099378  file  video/text               # the font
0994B1  file  video/video              # the board's video: the video registers and the screen mode, block copies and fills, palette RAM and its copies at VBLANK, the VRAM scripts that build the tilemaps
0995CC  file  video/text               # printing on the text layer
09986B  file  video/video              # the VRAM scripts
099A57  file  video/text               # digits
099AE7  file  video/bitmap             # the bitmap layer: clearing it, its address, scroll and window, pixels, lines, spans and rectangles; an unused packed picture decoder and LZ unpacker
099F11  file  engine/sprites           # the sprite list
09A4A9  file  video/video              # palette RAM
09A56D  file  system/sound             # the sound the game asks the Z80 for: init_sound, play_sound and the sound queue
09A5F4  file  stage/layers             # load_stage_palette
09A621  file  system/main              # split_frame_count
09A6DC  file  video/bitmap             # the bitmap layer
09B187  file  video/shapes             # shapes on the bitmap layer, mostly unused: triangles, quadrilaterals, circles, flood fill, saved rectangles, and the clipping the lines and rectangles use
09C4E2  file  system/main              # system modes 0, 1 and 7
09C5C0  file  system/settings          # what the board keeps when it is off: the settings in the 93C46 EEPROM and the bookkeeping in the backup RAM, with their checksums
09C8EC  file  system/test_mode         # the test mode: the menu, the memory, input and sound tests, the CRT test, the game and coin assignments, the bookkeeping screen, the backup clear, the output test, the exit
09F500  file  system/main              # the interrupt handlers
09F6B7  file  system/coins             # coins and credits: the coin inputs and counters, the credits and start prompts drawn outside a game
09F9DD  file  system/settings          # the bookkeeping
09FB98  file  system/coins             # the credits and prompts outside a game
09FFF0  file  system/main              # the reset vector

09FFF0  func  reset              # V60 reset vector (0xFFFFFFF0 on a 24-bit bus): "jmp 0"
000000  func  boot               # SBR = 0FF000, SP = 210000, R25 = 208000, first interrupt controller setup
00004D  code  boot_main          # entered by pushing its address and executing "retis"

09F000  data  vector_table       # 256 x 32-bit entries, base loaded into SBR by boot (as 0FF000)
09F5B8  func  int_default        # target of 247 of the 256 vector table entries
09F5BC  func  int_vector_02      # NMI
# The V60 takes maskable interrupts at vector 0x40 + the number the interrupt controller supplies;
# boot copies 00 01 02 03 04 (at 00008E) into the controller's five vector registers (D00000).
09F500  func  irq_vblank_start   # vector 0x40
09F560  func  irq_vblank_end     # vector 0x41
09F578  func  irq_sound          # vector 0x42
09F584  func  irq_timer0         # vector 0x43
09F59C  func  irq_timer1         # vector 0x44

# The frame loop: every frame of the game starts when wait_for_frame returns. It polls 7000[R25], which
# the VBLANK end interrupt decrements, and then falls into read_controls.
098EC6  func  wait_for_frames      # 7000[R25] = 7001[R25] (frames_per_wait: 0, so one frame; only the unused init_sprites_swapping_every_frame sets 1), then wait_for_frame
098ECE  func  wait_for_frame  # Waits for the next frame: sets sprite_list_ready, polls until irq_vblank_end has counted frames_to_wait below 0, reloads it from frames_per_wait,...
098ED6  code  frame_wait_loop
098EDE  code  frame_wait_done
098EFB  func  read_controls        # ports, then the three trackballs into 7062[R25]..706D[R25]

# Stage objects. 706E[R25] holds the stage number and 6435[R25] how far the stage has scrolled; both
# spawners walk the current stage's list and start every object whose position has been reached.
# A new object runs the handler stored at 1C of its slot ("jsr [1C[R20]]" in the object loop).
100000  data  object_types             record=8 count=45 code=4   # +0 a word read into 8[R25], +4 handler
struct object_type_record 8 rom            # an object_types record (object_types + object_type x 8: spawn_objects_by_type 08DC9A-08DCA4 "movea.b 100000, R8; mul.w #8, R7; add.w R7, R8"): it reads "mov.w [R8+], R1" (08DCA7; its low half into spawn_count, "mov.h R1, 8[R25]") and, after spawn_object_with_pieces, "mov.w [R8], 1C[R20]" (08DCD1); census ROM 100000-100167 (45 types)
field object_type_record 00 part_count u32  # spawn_count for spawn_object_with_pieces (the object and its pool 2 pieces): 1, 2, 3, 4, 5, 8, A
field object_type_record 04 handler u32     # the new object's handler (1C): 00EC9E-01586E (vine_init, fence_post_init, centipede_init...)
100168  data  stage_spawns_by_type     # 19 pointers, one per stage, to lists of 14-byte records: scroll position,
                                       # projected_x (2A), projected_y (2C), z, object type (index into object_types), 92, 94; FFFF ends
struct type_spawn_record E rom             # a stage_spawns_by_type record (type_spawn_cursor): spawn_objects_by_type 08DC75 "movz.hw [R10+], R1" (cmp.h #FFFF, then cmp.w with scroll_position: unsigned), 08DC86 "movz.hw [R10+], R4", 08DC89 / 08DC8C "movs.hw [R10+]" into R5 / R6, 08DC97 "movz.hw [R10+], R7", 08DCD6 / 08DCDC "mov.h [R10+]" into 92 / 94; 14 bytes a record ("add.w #E, [R25]" 08DC91); census ROM 1001B4-10162D (369 records)
field type_spawn_record 00 position u16     # the scroll_position it spawns at (once passed); FFFF ends the list
field type_spawn_record 02 projected_x u16  # into projected_x (2A): 13AE, 144E...
field type_spawn_record 04 projected_y s16  # sign-extended, << 16 into projected_y (2C): FCCE, FD1E...
field type_spawn_record 06 z s16            # sign-extended, << 16 into z (40): 0-530
field type_spawn_record 08 object_type u16  # its object_types record (x 8): 0-2C
field type_spawn_record 0A spawn_arg_1 u16  # into spawn_arg_1 (92)
field type_spawn_record 0C spawn_arg_2 u16  # into spawn_arg_2 (94)
081224  data  stage_spawns_by_handler  lists=19 record=12 end=FFFF code=8   # position, 4E, 52, 04, handler
struct handler_spawn_record C rom          # a stage_spawns_by_handler record (handler_spawn_cursor): spawn_objects_by_handler "cmp.h [R10], 6435[R25]" + bl (080023: unsigned), "sub.h [R10], R0" (080031), then from + 2 "mov.h [R10+]" into 4E, 52 and 04 (08004D, 080057, 080061) and "mov.w [R10+], 1C[R11]" (080066); 12 bytes a record ("add.w #C, 70DA[R25]" 08006B); census ROM 08124A-082063 (298 records, FFFF ends each list)
field handler_spawn_record 00 position u16  # the scroll_position it spawns at; skipped once 100 or more behind
field handler_spawn_record 02 x u16         # into x_int (4E), x_fraction 0: 0, 1248...
field handler_spawn_record 04 y u16         # into y_int (52), y_fraction 0
field handler_spawn_record 06 handler_arg u16  # into sprite_frame (04), a parameter the handler reads (stage_03_floor_block_start's block kind, sign_start's kind): 0-17
field handler_spawn_record 08 handler u32   # into handler (1C): 009B4D-094C3F
01F206  data  stage_template_spawns    # func_01E37E: 19 offsets to lists of 12-byte records: position, offset
                                       # into stage_object_templates, count, x, y, dx, dy; FFFF ends
01F4D2  data  stage_object_templates   record=26 count=6 code=18   # 2, 4, 6, 10, 82, 44-47, 48, handler, 64-66
093269  data  stage_object_records     record=22 count=23 code=6   # 2, 4, 6, handler, 48 (data), 10, 12, 64, 65, 96
093EC9  data  stage_record_spawns      # func_09383A: 19 offsets to lists of 14- or 24-byte records by type;
                                       # the 24-byte ones index stage_object_records_2 at +22; FFFF ends
093F59  data  stage_object_records_2   record=24 count=4 code=6   # 2, 4, 6, handler, 48, 48 alternative, 10, 12, 96

# A 16-bit count and that many handlers: func_001CFD and func_001CF1 start one object per player slot
# (400[R25] + 200 each); the handlers set 9A to the player number
002405  data  player_objects           record=4 count=prefix code=0
00241F  data  player_objects_alt       record=4 count=prefix code=0   # 291F is replaced by 28DB or 28D1
struct object_start_list 2 rom            # the head of player_objects / player_objects_alt (data lines: count=prefix): reset_objects_from_list "movz.hw [R10+], R13" (001D1A); census ROM 002405, 00241F (6 each)
field object_start_list 00 count u16      # how many handlers follow (objects started)
elements object_start_handlers u32        # the handlers after the count, one per object: reset_objects_from_list "mov.w [R10+], 1C[R11]" (001D23, 001D7B); census ROM 002407-00241E (player_1_start, _2, _3, shadow_1_start...), 002421-002438 (player_start_stage x 3, the shadows)
084FB5  data  object_kinds             record=12 count=34 code=8   # indexed by 4[object]: 42, 4, 34, 80, handler
097419  data  timed_spawns             record=4 end=FFFFFFFF code=2:973B4   # func_0973B4: delay, handler offset
01C49C  data  timed_spawns_2           record=4 end=FFFFFFFF code=2:1BFC4   # func_01C458: delay, handler offset
01D372  data  timed_spawns_3           record=4 end=FFFFFFFF code=2:1C82F   # read at 01D32C: delay, handler offset
082250  data  object_pair_templates    record=26 count=2 code=0,18   # by 34[object]: handler, 48, 64, 65, 38, ...;
                                                                     # +12 the child's 10, 12, 7C, 7D, handler, 48
08340D  data  object_steps             record=6 count=3 code=2    # spawn_chaser_back_zone, by 3 x index: 80, handler; records 3-7 (no handler) are the roller's back zone limits
085D1F  data  scenery_sprite_kinds           lists=14 record=12 count=1 code=8   # by 4[object]: 2, 4, 6, 8A, handler
08530D  data  spawns_with_next_handler record=14 count=prefix code=10   # read at 083670: 4E, 52, 96, 9A, 42 and
struct machine_fireball_spawns 2 rom     # spawns_with_next_handler (08530D): the count of the fireballs machine_fire_update spawns once the machine is wrecked, their records (machine_fireball_spawn) after it: "movea.b 8530D[PC], R12" (083670)
field machine_fireball_spawns 00 count u16  # 4: "cmp.h 6900[R25], [R12]" (083675, against free_object_stack), "mov.h [R12+], 70EA[R25]" (08367D, the loop's count)
struct machine_fireball_spawn E rom      # one fireball of spawns_with_next_handler (after its count): machine_fire_update copies it with [R12+] (083697-0836C1); census ROM 08530F-085346
field machine_fireball_spawn 00 x s16     # "mov.h [R12+], 4E[R11]" (083697): x_int F6FA, F6DA
field machine_fireball_spawn 02 y s16     # "mov.h [R12+], 52[R11]" (0836A1): y_int 0A80
field machine_fireball_spawn 04 target_x s16  # "mov.h [R12+], 96[R11]" (0836AB): fireball_start.target_x F74C, F700
field machine_fireball_spawn 06 target_y s16  # "mov.h [R12+], 9A[R11]" (0836B1): fireball_start.target_y 0AC0, 0AF7, 0B30, 0AEE
field machine_fireball_spawn 08 z s16     # "mov.h [R12+], 42[R11]" (0836B7): z_int 04A0, 04B0, 0490, 0470
field machine_fireball_spawn 0A next_handler u32  # "mov.w [R12+], 9C[R11]" (0836C1): 083BAB, 083B35
                                                                        # 9C, the handler 083B14 moves into 1C

# ======== Names for the decompiled C (tools/decompile.py) ========
# Besides functions and labels, symbols.txt names what the decompiled C reads and writes:
#   register RN NAME              a register the whole program shares (a global variable in C)
#   ADDRESS var NAME TYPE         a variable in work RAM          TYPE: u8 s8 u16 s16 u32 s32
#   ADDRESS io NAME TYPE          a register of the board
#   ADDRESS table NAME TYPE       data in the program ROM the code reads by index (TYPE per element)
#   ADDRESS table NAME STRUCT     a table of the program ROM's records, read by index: name[i].field
#   elements NAME TYPE [STRUCT]   values of the program ROM a pointer walks: NAME(p)[i] (STRUCT: what each points at)
#   struct NAME SIZE              a structure in work RAM (SIZE in hexadecimal)
#   struct NAME SIZE rom          a record of the program ROM, read through a pointer: NAME(p)->field, NAME(p)[i].field
#   field STRUCT OFFSET NAME TYPE one of its fields (OFFSET in hexadecimal); TYPE[N] an array of N in it,
#                                 TYPE[] one after a record's end: NAME(p)->field[i]
#   scoped STRUCT FAMILY OFFSET NAME TYPE  a field as the code of one family of functions (whose names
#                                 start with FAMILY) uses it in the structure it runs on (an object type's own variables)
#   scoped global FAMILY ADDRESS NAME TYPE  work RAM as one family of functions uses it (scratch variables)
#   param FUNCTION RN NAME        what a function takes or hands back in register RN; its callers name the
#                                 variables they pass in RN, or get back from it, the same way
#   result FUNCTION RN NAME       what a function nothing calls hands back in register RN (its callers say
#                                 it for the others); every register such a function reads before writing
#                                 is one of its parameters
#   constant FUNCTION VALUE NAME  a number the function uses, by name (VALUE in hexadecimal; a #define in ram.h)
#   local FUNCTION WHERE RN NAME  the local variable that holds what the instructions at WHERE (addresses,
#                                 or "entry": where the function starts) write in register RN
# A second var or field at the same place with a narrower type names the first bytes of the wider one
# (x_fraction: the low half of the 16.16 x).
# A struct name after the TYPE of a var or field says its value points at one: a u32 holds the address,
# a u16 the low half of it (the game links its structures in the work RAM page by 16-bit addresses).
# "rom" after a u32 says it points into the program ROM.
# Whatever has no name yet appears as unk_ADDRESS, rom_ADDRESS, VIDEO_ADDRESS... or unk_OFFSET in C.

# -- the registers the whole program keeps things in --
register R18  current_player        # the player block being run (0x208400 + 0x200 per player); some code keeps other things in it: the player's object (shadow_update, draw_attack_effect), a row count (build_ranking_display)
register R20  current_object        # the object being run: the object loop sets it before its handler
register R21  free_objects          # mostly the stack of free objects (0x20E900); also a counter at times
register R22  first_object          # the object list's first ...
register R23  object_cursor         # ... the object loop's position in it ...
register R24  last_object           # ... and its last
register R26  fill_value            # what the string moves (MOVCFU) fill with
register R32  stack_pointer         # SP, in the functions whose stack stays in memory (decompiler/stack.py: code the game never calls uses its stack frame as an array); 20FF00 after boot

# -- objects: a doubly linked list of 0xA0-byte blocks in work RAM, linked by 16-bit addresses --
207FE0  var  object_count   u16     # one more when an object joins the list, one less when it leaves
207FE2  var  first_object_address  u32 object
207FE6  var  last_object_address   u32 object
20E900  var  free_object_stack     s16 object     # [0] how many are free, then their 16-bit addresses  (TYPE: 26 of its 30 uses compare it signed, "(int16_t)free_object_stack < n" before spawning)
20EA10  var  free_object_stack_2   s16 object     # the same for a second pool  (TYPE: 9 signed compares of [0], 3 == 0 tests, the rest pushes/pops)
struct object A0
field object 1C handler    u32      # the code the object loop runs for it
field object 20 previous   u16 object      # 16-bit address of the object before it in the list
field object 22 next       u16 object      # ... and after it
field object 34 speed_x    s32      # 16.16 fixed point, added to x every frame
field object 34 speed_x_fraction u16  # the low half of speed_x (16.16)
field object 36 speed_x_int s16         # the whole part of speed_x: pixels a frame
field object 38 speed_y    s32
field object 38 speed_y_fraction u16
field object 3A speed_y_int s16         # the whole part of speed_y
field object 3C speed_z    s32      # vertical: gravity adds 0x6000 a frame; a jump sets it to -8.0
field object 3C speed_z_fraction u16
field object 3E speed_z_int s16         # the whole part of speed_z
field object 40 z          s32      # height
field object 40 z_fraction u16
field object 42 z_int s16               # the whole part of z
field object 4C x          s32
field object 4C x_fraction u16         # the low half of x: the code clears it to put an object on a whole pixel
field object 4E x_int s16               # the whole part of x
field object 50 y          s32
field object 50 y_fraction u16
field object 52 y_int s16               # the whole part of y
field object 9A player     u8       # the player the object belongs to (player handlers set it)

# -- players: 0x200-byte blocks from 0x208400, one per player --
struct player 200

# -- the frame loop --
20F000  var  frames_to_wait   s8    # wait_for_frame polls it; the VBLANK end interrupt counts it down
20F001  var  frames_per_wait  u8    # wait_for_frames copies it into frames_to_wait; 0003DD and init_sprites set it to 0
20F061  var  game_mode        u8    # the main dispatcher (func_000652) switches on it; 8 is stage select
20F06E  var  stage            u16   # the stage being played (the protection writes it, see below)
20E5C4  var  cleared_stages   u16   # writing it makes the protection device (FD1149) pick the next stage

# -- the I/O chip (315-5296) --
C00000  io  io_port_a     u8        # player 1 button (bit 0), player 3 button (bit 2)
C00002  io  io_port_b     u8        # player 2 button (bit 0)
C00004  io  io_port_c     u8
C00006  io  io_port_d     u8        # outputs: coin counters; EEPROM data in (bit 7), chip select (5), clock (6)
C00008  io  io_port_e     u8        # service, test, coins 1-2, starts 1-2, coin 3, start 3
C0000A  io  io_port_f     u8        # DIP switches (bits 0-3), PCB buttons (4-5), EEPROM data out (bit 7)
C0000C  io  io_port_g     u8
C0000E  io  io_port_h     u8        # the external tile bank
C0001C  io  io_cnt        u8        # bit 1 turns the display on, bit 2 lets the sound CPU run
C0001E  io  io_direction  u8        # the ports used as outputs

# -- the interrupt controller --
D00007  io  irq_acknowledge  u8
D00008  io  timer_0_reload   u16
D0000A  io  timer_1_reload   u16

# -- functions --
0991C1  func  run_objects           # from the last object through the list: R20 = object, jsr [1C[R20]]
099246  func  allocate_object       # R0 = how many were free (0: none); R11 = the object
099353  func  allocate_object_2     # the same from the second pool
09C724  func  eeprom_read_all       # 0x40 words into 7600[R25]
09C79C  func  eeprom_read_word  # Reads one EEPROM word: eeprom_send_command(command), then 0x12 clocks reading port_f bit 7, then eeprom_deselect; hands back the 16-bit word in...
09C7CB  func  eeprom_write_word     # then waits for the chip to say it is ready (data out high)
09C82B  func  eeprom_send_command  # Starts an EEPROM command on port_d: lines low, a clock, chip select up, then the start bit and command's 8 bits, high bit first, each on the data...
09C87B  func  eeprom_select         # R13 = port D, R12 = port F; interrupts off
09C8E0  func  eeprom_deselect  # Clears the EEPROM's chip select, clock and data lines (bits 5-7 of lines) on port_d and hands back lines
09C8E4  func  eeprom_set_lines      # writes R2 to port D

# ======== system (from build/naming/system.txt) ========
# System and hardware: proposals from the system helper (symbols.txt syntax, one per line, "# evidence")
#
# How it works (summary; details in the comments):
# - boot: copies 8 byte pairs from 00007E to E00000 (unmapped on this board, MAME and s32ref ignore them) and
#   00 01 02 03 04 00 00 00 FF FF FF FF from 00008E to D00000-D0000B (vectors 0-4, D00005 = 0, mask 0, acknowledge
#   all, both timers 0xFFF). Then delay_after_reset, init_system, and the loop: wait_for_frame + run_frame, or,
#   while test_mode is set, start_test_mode + wait_for_frame + run_test_mode; leaving test mode sets
#   system_mode_step/system_mode to 0/3 (the title) and runs init_system again.
# - run_frame (once per frame): frame_counter++, outside system modes 0 and 6 checks coin starts (credits >=
#   credits_per_start and the start button, or the attack button when setting_no_start_buttons is set: then
#   system_mode 6, game_mode 0, game_in_progress FF), runs system_mode_handlers[system_mode], run_objects,
#   build_sprite_list, draw_queued_strings.
# - system_mode (20F007): 0 attract_warning (power-on, 10 s), 1 system_mode_idle (never selected), 2 attract_ranking,
#   3 attract_title, 4 attract_demo_1, 5 attract_demo_2, 6 run_game_mode (the game), 7 wait_attract_timer.
#   advance_attract_screen walks attract_sequence (2 3 4 2 3 5 3 5 ...); s32ref attract: warning 0-602, ranking
#   602-1361, title 1361-2289, demo 1 2289-2866 (0x240 frames), ranking, title, demo 2 4553-5130, ...
# - game_mode (20F061), case labels below: 0/33 start game -> 1B character select -> 1 intro -> 2 start_stage ->
#   3 play; stage events switch to the scripted modes (stages.txt); the stage end goes 23 (stage clear) -> 29
#   (results) -> 4 (fade, next stage) or, on stages B-F, 30; all players out -> 7 game over (back to attract
#   after 0x78 frames). Nothing sets game_mode 9, 0x0B, 0x0E (the global "CONTINUE ?") or 0x0F: continues are
#   per player (player state 0x23, players.txt). current_game_mode (20E5A9) is the copy the rest of the program
#   tests.
# - Interrupts (vector 0x40 + n): VBLANK start: the mixer shadow (20F1C0, mixer_text_blend on) -> 610030, run_vblank_copies, vblank_start_hook
#   (apply_video_registers), send_sound_command, and outside test mode count_frames_and_check_test_switch and,
#   unless free_play, update_coins; display_off_frames; acknowledge bit 0. VBLANK end: vblank_hook (20F100,
#   swap_sprite_buffers) unless debug_pause, frames_to_wait--, acknowledge bit 1. Sound: acknowledge bit 2 only.
#   Timers 0/1: count timer_0_ticks/timer_1_ticks (never read), reload 0x32/0x23, acknowledge bits 3/4.
# - Sound: play_sound/queue_sound put ids in a 32-entry ring (sound_queue); every VBLANK send_sound_command takes
#   one id and writes the two bytes of sound_commands[id] to the shared RAM mailbox: 700701 = group, 700700 =
#   number. Groups: 80 control (id 96 = 80 01, queued by init_sound: probably reset; 94 = 80 03 and 95 = 80 00,
#   queued before every sound test sound, at test mode entry, game over...), 81 music (ids 0-4, 7B-7E, 81, 82,
#   8D-93), 82 effects (ids 5-2E, 7F = the coin sound, 83-89, 8B, 8C), 83 a third set (ids 2F-7A, 80, 8A,
#   probably voices/PCM). The Z80 program spins at 012D while sound_cpu_hold (Z80 FFFF) is not 0. The program
#   never writes D0000C-D0000F (s32ref: 0 sound requests): the Z80 polls the mailbox.
# - Battery-backed shared RAM (701F00-701FFF; the CRCs and backup copies only make sense if it survives power
#   off): the bookkeeping block 701F02-701F41 (CRC at 701F00, copy at 701F42-701F81), the ranking 701F82-701FF9
#   (CRC at 701FFA), the DIP 1 state (701FFE) and sound_cpu_hold (701FFF). All CRCs are compute_crc16
#   (polynomial 1021, seeded DEBDEB); the program's own ROM checks reproduce the four rom_checksums exactly
#   (verified in Python on build/rom/program.bin).
# - EEPROM (93C46, 0x40 words) shadow at 20F600-20F67F: header 'A23S', ROM stamp, CRC of 760A-763F, write count,
#   the settings 760E-7627, and a full backup copy at 20F640. load_settings: magic wrong -> defaults (magic 'SEGA':
#   defaults and write count 100); ROM stamp changed -> settings reset (760E-763F from the defaults); bad CRC ->
#   the backup if its CRC is good, else settings reset; then saved. Defaults (09C580): advertise sound off,
#   1 coin 1 credit, common chutes, 2P cabinet, 1 credit to start, difficulty 2/4, vitality 2/4.
# - Coins: update_coins (VBLANK) counts coins on release (inputs_released bits 2, 3, 6 = coin 1, 2, 3; service
#   bit 0 or the PCB service button bit 12 gives a service credit), with chute multipliers, bonus adder and
#   coins per credit, credits max 9, three credit blocks of 4 bytes at 20F412/416/41A (common chutes use the
#   first), pulses the two coin counters (port D bits 0/1, 6 frames on and 6 off per coin), plays sound 7F,
#   saves credits into the bookkeeping. COIN/CREDIT SETTING 26 (bonus adder 1) is free play.
# - DIP switch 1 (port F bit 0, low = on) is the moving seat cabinet: check_moving_seat_dip keeps its state in
#   moving_seat_dip (701FFE) and the test menu then shows OUTPUT TEST (port D bits 2/3 = 1P/2P seat) and the
#   MOVING SEAT option.
# - Port D (outputs): bits 0-1 coin counters, 2-3 the seats (OUTPUT TEST), 4 set at init, 5-7 EEPROM; bit 3 is
#   also cleared by init_sound (unreached code at 09A593 sets it when sound is allowed).

# ======== boot, frame loop, system modes ========
0000DF  func  run_frame                 # boot's loop calls it every frame unless test_mode: frame_counter++, start checks (credits >= credits_per_start, cabinet_config switch) outside system modes 0/6, system_mode_handlers[system_mode], run_objects, build_sprite_list, then jmp draw_queued_strings
0003DD  func  init_system               # boot calls it at power-on and after test mode: interrupts off, clear_work_ram, init_io_ports, init_sound, load_settings, load_bookkeeping, apply_settings, init_mixer, load_default_palettes, init_sprites, load_text_font, init_video_registers, three read_controls, D00007 = 0 (acknowledge all), D00005 = 0, check_moving_seat_dip, init_game_settings, interrupts on
0004DB  func  clear_work_ram            # zeroes 208000-20FDFF (0xFC x 0x80 bytes), saving and restoring the halfword at 20F006 (system_mode_step/system_mode)
00049E  func  init_io_ports             # port D = 10, G = 0, H = 0, CNT = 80, direction = C8 (D, G, H outputs), twice; cabinet_events = 1F
000484  func  init_mixer                # 0x28 halfwords of mixer_defaults to the mixer 610000-61004F, the last 0x10 also to the mixer shadow at 20F1C0 (mixer_text_blend on)
00055D  func  init_game_settings        # cabinet_config = setting_cabinet_config, game_in_progress = 0, clears 20E617/20E618, load_ranking, stage_order to stage_order_copy
00058A  func  delay_after_reset         # boot: an empty loop of 0x8000 before the first init_system
0003BC  func  advance_attract_screen       # attract_step = (attract_step + 1) & 7, system_mode = attract_sequence[attract_step], system_mode_step = 0
09C4E2  func  attract_warning           # system_mode 0 (boot): step 0 attract_timer = -600, display off for 3 frames, fonts, video registers, sprites, set_screen_mode(1: wide), init_sound, prints the "only in JAPAN" warning (104BD2); step 1 wait_attract_timer; step 2 advance_attract_screen
09C551  func  system_mode_idle          # system_mode 1: only clears system_mode_step; attract_sequence never holds 1
0002AC  func  attract_ranking           # system_mode 2 (debug name "Ranking Mode" at 00060D): set_screen_mode(0), clear_text_layer, clear_page_0_and_scroll, reset_objects, start_ranking_display, current_screen = 2; advance_attract_screen when ranking_done (s32ref: 759 frames)
000215  func  attract_title             # system_mode 3 (" Title Mode "): current_screen = 3, display off, prints (c)SEGA1992 (record at 000644), runs 096E24 until title_done
000310  func  attract_demo_1            # system_mode 4 (" Demo 1 Mode "): current_screen = 4, game_mode = 0, demo_input = 117876, attract_timer = -0x240, run_game_mode until attract_timer is 0
000362  func  attract_demo_2            # system_mode 5 (" Demo 2 Mode "): the same with demo_input = 118876, current_screen = 5
000652  func  run_game_mode             # system_mode 6 and the demos: jumps by game_mode (table at 00065F, 33 entries plus 33 falling into case 0), again while a case changes game_mode
09C558  func  wait_attract_timer        # system_mode 7, and step 1 of attract_warning: system_mode_step++ and frame_counter = 0 once attract_timer reaches 0
08F22D  func  start_ranking_display     # attract_ranking's step 0 calls it once; attract_ranking waits for ranking_done
08F0EA  func  load_ranking              # init_game_settings: ranking = saved_ranking (701F82, 0x78 bytes) when compute_crc16 of it matches ranking_checksum, else reset_ranking
08F11E  func  reset_ranking             # ranking from default_ranking (15 records), then saved_ranking and ranking_checksum; BACKUP DATA CLEAR calls it too
08EC87  func  enter_ranking_initials    # player state 0x2E (game over, from 0050D4): the trackball changes the letter (codes 0-2B), the button (bit number in 80[R20]) goes to the next (93, up to 3), 84 counts down; then the record (value 34, 36-3B) is sorted into ranking (lowest first), initials 'sex'/'xxx'/'x x'/'xx ' become 'tak' (08EEA1), ranking -> saved_ranking + ranking_checksum
000594  table system_mode_handlers  u32 # run_frame jumps through it by system_mode: 09C4E2 09C551 0002AC 000215 000310 000362 000652 09C558
0005B4  table attract_sequence  u8      # advance_attract_screen, by attract_step: 05 02 03 04 02 03 05 03
0005BC  table mixer_defaults  u16       # init_mixer: 0x28 values for 610000-61004F (sprite groups 00-1E, text 2F, NBG0-3 020E 020B 0208 0207, bitmap 2F, 0021, 0020, 4000 400B 4300 471B 4216 ... 3E49 0C00); 0005DE/0005E4 are the NBG0/NBG3 ones
08F1B5  table default_ranking  u8       # reset_ranking: 15 records of 8 bytes (u16 300..1000, two bytes, three ASCII letters converted by 08F194, 0): div, a m, 3rd, kus ... son; lowest first
20F004  var   test_mode  u8             # FF while the test mode runs: count_frames_and_check_test_switch sets it when test (bit 1) or the PCB test button (bit 13) is pressed without service; exit_test_mode clears it
20F006  var   system_mode_step  u8      # step inside the system mode handler (0 = set up, then 1...); advance_attract_screen clears it; boot writes 0300 to 20F006 after test mode (step 0, system_mode 3)
20F006  var   system_mode_and_step  u16     # system_mode_step and system_mode as one halfword: clear_work_ram saves and restores it; boot writes 0300 after test mode
20F007  var   system_mode  u8           # index into system_mode_handlers (run_frame); 6 = the game (run_frame sets it on a start), 0 at power-on
20F00A  var   game_in_progress  u8      # FF from a start until game over (run_frame, 000F11 clears it), 0 in attract and demos: selects demo input (play_demo_buttons), the demo stage and buttons, play_sound's attract filter
20F030  var   timer_0_ticks  u32        # irq_timer0 counts it; boot clears it; nothing reads it
20F034  var   timer_1_ticks  u32        # irq_timer1 counts it; boot clears it; nothing reads it
20F038  var   frame_counter  u8         # run_frame adds 1 every frame; blink_start_prompt and 002370 blink with it
20F039  var   display_off_frames  u8    # irq_vblank_start counts it down and sets CNT bit 1 (display on) when it reaches 0 (then FF); screen changes clear CNT bit 1 and set 2, 3, 0x14, 0x78
20F042  var   vblank_counter  u16       # irq_vblank_start adds 1; nothing reads it
20F044  var   attract_timer  s16        # wait_for_frame adds 1 every frame; the warning (-600) and the demos (-0x240) end when it reaches 0
20F046  var   power_time_save_divider  u8   # count_frames_and_check_test_switch subtracts 4 every frame: on the borrow (every 64 frames) power_on_frames goes into the bookkeeping
20F048  var   power_on_frames  u16      # low 16 bits of the 48-bit count of frames the board has been on (high 32 at 20F04A); load_bookkeeping restores it from bookkeeping_power_on_time
20F04A  var   power_on_frames_high  u32
20F050  var   game_frames  u32          # +1 every frame outside test mode; 0 at a new game (0006FE) and 00E1A9; player field 1D2 keeps the join time; add_play_time / record_player_game use it
20F060  var   attract_step  u8          # advance_attract_screen's index into attract_sequence (0-7); game over clears it
20F002  var   debug_pause  u8           # nonzero skips vblank_hook in irq_vblank_end; only unreached code at 099029 sets it: start 3 pauses (wait_for_frame loop, attract_timer kept) until start 3 again
20E5A9  var   current_game_mode  u8     # each game mode handler writes its number (1, 3, 4, 7, 0xA, 0x11, 0x17, 0x1B, 0x1D, 0x20...); player and object code test it (00D2BA, 00D637, 0846B5...)
20E5AE  var   game_mode_timer  s16      # frames in the current game mode: intro 0x21C, game over 0x78, results 0x1E0, character select >= 3, game_mode_stage_change_fade -1/1 phases
20E5AF  var   continue_count  u8        # game_mode_continue (unused mode 14): 9 down to 0, one per 0x3C frames of the low byte of game_mode_timer
20E5B0  var   saved_game_mode  u8       # game_mode when the game went to game over (000972, 001444...); game_mode_continue would resume it
20E5D3  var   current_screen  u8        # 0 boot, 2 ranking, 3 title, 4 demo 1, 5 demo 2, 6 game (0006F8); the demo playback runs for 4 and 5
208036  var   ranking_done  u8          # attract_ranking clears it and goes on when the ranking display sets it
20900A  var   title_done  u8            # attract_title clears it and goes on when the title code (096E24) sets it
208093  var   character_select_timer  s16   # game_mode_character_select waits until it is 0; 08F9AB sets 0x258 (10 s); 08FBB4/08FDBA/08FF9A use it too  (TYPE: every compare is signed: <= 0, < 1E)
20CA4E  var   stage_results_done  u8    # game_mode_stage_results goes to game_mode 4 once it is set; 0983FE clears it
20CE00  var   ranking  u16              # 15 records of 8 bytes (load_ranking copies saved_ranking here); enter_ranking_initials writes it back

# ======== game mode handlers (case labels of run_game_mode) ========
0006C2  func  game_mode_start_game      # game_mode 0 and 33: demo: stage = [demo_input]; game: cleared_stages = 0, current_screen = 6, game_frames = 0; then mode 1B (game) or 2 (demo, stage_start_kind FF)
0009E5  func  game_mode_intro           # game_mode 1 (stage 7, cleared_stages 0): set up once (20F0BC bit 13, stage_start_section 6), stage_start_sounds[stage]; players get state 0x1E while current_game_mode is 1 (00D346); mode 2 after 0x21C frames or when a playing player presses the button
000928  func  game_mode_play_stage      # game_mode 3, 5 and 6: one frame of the stage (check_stage_clear, spawners, contacts, run_stage_events); game_mode 7 when player_status (4 bytes) is all 0
000C95  func  game_mode_stage_end_fade  # game_mode 4 (after the results): sounds 95 then 03 once, clears blending (bit 11 of 20F1DE, the 61004E copy), 095976 (fade steps in 2090D5); game_mode 2 when 2090D5 reaches 5
000E9E  func  game_mode_game_over       # game_mode 7: sounds 94 and 73 (76 on stage 11), cabinet_events 1F, "GAME OVER" (queue_string_record 0025D0) or 002342; after 0x78 frames attract (advance_attract_screen); a start with credits begins a new game
001474  func  game_mode_stage_select    # game_mode 8 (debug, poke): "Stage Select Mode"/"Stage No." (00255A/00256D); trackball 1 x steps stage 0-11 (trackball_step), start 1 writes stage_select_cleared_values[stage] and goes to mode 2
00156B  func  game_mode_event_09        # game_mode 9: no code or stage event sets it; runs four routines and returns
001586  func  game_mode_event_0A        # game_mode 0x0A and 0x0B (stage 5 at scroll 100: run_stage_05_switch); 0x0B is never set; game over if player_status is all 0
0015B0  func  game_mode_event_0C        # game_mode 0x0C: stage 4 at scroll 0 (jumps to 09484A)
0015BC  func  game_mode_event_0D        # game_mode 0x0D: stage 8 at scroll 0 (09469B)
000B42  func  game_mode_continue        # game_mode 0x0E, set by nothing: "CONTINUE ?" (0025C2), continue_count 9..0 drawn at 31F730, a start with credits resumes saved_game_mode, at 0 game_mode 7
0015D5  func  game_mode_event_0F        # game_mode 0x0F: no code or stage event sets it (0945CE)
0015E1  func  game_mode_event_10        # game_mode 0x10: stage 6 at scroll 0, the first stage of a game (update_stage_06_background, players in state 0x1C)
000CD8  func  game_mode_screen_wipe     # game_mode 0x11: first event of stages 3, 4, A, C, 10: plays the stage while update_screen_wipe opens the screen, game_mode 3 when it returns 0F
0015EE  func  game_mode_event_12        # game_mode 0x12: stage 9 at scroll 4C00 (0943EF)
0015FB  func  game_mode_event_13        # game_mode 0x13: stage 9 at scroll 0 (094398)
001608  func  game_mode_event_14        # game_mode 0x14: stage 9 at scroll 1800 (094245)
001615  func  game_mode_event_15        # game_mode 0x15: stage 3 at scroll A360 (0941AF)
001622  func  game_mode_event_16        # game_mode 0x16: stage 3 at scroll 1B80 and 8610 (094128)
000D47  func  game_mode_stage_clear     # game_mode 0x17 (check_stage_clear): 0x14A frames, two objects (001674, 001664), sounds 94 and 7B (music group), then game_mode 0x1D
00162F  func  game_mode_event_18        # game_mode 0x18: stage 11 at scroll 0 (095177)
00163D  func  game_mode_event_19        # game_mode 0x19: stage 10 at scroll C80 (094012)
0015C8  func  game_mode_event_1A        # game_mode 0x1A: stage 8 at scroll 3100 (094F0E)
000FFF  func  game_mode_character_select  # game_mode 0x1B (from start_game): once (20F0BC bit 16): stage set up, load_stage_palette, cabinet_events 15; game_mode 1 when character_select_timer is 0 (after >= 3 frames); players join in state 0x34
00164A  func  game_mode_event_1C        # game_mode 0x1C: stage 8 at scroll 7A8D (095053)
001105  func  game_mode_stage_results   # game_mode 0x1D: game_mode_timer 0x1E0, 0983FE (per player 13C against per-stage totals 09864E/09865F, percentages, result objects); game_mode 4 when stage_results_done
001185  func  game_mode_stage_change_fade  # game_mode 0x1E (check_stage_clear on stages B-F): fade out (update_fade), start_stage, fade in, game_mode 3 (directly on stage 11)
001657  func  game_mode_event_1F        # game_mode 0x1F: stage B at scroll F00 (0951FA)
0012CD  func  game_mode_you_failed      # game_mode 0x20 (set at 095320, in game mode 0x1F's event code): sound 94, color offsets up to 1F (white), "YOU FAILED" (queue_string_record 0025FA) at 0x78 frames, game_mode 7 when player_status is all 0

# ======== frame loop helpers and small utilities ========
098F9D  func  play_demo_buttons         # read_controls, demo only (current_screen 4/5, current_game_mode 3): puts the 3 low bits of [demo_input] into bits 25-27 of the inputs (the demo players' buttons, 00B63F), demo_input + 1
098FEA  func  play_demo_trackballs      # read_controls, demo only: the next 6 signed bytes of demo_input replace trackball_1_x..trackball_3_y, demo_input + 6
0990F1  func  convert_to_bcd            # R0 = up to 8 decimal digits of R0 in BCD (test mode and credit numbers)
09A621  func  split_frame_count         # R0/R1 (48-bit frames) -> R8 days (/4E9FA0), R7 hours (/346A7), R6 minutes (/DFB), R5 seconds (/3C): the bookkeeping times
099A57  func  draw_hex_2                # 2 digits of R0 (0-9, A-F) leftwards from R11 with attribute R1, leading zeros blank
099A5F  func  draw_hex_4                # 4 digits
099A73  func  draw_hex_8                # 8 digits

# ======== interrupts, video and mixer ========
09F5C0  func  run_vblank_copies         # irq_vblank_start when vblank_copies_pending: each entry of vblank_copies (source, destination whose top byte repeats the copy, halfwords), then vblank_copy_count = 0
09A545  func  queue_vblank_copy         # R10 source, R11 destination (top byte = how many copies), R12 halfwords: adds an entry to vblank_copies, vblank_copies_pending = 1
099531  func  apply_video_registers     # vblank_start_hook: video_registers_latched -> 31FF10-31FF4F (0x20 halfwords) and its byte at 20F302 -> 31FF8F, then 20F280-20F2C1 (scroll, centres, pages) -> video_registers_latched: the hardware gets them one frame later
0994B1  func  init_video_registers      # vblank_start_hook = apply_video_registers; 31FF00-0F from video_register_defaults, 31FF10-4F = 0, zoom steps 31FF50-5A = 200, 31FF5C-5F, five clip rectangles 0,0-13F,DF at 31FF60-87, 31FF88-8F; set_screen_mode(0), clear_page_0_and_scroll
099552  func  set_screen_mode           # R0 bit 0 wide (416 px): 31FF00 bit 15 and sprite_wide_screen; R0 bit 1 xor setting_monitor_flip bit 1: 31FF00 bit 9 and sprite_flip = 3; screen_mode = R0 ^ flip
099503  func  clear_page_0_and_scroll   # zeroes video RAM 300000-3003FF and 20F280-20F2FF (the scroll shadow)
099378  func  load_text_font            # video_text_control = 1F7 (map at 31F000, characters at 31C000), copies 0x9A0 halfwords of font from 102136 to 31C000, then expand_font for 0x5B more characters and clear_text_layer
0993A5  func  clear_text_layer          # zeroes the text map 31F000-31FDFF (0xE0 x 16 bytes)
0993B3  func  expand_font               # 1-bit font rows from R10 into 4-bit characters at R11 with color R0 (R1 characters)
09A4A9  func  load_default_palettes     # vblank_copies_pending = 0, vblank_copy_count = 0, then load_palette_list(104D60)
09A4BC  func  load_palette_list         # R12 = count, then (source, palette RAM offset, kind, ...) entries into 600000+: kind 0 copy_palettes_with_pen_0, 2 copy_palette_block, 4 repeat_palette_block (offsets into the jump table at 09A4DF; 6 would be copy_palettes_with_pen_0 with pen 0 = 0006, no list has it)
09A4E5  func  copy_palettes_with_pen_0  # 16-color palettes: pen 0 = R0 (0), pens 1-15 from the source
09A512  func  copy_palette_block        # R1 bytes (halved for the MOVCU's halfword count) from R10 to R11
09A523  func  repeat_palette_block      # each 16-color block of the source repeated by a count list
09A6DC  func  clear_bitmap_layer        # set_bitmap_scroll(0, 0, depth from R0), bitmap address 0,0, clears 31FF02 bits 5/10/15, bitmap clip 0,0-13F,DF (19F wide), fills the screen (09A780)
09A732  func  get_bitmap_address        # R0 x, R1 y -> bitmap_address (300000 + y * row + x for the depth in bitmap_8bpp)
09A75A  func  set_bitmap_scroll         # R0 -> video_bitmap_scroll_x, R1 -> video_bitmap_scroll_y, R2 bit 0 -> bitmap_8bpp and 31FF00 bit 11
09A780  func  set_bitmap_window         # R0-R3 (x1, y1, x2, y2), sorted, into bitmap_window_left/top/right/bottom: the drawing clip of draw_line and fill_rect
09C383  func  get_window_outcode        # swaps the two points (R0/R1 with R2/R3) and returns in R10 where the point is outside bitmap_window (1 left, 2 right, 4 above, 8 below)
09C3BA  func  clip_line_to_window       # moves one end of the line onto the window edge its outcode names (jump tables at 09C3D2)
09C466  func  clip_rect_to_window       # R0-R3 rectangle clipped to bitmap_window; R10 = 0 when it is wholly outside
09A86A  func  draw_line                 # bitmap layer line from R0,R1 to R2,R3 in color R4, clipped (get_window_outcode, clip_line_to_window), 4 or 8 bits per pixel (bitmap_8bpp); the C.R.T. crosshatch
09AF18  func  fill_rect                 # clip_rect_to_window, then fills R0,R1-R2,R3 with color R4 on the bitmap layer (test bars and boxes); 09AEFF fills the whole window with 0
20F206  var   bitmap_window_left  s16   # set_bitmap_window; compared signed by get_window_outcode
20F208  var   bitmap_window_top  s16
20F20A  var   bitmap_window_right  s16
20F20C  var   bitmap_window_bottom  s16
0995EA  func  print_text_list           # print_text for each record until a 0 halfword
0995F4  func  print_text                # record: text map offset (31F000 | u16), attribute byte, characters, 0
0995FE  func  print_text_here           # attribute byte, characters, 0 at R11
099620  func  erase_text                # writes 0 over the cells a print_text record covers
09962D  func  erase_text_here           # the same at R11
09963B  func  print_text_lines          # characters with bit 7 set start the next line: the line's first cell + 0x80, one text row down
09965F  func  erase_text_lines          # the same writing 0
0995CC  func  draw_fraction             # two cells at R11: character 80+R1 then 90+R2 (small digits), attribute R0: coins toward a credit, the coin table
0995C4  func  fill_halfwords            # R0 into R1 halfwords at R11
0995B0  func  clear_blocks              # R1 x 16 bytes of 0 at R11
09959B  func  copy_blocks               # R1 x 16 bytes from R10 to R11
09A676  table video_register_defaults  u16  # init_video_registers: 31FF00-0F (0000 0020 0000 8421 ...), 31FF5C-5F (01F7 0200), 31FF88-8F (0000 00E0 0010 0000)
20F005  var   vblank_copies_pending  u8 # queue_vblank_copy sets 1, run_vblank_copies clears it
20EE00  var   vblank_copy_count  u16    # entries in vblank_copies (several screen setups clear it)
20EE02  var   vblank_copies  u32        # 10-byte entries: source, destination, halfwords
20F009  var   screen_mode  u8           # set_screen_mode: bit 0 wide screen, bit 1 flipped
20F1D0  var   color_offset_red  u16     # the mixer shadow (20F1C0) + 10 -> 610040 (MAME: red offset, 6-bit signed: 20 black .. 1F white); fades set it with the two below
20F1D2  var   color_offset_green  u16   # -> 610042
20F1D4  var   color_offset_blue  u16    # -> 610044
20F1DE  var   mixer_blend  u16          # -> 61004E: bit 11 blending (game_mode_stage_end_fade clears it, apply_stage_mixer_settings sets it with bits 8-11)
20F200  var   bitmap_8bpp  u8           # set_bitmap_scroll: 1 = 8 bits per pixel
20F202  var   bitmap_address  u32       # get_bitmap_address result, used by the bitmap drawing (09A86A, 09A943)
20F27C  var   vblank_start_hook  u32    # irq_vblank_start calls it ("jsr [727C[R25]]" at 09F51B); init_video_registers stores apply_video_registers
20F2C2  var   video_registers_latched  u16  # 0x21 halfwords: what apply_video_registers writes at the next VBLANK
31FF06  io    video_clip_select  u16    # 4 bits per NBG layer choosing clip rectangles (default 8421: NBG0-3 each its own); 01DA21 writes 0400 with 31FF02 = 2122
31FF48  io    nbg2_page_select  u32     # 01DA21 writes 02020202
31FF5C  io    video_text_control  u16   # 1F7 (load_text_font): text map page (bits 4-8: 31F000) and character bank (bits 0-2: 31C000) (render_state.py)
31FF68  io    video_clip_1_left  u16    # clip rectangle 1 (31FF60 + 8n: left, top, right, bottom): 01CAAC sets B1, 21, 120, 78 and 01D3E7 opens it to 0, 0, 140, E0
31FF6A  io    video_clip_1_top  u16
31FF6C  io    video_clip_1_right  u16
31FF6E  io    video_clip_1_bottom  u16
31FF80  io    video_bitmap_clip_left  u16   # the bitmap layer's rectangle (31FF80-86): clear_bitmap_layer 0,0-13F,DF; update_screen_wipe moves it
31FF82  io    video_bitmap_clip_top  u16
31FF84  io    video_bitmap_clip_right  u16
31FF86  io    video_bitmap_clip_bottom  u16
31FF88  io    video_bitmap_scroll_x  u16   # set_bitmap_scroll; 096761 writes 0
31FF8A  io    video_bitmap_scroll_y  u16   # set_bitmap_scroll; default E0
610022  io    mixer_nbg0  u16           # NBG0 priority (bits 0-3) and palette: update_stage_06_background raises it to mixer_defaults | F (017A55) and restores it (017BEC)
610028  io    mixer_nbg3  u16           # the same for NBG3 (017A66, 017BF8)
61002A  io    mixer_bitmap  u16         # bitmap layer: 0021 in the test mode and 098203, 002F at 0967B5
400010  io    sprite_list_jump  u16     # entry 1 of the sprite list: swap_sprite_buffers writes the jump to the list just built (sprite_buffer_swaps)
D00000  io    irq_vectors  u8           # five vector registers: boot writes 00-04 (vectors 0x40-0x44)
D00006  io    irq_mask  u8              # boot writes 00: nothing masked (machine.c: pending = request & ~mask)
C00074  io    cabinet_events  u8        # not modeled (MAME logs it as unknown custom I/O; players.txt calls it lamps): 1F at init and game over, 11 per coin, 01/03/05 when player 1/2/3 pays (00D5F9), 00/02/04 when their game ends (005FBA), 14/15 at stage starts (by cleared_stages parity), 15 character select

# ======== sound ========
09A56D  func  init_sound                # sound_queue_read 0, write 1, queue[0] = 96 (sound_commands: 80 01), CNT bit 2 (sound CPU runs), port D bit 3 off, sound_cpu_hold = 0
09A5BC  func  play_sound                # R0 = id: queued always in a game; in attract only when setting_advertise_sound and attract_sound_mask has the id's bit
09A5DE  func  queue_sound               # R0 = id into sound_queue at sound_queue_write (wraps at 32); no filter (test mode, coins)
09F5FA  func  send_sound_command        # irq_vblank_start: one id per frame from sound_queue to the mailbox: sound_commands[id] byte 0 -> 700701, byte 1 -> 700700
09D793  func  test_sound                # SOUND TEST: service steps sound_test_number 0-8C (wraps), queues 94, 95 and the number (0: 94, 95 twice); test exits with 94
09D83F  func  wrap_sound_test_number    # sound_test_number > 8C -> 0
20ED80  var   sound_queue_read  u8      # send_sound_command's index (wraps at 32)
20ED81  var   sound_queue_write  u8     # queue_sound's index
20ED82  var   sound_queue  u8           # [32] sound ids
115A32  table sound_commands  u16       # by sound id: low byte -> 700701 (80 control, 81 music, 82 effects, 83 a third set), high byte -> 700700 (number in the group)
09A692  table attract_sound_mask  u32   # play_sound in attract: bit (id & 31) of word (id >> 5) set = may play (clear: ids 0-4, 7B-7E, 8E, 8F)
700700  io    sound_command  u8         # mailbox to the Z80 (Z80 E700): the number within the group; send_sound_command writes it after the group
700701  io    sound_command_group  u8   # Z80 E701: the group byte (80-83)
701FFF  io    sound_cpu_hold  u8        # the Z80 program loops at 012D while its FFFF is not 0; test_memory sets 1 (it tests and restores the shared RAM), init_sound sets 0

# ======== coins and credits ========
09F6B7  func  apply_settings            # init_system: coin variables from the EEPROM (credit_coin_cost, credits_per_start, multipliers, bonus_adder, free_play, shared_coin_counter), health_setting/starting_health; credits restored from saved_credits (fractions dropped, max 9)
09F765  func  update_coins              # irq_vblank_start unless free_play: clears the changed flags, coin sounds, a service press (bits 0/12) gives every player's block a credit, coins on release (bits 2, 3, 6 = chutes 1, 2, 3) through add_coin: common chutes -> block 1; individual 3P: chute 3 -> block 1, 2 -> 2, 1 -> 3; 2P: chute 2 -> 1, 1 -> 2; coin counters; save_credits
09F8DA  func  pulse_coin_counter        # R7 = counter state (on frames, off frames, pending coins), R1 = port D bit: on for 6 frames, off for 6, per pending coin
09F91F  func  add_service_credit        # R10 = credit block: below 9 credits: changed flag, credits + 1, service_coin_sounds + 1, R8 + 1 (bookkeeping_service_credits)
09F944  func  add_coin                  # R7 bookkeeping chute counter (+1), R5 coin counter pending (+1), R4 chute multiplier, R10 credit block: bonus adder, coins per credit -> credits (max 9), coin_sounds + 1, R9 = credits added
09F9AF  func  save_credits              # coins/credits of the three blocks -> saved_credits and its backup, with update_bookkeeping_checksum
09F9CD  func  copy_credit_counts        # first halfword of three credit blocks (R13, 4 apart) to R12
09FB98  func  draw_attract_credits      # run_frame outside the game, by cabinet_config: credit counts (draw_credit_count) and prompts: common chutes (configs 0, 1, 4) one count and blink_demo_prompt, individual chutes (2, 3) a count and blink_start_prompt per player; "2 credits 1 play" when credits_per_start is 2 (common chutes, not in free play)
09FCF4  func  blink_start_prompt        # every 16 frames: "Push start button", "Insert more coin", "Insert coin" or "2 credits start" (strings at 09FEE6), blinking (port D bits 2-3 cleared when off); also from the HUD (00AA1E)
09FD90  func  draw_credit_count         # R12 credit block, R11 text position: redraws (draw_credit_count_now) the first time (20F0BC bits F/12/13 for blocks 1/2/3) or when its changed flag is set
09FDC2  func  draw_credit_count_now     # "Free play", or "credit"/"credits" and the count (and draw_fraction of coins/credit_coin_cost); also from the HUD (006032)
09ED70  func  check_moving_seat_dip     # init_system: when DIP 1 (port F bit 0, low = on) differs from moving_seat_dip: stores it there and in setting_moving_seat, DIP on forces setting_cabinet_type 2P; saves the EEPROM
20F401  var   free_play  u8             # 1 when bonus_adder is 1 (COIN/CREDIT SETTING 26): run_frame and try_join need no credits, update_coins is skipped, "Free play" is drawn
20F402  var   credit_coin_cost  u8      # coins per credit (from setting_coins_per_credit)
20F404  var   shared_coin_counter  u8   # FC when both chute multipliers are equal: chute 2 coins then use the chute 1 multiplier and coin counter 1 (counter 2 is not pulsed)
20F406  var   coin_counter_1_on  u8     # frames coin counter 1 (port D bit 0) stays on; 20F407 the frames off after it
20F408  var   coin_counter_1_pending  u8    # coins still to count on counter 1
20F409  var   chute_1_multiplier  u8
20F40A  var   coin_counter_2_on  u8     # port D bit 1; 20F40B off frames
20F40C  var   coin_counter_2_pending  u8
20F40D  var   chute_2_multiplier  u8
20F40E  var   bonus_adder  u8           # n coins give one more coin (0 none, 1 free play)
20F410  var   credits_per_start  u16    # 1 or 2 (setting_credits_to_start)
20F412  var   p1_coins  u8              # credit block 1 (player 1, or everyone with common chutes): +0 coins toward the next credit, +1 credits, +2 coins toward a bonus coin, +3 changed; blocks 2/3 at 20F416/20F41A
20F413  var   p1_credits  u8
20F414  var   p1_bonus_coins  u8
20F415  var   p1_credits_changed  u8    # set by add_coin/add_service_credit, cleared every VBLANK by update_coins; draw_credit_count redraws then
20F416  var   p2_coins  u8
20F417  var   p2_credits  u8
20F418  var   p2_bonus_coins  u8
20F419  var   p2_credits_changed  u8
20F41A  var   p3_coins  u8
20F41B  var   p3_credits  u8
20F41C  var   p3_bonus_coins  u8
20F41D  var   p3_credits_changed  u8
20F41E  var   cabinet_config  u8        # bit 0 3-player cabinet, bit 1 individual coin chutes (setting_cabinet_config at init); run_frame, try_join, players and HUD switch on it
20F41F  var   service_coin_sounds  u8   # update_coins plays sound 7F and counts it down
20F420  var   coin_sounds  u8           # the same for coins

# ======== EEPROM settings (20F600-20F67F mirror the 93C46) ========
09C649  func  load_settings             # eeprom_read_all, then checks magic, ROM stamp and the two CRCs (backup, defaults or settings reset), saves; also stores DIP 1 in moving_seat_dip and clears bookkeeping_rom_stamp when it rebuilds
09C75F  func  eeprom_save               # eeprom_write_count + 1, compute_eeprom_checksum, copy to eeprom_backup, write enable (command 30), the 0x40 words (command 40 + address)
09C758  func  eeprom_save_and_lock      # eeprom_save then eeprom_lock (the test mode and check_moving_seat_dip)
09C613  func  eeprom_lock               # command 00 (write disable); 09C618 (unreached) is the write enable entry (command 30)
09C899  func  compute_eeprom_checksum   # compute_eeprom_crc of 760A-763F (eeprom_checksum)
09C892  func  compute_eeprom_backup_checksum  # compute_eeprom_crc of 764A-767F (eeprom_backup_checksum)
09C89E  func  compute_eeprom_crc        # compute_crc16 of 0x36 bytes from R20 (same loop as compute_crc16)
09C580  table eeprom_defaults  u8       # 0x40 bytes (load_settings); from +0E (09C58E) the settings part only
20F600  var   eeprom_magic  u32         # 'A23S' (bytes 41 32 33 53) in a valid EEPROM; 'SEGA' loads the defaults with eeprom_write_count 100
20F604  var   eeprom_rom_stamp  u16     # rom_checksums[0] + [1] when written: another program version resets the settings
20F608  var   eeprom_checksum  u16      # CRC of 760A-763F
20F60A  var   eeprom_write_count  u32   # eeprom_save adds 1; exit_test_mode warns from 10000 ("YOU HAVE EXCEEDED THE EEPROM'S WRITING FREQUENCY LIMIT"); the menu shows it when service is held
20F60E  var   setting_advertise_sound  u8   # GAME ASSIGNMENTS "ADVERTISE SOUND" OFF/ON (default off): play_sound in attract
20F60F  var   setting_coins_per_credit  u8  # "COIN TO CREDIT n COIN(S) 1 CREDIT" 1-9
20F610  var   setting_chute_1_multiplier  u8   # "COIN CHUTE #1 MULTIPLIER: 1 COIN COUNTS AS n COIN(S)"
20F611  var   setting_chute_2_multiplier  u8
20F612  var   setting_bonus_adder  u8   # "BONUS ADDER": n coins give 1 extra coin (0/1 none in the text; 1 = free play)
20F613  var   setting_cabinet_config  u8    # (setting_coin_chute_type << 1) | setting_cabinet_type, written by the test exits
20F614  var   setting_credits_to_start  u8  # "CREDIT TO START" 1 or 2
20F618  var   setting_monitor_flip  u8  # bit 1: "MONITOR FLIP" REVERSE (set_screen_mode, init_sprites)
20F61A  var   setting_coin_chute_type  u8   # "COIN CHUTE TYPE": 0 COMMON, 1 INDIVIDUAL
20F61B  var   setting_coin_number  s8   # "COIN/CREDIT SETTING #" - 1 with common chutes (0-0x19: 4 bytes each at 116EC5 -> 760F 7612 7610 7611); FF = MANUAL SETTING
20F620  var   setting_difficulty  u8    # "GAME DIFFICULTY" 0-3 (shown 1-4 /4; default 1); apply_settings copies it to health_setting
20F622  var   setting_initial_vitality  u8  # "INITIAL VITALITY" 0-3 (default 1): starting_health = 115B92[n] (09 10 12 18)
20F623  var   setting_no_start_buttons  u8  # "START BUTTON": 0 USE (start 1/2/3, bits 4/5/7), 1 NO USE (the attack buttons start, bits 16/24/18)
20F624  var   setting_moving_seat  u8   # "MOVING SEAT" USE (only with DIP 1): 002370, 00E3BB and blink_start_prompt drive port D bits 2-3
20F625  var   setting_cabinet_type  u8  # "CABINET TYPE": 0 2P, 1 3P
20F626  var   setting_coin_number_individual  s8   # COIN/CREDIT SETTING with individual chutes: index 0-0xE into 1167B5 (the allowed setting numbers)
20F640  var   eeprom_backup  u8         # second copy of 7600-763F (eeprom_save; load_settings falls back to it)
20F648  var   eeprom_backup_checksum  u16

# ======== bookkeeping and backup RAM (shared RAM, battery-backed) ========
09F9DD  func  load_bookkeeping          # init_system: bookkeeping_rom_stamp wrong -> reset_bookkeeping; else CRC of the block, or of the backup, decides which copy wins; power_on_frames restored
09FA62  func  reset_bookkeeping         # 0x20 halfwords of bookkeeping_defaults to 701F02 and 701F42, stamps, update_bookkeeping_checksum (also BACKUP DATA CLEAR)
09FA9D  func  update_bookkeeping_checksum   # bookkeeping_checksum = compute_bookkeeping_crc(701F02)
09FAAF  func  compute_bookkeeping_crc   # compute_crc16 of 0x40 bytes
09FAB3  func  compute_crc16             # R2 data, R3 bytes: CRC (polynomial 1021, seeded DEBDEB, 8 extra shifts) in R0 (EEPROM, bookkeeping, ranking, ROM test)
09FAEB  func  compute_crc16_alternate_bytes # every other byte with "inc.b R2": the even pass (IC18) stays inside 100000-1000FF, over and over; on the odd pass the carry of [R2+] crosses into the next page, so it reads the odd bytes 100001-17FFFD (IC9); rom_checksums holds exactly those values
09FB25  func  add_play_time             # bookkeeping_play_time += game_frames (48-bit)
09FB3B  func  record_player_game        # player game end (005F9D): bookkeeping_games + 1, player time (game_frames - player 1D2) into bookkeeping_player_time, longest, shortest; add_play_time when no player_status is negative, 1 or 4 (nobody still playing); backup copy
09F660  func  count_frames_and_check_test_switch   # irq_vblank_start outside test mode: power_on_frames + 1 (saved to the bookkeeping every 64 frames), game_frames + 1, test_mode on test or PCB test (bits 1/13) without service
09F08C  table rom_checksums  u16        # compute_crc16 of IC17 (000000-01FFFF) 3C5C, IC8 (080000-09EFFF) 7124, then IC18 89DC and IC9 A0A1 as compute_crc16_alternate_bytes gives them (check_rom_checksums)
elements rom_chip_checksums u16       # the words of rom_checksums check_rom_checksums compares one after another with "cmp.h [R10+], R0" (09CD5C, 09CD7C, 09CD9C, 09CDBC); census ROM 09F08C-09F093 (3C5C 7124 89DC A0A1)
115B60  table bookkeeping_defaults  u16 # reset_bookkeeping: 0x20 halfwords, all 0 except bookkeeping_shortest_game FFFFFFFF
701F00  io    bookkeeping_checksum  u16 # CRC of 701F02-701F41 (also accepted for the backup)
701F02  io    bookkeeping_coin_credits  u16     # BOOKKEEPING "COIN CREDITS" (add_coin's credits)
701F04  io    bookkeeping_service_credits  u16  # "SERVICE CREDITS"
701F06  io    bookkeeping_coin_chute_1  u16     # "COIN CHUTE #1"
701F08  io    bookkeeping_coin_chute_2  u16
701F0A  io    bookkeeping_coin_chute_3  u16
701F0C  io    bookkeeping_games  u16    # "NUMBER OF GAMES" (record_player_game: one per player game)
701F0E  io    bookkeeping_longest_game  u32     # frames, "LONGEST GAME TIME"
701F12  io    bookkeeping_power_on_time  u32    # frames, low 32 bits of "TOTAL TIME" (high 16 at 701F16)
701F16  io    bookkeeping_power_on_time_high  u16
701F18  io    bookkeeping_play_time  u32    # frames, "GAME PLAY TIME" (high 16 at 701F1C)
701F1C  io    bookkeeping_play_time_high  u16
701F1E  io    bookkeeping_player_time  u32  # sum of the players' game times ("AVERAGE GAME TIME" = / games; high 16 at 701F22)
701F22  io    bookkeeping_player_time_high  u16
701F24  io    bookkeeping_shortest_game  u32    # frames, "SHORTEST GAME TIME" (FFFFFFFF = none)
701F28  io    bookkeeping_rom_stamp  u16    # rom_checksums[0] + [1]; load_settings clears it so the bookkeeping is reset
701F2E  io    saved_credits  u16        # [3] coins and credits of the credit blocks (save_credits; apply_settings restores the credits)
701F42  io    bookkeeping_backup  u16   # copy of 701F02-701F41
701F52  io    bookkeeping_backup_power_on_time  u32
701F56  io    bookkeeping_backup_power_on_time_high  u16
701F68  io    bookkeeping_backup_rom_stamp  u16
701F6E  io    saved_credits_backup  u16
701F82  io    saved_ranking  u8         # 15 records of 8 bytes (load_ranking, reset_ranking, enter_ranking_initials)
701FFA  io    ranking_checksum  u16     # compute_crc16 of saved_ranking (0x78 bytes)
701FFE  io    moving_seat_dip  u8       # DIP 1 as last seen (1 = moving seat cabinet); gates OUTPUT TEST and MOVING SEAT in the test mode

# ======== test mode (09C8EC-09EDC0) ========
09C8EC  func  start_test_mode           # boot's loop when test_mode is set: display off, show_eeprom_write_count if service is held, palettes, sprites, init_sound, sound 94, text and bitmap layers (31FF02 = F, bitmap 8 bpp filled with 09), color offsets 0, menu state
09CA00  func  run_test_mode             # every frame in test mode: jumps by test_screen (table at 09CA0E)
09CA32  func  test_menu                 # test_screen 0: service (bits 0/12) moves test_cursor (item 9 OUTPUT TEST only with moving_seat_dip), test (bits 1/13) opens item n as test_screen n + 1
09CA53  func  draw_test_menu            # "TEST MODE" menu (115BE9), OUTPUT TEST line with moving_seat_dip, eeprom_write_count when show_eeprom_write_count
09CB39  func  toggle_test_continue_mode # menu item 0: test_continue_mode INDIVIDUAL/CONTINUE
09CB69  func  test_memory               # MEMORY TEST: sound_cpu_hold = 1, check_rom_checksums, the RAM tests, then video/sound init again and the result per chip from memory_test_errors
09CD41  func  check_rom_checksums       # compute_crc16 of IC17/IC8 and compute_crc16_alternate_bytes of IC18/IC9 against rom_checksums: memory_test_errors bits 0, 2, 1, 3; the MEMORY TEST screen shows bit 0 (000000-01FFFF, IC17 in MAME's set) by its "IC8" label and bit 2 (080000) by "IC17": the screen's chip names and MAME's differ
09CDC9  func  test_work_ram             # saves 200000-20FFFF in video RAM, writes and compares patterns (9ED5C), restores; R25 = 308000 meanwhile so the error bits (5, 6) land in the saved copy of memory_test_errors (memory_test_video_word)
09CE94  func  test_video_ram            # 300000-31FFFF, error bits D-10 by nibble and byte lane
09CF88  func  test_sprite_ram           # 400000-41FFFF, error bits 9-C
09D078  func  test_palette_ram          # 600000-607FFF in two halves, error bits 7-8 and 11-12; each fill (8 passes of 4 x F9 halfwords) ends at 603E40 / 607E40, so its exit at 604000 / 608000 is never taken and 603E40-603FFF, 607E40-607FFF go untested
09D1E3  func  test_sound_ram            # 700000-701FFF (saved in video RAM and restored), error bit 4
09D289  func  test_inputs               # INPUT TEST: starts, attacks, coins, service, test (ON/OFF from inputs_held 701C), trackball cursors
09D5ED  func  move_input_test_cursors   # moves the three trackball cursors, "GOOD" once a trackball went all four ways
09D76B  func  get_trackball_quadrant    # R1 x, R2 y -> R3 = 1, 2, 4 or 8 by signs
09D89F  func  test_crt                  # C.R.T. TEST 1/2 (color bars) and 2/2 (crosshatch on the bitmap layer)
09DA7E  func  test_game_assignments     # GAME ASSIGNMENTS: difficulty, vitality, advertise sound, monitor flip, cabinet type, start button, moving seat, exit (saves when test_settings_changed)
09DDCE  func  draw_vitality_setting     # bar and number for setting_initial_vitality
09DE65  func  draw_difficulty_setting   # bar and number for setting_difficulty
09DEF3  func  test_coin_assignments     # COIN ASSIGNMENTS: chute type, credit to start, coin/credit setting, manual setting page, exit
09E1F0  func  apply_coin_setting        # copies the 4 bytes of the chosen setting (116EC5) into setting_coins_per_credit, setting_bonus_adder, chute multipliers; draws it
09E2BC  func  draw_coin_setting_number  # convert_to_bcd and draw_hex_2 at 31F4BE
09E5EE  func  clamp_coin_setting        # [R10] kept in 1-9
09E603  func  draw_coin_credit_table    # "1 COIN COUNTS AS" table: credits for 1-9 coins per chute
09E638  func  draw_coin_credit_cell     # credits (and draw_fraction) for R4 coins with multiplier R1
09E717  func  test_bookkeeping          # BOOKKEEPING: coin chutes, total coins, coin/service/total credits, games, total/play/average/longest/shortest times (split_frame_count)
09EA2F  func  test_backup_clear         # BACKUP DATA CLEAR: YES -> reset_bookkeeping and reset_ranking, "COMPLETED"
09EB35  func  test_outputs              # OUTPUT TEST (moving seat): 1P/2P attack held -> port D bit 2/3 on
09EC2B  func  exit_test_mode            # EXIT: the EEPROM caution when eeprom_write_count >= 10000, clears credits and bonus coins, test_mode = 0
09EC91  func  flip_test_coordinates     # mirrors R0-R3 around 160, 112 when the screen is flipped (31FF00 bit 9)
09ECC0  func  blink_test_cursor         # ">" (115CEB) and " " (115CED) every 0x1E frames at the cursor
09ED0F  func  erase_test_cursor         # cursor position of test_cursor from the table in R10, then a blank there
09ED31  func  set_test_cursor           # test_cursor wraps to 0 at the FF end of test_cursor_positions; loads its column/row
20CA1C  var   test_screen  u8           # 0 menu, 1 INDIVIDUAL/CONTINUE, 2 memory, 3 input, 4 sound, 5 C.R.T., 6 game assignments, 7 coin assignments, 8 bookkeeping, 9 backup clear, 0xA output, 0xB exit
20CA1D  var   test_screen_ready  u8     # 0: the screen draws itself first, then 1
20CA1E  var   test_cursor  u8           # item under the cursor
20CA23  var   test_cursor_column  u8
20CA24  var   test_cursor_row  u8
20CA25  var   test_cursor_blink  s8  # (TYPE: draw_blinking_test_cursor counts it from 1E down below 0 and compares it signed with -F)
20CA26  var   test_menu_item  u8        # menu item to come back to
20CA27  var   test_continue_mode  u8    # 0 INDIVIDUAL (back to the menu after a test), FF CONTINUE (on to the next test)
20CA28  var   sound_test_number  u8
20CA29  var   crt_test_x  u16
20CA2B  var   crt_test_y  u16
20CA2D  var   crt_test_color  u8
20CA3C  var   coin_manual_page  u8      # COIN ASSIGNMENTS: 1 while the MANUAL SETTING page is shown
20CA46  var   test_settings_changed  u8 # set by any option change; the EXIT items then save (eeprom_save_and_lock)
20CA4F  var   backup_clear_timer  u8
20CA50  var   memory_test_errors  u32   # one bit per failed ROM/RAM chip (MEMORY TEST); test_memory clears it, which also clears show_eeprom_write_count (byte 20CA51); its bits 8-F (palette, sprite and video RAM errors) are that byte, so they also turn the menu's EEPROM write count on
20CA51  var   show_eeprom_write_count  u8   # start_test_mode: 1 when service is held (inputs_held 0x1001); draw_test_menu then shows eeprom_write_count. Also bits 8-F of memory_test_errors: cleared by test_memory, set by palette, sprite or video RAM errors

# ======== objects (from build/naming/objects.txt) ========
# Proposals: the object system and drawing (objects, sprites, projection, collision, movement)
# Format as in symbols.txt; every line carries its evidence. Addresses are the ones the C and the listing use.
# "Reference run" = build/bin/s32ref.exe with tests/controls/stage_00.txt (frame 3700 dumped and --watch).

# ======== struct object (0xA0 bytes) ========
# 00-1B is a sprite descriptor that build_sprite_list (09A27B) turns into one 16-byte System 32 sprite
# entry. Pool 2 blocks (0x30 bytes, see allocate_object_2) only have 00-2F.
# 7E-9F (except 9A) are each object type's own variables: 80-8C are timers, counters, step indexes
# (08E49E, 08E7FD, 01E86E), 92/94 come from the stage list (08DC63), 9C is the creator's return address
# for spawn_objects (08DD56) and a "next handler" for the objects 083B14 restarts. Not named here.
field object 00 draw_next u16 object          # next object in the same depth bucket: the queue_sprite_* routines (099FC0...) push the object with "mov.h [bucket],[R20]"; build_sprite_list follows it until it points back at the bucket
field object 02 sprite_flags u16       # word 0 of the sprite entry (MAME segas32_v: 13 indirect palette, 11 shadow, 9 8bpp, 8 pen 0 transparent, 7 y flip, 6 x flip, 5/4 add the y/x offset set by entry 1 (screen centre 70/A0), 3-2 y anchor (1 bottom), 1-0 x anchor (0 centre)); 00DFC1/00E058 negate dx/dy with bits 6/7. The game reuses bits 15,14,12 to pick how 09A27B writes it: 000 scaled frame, 001 unscaled frame (0x1004 is the usual value), 01x the 16 bytes 02-11 as they are (clip entries, 0x5000), 100 a jump to the next entry (unused), 101 sized frame (18/1A sizes, 08 = address), 110/111 multi-part sprite (0xC000/0xD00x). init_object_fields sets 4
field object 04 sprite_frame u16 wall_kind_parts@wall_kinds+6       # offset of the frame's 4-word record (width, height + address bits, bank bits, address) in sprite_frame_records (7112[R25] = 0x130000); 00DEB3 & co. load it from the animation. Multi-part sprites keep a 32-bit pointer to a part list in 04-07 (count <= 16, then per part frame, flags, s8 dx, s8 dy, palette: 09A3F2). The walls' spawner keeps its wall kind's byte offset into wall_kinds in it (spawn_walls tests that kind's second_sprite at wall_kind_second_sprites + it), hence its link: a wall_kind_parts record that many bytes from wall_kinds + 6
field object 04 part_list u32            # multi-part sprites (flags 0xC000/0xD00x): the 32-bit address of the part list in 04-07, in place of sprite_frame and sprite_palette
field object 06 sprite_palette u16     # word 7 of the sprite entry (09A2CB "mov.h 6[R20],[R19+]"): palette; the bits above it pick the mixer's priority group (00C2C7 copies the player's bits 13-14 to its extra sprites, 01E4ED moves a shadow to 0x2000); xFFE is the shadow colour (shadows use 9FFE/1FFE)
field object 08 sprite_scale u16       # 0x100 = 1:1: 09A317 multiplies both frame sizes by it (+0x80, >>8); init_object_fields sets 0x100; 01E9C1 shrinks rings by 0x10 a frame. Sized frames (flags 9xxx) use it as the address word instead (0953BF loads it from the frame record +6)
field object 0A sprite_layer u16       # low byte = depth bucket for queue_sprite_in_layer (099FF9); HUD objects set 0-9 just before calling it (01C3D4 6, 01C53A 5, 095B0B 1)
field object 0C screen_y s16           # sprite entry word 4 (09A2C2 "mov.w C[R20]" writes 0C then 0E); update_screen_position: (projected_y + z) >> 16 - view_y + sprite_offset_y. Reference run: player 137 = 495 - 368 + 10
field object 0E screen_x s16           # sprite entry word 5; projected_x - view_x + sprite_offset_x (player 104 = 233 - 129 + 0)
field object 10 hitbox_size_x u16      # box hitboxes span x + hitbox_offset_x .. + this (check_hit 001E6A, check_zones 0018E5, check_walls 0022E7); round hitboxes (flags bit 1 clear) use it as the radius (001F64: radius sum, squared distance)
field object 12 hitbox_size_y u16      # the box along y (001E97, 001900); round hitboxes use its two bytes as s8 centre offsets in x (12) and y (13) (001F78, 001F9D)
field object 14 anchor_x s16           # 00DFC1: projected_x = this + the animation step's dx; handlers copy projected_x here first (080EA8, 088D5D, 08CB40). check_zones stores the touching partner here instead (00193C), which 00CF6A reads
field object 16 anchor_y s16           # 00DFC1: integer part of projected_y = this + the step's dy
field object 18 sprite_height s16      # sized frames (flags 9xxx): ORed into entry word 2, the height on screen (09A386); 0953BF sets it  (TYPE: 4 of its 5 reads are signed: player_update/player_animate, shadow_update and draw_opening_machine_shadow keep a signed y offset here that they scale by the zoom; only build_sprite_list ORs it)
field object 1A sprite_width s16       # sized frames: ORed into entry word 3, the width on screen (09A373)  (TYPE: the same scaled x offsets, and player_hud_update and message_box_show compare it signed; only build_sprite_list ORs it)
field object 24 direction u16          # angle, 0x400 a turn: set_speed_from_direction gives speed_x = sine[24] * speed, speed_y = -cosine[24] * speed (0 = towards -y, 0x100 = towards +x); face_nearest_player sets it (about towards player 1: see there)
field object 26 parent u16 object             # 16-bit address of the object that created it: spawn_objects/spawn_object_with_pieces store R23 (object_cursor); pool 2 pieces get the main object (08DDB2); 082E82, 01E37E too
field object 28 child u16 object              # first object of the chain this object owns, FFFF = none; the chain goes on through each child's 28 (08DDAE, 01E37E: a ring's shadow); delete_object_and_children frees the whole chain
field object 2A projected_x s16        # project_object: ((x + y) >> 8) * 0xB5 >> 16, the isometric screen x before the camera; unproject_object turns 2A/2C back into x/y
field object 2C projected_y s32        # project_object: ((y - x) >> 9) * 0xB5, 16.16: screen y of the ground point (2E = whole pixels); screen_y adds z; sprites queued at their floor sort by it
field object 2C projected_y_fraction u16
field object 2E projected_y_int s16     # the whole part of projected_y (16.16): the screen y of the ground point
field object 30 flags u32              # bits: 1 box hitbox (else round), 2 touched (check_hit sets it on both, the other in touched_by), 3 on a platform (check_platforms, the other in platform), 4 was on a platform last frame (00B5C6), 5 against a wall (check_walls, the other in wall), 6 hit by an attacking player (00E488 sets it on the touched object; the player handlers also set it on themselves), 7 animate backwards (00DEB3), 9 players ignore its touches (00E488; scattered rings for their first 0x1E frames), 10 player out of the game (08DCEC, 001A7F, 002CEC), 11 has been on screen (01F0EF), 27 touched in check_zones (the other in anchor_x). The player handlers use other bits for their own states; timer objects use byte 0 as "started" and the high word as a frame counter (0973B4)
field object 44 animation_speed u8     # frames per animation step: animate reloads animation_timer from it
field object 45 animation_timer s8     # counted down every frame by animate (00DEA2) and the other animate_* routines; the step advances when it runs out (reference run, player: 2C, 2B ... 01, then 46 = 6 and 2C again)
field object 46 animation_frame u8     # index of the current step (animate_step: + 1, wraps to 0 after the last step number; - 1 with flags bit 7)
field object 47 kind u8                # what the object is to collisions: 0-2 the players (002808 copies player into it), 3 ring (01E709, all stage_object_templates), 9 object_steps (082E82), 0xC the most common enemy kind...; check_hit skips pairs where both are 0-2; the player switches on the touched object's kind (00E488, 00CF6A)
field object 48 animation u32          # ROM pointer to the animation: a word (last step number in its low byte), then the steps (animate_step: frames; 00DE60: flags + frame; 00DEFD/00DF72/00DFF5: frame, dx, dy)
field object 54 floor_z s32            # z of the floor under the object (same units as z; larger = lower): find_floor sets it from the floor block (+10) and its slope; 0x800 = no floor (no_floor_block); fall_to_floor lands z on it; check_platforms compares riders with a platform's 56
field object 54 floor_z_fraction u16
field object 56 floor_z_int s16         # the whole part of floor_z
field object 58 previous_floor_z s32   # find_floor saves floor_z here before replacing it (while floor_block >= 0 and the floor < 0x7FF); 01E8C8 and the player restore it when they step back
field object 58 previous_floor_z_fraction u16
field object 5A previous_floor_z_int s16   # the whole part of previous_floor_z
field object 5C floor_block s32        # offset of the floor block under the object in floor_blocks (from +2), -1 = none (find_floor). Reference run: player 0x16 (the second 22-byte block)
field object 60 previous_floor_block s32   # find_floor saves floor_block here; restored together with previous_floor_z (01E8C8, 00C920). Timer objects use byte 61 as their list index (0973B4)
field object 64 sprite_offset_x s8     # added to screen_x (00C50D "+ (int8_t)AS8(unk_64)"); stage_object_templates and the spawners set it (01E37E, 09383A 0x2E)
field object 65 sprite_offset_y s8     # added to screen_y (00C50D); queue_sprite_at_base takes it back off for the depth. Player: 0x0A
field object 66 hitbox_height u16      # how far the hitbox reaches above z: check_hit 00205F needs "42 - 66" of each <= the other's 42; check_walls compares the height difference with it. Player: 0x18, rings: 0x20
field object 68 hit_list_next u16 object      # link in the hitter, touchable and zone lists (0020A5, 0020C2, 001956 push the object; 001DDD, 001E33, 0018B6 walk until a negative value)
field object 6A touched_by u16 object         # check_hit (00208A) stores each object of a touching pair in the other's touched_by; the player reads its kind (00E488), rings read it to find the player that took them (01E4ED)
field object 6E speed s32              # speed along direction: set_speed_from_direction uses its high word (70), 0x100 there = 1 pixel a frame; scattered rings get 0x800000-0xFFFFFF (01E709)
field object 6E speed_fraction u16
field object 70 speed_int s16           # the high word of speed: 0x100 is 1 pixel a frame
field object 72 platform_list_next u16 object # link in the rider, platform and wall lists (0020DB, 0020F4, 0022A6 push; 002108 and 0022BA walk)
field object 74 platform u16 object           # check_platforms (002178): on a rider the platform it stands on, on the platform the rider; find_floor takes floor_block and floor_z (the platform's z) from it while flags bit 3 is set
field object 76 floor_slope u8         # slope type of the floor block (+13), index into floor_slopes: find_floor adds the slope to floor_z, check_hit corrects round hitboxes with it (001FBE)
field object 77 floor_type u8          # byte 0 of floor_types for the block's type (+12) (find_floor); the player and the rings test it (7 in 01E4ED, 0xD in 002CEC)
field object 7A floor_attribute u16 floor_attribute_record@floor_types    # word 2 of floor_types for the block's type (find_floor); the player tests it (4, 5 in 00E488/002CEC)
field object 7C hitbox_offset_x s8     # box hitboxes start at x + this (001E55, 0018D8, 0022DA)
field object 7D hitbox_offset_y s8     # box hitboxes start at y + this (001E82, 0018F3, 0022F5)
field object 7E health s8              # enemies: 00E488 subtracts the attacking player's strength (player block 72) when it hits them, they die at 0 (00F8E1), the sturdiest start at 0x7F (00EED2, 00F1F1); players: their rings (00E488 adds ring pickups, hits take 4 or 8 off in 002CEC, at 8 or less some hits put the player out with flags bit 10; starts at 20E66A: 0x10 in the reference run)
# 7F-9F are each object type's own variables. Where symbols.txt names them for a family of functions
# ("scoped object" lines) the C reads them by those names; elsewhere by these, named after their offset
# like the generic slots of other decompilations (var_80: this object type's variable at 0x80)
field object 7F var_7F u8
field object 80 var_80 u8
field object 81 var_81 u8
field object 82 var_82 u16
field object 84 var_84 u16
field object 86 var_86 u16
field object 88 draw_depth u16  # The depth an object others draw relative to keeps here: every object a player or a crawler stands on, and every one a player falls over or drops from (platform 74, touched_by 6A), keeps its draw depth (screen y + depth offset) at 88, and the player states read it (states.c: 16 reads, platform/touched_by's 88 -1 / +1; check_player_floor_edge), as do crawler_head_update/crawler_segment_update (platform's 88 - F), icicle_update (its platform piece's + 1), icicle_buried_part_draw / icicle_cracks_spread / icicle_shadow_follow (the icicle's), rimmed_sinkhole_edge_zone_update (the hole's) and saw_sparks_update (the blade's + 1); lava_rock, crumbling_floor, falling_grate, river_water, sand_block, rope_bridge_plank, collapsing_bridge_plank, ice_floor_block, ice_platform_piece, icicle_update, river_raft_update, saw_blade, sinkhole, rimmed_sinkhole and clipped_copy_sprite all name their 88 draw_depth. Not a depth, read through another object's pointer (no family yet, N1): fire_vent_burst_animate's AS8 store of FF (the vent's burst_state) and saw_sparks_update's test of the blade's parent's 88 (the rising saw's base_speed: drawn while it moves). Families name 88 their own way in their own code (fire_front base_speed...)
field object 8A var_8A u16
field object 8C var_8C u16
field object 8E var_8E u8
field object 8F var_8F u8
field object 90 var_90 u16
field object 92 spawn_arg_1 u16          # the stage lists' objects get the record's next-to-last word here (spawn_objects_by_type)
field object 94 spawn_arg_2 u16          # ... and its last
field object 96 var_96 u16
field object 98 var_98 u16
field object 99 var_99 u8
field object 9C next_handler u32         # spawn_objects stores the creator's return address here; the objects 083B14 restarts keep their next handler

# ======== the object list and the two pools ========
# Pool 1: 0x80 blocks of 0xA0 bytes at 0x200000-0x204FFF (stack free_object_stack, 16-bit addresses).
# Pool 2: 0xFF blocks of 0x30 bytes at 0x205000-0x207FCF (stack free_object_stack_2): sprite-only pieces
# (shadows, chain parts) that hang from a pool 1 object through child (28) and are never in the list.
# The list is circular through previous/next; run_objects starts at last_object's next (the first) and
# ends with last_object. New objects go at the end, so they run in the frame they are created.
0991B3  func  reset_objects            # RENAME of reset_objects: "bsr 991EE; bsr 99317; br 991C1" empties the list and both pools, then run_objects finds object_count 0 and only reloads R21; called on game mode changes (000243, 0002D4, 000446, 001D0F, 09C94A, 09CC03), never every frame (the frame routine 0000DF calls run_objects itself)
0991EE  func  reset_object_list        # first/last_object_address = 0x200000, free_object_stack = 0x80 then 0x0000, 0x00A0 ... 0x4F60, object_count = 0
099317  func  reset_object_pool_2      # free_object_stack_2 = 0xFF then 0x5000, 0x5030 ... (0x30-byte blocks)
099218  func  take_free_object         # pops the free stack into R11 (0x200000 | entry) without linking it (08A4ED, 08A9DC, 08C6E1, 08D32D)
099229  func  allocate_object_or_start_list   # allocate_object, or when object_count is 0: pop one and make it first and last, linked to itself (08F22D, 001D07, 096E24)
099256  func  link_object              # the tail of allocate_object: R11 goes between last_object and first_object (previous/next), becomes last_object, object_count + 1
0992A4  func  delete_current_object    # unlinks object_cursor (R23), moves R23 back to the previous object so run_objects goes on with the right next one, R20 = the freed block, then push_free_object (216 callers: "this object is done")
0992D3  func  push_free_object         # free_object_stack + 1, stores R20 on top
0992E4  func  delete_object            # the same for the object in R0 (unlink, fix first/last, push on the free stack)
099369  func  free_object_2            # pushes R0 back on free_object_stack_2 (pool 2 blocks are never linked)
01F1D7  func  allocate_object_outside_loop   # reloads R21/R22/R24 from 20E900 and first/last_object_address, then allocate_object: the spawners that run from the game mode code use it
01F1EA  func  delete_object_outside_loop     # the same around delete_object, storing first/last back (090CFA; unreached in the tests)
08DE6E  func  init_object              # init_object_fields with R12 = 0: every field
08DE74  func  init_object_2            # R12 = 1: only 00-2F, the size of a pool 2 block
08DE77  func  init_object_fields       # draw_next 0, sprite_flags 4, frame/palette 0, scale 0x100, 0A-1A 0, handler = delete_object_and_children, 24/2A/2C 0; with R12 = 0 also 30-9F (08DEE5 is an unused entry that skips z and below)
08DDD2  func  spawn_objects            # spawn_count pool 1 objects chained through child, each init_object, parent = R23, 9C = the caller's return address ("mov.w [SP],9C[R11]"); R3 = the first, R0 = 1; R0 = 0 when the free stack is too short
08DD15  func  spawn_object_with_pieces # one pool 1 object (init_object, parent = R23, 9C = return address) and spawn_count - 1 pool 2 pieces chained from it (add_object_pieces); R3 = the object (08DC63, 08F263, 08F9AB and 20 enemy routines); 08DD2D tests the whole R2 after "mov.h [R21], R2", so the high half the caller left in R2 takes part in the no-free-objects test
08DD7E  func  add_object_pieces        # allocates R1 pool 2 blocks (init_object_2) chained through child after spawn_last, each with parent = spawn_first
08DD77  func  add_pieces_to_current_object   # add_object_pieces for R23 (unreached)
08E0CE  func  delete_object_and_children     # the default handler (init_object_fields): frees the child chain (pool 1 children with delete_object, pool 2 with free_object_2; traps if free_object_stack_2 goes past 0x100), then delete_current_object; R0 = 1
08E0A5  func  delete_if_off_screen     # R0 = 0 while screen_x is in -0xC0..0x200 and screen_y in -0xC0..0x1A0, else delete_object_and_children (R0 = 1); 38 callers
08E07A  func  is_on_screen             # the same test, R0 = 1 inside (unreached)
208008  var   spawn_count  u16         # how many blocks spawn_objects / spawn_object_with_pieces allocate (object_types +0 through 08DC63, enemies set it before calling)
20800A  var   spawn_first  u32 object         # the first object they allocated (returned in R3, parent of the pieces)
20800E  var   spawn_last   u32 object         # the last object of the chain while it is built (its child gets the next one)
208012  var   deleted_child  u16 object       # the child delete_object_and_children is freeing

# ======== spawning from the stage lists ========
# Each spawner keeps a cursor into the current stage's list (loaded once, see stage_init_flags) and
# spawns every record whose position the stage has reached (20E435); most of them drop records that are
# more than 0x100 behind.
08DC63  func  spawn_objects_by_type    # stage_spawns_by_type: object_types[type] gives spawn_count (+0) and the handler (+4); the record's words go to projected_x, projected_y, z (not x/y: unproject_object computes those), 92 and 94; a record is dropped when the pools are full
08DC3C  func  start_spawns_by_type     # type_spawn_cursor = stage_spawns_by_type[stage], type_spawn_stage = stage; also empties string_queue
08DCEC  func  count_active_players     # active_player_count = players whose object (player block 14) lacks flags bit 10 (player_block_offsets)
080000  func  spawn_objects_by_handler # stage_spawns_by_handler: x, y, sprite_frame (a parameter the handler reads: 080072 indexes 82284 with it), handler
01E37E  func  spawn_ring_rows          # stage_template_spawns: count objects from stage_object_templates (all six are rings: frame 5E70, kind 3) along a line (dx, dy), each with a pool 2 shadow (frame 5D90) as child; z = floor_z - template word 8 (kept in 82)
091220  func  spawn_object_groups      # the list at object_group_spawns: type 0/2/6 records make groups from stage_object_records (093269); types 1/8, 3, 4, 5, 7 call 0916F1, 091F1B, 087EDE, 089EB0, 08CC66
09383A  func  spawn_object_groups_2    # stage_record_spawns: groups from stage_object_records_2 (093F59); types 1/4 call 0916F1
0973B4  func  timed_spawner_run        # handler: flags byte 0 = started, flags word 2 counts frames, byte 61 = next entry of timed_spawns; spawns each handler (offset from 0973B4) when its frame comes, deletes itself at FFFF
01C458  func  run_timed_spawns_2       # one entry of timed_spawns_2 for the calling object (61 = entry, flags word 2 = frames)
01D328  func  run_timed_spawns_3       # one entry of timed_spawns_3 (deletes the object at FFFF)
001D07  func  reset_objects_from_list  # reset_objects, then one object per handler of the list at 20F0EA (count, handlers) with allocate_object_or_start_list/allocate_object; 291F becomes 28D1/28DB from the player block's state (20F0EC walks the player blocks, + 0x200 each)
001CFD  func  start_player_objects     # reset_objects_from_list with player_objects (002405)
001CF1  func  start_player_objects_alt # reset_objects_from_list with player_objects_alt (00241F)
208000  var   type_spawn_cursor  u32 type_spawn_record   # pointer into the current stage's stage_spawns_by_type list (08DC63 reads it and moves on by 14)
208006  var   type_spawn_stage  u16    # the stage type_spawn_cursor was loaded for (08DC63 reloads when stage differs)
20F084  var   ring_spawn_cursor  u32 ring_row  # pointer into the stage_template_spawns list (01E37E, 12 a record)
struct ring_row C rom                        # a ring row of the stage_template_spawns lists (01F206 + stage_template_spawns[stage], 12 bytes): spawn_ring_rows reads the one ring_spawn_cursor points at (01E39C "mov.w 7084[R25], R10", 01E3A1 "cmp.h [R10], 6435[R25]", 01E3AF-01E3EC) and moves the cursor 12 on; FFFF ends a list; census ROM 01F22C-01F4D1
field ring_row 00 position u16                # the scroll position it spawns at (cmp.h + bl, unsigned); skipped 100 or more behind
field ring_row 02 template_offset u16         # "mov.h [R10+], 6502[R25]" (ring_row_template): its offset into stage_object_templates (1A)
field ring_row 04 ring_count s16              # compared signed with free_object_stack and free_object_stack_2 ("cmp.h 6900[R25], 2[R10]; bgt", 01E3BA-01E3CB); "mov.h [R10+], 6500[R25]" (ring_row_rings_left): 1-7
field ring_row 06 x u16                       # "mov.h [R10+], 6504[R25]" (ring_row_x): the first ring's x
field ring_row 08 y u16                       # "mov.h [R10+], 6506[R25]" (ring_row_y)
field ring_row 0A dx s8                       # "movs.bh [R10+], 6508[R25]" (ring_row_dx): x step between rings
field ring_row 0B dy s8                       # "movs.bh [R10+], 650A[R25]" (ring_row_dy): y step
20F0DA  var   handler_spawn_cursor  u32 handler_spawn_record   # pointer into the stage_spawns_by_handler list (080000, 12 a record)
20E55C  var   group_spawn_cursor  u32 group_spawn_record  # pointer into the object_group_spawns list (091220)
20F08C  var   group_spawn_cursor_2  u32 group_spawn_record   # pointer into the stage_record_spawns list (09383A)
0920F9  table object_group_spawns  u16  # 091220: per-stage 16-bit offsets to lists of typed records (types 0, 2, 6: 24-byte group records that index stage_object_records; the others 14 bytes)
struct group_spawn_record 18 rom           # a record of an object_group_spawns or stage_record_spawns list (group_spawn_cursor, group_spawn_cursor_2): spawn_object_groups and spawn_object_groups_2 read every record's position and kind ("cmp.h [R10+], 6435[R25]" + bl 091244 / 093892: unsigned; "movz.bw [R10+], R0" 091253 / 09389B); the rest is a group's (kinds 0/2/6 of object_group_spawns, 0/2/3 of stage_record_spawns: 24 bytes, "add.w #18, 655C[R25]" 0914C7), which spawn_object_groups_build_group / spawn_object_groups_2_build_group read from + 3 ("sub.h -3[R10], R0" 0912E4 / 093910, then [R10+] 0912F0-091339 / 09391C-093968); the other kinds' records are 14 bytes (leader_spawn, ice_leader_spawn after the kind); census ROM 09211F-093266, 093EEF-093F56
field group_spawn_record 00 position u16    # the scroll_position it spawns at; a group 100 or more behind is skipped
field group_spawn_record 02 kind u8         # x 2 into the jump table (09125E, 0938A6)
field group_spawn_record 03 leader u8       # the group_leaders entry the members follow ("movz.bw [R10+], R0; mov.h 6562[R25](R0), 70F4[R25]" 0912F0)
field group_spawn_record 04 takes_slot u8   # into group_takes_slot: non-zero takes a group_slots slot (byte 05 is skipped: "inc.w R10" 091302)
field group_spawn_record 06 floor_block u32  # into group_floor_block: the members' block (an offset in floor_blocks); spawn_object_groups_2_pick_bridge reads its high half (0) as if it were the member count ("cmp.h 5[R10], 6900[R25]" 0938DB)
field group_spawn_record 0A member_count u16  # into group_member_count, which waits for that many free objects ("cmp.h 7094[R25], 6900[R25]" + blt 091310)
field group_spawn_record 0C step_x u16      # into group_step_x: added to the member x after each member
field group_spawn_record 0E step_y u16      # into group_step_y
field group_spawn_record 10 direction u16   # into group_direction (the members' 24)
field group_spawn_record 12 member_x u16    # into group_member_x: the first member's x_int
field group_spawn_record 14 member_y u16    # into group_member_y
field group_spawn_record 16 member_template u16  # offset of the members' template from stage_object_records ("movz.hw [R10+], R11; add.w #93269, R11" 091339) or stage_object_records_2 (093968, + 93F59 at 0939D1)
08E2D5  table player_block_offsets  u32 player   # 0x400, 0x600, 0x800 from 208000 (R25): the three player blocks (08DCEC, 08E1B5)

# ======== drawing: depth buckets and the sprite list ========
# Handlers queue what they want drawn into 256 depth buckets (sprite_buckets, linked through
# draw_next). After run_objects the frame routine 0000DF calls build_sprite_list, which walks the
# buckets from the last (FF) to the first (0) writing one sprite entry per object into the sprite RAM
# back buffer, and draw_queued_strings, which adds the text and the end command. The bucket is
# FF - clamp(depth + 0x10, 0, 0xFE): the lower an object is on screen, the later it goes out and the
# more it covers. Two buffers in sprite RAM (400020 and 404000); entry 1 (400010) is a jump that the
# VBLANK hook points at the finished one (with the global offset = screen centre, 70/A0).
099F2D  func  init_sprites             # sprite_flip from 20F618 and 500006-50000C from sprite_control_init, entries 0-1 from sprite_list_header, back buffer 400020, end marks at 400020/402010/404000/405FF0, vblank_hook, sprite_frames = 0x130000, then clear_sprite_buckets (the code from 099F35 and from 099F3B is init_sprites_from_frame_wait and init_sprite_hardware: the unused init_sprites_without_swaps and init_sprites_swapping_every_frame branch there)
elements sprite_control_settings u8      # sprite_control_init's bytes: init_sprite_hardware copies them to the sprite control registers 500006-50000C ("movz.bh [R10+], [R11+]" 099F50, 4 times); census ROM 09A6B2-09A6B5 (00 01 02 00)
099FA6  func  clear_sprite_buckets     # sprite_buckets_end = FFFF, each of the 256 buckets = its own address (empty)
099FBC  func  queue_sprite             # depth = screen_y, then queue_sprite_at_depth (65 callers)
099FC0  func  queue_sprite_at_depth    # R1 = depth: bucket FF - clamp(R1 + 0x10, 0, 0xFE), push R20 there through draw_next (145 callers)
099FF9  func  queue_sprite_in_layer    # bucket = low byte of sprite_layer (HUD and overlays)
09A012  func  queue_sprite_on_top      # bucket 0, drawn last
09A015  func  queue_sprite_in_bucket   # bucket = R0 (011EAB queues its shadow in 0xFF: drawn first, under everything)
09A02A  func  queue_sprite_at_floor    # depth = (projected_y + floor_z) >> 16 - view_y: sorts by the ground point under the object (rings, objects in the air)
09A074  func  queue_sprite_at_previous_floor   # the same with previous_floor_z
09A163  func  queue_sprite_at_previous_floor_2 # identical to 09A074 but leaves the bucket in R10 (unreached)
09A0BE  func  queue_sprite_at_base     # depth = screen_y - sprite_offset_y (the player, 002CEC)
09A102  func  depth_to_bucket          # R0 = FF - clamp(R1 + 0x10, 0, 0xFE), nothing queued
09A27B  func  build_sprite_list        # walks the buckets from 20FC00 down to 20FA02 writing entries from sprite_list_start (R19), emptying each bucket; the mode comes from sprite_flags bits 15/14/12 (jump table at 9A2E0); R19 ends after the last entry
09A1E9  func  swap_sprite_buffers      # vblank_hook: only when sprite_list_ready (then clears it): sprite_buffer_flip += 4, back buffer low word and the entry 1 jump (A002 / A400) from sprite_buffer_swaps; reads sprite_status (from the second += 2 on it is swap_sprite_buffers_now, which it falls into)
08E8AE  func  queue_string             # adds a text record (x, y, palette, scale, string pointer) to string_queue; R5 = 0 takes the 4 words from [R0] and the string after them; ignored when 43 are queued
08E8A6  func  queue_string_record      # queue_string with R5 = 0 (records like the "GAME OVER" one at 0025D0: 58, 68, 8440, 0200, text)
08E8AB  func  queue_string_at          # queue_string with R5 = 1: R0-R3 = x, y, palette, scale, R4 = the string
08E8F8  func  draw_queued_strings      # after build_sprite_list (R19): one entry (flags 000A, scaled by the record) per character through text_frames, then sprite_list_end_command; empties string_queue
20F008  var   sprite_list_ready  u8    # wait_for_frame sets it to FF when the frame's work is done; swap_sprite_buffers swaps (and clears it) only when it is set
20F100  var   vblank_hook  u32         # irq_vblank_end calls it ("jsr [7100[R25]]" at 09F566); init_sprites stores swap_sprite_buffers
20F106  var   sprite_buffer_flip  u16  # +4 at every swap; bit 2 picks the half (wait_for_frame keeps only bit 2)
20F108  var   sprite_back_buffer  u32  # sprite RAM address of the buffer the game fills next: 400020 or 404000 (swap_sprite_buffers rewrites its low word)
20F10C  var   sprite_list_start  u32   # wait_for_frame copies sprite_back_buffer's low word into it every frame; build_sprite_list starts writing there
20F110  var   sprite_list_end_command  u16   # FFFF (command 3, end of list), written after the last entry by draw_queued_strings
20F112  var   sprite_frames  u32 rom       # 0x130000: sprite_frame_records, the base sprite_frame points into (09A27B, 08E8F8, 0953BF)
20FA00  var   sprite_buckets_end  u16  # FFFF below the first bucket: build_sprite_list stops when a bucket holds a value above its own address
20FA02  var   sprite_buckets  u16      # 256 list heads, linked through draw_next; an empty bucket holds its own address
20C800  var   string_count  s16        # strings queued this frame (queue_string adds, draw_queued_strings empties)
20C802  var   string_queue  u16        # 12-byte records: x, y, palette, scale, u32 string pointer
130000  data  sprite_frame_records     # 4 words per frame: width in pixels, height (low byte) and address bits (high byte), bank bits, address low
09A6B2  data  sprite_control_init      # init_sprites: 4 bytes for 500006-50000C (0, 1, 2, 0)
09A6B6  data  sprite_list_header       # init_sprites: entry 0 (7000: clip to 0-13F x 0-DF, no exclusion) and entry 1 (offset 70/A0)
09A6CC  table sprite_buffer_swaps  sprite_buffer_swap # swap_sprite_buffers: (back buffer low word, entry 1 jump) at +0 (4000, A002) and +8 (0020, A400)
struct sprite_buffer_swap 4 rom            # a sprite_buffer_swaps record, by sprite_buffer_flip & 6 (swap_sprite_buffers_now "movea.h 9A6CC[PC](R0), R0" 09A20B: x 2): "mov.h [R0+], 7108[R25]" (09A211), "mov.h [R0], 400010" (09A217); census ROM 09A6CC-09A6DB (4000 A002, 4000 A201, 0020 A400, 0020 A5FF)
field sprite_buffer_swap 00 back_buffer_offset u16  # into sprite_back_buffer_offset: the buffer the game fills next (4000 or 0020)
field sprite_buffer_swap 02 list_jump u16  # into sprite_list_jump (entry 1 of the sprite list): the jump to the list just built
500002  io    sprite_status  u8        # swap_sprite_buffers reads it ("test.b 500002"); MAME: 1 normal, 2 overdraw
500004  io    sprite_flip  u8          # init_sprites writes 20F618 into it, 099552 the flip bits (MAME: D1 vertical, D0 horizontal)
50000C  io    sprite_wide_screen  u8   # 099552 writes bit 0 of its argument (MAME: 1 = 416 pixels wide, 0 = 320)

# ======== projection, camera and movement helpers ========
# 2:1 isometric view: x/y (16.16) are the ground plane, z is the height with larger values lower on
# screen (gravity adds to speed_z). Screen x grows with x + y, screen y with y - x and with z.
00C4DC  func  project_object           # projected_x/projected_y from x and y (0xB5/256 = cos 45 degrees), then update_screen_position (125 callers)
00C50D  func  update_screen_position   # screen_x = projected_x - view_x + sprite_offset_x; screen_y = (projected_y + z) >> 16 - view_y + sprite_offset_y (checked against the reference run)
08E014  func  update_screen_position_no_offset   # the same without the sprite offsets
08DFDD  func  unproject_object         # x/y back from projected_x/projected_y, then find_floor and update_screen_position_no_offset (53 callers)
08DFBD  func  move_object_projected    # 88 += speed_x (projected_x takes the whole pixels from 8A), projected_y += speed_y, z += speed_z, then unproject_object (34 callers, the enemies)
08E037  func  move_object_projected_no_floor   # the same without unproject_object/find_floor (unreached)
00C53E  func  project_point            # R7/R8 = x/y, R9 = z: screen position of a point (R7 = x, R1/R8 = y) through point_to_screen
00C564  func  point_to_screen          # R0 = projected x, R1 + R9 = projected y + z: subtracts view_x/view_y
00C57C  func  project_object_2         # project_object, but once 20E435 reaches A5F0 screen_y uses the player block's DE instead of view_y (unreached)
00C453  func  set_speed_from_direction # speed_x = sine[direction] * speed >> 6, speed_y = -cosine[direction] * speed >> 6 (speed's high word)
00C4AD  func  set_speed_from_player_speed  # the same with the player block's 66 as the speed
00C482  func  polar_to_xy              # R5 = angle, R6 = length: R5 = dx, R6 = dy (same tables)
08E405  func  direction_to_speed       # R0 = angle (& 3FF), R1 = length: R2 = dx, R3 = dy
08E391  func  fall_to_floor            # while z is above floor_z: speed_z += 0x6000, lands (z = floor_z, speed_z = 0) when z + speed_z reaches it; R0 = distance left, R1 = 1 over a pit (floor_z >= 0x800)
00C6A9  func  find_floor               # floor_block, floor_z (with the slope), floor_slope, floor_type, floor_attribute for the object's x/y among nearby_floor_blocks; while flags bit 3 is set they come from the platform; no block = no_floor_block
00C605  func  find_nearby_floor_blocks # lists in nearby_floor_blocks the floor blocks whose screen box (floor_block_bounds) reaches view_x - 0x80 .. + 0x1C0, view_y - 0x80 .. + 0x160 (the stage mode calls it every frame, 095177)
00C409  func  side_of_box              # R3 bits: where the current object's x/y is around the box of the object in R10 (8 before it in x, 4 past it, 2 before it in y, 1 past it)
08E130  func  copy_position_to         # x, y, projected_x/y, z, floor_z of object_cursor into the object at R3
08E155  func  place_sprite_near_cursor # screen_x/screen_y = object_cursor's + R8/R9
08E16A  func  copy_position_to_cursor  # projected_x/y, z, floor_z of the current object into object_cursor (unreached)
08E183  func  copy_position_from_cursor   # x, y, projected, z, floor_z from object_cursor (unreached)
08E3E5  func  place_sprite_at_cursor_floor   # the current object's screen position = object_cursor's, moved down to object_cursor's floor (shadows); R0 = FF
08E42E  func  cursor_floor_screen_y    # R1 = screen_y + object_cursor's floor_z - z (whole pixels)
08E1B5  func  face_nearest_player      # distance and direction to every active player (square_root, angle_of_vector on projected coordinates; players 3, 2, 1 in that order); fills the nearest_player variables; direction = the last angle worked out (player 1's when player 1 is in) + direction_to_player_offsets[nearest_player] (1C-20: an add where a load was meant), so not towards the nearest player
08E295  func  find_nearest_player_x    # nearest_player_distance = smallest |player x - x| (unreached)
08E2ED  func  face_object              # direction = angle to the object at R14 (projected), then distance_to_object (unreached)
08E30D  func  distance_to_object       # nearest_player_distance = square_root(dx^2 + dy^2) to the object at R14, projected (unreached)
08E347  func  direction_to_object      # direction = angle to the object at R14 (unreached)
08E368  func  distance_xy_to_object    # distance_to_player_1 = |dx|, distance_to_player_2 = |dy| (integer x/y) to the object at R14
08E730  func  spawn_debris             # R1 pieces (spawn_objects) thrown up from the current object with random scale, speed and direction, handler debris_fly
08E7FD  func  debris_fly               # move_object_projected, delete_if_off_screen, fall_to_floor; steps its frame through debris_frames every 4 frames while in the air
08E87E  table debris_frames  u16        # sprite_frame for each step of debris_fly (index 80, up to 81, 9 back)
08E52C  func  drop_rings               # 208032 rings (scatter_rings_random one at a time) from the object at R23, spread around direction 208030 (unreached); it does not check that scatter_rings_random made a ring: 08E54E takes R17 as the ring whatever it holds
099049  func  square_root              # R0 = integer square root of R0 (16 rounds); distances in 08E1B5/08E30D
099082  func  angle_of_vector          # R0 = dx, R1 = dy (16-bit): angle 0-3FF from angle_ratios and angle_octants
101632  table sine_table  s16          # 0x4000 = 1.0, 0x400 entries a turn: starts 0000 0065 00C9...; read with direction (00C453, 08E405)
101832  table cosine_table  s16        # sine_table + 0x100 entries (a quarter turn later): starts 4000 4000 3FFF...
struct sine_and_cosine 202 rom             # an angle's place in sine_table seen as a record: its sine, and 0x100 entries on (cosine_table) its cosine; draw_circle_polygon reads both through its sine_entry, zero-extended ("movz.hw 200[R6], R3" 09B9A5, "movz.hw [R6], R3" 09B9E5); census ROM 101632-101932
field sine_and_cosine 000 sine u16         # sine_table[angle] (0x4000 = 1.0), read unsigned
field sine_and_cosine 200 cosine u16       # cosine_table[angle], read unsigned
102034  table angle_ratios  u8         # 099082: angle within an octant for small * 256 / big
09A666  table angle_octants  u16       # 099082: base angle per octant (complemented = mirrored)
10DF44  data  floor_slopes             # 8 bytes per slope type (floor_slope): +0 flags (bit 1 = slope along x), +2 slope factor (find_floor, check_hit)
10DF44  table floor_slopes floor_slope   # find_floor, check_hit, spawn_object_groups, get_floor_z_at_point read a slope's record by its number
10E14C  table floor_types floor_block_type   # 4 bytes per floor type (block +12): +0 floor_type, +2 floor_attribute (find_floor, 09383A), by a block's type_index (x 4); the attribute records after the 40 types are reached by byte offsets (floor_attribute_record)
struct floor_attribute_record 10 rom      # the record a floor type's floor_attribute reaches, floor_types + it (a byte offset; tools/formats/stages.py): a climbable wall's (floor type 2, 16 bytes: census ROM 10E1EC-10E25B, floor_types + A0 to + 100, 7 walls), a moving floor's (7, 6 bytes: push_x, push_y, ride) or a push's (4, 5, 0B, 4 bytes: push_x, push_y; census ROM 10E264-10E29B, floor_types + 118 to + 14F); each kind reads only its own fields. Held in R12 by player_state_climbing (004726, handed to the player_climbing_* pieces), start_climb_at_spawn (00D8DF), apply_floor_push (00DADD "add.w #10E14C, R12", then "add.h [R12+], 94[R18]" and "add.h [R12+], 96[R18]": 00DAE4, 00DAEA), the geyser front's rise (08C91B-08C939) and the stage 8 climb (08B69F-08B6B1); read at floor_types + floor_attribute + the field by start_climb (00D9BC-00D9C3), the moving floor (apply_moving_floor) and the machine fire, bridge fire, grate steps and saw shaft (their x_per_rise)
field floor_attribute_record 00 push_x s16        # a push's or a moving floor's: added to the player's push_x (94)
field floor_attribute_record 00 axis u8           # a wall's: "test.b [R12]" (0047B3, 004911, 004936, 004A80, 004A99, 004C5C, 004C90, 004DEF, 004E10, 00D8FC): 0 the wall runs along y, else along x
field floor_attribute_record 02 push_y s16        # a push's or a moving floor's: added to push_y (96)
field floor_attribute_record 04 x_per_rise s16    # a wall's: 004C52 "movs.hw 4[R12], R0": the screen shift per z step (also machine_fire_update, bridge_fire_rise, build_grate_steps x 1E, build_saw_shaft_base x 1D)
field floor_attribute_record 04 ride u8           # a moving floor's: 1 the floor carries the player along x (apply_moving_floor)
field floor_attribute_record 06 height s16        # a wall's climb: "mov.h 6[R12], 2C[R18]" into climb_remaining (0048DC, 00D8E6, 00D9C3); 004C84 "cmp.h 6[R12], 2C[R18]" + a signed branch
field floor_attribute_record 08 top_z s16         # a wall's: 004769 "cmp.h 8[R12], 42[R20]" (z_int, signed); 004DAC "mov.h 8[R12], 42[R20]" when pulling up
field floor_attribute_record 0A along_min u16     # a wall's: "mov.h A[R12], R3" (0047BB, 004AA4): where the wall starts along its axis (then compared signed)
field floor_attribute_record 0C along_length u16  # a wall's: "add.h C[R12], R3" (0047C5, 004AAD): the wall's end = along_min + it
field floor_attribute_record 0E rise u16          # a wall's: 00D8EC "mov.h E[R12], R0": z gained per unit along the floor (start_climb_at_spawn)
struct floor_block_type 4 rom              # a floor_types record by a block's type_index (floor_block +12; tools/formats/stages.py _FLOOR_TYPE): find_floor "movea.w 10E14C(R0), R12" (00C6F2, 00C7E4: R0 x 4), "mov.b [R12+], 77[R20]" (00C6FA, 00C7EC), "mov.h 1[R12], 7A[R20]" (00C6FF, 00C7F1); spawn_object_groups_2_build_group the same (093A83); census ROM 10E14C-10E1EB (40 types; the attribute records follow)
field floor_block_type 00 floor_type u8    # into the object's floor_type (77): 0-D (byte 01 is 0 in every type)
field floor_block_type 02 floor_attribute u16  # into floor_attribute (7A): a byte offset from floor_types to the type's attribute record (A0, 118, 128...) or a small number (1, 2, 3, 5)
10B70A  data  no_floor_block           # the floor block find_floor uses when none contains the point: height (+10) 0x800
20F070  var   floor_blocks  u32 rom        # pointer to the stage's floor data (22-byte blocks from +2: x min, x max, y min, y max (s32), height +10, type +12, slope +13); 001BED loads it from 10B6CC
20F07A  var   floor_block_bounds  u32 floor_outline_list  # pointer to the stage's screen boxes of the floor blocks (count, then 16 bytes each) that find_nearby_floor_blocks tests (001BED, from 10E29C)
struct floor_outline_list 2 rom            # the head of a stage's floor_block_bounds list: find_nearby_floor_blocks "mov.w 707A[R25], R17; movz.hw [R17+], R7" (00C630, 00C635), the floor_outline records follow (one per floor block); census ROM 10E2C2-10F6AB (12 lists, 316 outlines)
field floor_outline_list 00 count u16      # how many outlines find_nearby_floor_blocks tests (dbr R7 00C69F): 1 for stage 0
struct floor_outline 10 rom                # a floor block's outline on the map (tools/formats/stages.py _BOUNDS): its leftmost, rightmost, topmost and bottommost corners; find_nearby_floor_blocks tests the box first, signed: "cmp.h R11, [R15+]" + bgt (00C64C), "cmp.h R10, [R15+]" + blt (00C654), "cmp.h R13, [R15+]" + bgt (00C65C), "cmp.h R12, [R15+]" + blt (00C664); 16 bytes a block ("add.h #10, R4" 00C695); census ROM 10E2C4-10F6AB (stage 0: F40,700 1100,700 1020,690 1020,770)
field floor_outline 00 left_x s16          # tested <= the view's right edge (view_x + 1C0)
field floor_outline 02 left_y s16
field floor_outline 04 right_x s16         # tested >= the view's left edge (view_x - 80)
field floor_outline 06 right_y s16
field floor_outline 08 top_x s16
field floor_outline 0A top_y s16           # tested <= the view's bottom edge (view_y + 160)
field floor_outline 0C bottom_x s16
field floor_outline 0E bottom_y s16        # tested >= the view's top edge (view_y - 80)
struct floor_outline_corner 4 rom          # a corner of a floor_outline, the four one after another: find_nearby_floor_blocks' corner loop "mov.h [R15+], R0; mov.h [R15+], R1" (00C672, 00C675); minus the view's left / top edge, a corner within 240 x 1E0 of it (unsigned: bnl) lists the block
field floor_outline_corner 00 x u16
field floor_outline_corner 02 y u16
209F80  var   nearby_floor_block_count  u16   # find_nearby_floor_blocks
209F82  var   nearby_floor_blocks  u16 # offsets of those blocks into floor_blocks (22 apart)
208016  var   nearest_player_distance  s16   # face_nearest_player (also distance_to_object's result); enemies compare it with their range (00F1A9, 00F59E, 0128EA)
208018  var   nearest_player_dx  s16   # projected x difference to the nearest player (08E1B5)
20801A  var   nearest_player_dy  s16   # projected y difference to the nearest player
20801C  var   direction_to_player_1  u16   # face_nearest_player through 8E2E7 (7FFF when the player is out); 0121C9 picks one
20801E  var   direction_to_player_2  u16
208020  var   direction_to_player_3  u16
208022  var   distance_to_player_1  s16    # face_nearest_player through 8E2E1 (7FFF when out); distance_xy_to_object reuses it for |dx|  (TYPE: its readers compare it signed: electric_pole_update <= F, whirlwind_aim)
208024  var   distance_to_player_2  s16    # ... and this one for |dy|  (TYPE: signed compares in push_crate_stop_post_update (<= 33) and whirlwind_aim)
208026  var   distance_to_player_3  s16  # (TYPE: its one compare is signed)
208028  var   active_player_count  u8  # count_active_players
208029  var   nearest_player  u8       # 0-2 (face_nearest_player)
20802A  var   nearest_player_z  s16    # the nearest player's object 42 (z)
20802C  var   nearest_player_floor_z  s16  # the nearest player's object 56 (floor_z); 0123CC tests < 0x800
20802E  var   direction_to_nearest_player  u16   # also stored in direction

# ======== animation ========
00DEA2  func  animate                  # animation_timer - 1; when it runs out: reload from animation_speed and animate_step (74 callers); R7 = FF after wrapping, R8 = FF on the last step
00DEB3  func  animate_step             # animation_frame + 1 (- 1 with flags bit 7), wraps, sprite_frame = the step's word
00DE4F  func  animate_with_flags       # the same timer, then animate_step_with_flags
00DE60  func  animate_step_with_flags  # steps of two words: flags (into sprite_flags bits 6-7, the flips) and frame
00DEFD  func  animate_and_move         # steps of frame, dx, dy: projected_x/projected_y move by dx/dy (negated by the flips)
00DF72  func  animate_around_anchor    # steps of frame, dx, dy: projected position = anchor + dx/dy (set_step_around_anchor)
00DFC1  func  set_step_around_anchor   # projected_x = anchor_x + dx, projected_y = anchor_y + dy (negated by the flips)
00DFF5  func  animate_with_screen_offset   # steps of frame, dx, dy added to screen_x/screen_y (add_step_screen_offset)
00E049  func  apply_step_screen_offset # the current step's dx/dy again without advancing (animate_with_screen_offset between steps)
00E058  func  add_step_screen_offset   # screen_x += dx, screen_y += dy of the step at R10 (negated by the flips)

# ======== collisions between objects ========
# Objects put themselves in lists while they run; the stage's mode routine (095177, also 000652 and
# 09492F) tests the lists in the next frame before run_objects (check_zones, check_hits, check_walls,
# check_platforms), sets flags bits and partner fields on both objects of each pair, and empties the
# lists. Lists are 16-bit addresses ending with FFFF.
20F07E  var   hitter_list  u16 object         # objects that test themselves against every touchable object and each other: the players (00B5BA) and 011EAB; through hit_list_next
20F080  var   touchable_list  u16 object      # objects the hitters can touch (rings, enemies, items: 77 handlers call add_to_touchable_list)
20F016  anchor  hitter_list     68             # check_zones starts its walk of the hitter list here: an object at 20F016 would have hitter_list as its hit_list_next (68)
20F018  anchor  touchable_list  68             # check_hits' first 'previous' of the touchable list: an object at 20F018 would have touchable_list as its hit_list_next
20E5F0  var   zone_list  u16 object           # objects whose box the hitters' x/y is tested against (check_zones; 010AD7, 080372, 082ED3...)
20F090  var   rider_list  u16 object          # objects that can stand on platforms and enter areas (the players, rings...); through platform_list_next
20F092  var   platform_list  u16 object       # platforms (085F80, 087FA5, 08815E...)
0020A5  func  add_to_hitter_list       # pushes R20 on hitter_list; clears flags bits 2 and 27
0020C2  func  add_to_touchable_list    # pushes R20 on touchable_list; clears flags bits 2 and 6
001956  func  add_to_zone_list         # pushes R20 on zone_list; clears flags bit 27
0020DB  func  add_to_rider_list        # pushes R20 on rider_list; clears flags bits 3 and 5
0020F4  func  add_to_platform_list     # pushes R20 on platform_list; clears flags bit 3
001DDD  func  check_hits               # every hitter against the hitters after it and, if it is not touched yet, against touchable_list; empties both lists
001E33  func  check_hit                # R17 against the list from R10: skips pairs where both kinds are 0-2; in x/y box/box, box/round (the round one as a square of its radius), point/box or circle/circle (radii summed, slope-corrected); then the heights; a hit sets flags bit 2 on both, touched_by to each other, 6C = a coordinate from the test (R2)
0018B6  func  check_zones              # every hitter whose x/y is inside a zone object's box with the heights overlapping: flags bit 27 on both, anchor_x = each other; empties zone_list
002108  func  check_platforms          # every rider whose x/y is inside a platform's box (no offsets): rider flags bit 3, platform fields of both; the platform also gets bit 3 when the rider's z is not above its floor_z (56), and the first time adds its 6F to the lists at 209400/209500 (sorted by 0021F9); empties both lists
00B5BA  func  add_player_to_lists      # add_to_hitter_list then add_to_rider_list (the player handlers)
00B5C6  func  remember_platform        # flags bit 4 = bit 3 (was on a platform)
01F0EF  func  check_left_screen        # R8 = -1 once an object that has been on screen (flags bit 11) is out of it by 0x20; sets bit 11 while it is inside
01F141  func  check_left_screen_margin # the same with the margin in R1
01F18C  func  check_left_screen_margins   # margin R1 once seen, R2 before
01F091  func  is_off_screen            # R8 = -1 when screen_x + 0x20 >= 0x180 or screen_y + 0x20 >= 0x120
01F0BC  func  is_off_screen_margin     # the same with margins R1 (x) and R3 (y)

# ======== shadows and the rings the generic code makes ========
01EF99  func  draw_child_shadow        # puts the child (pool 2, frame 5D90) at the floor under the object (projected_y + floor_z, sprite offsets), palette 9FFE, or 87A0 every other frame when flags bit 4; queues it at its screen_y - 0x10 (behind the object)
01E709  func  scatter_rings            # R8 rings (at most 10) at the current object: kind 3, flags 0x204 (bit 9 for the first 0x1E frames), speed/direction/jump (random unless 20F0EA says so), a pool 2 shadow each, handler scattered_ring_wait
01E701  func  scatter_rings_random     # 20F0EA = 0: random direction, speed and jump for each ring (00F8E1, drop_rings)
01E6F7  func  scatter_rings_fixed      # 20F0EA = FFFF (0826CE; unreached)
01E85B  func  scattered_ring_wait      # handler: 7E counts down with flags bit 9 set, then scattered_ring_live
01E86E  func  scattered_ring_live      # handler: 80 counts 0x12C frames, then the ring shrinks away; touched: taken (sound 13, 01EA86, deleted with its shadow) unless the player is in state D/20 with C3 set
01E8C8  func  move_scattered_ring      # moves on x/y (steps back and turns round when the floor rises more than 8), gravity 0x6000, bounces keeping 3/4 of speed_z, animates; out of the screen (is_off_screen_margin 0x20/0x58) it switches to scattered_ring_vanish
01E9C1  func  scattered_ring_vanish    # handler: ring and shadow sprite_scale - 0x10 a frame, then deleted
01EEC2  func  next_random              # reads two words through the random number area at D80000 (position 20F0FC, moved on by each value); R3 = the result

# ======== players (from build/naming/players.txt) ========
# Naming proposals: THE PLAYERS (struct player, the player handlers and state machine, player globals)
# Format: symbols.txt syntax, one proposal per line, "# evidence" after it.
# Conventions used below: "p" is the player number (OBJECT->player, 0-2); "the object" is the player's object
# (struct player +14 points at it, the object's 9C points back at the block).
#
# ======== struct player (0x200 bytes from 0x208400, R18 = current_player) ========
field player 00 state u8                    # 002CEC jumps through the table at 002D3E (58 entries) and 002FF1 through 002FFC; values below
field player 01 facing u8                   # 16-way sprite direction: ((facing_angle + 0x20) >> 6) & 0xF (00BD2C); indexes every animation table by 2 x facing
field player 03 substate u8                 # step inside a state (AT8(unk_02, 1) in the C): states switch on it, reset to 0 on most state changes
field player 04 state_timer s16             # per-state countdown/counter: 0x3C on a ledge (state 5), 0x1E/0x14/6 hurt and bounce, 0x168 goal run, +1 in state 0x24
field player 06 control_lock u8             # frames the trackball is ignored: counts down in the normal states (002DEC); 00B90A runs only when 0; set 4/0xA after bounces, 0x10/0x7F/0xFF in scripted states
field player 07 jump_delay s8               # 00B60A: counts down, the button is only checked (00B62D) when it reaches 0; 1 after a jump, 2 after being pushed out of an object
field player 08 standing_started u8         # 0030AD: 0xFF the frame the standing animation starts (long first frame); frames then last 3, except 2 and 6
field player 09 is_joining u8               # 0xFF from the credit (states 0xB/0x23/0x24) until the join sequence 00E1A9 ends; 001D07 then starts the object at 0028DB
field player 0A trackball_x s16             # 00BDCE: this player's trackball_N_x (0x20F062 + 4p) & ~1, shifted left 8 (trace: dx 11 -> 0x0A00)
field player 0C trackball_y s16             # 00BDCE: the same from trackball_N_y (0x20F064 + 4p) (trace: dy -4 -> -0x400)
field player 0E invulnerable_timer s16      # 002CEC decrements it every frame and stops at -1; >= 0 ignores hazards (00E488); 600 while hurt, 120 after a hurt or a respawn; blinks via object 30 bit 29 (00B5DC)
field player 12 heading u16                 # angle (0-0x3FF) the player moves toward; turns toward target_heading at a limited rate (00BC50)
field player 14 object u16 object                  # 002808: the player's object (low 16 bits); 001A7F, 00BED5, 015F80, 0983FE read it
field player 1A last_hazard u16             # 00E488: object that last hurt the player; a hit from another object restarts the hurt substate
field player 1C scene_hop_z s32             # state 0x18 scene step 6: height added to z while the character hops in place (speed_z per character)
field player 20 ledge_pullback s16          # 00B361: trackball movement back toward the floor while teetering (state 5) or slipping (7); >= 0x10/0x14 recovers
field player 22 ledge_side s16              # 00B3AC: side of the floor block the player went off (0-3), 4 = still inside; -1 when unused
field player 24 slip_distance s32           # state 7: += speed every frame; ends the slide at 0x20000000 (0x8000000 on floor type 9)
field player 28 target_heading u16          # angle from the trackball (00BBEC, 00B2C0) or the autopilot path (00DCC5); heading follows it
field player 2C climb_remaining s16         # state 0xA: height left to the top of the wall (0 = climb over, table +6 = back at the bottom); state 0x27: the x that ends the climb (object 94)
field player 2E climb_from_top u8           # state 0xA: 0xFF when the player reached the wall at or above its top (climbs down; falls if it came jumping)
field player 2F hit_stop u8                 # 00E488 sets 3 when the jump takes the enemy's health to 0 (sturdy kinds excepted; otherwise the player bounces off); while nonzero the player does not move (002EF7)
field player 30 step_accumulator s32        # state 0xA: speed_y summed, +-4 climbs one step; state 0x27 (008AD2): speed summed in the high word, 0x400 = one step
field player 34 screen_x s32                # 16.16 screen x used when the code places the sprite directly: climbing, dying (0x16), scenes 0x18/0x1C, riding (0x27)
field player 36 screen_x_int s16        # the whole part of screen_x
field player 38 in_game s8                  # 1 while the player has a game going (credit used), 0 when out; 002953 respawns only when > 0
field player 3C skid_slowdown s32           # state 0xC: ground_speed / 8 (/32 on floor type 7), subtracted every frame
field player 40 skid_speed_slowdown s32     # state 0xC: object speed (6E) / 8 (/32 on floor type 7), subtracted every frame until 0
field player 46 free_turn_timer u8          # counts down in the normal states; while nonzero 00BC50 sets heading = target_heading at once (no skid)
field player 48 skid_dust_timer s8          # state 0xC: a dust puff (00A669) every 4 frames
field player 49 bounce_direction u8         # state 0xE: 0-3, picks the bounce animation (table 1139E8 + 6 x dir); set by 00C920/00E488 (as a 16-bit write that also hits 4A)
field player 4A bounce_speed_z s32          # state 0xE and 0x1D: the bounce animation advances a frame each time speed_z passes it; += bounce_step
field player 4E bounce_step s32             # state 0xE: step added to bounce_speed_z (0x34000, then 2/3 of the rebound)
field player 52 previous_state u8           # saved before a forced state (stage events, ledges, scenes) and restored by them
field player 53 ignore_screen_edges u8      # 00AD91 does not push the player back from the screen edges while set (only cleared, in 0029DF)
field player 5C path_pointer u32 player_path_step  # 00DCC5: current record of the autopilot path (ROM 115528): +0 speed, +2 x, +4 y, +6 flags (bit 0 jump), 8 bytes each
field player 64 ground_speed s32            # 16.16 speed along heading after the slope tables (00B90A, 00BD5F); 00C4AD turns it into speed_x/speed_y
field player 68 walk_cycle s16              # walking: += animation_speed >> 16 every frame; each 0x200 the walk animation steps and the pending movement is applied
field player 6A walk_pending_x s32          # state 1 at low speed: speed_x summed and added to x only on animation steps (002EB0)
field player 6E walk_pending_y s32          # the same for y
field player 72 attack_power s8             # set to 4 when a jump starts (constant in this build; sound 0x1F); subtracted from the enemy's health (object 7E) in 00E488
field player 75 joined_health_setting u8    # 002808: copy of health_setting at join; indexes the max health table 113EF2 (00E488)
field player 76 previous_x s32              # x before this frame's movement; restored when blocked (00B6AC, 00C920, 00E488)
field player 7A previous_y s32              # the same for y
field player 7E previous_z s32              # the same for z (height)
field player 84 screen_edge u16             # 00AD91: which screen edge (0-3) pushes the player back; indexes 113EE0 and 113CE7
field player 8A heading_snapped u16         # 00BD0B: heading rounded to 16 directions; object 24 gets it + 0x80
field player 94 push_x s16                  # 00DA9B: floor push (table 10E14C + 7A) or quicksand pull (00E488 kind 28); added to the trackball in 00B90A
field player 96 push_y s16                  # the same for y
field player 98 animation_speed s32         # smoothed trackball speed: 0 -> standing, < 0x200 walking (1), else running (0x13), > 0x4F0 running fast (0x14) (00B90A)
field player 9C holder u16 object.vine_bar  # state 0x15: the object (kind 8/10) holding the player; set in 00E488 (pointer typed: last_names.txt N1)
field player A0 pushed_out s8               # 00E488 kind 5/12/32/33: 0xFF while the player is stuck in a solid object and being pushed out
field player A1 push_out_delay s8           # 00E488: 0x1E frames against the same solid object before pushing out
field player A2 push_out_x s32              # 00CCB0: push-out speed toward the screen centre
field player A6 push_out_y s32              # the same for y
field player AA fall_draw_depth u16         # states 6-9: sprite sort key while falling into a pit (floor screen y, adjusted by floor block type)
field player AC jump_first_tile u16         # state 4: first sprite of the jump animation; gravity applies once the animation leaves it
field player B0 path_speed u16              # 00DCC5: speed of the current path record (0 = wait path_wait frames)
field player B2 path u8                     # 00DCC5: autopilot path being followed (0 = trackball control); the trackball is ignored while set
field player B4 mishap_flag u8              # 0xFF when hurt, falling into a pit, joining...; 001A7F turns any of them into 20E5A3 and clears all three
field player B5 saved_substate u8           # 002F46: substate kept when a floor type forces a new state
field player B6 facing_angle u16            # angle the sprite faces (from the trackball, 00BBEC); facing = 16-way version
field player B8 scene_step_seen u8          # states 0x18/0x19/0x1C/0x1E: last scene_step handled, so each step runs its setup once
field player B9 path_request u8             # path to start (00DCC5 copies it into path): 0x14 intro run, 5, 0xE, 0x2F, 0x4D, 0x50, 0x53, exit paths
field player BA scene_zoom s32              # states 0x18/0x1C: drives the object's zoom (08) in the stage scenes
field player BE hud_blink_timer s8          # 00AD0D: 0x1E frames per blink of the player's HUD message
field player BF hud_blink_on u8             # 00AD0D: toggles; draws or erases the message
field player C0 path_wait s16               # 00DCC5: frames to wait at a path record with speed 0
field player C2 entry_status u8             # 002BC5/00D2BA: 0xFF playing, 0x81 dropping in from above, 0x82 waiting for the stage; copied to player_status[p]
field player C3 hurt_contact_delay s8       # states 0xD/0x20: frames before 00E488 checks contacts again (2 after a hit, 0xA/0x78 at start)
field player C4 speed_scale u16             # 00D2A7: target speed x speed_scale / 16 when nonzero; the pacing object 0843D5 sets 0xD on one player by position
field player C6 last_damaged u16            # 00E488: enemy last hit by the jump; the same enemy is not hit twice in a row
field player C8 escape_progress s32         # trackball shaking toward breaking free: states 0x1D (007834), 0x25 step 12, 0x32
field player CC draw_depth u16              # sprite sort key passed to 099FC0 (floor screen y): 0030AD, 0032DD, 00D22E
field player CE side_step_accumulator s32   # state 0xA: speed_x summed, +-2 moves one step along the wall
field player D2 saved_screen_x s32          # state 0xA: screen_x before the last step, restored by 00E488 when the step is blocked
field player D6 saved_screen_y s32          # state 0xA: object 2C before the last step
field player DA saved_climb_remaining u16   # state 0xA: climb_remaining before the last step
field player DC goal_step u8                # state 0x21 (stage exit): 0 start the exit path, 1 walking it, 2 done
field player DD effect_spawned u8           # state 0x22: the effect (084BBA type 0xB) was already started
field player E0 last_wall u16               # 00B6AC: wall (object 78, list 20F0AE) that last pushed the player back
field player E2 wall_timer s8               # 00B6AC: 0x1E frames of being pushed back by the same wall
field player E3 drift_anim_step u8          # 0096F1: animation step while carried by the scroll (states 0x25/0x26)
field player E4 continue_frames s8          # state 0x23: frames of the current second (60; the button jumps to the next)
field player E5 continue_seconds s8         # state 0x23: 9 down to 0, drawn by 099A57; at 0 the game is over (0x2E)
field player EA sequence_step u8            # step of the long scripted states 0x25 and 0x31 (they do not use substate)
field player EB ledge_direction s8          # when ledge_side is 4: side from the floor block's slope (0-3); 00D7E8, states 5/9/0x33
field player EC struggle_speed u16          # floor type 0xA: trackball speed >> 16; slows the sinking (00CE7B)
field player EE sink_depth s16              # quicksand: += 0x33 a frame (00CE08, 00CE7B), z = floor + sink_depth << 8; 0x2000 costs 8 health
field player F4 ground_clip_bottom u16      # clip record at +0xF0 (8 words from 002808: 0x5000, 0xE0, 0, 0x140): its bottom gets the ground's screen y so a sinking player is cut there
field player 114 flags u32                  # bit0 drawn clipped at the ground (shadow_update, sinking floors), bit1 on a kind 29 object (edges drop at once, 00B3E7), bit2 sinking in quicksand, bit3 no shadow (exit done), bit4 just joined, bit5 edge voice played, bit6 ring this frame, bit7 jumped into a pit
field player 118 swirl_turn_rate u16        # state 0x28: divisor of the turn toward the object at 20E5D4 (0x220)
field player 11E object_climb_steps u16     # state 0x27: 1 at the start, +1 per step; the draw depth uses it
field player 120 climb_hand s8               # state 0x27, character 1: 0xFF and 1 alternate (which hand); 0 for the others
field player 11D enter_on_respawn u8        # 0029DF: 0xFF in a game after a stage start; 00CFCA then runs the walk-in state 0x2A (if the stage has a path)
field player 122 entry_step u8              # state 0x2A: 0 start the entry path, 1 wait for it to end
field player 123 hud_message_shown u8       # 00A8E9: the "joining closed" message was drawn (cleared when it is erased)
field player 124 jump_frames s16            # state 4: frames since the jump started; > 0x50 ends it
field player 126 hud_ring_effect u16        # 00A6EE: address of this block's ring effect sprite record (+0x1A2)
field player 128 scene_path u8              # 00D026: path started when scene_step reaches 1 or 8 in state 0x18 (0xB or 0x11 in stage 5)
field player 129 ending_step u8             # state 0x38: step of the stage 0xB sequence
field player 12A edge_object u16 object            # 00B3E7: object of a moving edge (floor block type 0x10, floor type 1); state 7 compares positions with it
field player 12E fast_climb u8              # state 0xA: 0xFF on floor block type 0x20 above z 0x4DE; steps need 4 x the input
field player 130 climb_base_z s32           # state 0xA: floor height at the foot of the wall
field player 134 ending_done u8             # state 0x38: set at the end; 00D637 does not start it again
field player 135 game_over_done u8          # state 0x2E waits for 08EC87 to set it, then goes to 0x24
field player 137 stages_reached u8          # 1 when joining, +1 per stage exit (state 0x21; not after stages 5, 0xB-0xF) and in state 0x2B; 08EAC9 copies it to the ranking record
field player 138 ridden_object u16          # state 0x35: object the player rides (from 00E149)
field player 13A rings u16                  # +1 per ring (00E488 kinds 3/42), drawn in the HUD in decimal (00A8E9), the ranking score (08EAC9), +200 in state 0x2B; 0 when joining
field player 13C stage_rings u16            # +1 per ring, 0 at each stage start (00291F); 0983FE adds the three for the stage result percentage
field player 13E splash_zone u8             # state 0xA on floor block type 0x20: 0xFF while z is in 0x4C7-0x4E9; entering it calls 00E263
field player 13F was_in_splash_zone u8      # previous frame's splash_zone
field player 141 rings_to_health s8         # 00E488: rings since the last health point (1 health per 4 or 8 rings with some settings)
field player 144 hud_gauge_flags u16        # health gauge sprite record at +0x142 (object layout): +02
field player 148 hud_gauge_palette u16      # +06
field player 14E hud_gauge_y u16            # +0C screen y (table 113F10/113F1C per player)
field player 150 hud_gauge_x u16            # +0E screen x
field player 166 hud_gauge_health u8        # +24: health shown; a drop flashes the gauge (00A8E9)
field player 168 hud_gauge_anim_delay u8    # +26
field player 16A hud_gauge_anim u32         # +28: palette animation (0xCD4D hit, 0xCD37 ring, 0xCD55 low health)
field player 16E hud_gauge_anim_repeats u8  # +2C: loops left of the palette animation (00ACA1)
field player 16F hud_gauge_reset u8         # +2D: 0xFF while the player is not playing; 00A8E9 resets the HUD animations when it is cleared
field player 174 hud_portrait_flags u16     # portrait sprite record at +0x172: +02
field player 176 hud_portrait_tile u16      # +04 (per character, table 113F38)
field player 178 hud_portrait_palette u16   # +06
field player 17E hud_portrait_y u16         # +0C (table 113F24/113F30 per player)
field player 180 hud_portrait_x u16         # +0E
field player 197 hud_portrait_frame u8      # +25
field player 198 hud_portrait_anim_delay u8 # +26
field player 19A hud_portrait_anim u32      # +28: 0xCD63/6B/73 hurt, 0xCD7B/89/97 low health, per character
field player 19E hud_portrait_anim_repeats u8  # +2C
field player 1A4 hud_ring_effect_flags u16  # ring effect sprite record at +0x1A2: +02
field player 1A8 hud_ring_effect_palette u16  # +06
field player 1AE hud_ring_effect_y u16      # +0C (next to the gauge)
field player 1B0 hud_ring_effect_x u16      # +0E
field player 1C7 hud_ring_effect_frame u8   # +25: restarted (0) when a ring is collected, 0xFF when the animation ends (00A8E9, 00ACD6)
field player 1C8 hud_ring_effect_anim_delay u8  # +26
field player 1CA hud_ring_effect_anim u32   # +28
field player 1D2 join_time u32              # 00E1A9: frame counter 20F050 when the player joined; record_player_game (09FB3B) subtracts it (play time)
field player 1D6 last_scroll u16            # 00E415: stage scroll (20E435) seen last frame
field player 1D8 stall_frames s16           # 00E415: frames the scroll has not moved; 7200 (2 minutes) sets health to 0 and state 0xD (not in stage 5)
#
# Player states (field player 00). Main table at 002D3E (002CEC), animation table at 002FFC (002FF1); the
# code labels below name each state's entry.
#   00 standing (00B90A: no input)                 01 walking (animation_speed below 0x200)
#   02 falling to a lower floor (00C920)            03 inactive: the handler returns at once (no setter found)
#   04 jumping, the attack (button, 00B62D)         05 teetering at a pit edge (slow): pull back to recover, else 06
#   06 falling off the edge into the pit            07 over an edge at speed or jumping into a pit: slides, hangs on; pull back to climb up, else 08
#   08 dropping from the edge into the pit          09 falling into a pit from the air (00B465)
#   0A climbing a wall (floor type 2)               0B not playing: waits for a credit and the start button
#   0C skidding (turn of 90 degrees or more at speed, 00BC50)  0D hurt (-4 health, knocked back, invulnerable)
#   0E bouncing off a wall or an object at speed    0F launched by a bumper (kinds 6/7), bouncing to a stop
#   10 the same as 0F, from floor type 3            11, 12 the same code as 0F (no setter found)
#   13 running                                      14 running fast (animation_speed above 0x4F0)
#   15 held by an object (kind 8/10 caught while jumping) until it lets go
#   16 dying (health 0): flies off the screen, then 0x23
#   17 hopping (floor type 6, or released by a holder)
#   18 stage 0 / stage 5 scene (00D026): paths, poses and zoom by scene_step
#   19 thrown by the game mode 0x0D event (stage 8), by scene_step
#   1A inactive (returns at once)                   1B one 65-frame animation, then standing (no setter found)
#   1C opening scene of the first stage (game mode 0x10, 00D2BA)
#   1D stuck in an object (kind 17): shake the trackball to escape
#   1E game intro (mode 1): waits for its character's turn (intro_character), then runs path 0x14
#   1F blown away (kinds 19, 34): off the bottom of the screen, -8 health
#   20 hurt and tumbling (kinds 20, 23, deep floor type 0xD; -4 health)
#   21 stage exit (goal line reached, 00D026): walks the exit path
#   22 in a kind 0x16 zone (moves as usual)         23 continue countdown (9 to 0)
#   24 out of the game: message, then 0x0B         25 floor type 8 sequence (sequence_step; shake to escape at step 12)
#   26 pulled by scene_object (20E5F5), thrown, carried with the scroll, then skids
#   27 climbing along an object (kind 26) hand over hand to an x
#   28 swept away in a swirl (kind 30): up off the screen, -8 health
#   29 tripped (kind 31): a hop, then hurt          2A walking in along the stage's entry path (00CFCA)
#   2B game mode 0x18 (stage 0x11, the last): waits for 20E64F (object 01DDF4), +200 rings, then continue countdown
#   2C stuck on a kind 35 object, shake to escape (code not run in the scenarios)
#   2D held by an object of kind 37 (returns at once)
#   2E game over: ranking initials if the rings qualify (08EAC9, 08EC87)
#   2F stunned (kind 18; -4 health, 60 frames)
#   30 caught by scene_object, dropped and hurt (game mode 0x19, stage 0x10)
#   31 carried off (game mode 0x1A, stage 8; sequence_step) to player_carriers[p], then 0x32
#   32 trapped: shake the trackball to break free
#   33 sinking (floor block types 0x22/0x26): -8 health
#   34 choosing a character (game mode 0x1B, or after joining: 00E1A9)
#   35 riding (game mode 0x1C, stage 8)             36 knocked off the stage (kind 38): -8 health
#   37 slot disabled (player 3 with a two-player setting, 002808)
#   38 stage 0xB sequence (00D637): paths, a pose, waits, runs off
#   39 game mode 0x20 ("YOU FAILED"): continue countdown when 20E664 is set
002DEC  func  player_move                    # common code of states 0 1 2 4 0xC 0x13 0x14 0x17: timers, contacts, trackball physics, then 002FF1
003593  func  player_state_inactive          # state 3: rsr
00380D  func  player_state_teetering         # state 5
003B4A  func  player_state_falling_off_edge  # state 6
003DF9  func  player_state_over_edge         # state 7
0042F5  func  player_state_dropping          # state 8
00453B  func  player_state_falling_into_pit  # state 9
0046ED  func  player_state_climbing          # state 0xA
004E3B  func  player_state_not_playing       # states 0xB and 0x37
00558C  func  player_state_hurt              # state 0xD
005BF1  func  player_state_bouncing          # state 0xE
0053ED  func  player_state_launched          # states 0xF-0x12: substeps 005437...005446
00760C  func  player_state_held              # state 0x15
005256  func  player_state_dying             # state 0x16
006EF8  func  player_state_stage_scene       # state 0x18
007C0A  func  player_state_scene_thrown      # state 0x19
005172  func  player_state_inactive_2        # state 0x1A: rsr
0050F1  func  player_state_short_animation   # state 0x1B
006C0B  func  player_state_opening_scene     # state 0x1C
0076A8  func  player_state_stuck             # state 0x1D: substeps 007709...007715
006BE2  func  player_state_intro_turn        # state 0x1E
006A5E  func  player_state_blown_away        # state 0x1F
00649D  func  player_state_tumbling          # state 0x20
00639C  func  player_state_stage_exit        # state 0x21
00614E  func  player_state_in_zone           # state 0x22
005F1C  func  player_state_continue          # state 0x23
005E24  func  player_state_out_of_game       # state 0x24
008F31  func  player_state_floor_8_sequence  # state 0x25
008CCA  func  player_state_pulled            # state 0x26
00889A  func  player_state_climbing_object   # state 0x27: substeps 008A0A...008A13
008727  func  player_state_swept_away        # state 0x28
0085EA  func  player_state_tripped           # state 0x29
006295  func  player_state_entering_stage    # state 0x2A
0085BC  func  player_state_final_stage       # state 0x2B
009CED  func  player_state_stuck_on_object  # state 0x2C: substeps 009D34...009D40
009CEC  func  player_state_held_still        # state 0x2D: rsr
0050BA  func  player_state_game_over         # state 0x2E
0072AB  func  player_state_stunned           # state 0x2F
00A00D  func  player_state_caught            # state 0x30
007DA7  func  player_state_carried_off       # state 0x31
0080FF  func  player_state_trapped           # state 0x32
00A1FB  func  player_state_sinking           # state 0x33
007413  func  player_state_choosing_character  # state 0x34
008465  func  player_state_riding            # state 0x35
00A426  func  player_state_knocked_off       # state 0x36
007AC7  func  player_state_stage_b_sequence  # state 0x38
00745A  func  player_state_you_failed        # state 0x39 (game mode 0x20)
003191  func  player_animate_walking         # 002FF1 state 1 (table 10FD8C per character, walk_cycle)
003426  func  player_animate_falling         # 002FF1 state 2
003594  func  player_animate_jumping         # 002FF1 state 4 (also sets attack_power and gravity)
003299  func  player_animate_running_fast    # 002FF1 state 0x14 (table 11128C per character)
004EC3  func  player_animate_skidding        # 002FF1 state 0xC
005173  func  player_animate_hopping         # 002FF1 state 0x17
#
# ======== player functions ========
001A08  func  clear_player_blocks            # zeroes 0x600 bytes from 0x208400: the three player blocks
001A7F  func  update_player_summary          # game mode 3: player_dropping_in (scroll_mode saved and set to 0 meanwhile), no_player_in_play, player_mishap (clears the three mishap_flag), players_in_play
0027F2  func  player_1_start                 # player_objects[0]: object 9A = 0, then player_start
0027FA  func  player_2_start                 # player_objects[1]: 9A = 1
002802  func  player_3_start                 # player_objects[2]: 9A = 2
002808  func  player_start                   # new game: links block and object, health = starting_health, state 0xB (0x37 for player 3 on a 2-player setting), clip records
0028D1  func  player_resume                  # handler: next frame runs player_update (used when the player is already in the stage)
0028DB  func  player_join                    # handler after a credit: character select (00E1A9, state 0x34) unless game mode 0x1B, then player_reset_joined
00291F  func  player_start_stage             # player_objects_alt: a stage starts (object 30 bit 25), stage_rings = 0, HUD records (00A6EE), player_restart
00294A  func  player_respawn                 # handler set after losing health in a pit or off screen: player_restart without the stage-start flag
002953  func  player_restart                 # in_game > 0: player_reset; else not playing (state 0xB or 0x37) with handler player_update
00299C  func  player_reset_joined            # after joining: flags bit 4, health = starting_health, clears players_finished[p]/20E612[p], player_reset
0029DF  func  player_reset                   # clears the movement fields, 120 frames invulnerable unless a stage start, character voice, player_place
002BC5  func  player_place                   # start position (00CBAC), floor (00C6A9), entry status; waits here while dropping in (0x81/0x82)
002CEC  func  player_update                  # the player object's handler: prologue, state switch (table 002D3E), then player_animate
002FF1  func  player_animate                 # second switch on state (table 002FFC): animation and drawing; other states loop back to the first
0030AD  func  player_animate_standing        # 002FF1 state 0: standing animation (10F6CC/10FB4C/10F90C by character) and the clipped draw
0032BC  func  player_animate_running         # 002FF1 state 0x13: running animation table 110A4C/110FCC/110D0C, then 0032DD
0032DD  func  player_animate_run_cycle       # shared by the running states: animation table, walk_cycle steps (0x600, 0x4F0 when fast), draw
005437  func  player_launched_start_a        # state 0xF substep 0: 00544F
00543A  func  player_launched_start_b        # substep 1: 005446
00543D  func  player_launched_bounce         # substep 2: 0054ED
005440  func  player_launched_bounce_2       # substep 3: 0054ED
005443  func  player_launched_end            # substep 4: 005566
005446  func  player_launch_angle_200        # object 24 = 0x200, then player_launch
00544F  func  player_launch_angle_100        # object 24 = 0x100, substep 1, then player_launch
00545B  func  player_launch                  # speed_z = -16, speed 0x500, launch animation (11230E by character), then player_launched_fly
0054ED  func  player_launched_fly            # gravity 0xA000, bounces off x >= 0x120 on screen and off the floor (speeds halved); next substep on landing
005566  func  player_launched_land           # state 1 with all speeds 0
006B96  func  blown_away_effect_update       # handler of the object 006A5E/state 0x1F spawns: follows the player, deleted off screen
007709  func  player_stuck_step_0            # state 0x1D substep 0 -> 007715
00770C  func  player_stuck_step_1            # substep 1 -> 0077F6
00770F  func  player_stuck_step_2            # substep 2 -> 007834
007712  func  player_stuck_step_3            # substep 3 -> 00796D
007715  func  player_stuck_begin             # sprite, spawns the 007A1E object over the player, effect 084BBA type 0xD, sound 0x23
0077F6  func  player_stuck_wait_release      # waits until the holding object's 80 is 0, then the struggle animation and voice
007834  func  player_stuck_shake             # escape_progress grows with trackball speed aimed at 0x180-0x380; 0x10000 escapes (sound 0x2B)
00796D  func  player_stuck_jump_out          # bounce out: state 0xE with bounce_direction 1
007A1E  func  stuck_effect_update            # handler of the object over a stuck player; changes sprite with the player's substate
0083F7  func  trap_flash_start               # first object state 0x32 spawns (tile 0xB4F0): screen position, then trap_flash_update
008407  func  trap_flash_update              # counts 44 down from 8, drawn every other frame, deleted at the end
008426  func  trap_start                     # second object of state 0x32 (tile 0xB4F8, 80 = the player block), then trap_update
008436  func  trap_update                    # deleted off screen (01F091); tile 0xB500 once the player's substate reaches 3 (broke free)
008A0A  func  player_object_climb_step_0     # state 0x27 substep 0 -> 008A13
008A0D  func  player_object_climb_step_1     # substep 1 -> 008AD2
008A10  func  player_object_climb_step_2     # substep 2 -> 008B84
008A13  func  player_object_climb_begin      # at the object (6A), z 0x78, screen_x from x/y, climb animation (9CB3/9CDD, 9CCF for character 1)
008AD2  func  player_object_climb_push       # trackball aimed between 0x380 and 0x17F (wrapping) adds speed to step_accumulator; at 0x400 (on screen) a step: hand object, sound 0x64/0x6B/0x51
008B84  func  player_object_climb_move       # when the step animation ends: screen_x + 0x12, screen y - 9, the other hand (character 1)
008BFA  func  spawn_climb_hand               # object at the player with a hand tile (0x8300/0x8258, 0x82C8/0x82C0 by climb_hand), handler 008C69
008C69  func  climb_hand_update              # stays while its player is in state 0x27 and not at the end of the step animation
0096F1  func  player_move_with_scroll        # states 0x25/0x26: moves with the camera (20E606 speed, 20E60A angle from 00B173) on top of the trackball movement
0099F2  func  spawn_player_effect            # effect object at the player (handler 009A62, animation 9BF7): states 0x25, 0x30, 0x33
00A3F8  func  sinking_effect_update          # state 0x33 changes the effect's handler to this: moves down 1.5 a frame, deleted at the end
009B4D  func  scene_object_start             # object at a fixed spot that becomes scene_object (20E62E), handler 009B8E
009B8E  func  scene_object_update            # circles (x, z) while the scroll is between 0x4FF0 and its 80; deleted after
009A62  func  player_effect_start  # Projects the effect object, then player_effect_update
009A6F  func  player_effect_update           # animates, deleted when the animation ends
009A94  func  spawn_struggle_marker          # state 0x25 step 0xB: object (tile 0x77A0) that follows the player, handler 009ADC
009ADC  func  struggle_marker_update         # follows its player while state 0x25 step 0xC, else deleted
009D34  func  player_stuck_on_object_step_0  # state 0x2C substep 0 -> 009D40 (state 0x2C: like 0x1D on top of a kind 35 object; not run in the scenarios)
009D37  func  player_stuck_on_object_step_1  # -> 009DE8
009D3A  func  player_stuck_on_object_step_2  # -> 009E1E
009D3D  func  player_stuck_on_object_step_3  # -> 009F57
009D40  func  player_stuck_on_object_begin   # on the object (6A): its x and y, y - 0x20 (not z), sprite, effect 084BBA type 0xD, sound 0x23, 30 frames
009DE8  func  player_stuck_on_object_wait    # state_timer, then the struggle animation and voice
009E1E  func  player_stuck_on_object_shake   # escape_progress from trackball speed aimed at 0x180-0x380; 0xC000 escapes (sound 0x2B)
009F57  func  player_stuck_on_object_jump_out  # bounce out: state 0xE
00A669  func  spawn_skid_dust                # state 0xC: dust object (animation 113D13, handler 00A6D5)
00A6D5  func  skid_dust_update               # animates, deleted when it wraps
00A6EE  func  player_hud_init                # fills the three HUD sprite records (+0x142 gauge, +0x172 portrait, +0x1A2 ring effect)
00A85A  func  player_hud_start               # portrait tile per character, then player_hud_update
00A8E9  func  player_hud_update              # gauge length = 2 x health, palette animations on hits/rings/low health, ring count in decimal; start prompt when out of the game
00A9D9  func  draw_player_start_prompt       # blink_start_prompt (09FCF4) with the player's credit block (20F412, or + 4p with separate chutes) at its HUD text position
00ACA1  func  animate_hud_palette            # +26/+27 delay, +28 list of palettes into +06; r8 = -1 when the list wraps
00ACD6  func  animate_hud_tiles              # the same with tiles into +04
00AD0D  func  blink_player_hud_message       # every 0x1E frames draws or erases text 113FB7 (waiting to enter)
00AD91  func  keep_player_on_screen          # off the sides costs 8 health (respawn or dying); near an edge pushes back (screen_edge) and moves with the scroll
00AF5B  func  push_player_from_screen_edge   # moves the player away from screen_edge at speed 0x200 (screen_edge_push_directions); first back to its previous position, floor block and floor_z when its direction is more than a quarter turn off that entry's
00B173  func  compute_scroll_drift           # 20E606/20E60A: camera movement (20902F/209033) in world speed and angle for a floor block's slope
00B28F  func  slip_effect_update             # handler of the object state 8 spawns: animates, deleted at frame 0
00B2C0  func  set_speed_from_trackball       # speed = |input|^2 (max 0x5000000), angle = atan(input) + 0x80, then speed_x/y
00B313  func  set_climb_speed_from_trackball # the same with max 0x3000000 and no angle offset (climbing)
00B361  func  update_ledge_pullback          # 00B2C0, then adds the movement back toward the floor block to ledge_pullback
00B3AC  func  get_ledge_side                 # r3 = side of the previous floor block the object is past (3, 1, 0, 2) or 4 inside; r11 = the block
00B3E7  func  check_player_floor_edge        # no floor under the player: teeter (5), slip (7) or fall; jumping into a pit -> 7
00B465  func  check_fall_into_pit            # falling below the previous floor over a pit: state 9
00B5DC  func  update_player_blink            # invulnerable_timer < 0: visible; else blinks while object 30 bit 29
00B5EC  func  toggle_hidden                  # object 30 bit 19 ^= 1 (the drawing code skips hidden objects)
00B60A  func  check_player_jump              # floor type 6: hop (0x17); else after jump_delay, 00B62D
00B62D  func  start_jump_if_pressed          # button (bit 0x10/0x18/0x12 in game, 0x19/0x1A/0x1B in the demo) newly pressed: state 4
00B6AC  func  push_player_out_of_wall        # in a wall's box (object 30 bit 5, partner 78): back to previous position; bounced when jumping, else stopped or turned along it
00B8AF  func  player_steer_in_air            # no floor block: speed from the trackball, turning (00BBEC, 00BC50)
00B90A  func  player_steer                   # trackball (or path) to target speed, push, floor type factors; picks standing/walking/running; ground_speed
00BBEC  func  set_target_heading_from_trackball  # facing_angle = atan(x, 1.5 y); target_heading adds push_x/push_y
00BC50  func  turn_player                    # heading turns toward target_heading at a rate set by speed; >= 90 degrees at speed: skid (0xC)
00BD0B  func  set_heading_direction          # heading_snapped and object 24 from heading
00BD2C  func  set_facing                     # facing = 16-way facing_angle
00BD42  func  face_movement_direction        # heading = target_heading = facing_angle = object 24 - 0xA0
00BD5F  func  compute_ground_speed           # ground_speed from object speed (6E), direction table 113EC0 and slope (10DFCC)
00BDCE  func  read_player_trackball          # trackball_x/trackball_y from trackball_N_x/y (0x20F062 + 4 x player)
00BE20  func  shadow_1_start                 # player_objects[3]: object 9A = 0, then shadow_start
00BE28  func  shadow_2_start                 # player_objects[4]
00BE30  func  shadow_3_start                 # player_objects[5]
00BE36  func  shadow_start                   # shadow object (tile 0x5D90), its attack effect child (and a second in stage 9); handler shadow_update
00BED5  func  shadow_update                  # draws the player's shadow at the floor (none over a pit), water/quicksand variants, then 00C2C7
00C2C7  func  draw_attack_effect             # while object 30 bit 6 (attacking) and attack_power > 2: spin effect sprite (113848/113918) per character
00C920  func  check_player_step              # floor lower: falling (2); higher than a step: blocked, or bounce (0xE) at speed >= 0x500
00CA2C  func  bounce_off_wall_in_air         # jumping into a higher floor: back to previous position, turned away, control_lock 10
00CAAA  func  get_floor_block_side           # r3 = side (0-3) of floor block r10 for the point r11, r12
00CCB0  func  aim_at_screen_centre           # r5, r6 = speed 0x200 from the player toward screen point (0xA0, 0x70)
00CCE9  func  flash_hurt_palette             # every 84+1 frames swaps palette 06 between the normal one and 0x83B0
00CDB8  func  set_floor_sprite_priority      # sprite priority bits on floor types 4 and 0xC
00CE08  func  update_quicksand_object        # object 30 bit 31: drawn into the quicksand object; sinking past 0x2000 costs 8 health
00CE7B  func  update_sinking_floor           # floor type 0xA: sink_depth grows, struggle_speed slows it; 0x2000 costs 8 health
00CF08  func  apply_moving_floor             # floor type 7 / 0xC: adds the moving object's speed (20E5F6, object 74) to x or y
00CF6A  func  check_zone_contact             # object 30 bit 27 (zone partner in 14): kind 0x16 -> state 0x22; kinds 0x18 0xC 0x1E 9 0x21 0x14 count as touched (bit 2, 6A)
00CFCA  func  check_stage_entry_path         # enter_on_respawn and the stage has an entry path: state 0x2A
00D1E4  func  spawn_tumble_effect            # state 0x20: effect object over the player (animation 1142F6, handler 00D22E)
00D22E  func  tumble_effect_update           # follows the player while state 0x20 and its animation index < 0xB
00D2A7  func  apply_speed_scale              # r0 x speed_scale / 16 when speed_scale is set
00D2BA  func  set_player_entry_by_scene      # by current game mode (20E5A9): wait (0x82), opening 0x1C (mode 0x10), intro 0x1E (mode 1), 0x2B (mode 0x18), character select 0x34 (mode 0x1B)
00D4A4  func  try_continue                   # state 0x23: start pressed with a credit (00D4CF, cost r3)
00D4B8  func  try_join                       # start pressed with credits for a game (20F410); never for state 0x37
00D4CF  func  pay_and_join                   # by coin setting (20F41E): checks credits and start/trackball button, takes the credits, lamps (IO_C00074)
00D637  func  check_scene_states             # current game mode (20E5A9) and stage flags force states 0x19 0x1E 0x21 0x26 0x2B 0x30 0x31 0x35 0x38 0x39
00D7E8  func  set_fall_sprite_priority       # priority bits for the fall into a pit, from the floor block's table (113F7F) by side
00D885  func  start_climb_at_spawn           # placed on a wall (floor type 2): state 0xA substep 3
00D940  func  start_climb                    # floor type 2 under a riding player: state 0xA substep 3
00DA9B  func  apply_floor_push               # push_x/push_y from floor types 7, 0xB, 4 and 5 (floor_types + 7A); not while invulnerable
00DAF1  func  spawn_slip_piece               # state 8, character 2: one piece that waits, then falls (handler 00DB69)
00DB69  func  slip_piece_update              # spawn_slip_piece's piece: deleted off screen; waits while speed_x_fraction (a timer here) counts down from 0x10, then falls (gravity 0x6000); queued at its base when its direction (ledge_side) is 2 or more, else in bucket 0xFF
00DBAD  func  spawn_slip_pieces              # state 8, character 1: two pieces thrown apart (handler 00DC69)
00DC69  func  slip_pieces_update             # spawn_slip_pieces' two pieces: each moves by speed_x/speed_y, falls (gravity 0x6000), is deleted off screen (margins 0x20, 0x50) and animates; queued at its base when its direction (ledge_side) is 2 or more, else in bucket 0xFF
00DCC5  func  follow_player_path             # autopilot: path_request -> path, heads for each record, jumps on flag bit 0, waits, ends at a negative record
00E149  func  spawn_player_ride              # game mode 0x1C before 20E647: when x is outside 0xA60-0xAE0, an object (handler 0871C4) at y 0xEB2, z 0x530 becomes ridden_object
00E1A9  func  player_select_character        # state 0x34: join_time; 090096 starts a character select for the player (first free character, 208090 bits); done when 090161 clears the player's bit in 208090
00E263  func  spawn_climb_zone_effect        # state 0xA entering splash_zone: object at x 0x1CC, z 0x4CC (handler 00E2C3) tied to the player block (code not run)
00E2C3  func  climb_zone_effect_start        # project_object, then handler climb_zone_effect_update, run at once
00E2D2  func  climb_zone_effect_update       # follows the player's y while it climbs (state 0xA) in the zone, else deleted
00E313  func  spawn_intro_marker             # state 0x1C: object (tile 0xA0C0, animation E46D) that follows the player, handler 00E36D
00E36D  func  intro_marker_update            # follows its player with the zoom; deleted when scene_step reaches 2
00E22A  func  give_result_bonus              # called by the stage result (098C2A) with the block in R16: health + 8 (up to the max), rings + 2 x 20CA49
00E3BB  func  update_hurt_output             # port D bit 2 (player 1) or 3 (player 2) on while hurt (0xD 0x20 0x28 0x2C 0x33) if 20F624
00E415  func  check_player_stalled           # 7200 frames without scrolling (except stage 5): health 0, state 0xD
00E488  func  check_player_contacts          # switch on the touched object's kind (its 47): rings, hazards, solid, launchers, catchers...
#
#
# ======== work RAM ========
208414  var  player_1_object              u16 object    # 0983FE, 0843D5, 015F80 read the blocks' object field directly
208614  var  player_2_object              u16 object
208814  var  player_3_object              u16 object
2084B4  var  player_1_mishap_flag         u8     # 001A7F clears the three
2086B4  var  player_2_mishap_flag         u8
2088B4  var  player_3_mishap_flag         u8
2084C4  var  player_1_speed_scale         u16    # 0843D5 clears the three
2086C4  var  player_2_speed_scale         u16
2088C4  var  player_3_speed_scale         u16
2084EA  var  player_1_sequence_step       u8     # 000652 clears the three
2086EA  var  player_2_sequence_step       u8
2088EA  var  player_3_sequence_step       u8
208523  var  player_1_hud_message_shown   u8     # 000652 clears the three when stage 0xB starts
208723  var  player_2_hud_message_shown   u8
208923  var  player_3_hud_message_shown   u8
20853C  var  player_1_stage_rings         u8     # 0983FE reads the low bytes for the stage result
20873C  var  player_2_stage_rings         u8
20893C  var  player_3_stage_rings         u8
20E596  var  player_dropping_in           u8     # 001A7F: 0xFF while a player_status is 0x81 (dropping in from above); objects test it
20E597  var  player_dropping_in_before    u8     # 001A7F: last frame's player_dropping_in
20E594  var  saved_scroll_mode            u16    # 001A7F: scroll_mode (20E44B) kept while a player drops in, then restored
20E599  var  no_player_in_play            u8     # 001A7F: 0xFF when no player_status is negative
20E5A3  var  player_mishap                u8     # 001A7F: 0xFF when a player in the game had its mishap_flag set this frame; objects test it
20E598  var  skip_player_summary          u8     # set by 00075E, cleared by player_update: 001A7F does nothing while set
20E590  var  player_status                u8     # [3] 0 not playing, 1 dying, 2 continue countdown, 3 game over, 4 joining, 0x81/0x82 entering, 0xFF playing (2BC5, states)
20E5E0  var  players_finished             u8     # [3] 0xFF when the player has walked its stage exit path (states 0x18, 0x21); objects test all three
20E60D  var  stage_exit_started           u8     # 0xFF once a player starts the exit path (states 0x18, 0x21); players entering then wait (00D2BA)
20E618  var  joining_closed               u8     # set when stage 0xB starts with a game going (000652): no joining (0xB/0x24), no continue (0x23)
20E646  var  intro_character              s8     # character whose turn it is in the intro (state 0x1E); the first joining player sets it (state 0x34)  (TYPE: FF = none, tested "(int8_t) < 0")
20E669  var  health_setting               u8     # from the settings (20F620, apply_settings 09F6B7): rings per health point (0/4: 1, 1/2: 4, 3: 8) and max health (113EF2)
20E66A  var  starting_health              u8     # from the settings (20F622 & 3 -> 9, 16, 18, 24, table 115B92); object 7E at join
20E66B  var  players_in_play              s8     # 001A7F: players whose player_status is negative (0x81, 0x82, 0xFF)  (TYPE: both readers compare signed, >= 2)
20F062  var  trackball_1_x                s16    # read_controls: movement since last frame (C00040), clamped to +-0x60, negated
20F064  var  trackball_1_y                s16    # read_controls (C00044)
20F066  var  trackball_2_x                s16
20F068  var  trackball_2_y                s16
20F06A  var  trackball_3_x                s16
20F06C  var  trackball_3_y                s16
20F018  var  input_ports                  u32    # read_controls: ports B, A, F, E (active low)
20F01C  var  input_ports_active           u32    # read_controls: ~input_ports
20F020  var  inputs_pressed               u32    # read_controls: newly pressed this frame; bits 0x10/0x18/0x12 trackball buttons 1/2/3 (the jump)
20F024  var  inputs_released              u32    # read_controls: newly released this frame
20F0E2  var  scene_step                   u8     # step of the current stage scene (mode handlers and objects increment it); states 0x18, 0x19 and 0x1C switch on it, 0x25 waits for 2
20E640  var  player_carriers              u16    # [3] object that carries each player off (086B6E registers itself); state 0x31 step 6 moves the player to it
20E62E  var  scene_object                 u16 object    # object the players are pulled to or carried by (states 0x26, 0x30, 0x31); set by 009B4D and stage objects

# ======== stages (from build/naming/stages.txt) ========
# Stages: proposals from the stages helper (symbols.txt syntax, one per line, "# evidence")
# The object sections at the end come from six helpers this helper ran (one per address range), checked
# and adjusted here (names aligned with objects.txt/system.txt/players.txt, game modes written in hex).
#
# How the stage system works (summary; details in the comments below):
# - stage (20F06E) selects 19 slots. Writing cleared_stages makes the protection device set the stage from
#   the order table at 002639 (cleared 0..14 -> 07 06 05 08 09 0A 03 04 10 0C 0D 0E 0F 0B 11) and clear
#   stage_init_flags (20F0BC-20F0BF; MAME: CURRENT_LEVEL_STATUS); the program never writes stage for that.
#   Stage select (game mode 8, slots 00-11) writes stage_select_cleared_values[stage], so its slots 00, 01
#   and 02 come back as 05, 07 and 07 (checked with s32ref: stage_00 and stage_05 both play stage 5,
#   stage_01/_02/_07 all play stage 7). Slot 00 is entered only from inside stage 5: run_stage_05_switch
#   (game mode 0x0A) sets scroll_position 5E5A and stage 0. Slots 01, 02, 12 are never reached. Stage 6
#   (the first after a new game) has no play: its only event is game mode 0x10 (update_stage_06_background),
#   which ends in mode 2.
# - Shared data (per-stage offsets that coincide): stage 7 (the attract demo of game mode 1) reuses stage 3's
#   map (floors, block map, path); 6 reuses 5's; 0E reuses 0C's and 0F reuses 0D's map, path, goal and
#   restart points (only their spawn lists differ); 01, 02, 11 and 12 point at copies of other stages' tables.
#   Stage 9 is the only one whose scroll script starts in mode 0 (it scrolls by itself); the others follow
#   the players (mode 7).
# - game mode 2 = start_stage; game mode 3 = the stage's frame (update_scroll, update_stage_tile_layers,
#   find_nearby_floor_blocks, update_restart_point, the six spawners, check_stage_clear, run_stage_events).
#   run_stage_events switches game_mode to the stage's scripted modes (0x0A-0x1F) at scroll positions:
#   stage 3: 11, 16 at 1B80, 16 at 8610, 15 at A360; 4: 11, 0C; 5: 0A at 100; 6: 10; 8: 0D, 1A at 3100,
#   1C at 7A8D; 9: 13, 14 at 1800, 12 at 4C00; A, C: 11; B: 1F at F00; 10: 11, 19 at C80; 11: 18.
#   Game mode 0x11 opens the screen with update_screen_wipe. A stage ends when the players have walked their
#   exit path past the goal and goal_unlocked is set (an object of each stage sets it: the end machine, the
#   last icicle dropper, goal_unlocker_start...): check_stage_clear goes to 0x17 (stage clear banners), 0x1D
#   (ring tally), 4 (the island map), 2; stages B-F go to 0x1E (a fade straight into the next stage).
#   The only time limit is stage B's escape (game mode 0x1F: 20 seconds once escape_started is set).
# - The camera follows a path made of sections: per section a direction, an end position (in scroll units),
#   x/y limits, a speed and a scroll mode script. scroll_position counts the distance along the path and
#   keys everything: spawn lists, stage events, restart points, the goal.
# - Coordinates: objects have ground x/y (4C/50) and height z (40); project_object turns them into
#   projected coordinates (2A = (x+y)*0.707, 2C = (y-x)*0.354, isometric) and the screen position (0E/0C) is
#   projected - view (view_x/view_y follow the camera).
# - Collision with the map: the stage's floors are boxes (x/y ranges, height, slope, surface kind) in
#   floor_blocks; every frame find_nearby_floor_blocks lists the ones near the view and find_floor
#   gives an object its floor and height. Walls are objects (spawn_walls) that push the players back.
# - Stage objects come from six spawn lists keyed by scroll_position. Most hazards that follow the players
#   are "chasers": a path script of (position or coordinate, screen threshold, catch-up factor, speed,
#   delay) records; behind the threshold they move at max(speed, camera speed * factor >> 8), and they hold
#   back after a player mishap. chaser_object (20E5D4) is the current one (the stage 3 spiked roller, the
#   stage A tornado, the stage B/C fire front, the stage 10 propeller machine...); group leaders, the lava
#   flow, the icicle droppers and the geyser front move the same way.

# ======== Stage flow ========
00075E  func  start_stage                      # game mode 2: cleared_stages+1 (capped at 0E) unless stage_start_kind says not to, clears the stage state, start_stage_view, load_stage_map_pointers, load_stage_goal, find_nearby_floor_blocks, requests stage_start_sounds[stage], game_mode = 3
00196E  func  check_stage_clear                # scroll_position >= goal_scroll_position, 20E593 clear and stage_exit_started set: marks players_finished FF for the players not in play (player_status 0, 2, 3, 4, 82), then needs the u32 at 20E5E0 = FFFFFFFF (the three players_finished bytes and goal_unlocked: a 32-bit read across both variables): game_mode 0x17 (0x1E for stages 0B-0F)
001B74  func  continue_to_next_stage_slot      # when next_stage_slot_request is set: copies two floor_blocks records (stage_link_points), stage++ and load_stage_map_pointers; never runs (20E538 is only ever cleared)
001BED  func  load_stage_map_pointers          # current_restart_point = stage_restart_points[stage]; floor_blocks / floor_block_bounds pointers from 10B6CC / 10E29C
001C31  func  run_stage_events                 # stage_events[stage]: (scroll position, game mode) records; switches game_mode when scroll_position reaches one (within 100)
001CD6  func  update_restart_point             # advances current_restart_point (from itself on) to the first 8-byte record whose position is above scroll_position
001859  func  apply_stage_layer_blending       # stage_layer_blending[stage]: count, then (sprite blend mask, bit, offset from 208000, blend factor word) into the mixer shadows 20F1C0-20F1DE: the layer register gets the mask and the bit (12: blend with sprites, MAME segas32_v), 61004E the factor and enable bit 11
00CFF0  func  load_stage_goal                  # stage_goals[stage]: six u16 -> goal_scroll_position, goal_direction, goal_line, (the fourth is skipped: add.h #2, R12), goal_line_min, goal_line_max
00D026  func  check_player_reached_goal        # past goal_scroll_position and across the goal line (direction 0-3): player state 21 (stage 0: 18; stage 4 waits for goal_unlocked; stage 5 also on object 20E5CA's timer)
00CBAC  func  place_player_at_restart_point    # x/y from the current stage_restart_points record (three kinds), spread by player number; at a stage start (object flag 2000000, in a game) from the 6 bytes before the list
09A5F4  func  load_stage_palette               # stage_palettes[stage]: (source, count) copied to palette RAM at 60B800
016CE3  func  start_stage_view                 # stage != 11: init_stage_view (stage 11 only clears 20CA45)
0158E2  func  init_stage_view                  # display off; tile layer pages, zoom, mixer; the stage's four tile block sets; camera, scroll and section lists from the per-stage tables
01A959  func  move_camera_to_start_section     # load_camera_start_section, then update_camera_view
01A966  func  load_camera_start_section        # like init_stage_view's section setup but from section stage_start_section (attract demos start mid-stage)
016D09  func  run_stage_05_switch              # game mode 0x0A in stage 5: fade out, new tile layers, fade in, then scroll_position = 5E5A and stage = 0
0178AB  func  update_stage_06_background       # game mode 0x10 (stage 6's only event, the scene after a new game; a render shows a player running toward the screen ahead of a big machine with jaws): scripted tile layer and palette steps driven by stage_06_background_step
0968F4  func  update_screen_wipe               # moves four window edges (2090F0-2090FC) to their targets, writes them to 31FF80-31FF86; returns the done bits in R5 (game mode 0x11 waits for 0F)
016EC9  func  update_fade                      # mixer colour offsets 20F1D0-20F1D4 step from/to black or white; fade_kind < 0 when done

20F0BC  var   stage_init_flags      u32        # one bit per once-per-stage setup (bit 0 spawn_objects_by_handler, 2 spawn_ring_rows, 4 spawn_object_groups_2, 5 spawn_walls, 8 run_stage_events, 9 spawn_object_groups, B 001A7F, C 09532E, 6/D/E/10/11 game modes 8/1/17/1B/1D, F/12/13 players 1-3 in 0FFD90/00A85A): set on first run; the protection device clears it (and 20F0BE) with each write to cleared_stages (MAME: CURRENT_LEVEL_STATUS)
20F500  var   stage_order_copy      u16        # boot (00055D) copies stage_order here (11 words); nothing in the program reads it
20E538  var   next_stage_slot_request  u8      # continue_to_next_stage_slot acts on it; the program only clears it
20F0E8  var   stage_start_kind      u8         # start_stage: 0 advances cleared_stages (a new game too: 0 to 1 gives stage 6); stage select sets 1 (no advance); the attract demo path (game mode 0) sets FF: no advance and no player restart
209024  var   stage_start_section   u16        # load_camera_start_section starts the path at this section; start_stage reads it from the attract demo data (+2)
20E5E4  var   goal_scroll_position  u16        # stage_goals[stage]+0; check_stage_clear and check_player_reached_goal wait for scroll_position to reach it
20E5E6  var   goal_direction        u16        # stage_goals+2: which coordinate and side the goal line is crossed on (0-3)
20E5E8  var   goal_line             s16        # stage_goals+4: the x or y the players must cross  (TYPE: check_player_reached_goal compares it signed with object coordinates, 5 of 5 reads)
20E64A  var   goal_line_min         s16        # stage_goals+8: 0 or the start of the goal line on the other axis  (TYPE: signed compares)
20E64C  var   goal_line_max         s16        # stage_goals+A  (TYPE: signed compares)
20F088  var   current_restart_point u32 restart_point  # pointer into stage_restart_points, moved by update_restart_point (8 bytes a point); read by place_player_at_restart_point ("mov.w 7088[R25], R10; add.w #2, R10" 00CBFF-00CC04, then [R10+])
20E5C6  var   stage_06_shake        u16        # update_stage_06_background step 7: counts down by 80, its high byte shakes the NBG0/NBG3 y scroll
20908F  var   stage_06_background_step  u8     # update_stage_06_background switches on it; game mode 0x10 sets 3 and 5
209088  var   fade_step             u8         # update_fade: 0 start, 1 stepping, 2 done
209089  var   fade_kind             s8         # update_fade: 0 to black, 1 from white, 2 from black, 3 to white; FF when done
20908A  var   fade_delay            u16        # update_fade: frames between steps (1)
20908D  var   fade_delta            u16        # update_fade: added to the three colour offsets each step (+/- fade_speed)
20CA4D  var   fade_speed            u8         # init_stage_view sets 3; update_fade's step size
20908C  var   stage_05_switch_step  s8         # run_stage_05_switch switches on it; FF when done  (TYPE: FF = done, tested signed 4 times, like fade_kind)

002639  table stage_order           u16        # stage per cleared_stages value: 07 06 05 08 09 0A 03 04 10 0C 0D 0E 0F 0B 11; the protection device reads it (MAME: big-endian at 263A, cleared 0 = 7), the program only copies it to stage_order_copy
00265B  table stage_select_cleared_values  u16 # game mode 8 writes [stage] to cleared_stages so that stage_order gives the stage back
002681  table stage_start_sounds    u8         # start_stage (and game mode 1) requests the sound [stage] with 09A5BC
002492  table stage_events          u16        # 19 offsets from 002492 to lists of (u16 scroll position, u8 game mode, u8 0); FFFF ends
struct stage_event 4 rom                  # a stage_events record: run_stage_events "cmp.h [R10], 6435[R25]" (bl: not yet), "sub.h [R10+], R0" (001C62), "mov.b [R10], 7061[R25]" (001C76), then stage_event_cursor + 4; census ROM 0024B8-002521 (0000 0D, 3100 1A, 7A8D 1C, FFFF...)
field stage_event 00 position u16         # the scroll_position that starts it (unsigned); FFFF ends the list
field stage_event 02 next_game_mode u8         # into game_mode (0A-1F; the byte after it is 0)
002520  table stage_link_points     u16        # 19 offsets from 002520 to two u16 offsets into floor_blocks (continue_to_next_stage_slot)
struct stage_link_point 4 rom             # the record stage_link_points[stage] leads to: two offsets into floor_blocks (continue_to_next_stage_slot "movz.hw [R10+], R0" 001B93, 001BC7), each + floor_blocks + 2 a floor_block; census ROM 002520-002523 (offset 0: every stage but 1 reads the table's own first words, 0000 0026) and 002546-002549 (stage 1: 02BC 0028)
field stage_link_point 00 from_block u16  # the block of this stage's floor_blocks the link leaves from (stage_link_from_block)
field stage_link_point 02 to_block u16    # the block of the next stage's floor_blocks it reaches (stage_link_to_block)
002694  table stage_layer_blending  u16        # 19 offsets from 002694: u8 count, then 6-byte records (mask, bit, RAM offset of the layer register shadow, blend factor word); used by stages 5, 9, 10
struct layer_blend_list 1 rom             # the head of a stage_layer_blending list: its record count (apply_stage_layer_blending "test.b [R10]" 00187D, "movz.bw [R10+], R8" 001881), the layer_blend_record records follow; census ROM 0026BA (2), 0026C7 (1), 0026CE (0)
field layer_blend_list 00 count u8
struct layer_blend_record 6 rom           # a record of a stage_layer_blending list: apply_stage_layer_blending reads it with [R10+] (001884 "movz.bh [R10+], R1", 001887 "movz.bw [R10+], R2", 00188A "movz.hw [R10+], R11", 0018A5 "or.h [R10+], 71DE[R25]"); census ROM 0026BB-0026CC (3E 0C 71C8 0100, 3E 0C 71C2 0100, 3B 0C 71C2 0200)
field layer_blend_record 00 blend_mask u8       # or.h into the layer shadow's low 6 bits (3E, 3B)
field layer_blend_record 01 layer_bit u8        # set1 in the layer shadow (C: blend with the sprites)
field layer_blend_record 02 shadow_offset u16   # the layer's mixer shadow, from the variables (71C8, 71C2: 20F1C8, 20F1C2)
field layer_blend_record 04 blend_factor u16    # or.h into mixer_blend (bits 8-11: 100, 200), with the enable bit 11
11419A  table stage_goals           u16        # 19 offsets from 11419A to six u16 (load_stage_goal); FFFF position = no goal (stages 0-2, 6, 7, 11, 12)
114B84  table stage_restart_points  u16        # 19 offsets from 114B84 to 8-byte records: u16 scroll position, u8 kind (0/2: u16 x, u16 y at +4; 1: u8 at +3 and two words through 00CC75); the 6 bytes before each list are the stage start (u16 shift that spreads the players, x, y)
struct restart_point_record 8 rom         # a stage_restart_points record (current_restart_point): update_restart_point "cmp.h [R10], R4" + bnl (001CE6: unsigned) walks them 8 at a time; the rest (kind at +2, x/y at +4) is place_player_at_restart_point's; census ROM: lists from 114BB0 to 115520 (stage 0's first: 0010 00 00 ED20 1576)
field restart_point_record 00 position u16  # the scroll_position where the restart point starts counting
10B6CC  table stage_floor_blocks    u16        # 19 offsets from 10B6CC to the stage's floors (load_stage_map_pointers)
10E29C  table stage_floor_block_bounds  u16    # 19 offsets from 10E29C to the stage's floor outlines
108CD0  table stage_palettes        stage_palette        # 19 x (source address, count): load_stage_palette
struct stage_palette 8 rom                # a stage_palettes record (stage x 8: load_stage_palette "shl.w #3, R0", "add.w R0, R9" 09A603): "mov.w [R9+], R10" (09A606), "mov.w [R9], R1" (09A609) for move_halfwords to 60B800; census ROM 108CD0-108D67 (108D6C 310, 10938C 250, 10982C 270...)
field stage_palette 00 source u32         # the colours (108D6C-10B14C)
field stage_palette 04 halfword_count u32 # how many halfwords move_halfwords copies (1E0-390)
0026CF  table stage_name_banners    u16        # game mode 0x17: [stage] offset from 0026CF to screen x, screen y and the animation (field 48) of the stage's name banner (stage_clear_banner_reveal; "TRAP TOWER" for stage 3 in a render)
struct stage_name_banner 4 rom            # the record stage_name_banners[stage] leads to: screen x and y (game_mode_stage_clear "mov.h [R10+], E[R11]" 000DAA, "mov.h [R10+], C[R11]" 000DAF), the banner's animation follows ("mov.w R10, 48[R11]" 000DB4: its first word, the letter count, then the frames); census ROM 0026F5-002782 (the x, y words of the 7 records: x 40-74, y 60 or 88)
field stage_name_banner 00 screen_x u16   # into the banner object's screen_x (0E)
field stage_name_banner 02 screen_y u16   # into its screen_y (0C)

# ======== Scrolling and the camera ========
# update_scroll runs every frame of the stage modes. The scroll mode script (stage_scroll_modes) says what
# the camera does; in modes 7/8 the speed follows the leading player (field 6E of his object).
015F80  func  update_scroll                    # switches on scroll_mode; stops at scroll_end (then stage 3's end sequence); modes 7/8 pick the speed from the players pushing the screen edge
016A77  func  scroll_at_scroll_speed           # scroll mode 0: advance_scroll with scroll_speed
016A7C  func  advance_scroll_unless_ended  # advance_scroll, unless scroll_ended is set
016A83  func  advance_scroll                   # move_camera_along_path, then at a section end loads the next section (speed, follow speed, x/y limits, mode script, direction, end)
016B36  func  set_camera_velocity              # camera_velocity_x/_y = section direction * speed; update_camera_with_shake; then update_background_scroll
016B70  func  update_background_scroll         # NBG1/NBG3/NBG2 per-stage scroll; inside stage_fast_scroll_ranges forces follow_base_speed to 10
01699F  func  scroll_slowing_down              # scroll mode 3: scroll_speed - 1 every scroll_mode_duration frames, down to 1
0169CA  func  scroll_speeding_up               # scroll mode 4: scroll_ramp_speed + 1 every scroll_mode_duration frames up to scroll_speed, then mode 0; on the frame the ramp ends (0169FA) it goes on to set the camera velocity with the R3 its caller left (never ran in the scenarios)
016698  func  set_screen_push_edges            # from the section direction: edges 209041-209047 and push_edge_case (which edges count)
016837  func  is_player_facing_scroll          # the player block's angle (field 28, 400 per turn) within a half turn around the scroll direction's quadrant
0168C5  func  is_direction_in_quadrant         # looks for section_direction_y between consecutive sine_table entries of the quadrant set_screen_push_edges chose (4000/C000 count as found)
016267  func  is_player_pushing_edge           # per push_edge_case: R0 = 1 when the player is at the leading edges; a player at the trailing edge sets byte 2 of players_facing_scroll instead
016488  func  is_player_at_diagonal_edge_30     # screen y against screen x / 2 + 30 (and 30 - A); diagonal sections
0164E2  func  is_player_at_diagonal_edge_f0     # screen y against F0 - screen x / 2 (and F0 - A); diagonal sections
016542  func  is_player_at_diagonal_edge_c0     # screen y against C0 - screen x / 2 (and C0 - A); diagonal sections
0165A2  func  is_player_at_diagonal_edge_60     # screen y against screen x / 2 + 60 (and 60 - A); diagonal sections
0165FC  func  is_player_at_right_edge          # screen x (field 0E) >= push_edge_right; 10 more sets byte 3 of players_facing_scroll
016623  func  is_player_at_left_edge           # screen x <= push_edge_left
01664A  func  is_player_at_top_edge            # screen y (field 0C) <= push_edge_top
016671  func  is_player_at_bottom_edge         # screen y >= push_edge_bottom
017720  func  move_camera_along_path           # camera_x/_y += direction * speed until the section's x/y limits; scroll_position += speed until the section end; jump table by camera_limits_reached
0177FD  func  move_camera_free                 # case 0: advance_camera_scroll_x_y
017800  func  move_camera_at_section_end       # case 1: advance_camera_x_y
017803  func  move_camera_x_done               # case 2: advance_camera_y_scroll
017806  func  move_camera_x_done_at_end        # case 3: advance_camera_y
017809  func  move_camera_y_done               # case 4: advance_camera_x_scroll
01780C  func  move_camera_y_done_at_end        # case 5: advance_camera_x
01780F  func  move_camera_xy_done              # case 6: advance_scroll_position
017812  func  move_camera_all_done             # case 7: snap_camera_to_section_end
017815  func  advance_camera_scroll_x_y  # advance_scroll_position, advance_camera_x and advance_camera_y by speed: the scroll and the camera both go on
017828  func  advance_camera_x_y  # advance_camera_x and advance_camera_y by speed (scroll_position at the section's end)
017835  func  advance_camera_y_scroll  # advance_camera_y and advance_scroll_position by speed (the camera's x at its limit)
017842  func  advance_camera_y                  # camera_y += section_direction_y * speed
017850  func  advance_camera_x_scroll  # advance_camera_x and advance_scroll_position by speed (the camera's y at its limit)
01785D  func  advance_camera_x                  # camera_x += section_direction_x * speed
01786B  func  advance_scroll_position          # scroll_position += speed
017871  func  snap_camera_to_section_end        # camera at the section's x/y limits, scroll_position = the section end
0176E5  func  update_camera_view               # camera_x_pixels/_y_pixels from camera_x/_y; view_x/_y = those minus view_offset_x/_y
017707  func  update_camera_view_y  # camera_y_pixels from y's whole part (16.16) and view_y = it minus view_offset_y, which it hands back
0175F6  func  update_camera_with_shake          # update_camera_view; while camera_shake_active, every other frame adds sine * 20 to camera y (shake from 8 pixels down to 0)
017690  func  set_nbg0_scroll_from_camera      # NBG0 scroll = camera pixels; tile_block_row/tile_block_column for the block map
015F3B  func  clamp_camera_pixels              # x to 0..1FFF, y to 0..FFF

20E435  var   scroll_position       u16        # distance along the stage's camera path; spawn lists, stage events, restart points, section ends and the goal are keyed by it (advance_scroll_position)
20E42F  var   scroll_speed          u16        # per frame; stage_section_speeds per section (modes 3/4 ramp it, events set it)
20E44B  var   scroll_mode           u16        # update_scroll: 0 scroll, 1 stop, 2 wait, 3 slow down, 4 speed up, 5/6 lock/unlock nearby floors, 7/8 follow the players
20E445  var   scroll_mode_timer     u16
20E44D  var   scroll_mode_duration  u16        # reload value of scroll_mode_timer
20E447  var   scroll_mode_script    u32 scroll_mode_step     # pointer into stage_scroll_modes: (mode, duration) pairs, one per section
20E44F  var   scroll_ramp_speed     u8         # scroll mode 4's speed, 1 up to scroll_speed
20E42C  var   scroll_end            u16        # stage_scroll_ends[stage]; update_scroll stops scrolling there
209074  var   scroll_ended          s8         # FF once scroll_position reached scroll_end: scrolling and tile streaming stop
209081  var   follow_base_speed     u16        # stage_section_follow_speeds per section; added to the leading player's speed in scroll modes 7/8
20E40C  var   camera_x              s32        # 16.16, map coordinates (the projected plane, not ground x)
20E410  var   camera_y              s32
20E443  var   camera_x_pixels       u16        # camera_x >> 16 (clamped 0-1FFF)
20E441  var   camera_y_pixels       u16        # camera_y >> 16 (clamped 0-FFF)
20E404  var   view_x                s16        # camera_x_pixels - view_offset_x: an object's screen x is its map x (2A) - view_x
20E406  var   view_y                s16
20E43D  var   view_offset_x         u16        # stage_view_offsets_x[stage]
20E43F  var   view_offset_y         u16        # stage_view_offsets_y[stage]
20E45A  var   camera_origin_x       u16        # stage_camera_origins_x[stage], added to the section x limits
20E45C  var   camera_origin_y       u16
20E414  var   section_direction_x   s32        # per unit of speed (16.16): camera_x += it * speed; stage_section_directions_x
20E418  var   section_direction_y   s32
20E41C  var   section_direction_x_list u32 section_directions  # the next section's entry of stage_section_directions_x (advance_scroll 016B10 "movs.hw [R1+], R4")
20E420  var   section_direction_y_list u32 section_directions  # the next section's entry of stage_section_directions_y (advance_scroll 016B13 "movs.hw [R0+], R2")
20E424  var   section_limit_x       s16        # camera x (minus camera_origin_x) where the section stops moving in x  (TYPE: move_camera_along_path compares it signed, 2 of 2 reads)
20E426  var   section_limit_x_list  u32 section_camera_limits
20E437  var   section_limit_y       s16  # (TYPE: move_camera_along_path compares it signed; update_scroll and update_stage_00_exit_scene set FFFF)
20E439  var   section_limit_y_list  u32 section_camera_limits
20E45E  var   section_end_list      u32 section_end_positions     # pointer to the current section's end position (stage_section_ends)
20E431  var   section_speed_list    u32 section_scroll_speeds  # the next section's entry of stage_section_speeds (advance_scroll 016AAD "movz.bh [R1+], 642F[R25]")
209023  var   camera_limits_reached u8         # move_camera_along_path: bit 0 section end, bit 1 x limit, bit 2 y limit
20902F  var   camera_velocity_x     s32        # section_direction_x * speed (16.16); player code moves players with the scroll
209033  var   camera_velocity_y     s32
209066  var   camera_shake_active   u8         # set by heavy objects and stage events; update_camera_with_shake clears it at the end
209062  var   camera_shake_step     u16        # += A each frame up to camera_shake_length
209067  var   camera_shake_length   u16
209069  var   camera_shake_wave     u16        # offset into sine_table (200 = cosine)
209064  var   camera_shake_frame    u8         # odd frames add the shake
20905E  var   camera_shake_base_y   u32
209041  var   push_edge_top         u16        # screen y; set_screen_push_edges, is_player_at_top_edge
209043  var   push_edge_bottom      u16        # screen y; is_player_at_bottom_edge
209045  var   push_edge_right       u16        # screen x; is_player_at_right_edge
209047  var   push_edge_left        u16        # screen x; is_player_at_left_edge
209049  var   push_edge_case        u8         # 1-8 from the section direction; is_player_pushing_edge switches on it
20904A  var   players_pushing_edge  u8         # bit per player
20904B  var   players_facing_scroll u8         # bit per player facing the scroll (the four bytes from here are also cleared and tested as one word)
20904C  var   players_in_play_bits  u8         # bit per player in play
20904D  var   player_at_trailing_edge u8       # someone at the trailing edge (is_player_pushing_edge)
20904E  var   player_past_leading_edge u8      # someone 10 past a leading edge

0196EC  table stage_scroll_ends     u16        # [stage] -> scroll_end
01975A  table stage_camera_origins_x s16
019780  table stage_camera_origins_y s16
0197A6  table stage_view_offsets_x  u16
0197CC  table stage_view_offsets_y  u16
0193E0  table stage_section_ends    u16        # 19 offsets from 193E0 to u16 lists of section end positions (scroll units)
01A162  table stage_section_speeds  u16        # 19 offsets from 1A162 to u8 lists: scroll_speed per section
01A565  table stage_section_follow_speeds u16  # 19 offsets from 1A565 to u8 lists: follow_base_speed per section
018DFA  table stage_section_limits_x u16       # 19 offsets from 18DFA to u16 lists (camera x limits)
018E20  table stage_section_limits_y u16
01884A  table stage_section_directions_x u16   # 19 offsets from 1884A to s16 lists
018870  table stage_section_directions_y u16
0197F2  table stage_scroll_modes    u16        # 19 offsets from 197F2 to (mode, duration) pairs, one per section
01A128  table stage_fast_scroll_ranges u16     # 19 offsets from 1A128 to u16 positions, FFFF ends (update_background_scroll)
01A667  table stage_section_unused  u32        # [stage] -> 20E488, which advance_scroll steps by 2 per section and nothing reads; only entries 0-2 are pointers, the rest are the bytes that follow (nothing reads them)
elements section_scroll_speeds u8   # a stage_section_speeds list: scroll_speed per section, a byte each (init_stage_view 015BBB, advance_scroll 016AAD, load_camera_start_section 01AA51 "movz.bh [R1+], 642F[R25]"); census ROM 01A188-01A564 (2-20)
elements section_follow_speeds u8   # a stage_section_follow_speeds list: follow_base_speed per section, a byte each (015BD5, 016ABD, 01AA6E "movz.bh [R1+], 1081[R25]"); census ROM 01A5BD-01A666 (0-FF)
elements section_camera_limits s16  # a stage_section_limits_x or _y list: the camera's x (or y), minus camera_origin, where each section starts, the next where it stops (section_limit_x/_y): init_stage_view and advance_scroll "mov.h [R1+], 6424[R25]" (015C17, 016ADD), load_camera_start_section the start section's (01A997 "movz.hw [R1+], R6", << 10: camera_x), move_camera_along_path compares the two before the cursor signed ("cmp.h -4[R1], -2[R1]", 017743-017750), snap_camera_to_section_end the one before (017878); census ROM 018E46-0193DF (-1880 to 1C70)
elements section_directions s16     # a stage_section_directions_x or _y list: the section's direction per unit of speed (4000 straight along the axis): "movs.hw [R1+], R3" (init_stage_view 015C8B, advance_scroll 016B10, load_camera_start_section 01AA05); census ROM 018896-018DF9 (-4000 to 4000)
elements section_end_positions u16  # a stage_section_ends list: the scroll position where each section ends (the first is the stage's start), FFFF after the last: advance_scroll "movz.hw [R1], R1" then cmp.w (016A94: zero-extended), update_background_scroll "movz.hw -2[R0], R0" (016B9E: the current section's start), move_camera_along_path, snap_camera_to_section_end, load_camera_start_section 01AA2D; census ROM 019406-0196EB
struct scroll_mode_step 4 rom          # a stage_scroll_modes record: the (mode, duration) a section gives scroll_mode / scroll_mode_timer: init_stage_view (015C4E, 015C54), update_scroll (016950...), advance_scroll (016AED, 016AF3) and load_camera_start_section (01AA8E, 01AA94) read "mov.h [R2+], 644B[R25]; mov.h [R2+], 6445[R25]"; update_player_drop_in tests the previous one's mode (001ADA "test.h -4[6447[R25]]"); census ROM 019818-019F47 (modes 0, 2, 3, 7)
field scroll_mode_step 00 mode u16      # into scroll_mode (update_scroll's switch: 0 scroll, 2 wait, 3 slow down, 7 follow the players...)
field scroll_mode_step 02 duration u16  # into scroll_mode_timer and scroll_mode_duration (up to 18 in the ROM)
elements fast_scroll_starts u16     # a stage_fast_scroll_ranges list: scroll positions, FFFF ends: update_background_scroll "cmp.h #FFFF, [R10]" / "movz.hw [R10+], R1" (016BA2-016BAB, zero-extended, then cmp.w); census ROM 01A150-01A161 (FFFF, 4E00 5300 9200 92E8)
elements scroll_quadrant_sines s16  # sine_table's entries (0x4000 = 1.0) from the quadrant set_screen_push_edges picks: is_direction_in_quadrant compares two in a row with section_direction_y signed (01690D "mov.h [R11], R6", 016910 "mov.h 2[R11], R7", cmp.h + blt) and moves on by 2; census ROM 101632-101DE4
elements stage_start_tile_sets u32  # a stage_initial_tile_sets list: the four tile set numbers init_stage_view loads at the stage's start (0159FE "mov.w [R2+], R7": load_tile_set's tile_set); census ROM 01973A-019749 (15 16 18 19 for stage 0, 0 1 2 3 for the others)
elements block_tile_sets u8         # a stage_block_maps map: the tile set of each 512 x 256 block of the stage map, rows of stage_block_map_widths[stage] bytes: update_stage_tile_layers reads the 2 x 2 under the camera (015D6F "mov.b [R10+], R1"); census ROM 017C82-0188C1
elements tile_set_vram_scripts u32  # a tile set's list in stage_tile_sets: the address of each vram script load_tile_set runs, a negative one ends (015E63 "mov.w [R6+], R10"; test.w + blt); census ROM 140910-170D0C
elements stage_section_list_offsets u16 section_values@  # one of the per-stage tables scroll_skip_value_lists points at (stage_section_limits_x, _y, stage_section_ends): the stage's offset from the table to its list (scroll_skip_draw_values 01AB0E "movz.hw [R0], R0" after "add.h R5, R0", R5 = stage x 2); census ROM 018DFA-018E1F, 018E20-018E45, 0193E0-019405
elements section_values u16                # a stage's section list as scroll_skip_draw_values prints it (one of stage_section_limits_x/_y, a signed limit a section, or stage_section_ends, an end position): the value of the start section, at the list's offset + 2 x it

# ======== Tile layers (video RAM 300000-31FFFF) ========
# The stage map is split in blocks (512 x 256 pixels of the map); stage_block_maps gives each block a tile
# set. update_stage_tile_layers reads the 2 x 2 blocks around the camera; when a set changes it writes that
# set's tilemap pages into video RAM with run_vram_script. NBG0 holds the stage (scrolled with the camera),
# NBG1-NBG3 the background layers, scrolled per stage by update_background_scroll.
015D1E  func  update_stage_tile_layers         # animate_stage_palette; block sets around the camera from stage_block_maps; reload_changed_tile_set when they change
015DD7  func  reload_changed_tile_set          # one slot a frame: when the wanted set is not loaded yet, load_tile_set
015E4D  func  load_tile_set                    # stage_tile_sets[stage][set]: list of vram scripts (FFFFFFFF ends) run with run_vram_script
015E79  func  get_tile_set_script_bank         # +100000 for the stages that keep their scripts in the second half
015EB4  func  get_tile_set_list_bank  # Hands back script_list + 0x100000 for stages 3-0x10 and 0x13, unchanged for stages 0-2, 0x11 and 0x12
015EEE  func  get_tile_set_list                # stages 0-2, 11-13: u16 offsets + 170602; the others u32 pointers
01B1D4  func  set_stage_layer_mixing           # mixer 610022-610028 = stage_layer_priorities + 200; 31FF02 low nibble = stage_layer_disables; 31FF04 = stage_rowscroll_settings
01B2C0  func  init_nbg1_layer                  # four vram scripts (170722-17076A), NBG1 pages 0F-12, per-stage mixer bits
01B404  func  init_nbg2_layer                  # four vram scripts (1706C2-17070A), NBG2 pages 0A-0D; stages 1/2: sine row scroll tables at 30A000/30A800
01B622  func  init_nbg3_layer                  # per-stage vram scripts and NBG3 pages; stage_nbg3_widths
01B868  func  set_nbg3_page_5_flags            # 20F2C0 = A, palette 608000, sets bit 13 of the 400 cells at 301400 (tile pages 5 and 6)
01B388  func  update_nbg1_scroll               # jump table by stage
01B3D1  func  skip_nbg1_scroll  # Does nothing: update_nbg1_scroll's case for the stages whose NBG1 stays put, and its common end
01B3D2  func  set_nbg1_scroll_to_camera  # NBG1's scroll = the camera's position in whole pixels (camera_x_pixels, camera_y_pixels)
01B515  func  update_nbg2_scroll               # jump table by stage
01B58F  func  skip_nbg2_scroll  # Does nothing: update_nbg2_scroll's case for the stages whose NBG2 stays put, and its common end
01B55E  func  set_nbg2_scroll_to_camera  # NBG2's scroll = the camera's position in whole pixels (camera_x_pixels, camera_y_pixels)
01B590  func  set_nbg2_scroll_to_camera_2  # set_nbg2_scroll_to_camera again: a second copy, for stages 1 and 2
01B5A5  func  set_nbg2_scroll_stages_03_07     # camera; +10 once scroll_position passes 9159
01B5F9  func  set_nbg2_scroll_stage_04  # Stage 4: NBG2 on pages 0xA-0xD, scrolled with the camera (the original adds 0x20 to x and takes it off again)
01B891  func  update_nbg3_scroll               # per-stage parallax (view/2, view/4, view*3/4, camera)
01B9A5  func  select_nbg3_pages                # NBG3 page numbers from the blocks under its scroll (stage_nbg3_block_maps)
01BAA3  func  get_nbg3_block  # The NBG3 block under its scroll: nbg3_block_column = nbg3_scroll_x >> 9 (blocks of 0x200 pixels), nbg3_block_row = (nbg3_scroll_y + 0x400) >> 8...
01B096  func  init_nbg0_picture_screen         # display off, 31FF00 bit 10, palette set 1076AE, only NBG0 and text on (31FF02 = 2E), NBG0 scroll 0
01B112  func  show_nbg0_picture                # init_nbg0_picture_screen, NBG0 pages 1, vram script 17280A, palette row 1B154 (called by 08F22D and 08F9AB)

20E400  var   tile_block_row        u16        # (camera_y_pixels >> 8) * stage_block_map_width
20E402  var   tile_block_column     u16        # camera_x_pixels >> 9
20E408  var   wanted_tile_sets      u32        # the four sets (one byte each) the blocks around the camera want
20903B  var   loaded_tile_sets      u32        # the four sets in video RAM
20903F  var   tile_set_slot         u16        # reload_changed_tile_set checks one slot of wanted_tile_sets a frame (loading it when it is not among the loaded sets); 0 again when the wanted sets change
20904F  var   tile_sets_in_progress u32        # the wanted sets reload_changed_tile_set is working through; loaded_tile_sets takes them after the fourth slot
20E48C  var   stage_block_map_width u16        # stage_block_map_widths[stage]
20E48E  var   nbg3_block_map_width  u16
20E456  var   nbg3_block_column     u16
20E458  var   nbg3_block_row        u16
20E486  var   row_scroll_wave_step  u16        # init_nbg2_layer, stages 1/2

017C49  table stage_block_map_widths u8
017C5C  table stage_block_maps      u16        # 19 offsets from 17C5C to u8 per block: tile set
019712  table stage_initial_tile_sets u16      # 19 offsets from 19712 to four u32 (init_stage_view loads them)
01A695  table stage_tile_sets       u32        # [stage] -> list of vram script lists per tile set
01B231  table stage_layer_priorities u8        # 4 per stage (NBG0-NBG3)
elements nbg_layer_priorities u8          # a stage's 4 bytes of stage_layer_priorities, NBG0-NBG3: set_stage_layer_mixing "movz.bh [R1+], R2" (01B1EC), + 200 into the mixer registers 610022-610028; census ROM 01B231-01B27C (0B 0C 0E 07, 0C 07 0D 07...)
01B27D  table stage_layer_disables  u8
01B290  table stage_rowscroll_settings u16
01BADA  table stage_nbg3_widths     u8
01BAED  table stage_nbg3_block_maps u32
elements nbg3_block_pages u8              # a stage's NBG3 block map (stage_nbg3_block_maps' pointers): a page byte per block, rows of nbg3_block_map_width: select_nbg3_pages "movz.bw [R10+], R2" (01BA21); census ROM 01BB3D-01BCAE
elements nbg3_quadrants u8                # nbg3_quadrant_grid's bytes (3 x 3: 00 01 00 / 02 03 02 / 00 01 00): select_nbg3_pages "movz.bw [R11+], R3" (01BA51), << 3 into each page; census ROM 01BCBC-01BCC4

# The vram script interpreter: bytes 00-7F write word [n] of the dictionary at 20F300; 80-AA are commands
# (jump table of 3-byte entries at 09988E); AA ends.
09986B  func  run_vram_script  # Runs a VRAM script from script, writing tilemap cells at dest: a byte 0x00-0x7F writes that word of vram_script_dictionary; 0x80-0xA7 are the...
09988E  func  vram_script_set_address_entry  # run_vram_script's table entry: a br to vram_script_set_address (the C calls vram_script_set_address in the switch)
099891  func  vram_script_skip_entry  # run_vram_script's table entry: a br to vram_script_skip (the C calls vram_script_skip in the switch)
099894  func  vram_script_fill_entry  # run_vram_script's table entry: a br to vram_script_fill (the C calls vram_script_fill in the switch)
099897  func  vram_script_count_up_entry  # run_vram_script's table entry: a br to vram_script_count_up (the C calls vram_script_count_up in the switch)
09989A  func  vram_script_count_down_entry  # run_vram_script's table entry: a br to vram_script_count_down (the C calls vram_script_count_down in the switch)
09989D  func  vram_script_bytes_entry  # run_vram_script's table entry: a br to vram_script_bytes (the C calls vram_script_bytes in the switch)
0998A0  func  vram_script_words_entry  # run_vram_script's table entry: a br to vram_script_words (the C calls vram_script_words in the switch)
0998A3  func  vram_script_words_to_dictionary_entry  # run_vram_script's table entry: a br to vram_script_words_to_dictionary (the C calls vram_script_words_to_dictionary in the switch)
0998A6  func  vram_script_clear_pages_entry  # run_vram_script's table entry: a br to vram_script_clear_pages (the C calls vram_script_clear_pages in the switch)
0998A9  func  vram_script_fill_down_entry  # run_vram_script's table entry: a br to vram_script_fill_down (the C calls vram_script_fill_down in the switch)
0998AC  func  vram_script_fill_down_wide_entry  # run_vram_script's table entry: a br to vram_script_fill_down_wide (the C calls vram_script_fill_down_wide in the switch)
0998AF  func  vram_script_fill_up_entry  # run_vram_script's table entry: a br to vram_script_fill_up (the C calls vram_script_fill_up in the switch)
0998B2  func  vram_script_repeat_entry  # run_vram_script's table entry: a br to vram_script_repeat (the C calls vram_script_repeat in the switch)
0998B5  func  vram_script_skip_long_entry  # run_vram_script's table entry: a br to vram_script_skip_long (the C calls vram_script_skip_long in the switch)
0998BC  func  vram_script_set_address          # 80: destination = 300000 + word * 2
0998C8  func  vram_script_skip                 # 83: destination += s8 * 2
0998D0  func  vram_script_skip_long            # A7: destination += s16 * 2
0998D8  func  vram_script_fill                 # 86: count, word: the same word count times
0998E6  func  vram_script_count_up             # 89: count, word: word, word+1, ...
0998F6  func  vram_script_count_down           # 8C: count, word: word, word-1, ...
099906  func  vram_script_bytes                # 8F: count, high byte, then count low bytes
09991A  func  vram_script_words                # 92: count, then count words
099926  func  vram_script_words_to_dictionary  # 95: like 92, also stores the words in the dictionary at 20F300
09993A  func  vram_script_clear_pages          # 98: address word, page count: clears count * 200 words
099955  func  vram_script_fill_down            # 9B: count, word: down a column (+40)
099967  func  vram_script_fill_up              # A1: count, word: up a column (-40)
099979  func  vram_script_fill_down_wide       # 9E: count, word: down a column of a 64-cell map (+80)
09998C  func  vram_script_repeat               # A4: count, word: writes the word count times to the same address

# ======== Palettes animated per stage ========
01AD30  func  animate_stage_palette            # jump table by stage (20 entries of 3 bytes at 01AD41)
01AD7A  func  skip_stage_palette_animation  # Does nothing: animate_stage_palette's case for the stages without a palette animation
01AF10  func  cycle_palette                    # state (delay, frames, -, frame, timer): every delay frames copies the next 16-colour frame to palette RAM
01AEE6  func  animate_palette_stage_04  # Stage 4's palette animation: palette_cycle_0 every 4 frames through the 8 frames of stage_04_cycle_palettes, each copied to 2 rows at palette RAM...
01AEBC  func  animate_palette_stage_09  # Stage 9's palette animation: palette_cycle_0 every 3 frames through the 7 frames of stage_09_cycle_palettes, 2 rows at palette RAM + 0x88A0
01AE6D  func  animate_palette_stage_0a  # Stage 0xA's palette animation: palette_cycle_0 every 5 frames through the 6 frames of stage_0a_cycle_palettes_0 (1 row at palette RAM + 0x8A40),...
01ADCA  func  animate_palette_stage_0c  # Stage 0xC's palette animation: palette_cycle_0 every 6 frames through the 8 frames of stage_0c_cycle_palettes, 1 row at palette RAM + 0x8800
01ADF4  func  animate_palette_stage_0d_0e  # The palette animation of stages 0xD and 0xE: palette_cycle_0 every 6 frames through the 8 frames of stage_0d_0e_cycle_palettes, 1 row at palette...
01AE1E  func  animate_palette_stage_0f  # Stage 0xF's palette animation: palette_cycle_0 every 0xC frames through the 6 frames of stage_0f_cycle_palettes_0 (3 rows at palette RAM +...
01AD7B  func  animate_palette_stage_10  # Stage 0x10's palette animation: palette_cycle_0 every 6 frames through the 8 frames of stage_10_cycle_palettes_0 (6 rows at palette RAM + 0x8B40),...
01AF4D  func  cycle_palette_set_0              # update_stage_06_background
01AF5B  func  cycle_palette_set_1  # cycle_palette_set on the second record (palette_cycle_set = 0x2090A0), for update_stage_06_background
01AF69  func  cycle_palette_set                # like cycle_palette, state at palette_cycle_set
01AFD7  func  load_stage_palette_set           # load_palette_set_and_backdrop for the stage
01AFDC  func  load_palette_set_and_backdrop    # 31FF00 bit 10 (tile banking) from stage_tile_bank_bits[R1], palette set stage_palette_sets[R1] through 09A4BC, backdrop colour (609400) by stage
20CA00  var   palette_cycle_0       u8         # 5-byte cycle_palette state
20CA05  var   palette_cycle_1       u8

# ======== Video register shadows (copied by 099531 to 31FF10-31FF4F a frame later) ========
20F280  var   nbg0_scroll_x         u32        # 16.16 -> 31FF10/31FF12
20F282  var   nbg0_scroll_x_int     u16        # the whole part of nbg0_scroll_x
20F284  var   nbg0_scroll_y         u32        # -> 31FF14/31FF16
20F286  var   nbg0_scroll_y_int     u16        # the whole part of nbg0_scroll_y
20F288  var   nbg1_scroll_x         u32        # -> 31FF18/31FF1A
20F28A  var   nbg1_scroll_x_int     u16
20F28C  var   nbg1_scroll_y         u32
20F28E  var   nbg1_scroll_y_int     u16
20F290  var   nbg2_scroll_x_fraction u16
20F292  var   nbg2_scroll_x         u16        # -> 31FF22
20F294  var   nbg2_scroll_y_fraction u16
20F296  var   nbg2_scroll_y         u16        # -> 31FF26
20F29A  var   nbg3_scroll_x         u16        # -> 31FF2A
20F29E  var   nbg3_scroll_y         u16        # -> 31FF2E
20F2A0  var   nbg0_center_x         u16        # -> 31FF30 (zoom centre / offset)
20F2B0  var   nbg0_pages            u32        # -> 31FF40: four page numbers
20F2B4  var   nbg1_pages            u32
20F2B8  var   nbg2_pages            u32
20F2BC  var   nbg3_pages            u32
20F1C0  var   mixer_text_blend      u16        # first of the 0x10 words irq_vblank_start copies to 610030 (the mixer's blend controls: text, NBG0-3, bitmap...); bit 14 picks the colour offset
20F1C2  var   mixer_nbg0_blend      u16        # -> 610032
31FF00  io    video_control         u16        # MAME segas32_v: screen width, bitmap format, layer flips
31FF02  io    video_layer_disable   u16        # bits 0-3 NBG0-3, 4 text, 5 bitmap; clipping modes in the high byte
31FF04  io    video_rowscroll_control u16
31FF50  io    nbg0_zoom_x           u16        # 200 = 1.0
31FF52  io    nbg0_zoom_y           u16
31FF54  io    nbg1_zoom_x           u16
31FF56  io    nbg1_zoom_y           u16
31FF40  io    nbg0_page_select      u32
31FF5E  io    video_backdrop_control u16

# ======== Stage map collision: floors ========
# floor_blocks records (22 bytes): x min, x max, y min, y max (s32, 16.16), height (u16 at +10), surface
# (u8 at +12, index into floor_types), slope (u8 at +13, index into floor_slopes), +14, +15.
20902E  var   nearby_floor_blocks_locked u8    # scroll mode 5 sets it, 6 clears it; find_nearby_floor_blocks does nothing while set

# ======== Object coordinates (fields of struct object) ========

# ======== Stage spawners ========
# Every spawner walks the current stage's list (sorted by scroll position) and starts the objects whose
# position scroll_position has reached; the handler, ring row, wall and group spawners skip records
# passed by 100 or more, spawn_objects_by_type starts every passed record (one a frame). Their list
# pointers live in 20F0xx and are set the first time each spawner runs in a stage (stage_init_flags).
090CFA  func  spawn_walls                      # wall_spawns: walls (handler 090E49 in wall_list, which players and objects bounce off); the kind (wall_kinds) gives the box size and, for the visible ones, a strip of sprite parts (in stage 3 the first two have none)
0916F1  func  spawn_group_leader               # group_leaders[n & 3] = a new object with handler 09179A (09177D while 20E654/20E652)
091F1B  func  spawn_group_slot_trigger         # handler group_slot_trigger_update (091F63, stage 8 lava rocks); clears group_slots
087EDE  func  spawn_ice_platform_leader        # handler ice_platform_leader_move (087F5B, stage 9); 20E5F6 = it
089EB0  func  spawn_sand_block_leader          # handler sand_block_leader_move (089F55, stage A; sand_block_leader_wait 089F3B while 20E652)
08CC66  func  spawn_waterfall                  # handler waterfall_move (08CD1A, stage 10) with a 0D-word part copied from waterfall_parts (08D97E)
091122  func  build_wall_parts                 # second-pool parts from wall_kinds: per part a sprite and an x/y step
20E562  var   group_leaders         u16 object        # four object addresses (spawn_group_leader); group members keep theirs in field 26
209F00  var   group_slots           u16        # spawn_object_groups uses 40 words (1 taken, 8000 | delay once group_slot_trigger_update fires it; a group with flag 20E5A8 takes a free one, its index in member field 8A); spawn_object_groups_2 and rope_bridge_plank_update use the area as bytes
0903A2  table wall_spawns            u16      # 19 offsets from 0903A2 to 14-byte records: position, removal position (field 82), x, y, z, kind (offset into wall_kinds), part count, flags; FFFF ends
090A72  data  wall_kinds                       # 20-byte records by kind: bytes 0-5 offsets and direction, then from +6 box size (fields 10/12, negative = per part), palette, part step x/y, first part sprite (negative: no parts), second part step and sprite
093539  data  group_slot_triggers              # group_slot_trigger_update: (scroll position, slot, delay) records; slot 8000 sets goal_unlocked, FFFF ends
09357D  data  group_leader_data                # leader field 48 = 09357D + offset from the record

# ======== Object library used by the stage objects (08DFBD-08E7FD, 01EEC2-01F18C) ========
08E472  func  explode_object                   # sound 7, handler explosion_animate
08E49E  func  explosion_animate                # frames 1F-2B from explosion_frames, the next one every other frame (drawn every frame), then removed
08E61B  func  released_ring_wait               # field 84 frames, then sound 13 and released_ring_start
08E639  func  released_ring_start              # not collectable for field 7E frames
08E650  func  released_ring_bounce             # collectable (sound 13 when a player touches it) until field 80 runs out, then scattered_ring_vanish
08E6BE  func  released_ring_move               # bounces with move_object_projected / fall_to_floor, draws the ring and its shadow
08E4D2  table explosion_frames      u16        # sprite words (field 04) by frame
208030  var   released_ring_angle   u16
208032  var   rings_to_release      u16        # drop_rings
208034  var   released_ring_flip    u8
208035  var   released_rings_random u8

# ======== Rings (stage_object_templates, handlers 01E4ED-01EB11) ========
01E4ED  func  ring_spin                        # animates, a glint every loop of the spin (spawn_ring_glint); touched (flag 4): sound 13, spawn_ring_sparkle, removed, unless the toucher is a player (kind 0-2) in state D or 20 with field C3 set; the player touch code counts the ring (kind 3)
01E5AD  func  still_ring_wait                  # template 2 (no stage uses it): no animation; touched: sound 13, removed
01E5FA  func  carried_ring_wait                # templates 1/3: waits until it is on a platform (flag 8 in 30), then carried_ring_spin
01E61F  func  carried_ring_spin                # ring_spin following its platform's floor and height
01EA86  func  spawn_ring_sparkle               # animation 01F5CE
01EAA4  func  spawn_ring_glint                 # animation 01F5AC
01EAC1  func  init_sparkle_object  # Sets up the new sparkle or glint sparkle at the current object's x, y, z and floor_z: sprite_flags 0x1000, palette 0x1B0, flags 0, handler...
01EB04  func  sparkle_start  # First frame of a sparkle or glint: projects it to the screen, then becomes sparkle_animate
01EB11  func  sparkle_animate                  # removed at the end of its animation
01F56E  data  ring_animation

# ======== Signs (object_kinds 084FB5: z, sprite, duration, animation, handler) ========
# "GO!", "BREAK!!" and the arrow were seen with a sprite render of s32ref dumps (stage 5 cage, stage 8 start).
084B91  func  sign_start                       # stage_spawns_by_handler entries whose field 04 is the kind: init_sign_from_kind
084BAE  func  spawn_sign                       # (R5, R6, R8) = x, y, z; R7 = kind
084BBA  func  spawn_sign_2                     # the same with allocate_object
084BC4  func  place_sign  # Puts the object sign at x, y, z and sets it up from record kind of object_kinds, without the record's own z (init_sign_from_kind); hands back sign...
084BEC  func  init_sign_from_kind              # object_kinds record: sprite 04, duration in speed_x, animation 80, handler
084CC6  func  sign_show                        # animate_sign, counts speed_x down, drawn on top (bucket 0); removed at 0
084C43  func  sign_show_unless_mode_32  # sign_show, except in game mode 0x20 (YOU FAILED), where the sign is removed
084C52  func  sign_show_while_player_busy      # while the parent's player is in state 1D, 2C or 32
084C82  func  sign_show_until_goal_unlocked    # "BREAK!!" (stage 5): until goal_unlocked is set (game mode 0x0A sets it in stage 5)
084C8E  func  sign_show_while_parent_still     # until the parent's field 3E is non-zero
084CA0  func  sign_show_until_gate_closed  # sign_show until sign_stop_flag is set, then removed (only the unused exit gate sets the flag)
084CAC  func  sign_show_until_cage_bar_hit     # the arrow over "BREAK!!"
084CB8  func  sign_show_unless_stage_0  # sign_show, except in stage 0, where the sign is removed
084CE6  func  sign_follow_parent               # 38 above the parent while its flag 40
084D2B  func  sign_follow_player               # 28 above the parent while its player is in state 22
00CB15  func  animate_sign                     # palette (field 06) from the animation list at field 80, every field 85 frames

# ======== Object contacts (the lists every stage frame checks) ========
# Objects join these lists again every frame (they are emptied after the checks).
0022A6  func  add_to_wall_list                 # list wall_list (link 72): the walls of spawn_walls (handler 090E49)
0021F9  func  compute_bridge_sag               # sorts the bridge's load list (the planks with someone on them, between the anchored ends 0 and count + 1); each entry's sag is bridge_sag_curve[min(n, count - n)] (its distance from the nearer end) << 8, with straight lines between consecutive entries in the per-plank sag table (209450/209550); with no plank loaded it clears the table
0022BA  func  check_walls                      # rider_list objects inside a wall's box: flag 20 in 30 and field 78 = the wall, both ways
20F0AE  var   wall_list             u16 object
209400  var   bridge_0_load         u8         # entry count, then 0, bridge_0_plank_count and the numbers (field 6F) of the planks with someone on them; compute_bridge_sag writes the sag per plank to 209450
209500  var   bridge_1_load         u8         # the same for planks with field 95 set (rows of kind 3 in stage_record_spawns); sag at 209550
20F0B0  var   bridge_0_plank_count  u16        # spawn_object_groups_2 adds the rows of kinds 0/2; the bridge's last plank clears it when it falls (no more sag)
20E542  var   bridge_1_plank_count  u16        # spawn_object_groups_2 adds the rows of kind 3, and a second kind 0 group when bridge 0 is taken (stage 10)
002471  data  bridge_sag_curve                 # 21 bytes 00 03 06 ... 3E: the sag under a loaded plank by its distance from the nearer end of the bridge (compute_bridge_sag)
elements bridge_sag_depths u8            # bridge_sag_curve's bytes, by a plank's distance from the nearer end: compute_bridge_sag "movz.bh [R11](R0), R4" (002258) and the next load's (00226x), << 8; census ROM 002471-002491 (00 03 06 ... 32 33 ... 3E)

# ======== Stage clear (game mode 0x17), the ring tally (0x1D) and the map (4) ========
# After check_stage_clear: mode 0x17 shows the stage name and CLEARED banners (seen with the renderer at
# the end of stage 3 in walk_03: "TRAP TOWER" / "CLEARED"), 14A frames; mode 0x1D the ring tally ("TOTAL
# RINGS nn %", one panel per player); mode 4 the island map with the next stage (095976); then mode 2.
# Stages 0B-0F go to mode 0x1E instead (a fade straight into the next stage, no tally or map).
001664  func  cleared_banner_wait              # the second banner (CLEARED): waits for stage_name_shown, then stage_clear_banner_reveal
001674  func  stage_clear_banner_reveal        # animates the banner in, a sparkle at each step (spawn_banner_sparkle); at the end sets stage_name_shown (first) or cleared_shown (second)
0016B6  func  stage_clear_banner_hold          # drawn until banner_break: sound 1B and the banner splits into its letters (handler banner_letter_spin)
00175E  func  spawn_banner_sparkle             # ring sparkle animation 01F5CE at the banner's x + R0
0017B6  func  banner_sparkle_animate           # removed at the end of its animation
0017D1  func  banner_letter_spin               # animation 279D; at frame 0C spawns a sparkle (27BD); removed at the end
20E61B  var   banner_break          u8         # FF once stage_clear_timer reaches 78
20E61C  var   stage_name_shown      u8
20E61D  var   cleared_shown         u8
20E61E  var   stage_clear_timer     u16        # game mode 0x17: 14A frames, then game mode 0x1D
0026F5  table cleared_banner_x      u16        # game mode 0x17: screen x of the CLEARED banner (its animation starts at 0026F9)
0026F7  table cleared_banner_y      u16
0983FE  func  start_ring_tally                 # game mode 0x1D: ring_tally_percent = sum of the players' rings (player field 13C) * 100 / stage_ring_counts[stage]; under stage_ring_bonus_percents[stage] no bonus, else pick_ring_bonus_players; one panel per player in play
0985CC  func  pick_ring_bonus_players          # keeps the bonus bit (100) only for the players with the most rings
098670  func  ring_tally_percent_update         # "TOTAL RINGS" and three digits rolling at random, then the percentage; blinks when it reaches the bonus; removed when stage_results_done
09887C  func  update_ring_tally_percent        # after 12C frames stage_results_done; colour cycling (ring_tally_blink_palettes) at or above the bonus
0988D0  func  ring_tally_end_sound_wait             # when the counting stops: sound 73 if the percentage is under the bonus
09891C  func  ring_tally_ticker_update            # sound 83 every 6 frames while counting
098954  func  ring_tally_player_1_update  # Player 1's panel: the first frame builds it at the left (build_ring_tally_panel) and starts the count; then update_ring_tally_panel
098981  func  ring_tally_player_2_update        # its place depends on the cabinet setting (20F41E bit 0)
098A00  func  ring_tally_player_3_update  # Player 3's panel: built on the right (screen x 0x100); then update_ring_tally_panel
098A77  func  build_ring_tally_panel           # portrait (ring_tally_portraits[R0]), "NO BONUS"/bonus label, ring icon and three digits from the second pool
098C2A  func  update_ring_tally_panel          # counts the player's rings up; the bonus: 00E22A, sounds (by character) and 88/89
098E44  func  cycle_ring_tally_backdrop_palette  # the ring tally panel's portrait backdrop (the concentric squares, sprite 9DD8 at 40 of build_ring_tally_panel): its palette steps through ring_tally_backdrop_palettes
20CA47  var   ring_tally_counted    u8         # every player panel finished counting
20CA49  var   ring_tally_percent    u16
20CA4B  var   ring_tally_panels_counting u8
09864E  table stage_ring_bonus_percents u8     # [stage]: the percentage the players must reach for the bonus
09865F  table stage_ring_counts     u8         # [stage]: rings in the stage (the divisor of the percentage); end of stage 3 in walk_03: 2 + 7 rings of 39 shows 23% (render)
098900  table ring_tally_blink_palettes u16
098E7D  table ring_tally_character_sounds u8
098E80  table ring_tally_backdrop_palettes u16  # the backdrop's palettes, cycle_ring_tally_backdrop_palette steps through them
098E8E  table ring_tally_digits     u16        # sprite words for 0-9
098EA2  data  ring_tally_portraits             # u32 per character: palette, sprite, then the bonus sprite
095976  func  run_map_screen                   # game mode 4: map_screen_step 0 window wipe around the stage (start_window_wipe), 1 fade, 2 map_intro_update, 3-5 wait; its caller game_mode_stage_end_fade goes to game mode 2 when the step reaches 5
095A53  func  map_intro_update                 # by animation_frame: step 0 adds 4, so steps 1-3 (the stage's icon from stage_map_icons flying to the corner) never run; 4 loads the island map (palettes 1076BC, map_monitor_script, map_eggman_head_script) and starts the fade; 6 fades in, then spawns map_screen_update (which spawns map_eggman_update); 7 removes it
095B0B  func  move_map_stage_icon              # to its target over 3C frames, blinking
095D31  func  draw_map_markers_and_trail  # draw_map_stage_markers, then start_map_trail and draw_map_trail_dot: the markers with the trail's first dot, the trail starting over at every call...
095DB2  func  map_screen_update                # the trail to the next stage, the icon blinking at the stage's marker, a wipe, the icon flying to the next stage (map_stage_positions); map_screen_step = 5
09617C  func  map_eggman_update                # the big Eggman seen from behind at the bottom (sprites 5760-57C8): gestures, stage_map_sounds_1/2/3 (stage 9 one more)
09628D  func  map_eggman_hands_update          # sprites 5798+
0964E5  func  start_map_trail                  # stage_map_trails[stage]
09650F  func  next_map_trail_segment  # Starts the trail's next segment, from the point at trail_point to the one after it (next_trail_point): base = start, target = end, steps = (base -...
096579  func  draw_map_trail_dot  # Draws the trail's dot for object_cursor at base + offset, moving it one step along the segment while trail_done is clear (sprite 0x64E0, palette...
09660B  func  draw_map_stage_markers           # the numbered markers (map_marker_layouts), cleared ones in their other palette
0966E7  func  blink_current_map_marker  # While step_timer (the map screen's timer) counts down, draws the stage's name over its marker (map_marker_blink_sprites for...
096761  func  start_window_wipe                # the bitmap layer as a black frame (30E000 filled with 1111) whose window moves between the record's edges over its frame count (update_screen_wipe)
2090D5  var   map_screen_step       u8
2090D4  var   window_wipe_closing   u8
2090D6  var   window_wipe_left      u16        # targets of update_screen_wipe
2090D8  var   window_wipe_right     u16
2090DA  var   window_wipe_top       u16
2090DC  var   window_wipe_bottom    u16
209100  var   map_stage_icon        u16        # sprite word of the stage icon: next_stage_map_icons (map_screen_update); map_intro_update reads stage_map_icons in a step that never runs
096A4B  data  map_wipe_in_windows              # 6 bytes per stage: x, y, frames (start_window_wipe)
096ABD  data  map_wipe_out_windows
096B2F  data  map_wipe_next_windows
096B9B  data  map_stage_positions              # 4 bytes per marker: x, y of the icon on the map
096BC7  data  map_marker_layouts               # 12 bytes per marker: priority, x, y, sprite, palette, cleared palette
096CB5  table stage_map_markers     u8         # [stage]: marker of the stage on the map (negative: none)
096CC8  table stage_map_trails      u32
096D87  table stage_map_icons       u16
096DA9  table next_stage_map_icons  u16
096DCB  table stage_map_sounds_1    u8
096DDD  table stage_map_sounds_2    u8
096DEF  table stage_map_sounds_3    u8

# ======== Leftovers of the stage view ========
20E5E3  var   goal_unlocked         u8         # check_player_reached_goal needs it in stage 4; set to FF by stage objects at their end (00FE46, 080BBC, 081217, 08924E, 08C1A4, 091F63; stage 9 counts it up to 3 in 088832/088624) and by game mode 0x0A
20E5ED  var   map_screen_active     u8         # FF every frame of game mode 4 (the map); map_intro_update's step 0 would also set it once its timer reached 1E, but that step runs once; start_stage clears it: stage objects (walls, scenery, rings, the tornado, wrecks...) remove themselves while it is set
20E655  var   chaser_stopped        u8         # FF when the chaser stops: fire_front_chase (stages B/C, at the path entry whose factor is 0), spiked_roller_smash_wall (stage E), biting_head_wait at scroll 1200; brick_wall_wait bursts and biting_head_wait starts on it; start_stage clears it

# ======== Per-stage jump tables (entries of 3 bytes indexed by stage; each entry only branches) ========
01B174  table stage_palette_sets   u32        # [R1]: palette set for 09A4BC (load_palette_set_and_backdrop)
01B1C0  table stage_tile_bank_bits u8         # [R1]: bit 10 of 31FF00 (load_palette_set_and_backdrop)
elements tile_bank_selects u8             # stage_tile_bank_bits' bytes, one per stage: load_palette_set_and_backdrop "movz.bh [R3], R3" (01AFEC), "shl.h #A" into video_control bit 10 (tile banking); census ROM 01B1C0-01B1D2 (0 or 1)
01AD41  func  animate_stage_palette_stage_00   # stage 00: skip_stage_palette_animation
01AD44  func  animate_stage_palette_stage_01   # stage 01: skip_stage_palette_animation
01AD47  func  animate_stage_palette_stage_02   # stage 02: skip_stage_palette_animation
01AD4A  func  animate_stage_palette_stage_03   # stage 03: skip_stage_palette_animation
01AD4D  func  animate_stage_palette_stage_04   # stage 04: animate_palette_stage_04
01AD50  func  animate_stage_palette_stage_05   # stage 05: skip_stage_palette_animation
01AD53  func  animate_stage_palette_stage_06   # stage 06: skip_stage_palette_animation
01AD56  func  animate_stage_palette_stage_07   # stage 07: skip_stage_palette_animation
01AD59  func  animate_stage_palette_stage_08   # stage 08: skip_stage_palette_animation
01AD5C  func  animate_stage_palette_stage_09   # stage 09: animate_palette_stage_09
01AD5F  func  animate_stage_palette_stage_0a   # stage 0A: animate_palette_stage_0a
01AD62  func  animate_stage_palette_stage_0b   # stage 0B: skip_stage_palette_animation
01AD65  func  animate_stage_palette_stage_0c   # stage 0C: animate_palette_stage_0c
01AD68  func  animate_stage_palette_stage_0d   # stage 0D: animate_palette_stage_0d_0e
01AD6B  func  animate_stage_palette_stage_0e   # stage 0E: animate_palette_stage_0d_0e
01AD6E  func  animate_stage_palette_stage_0f   # stage 0F: animate_palette_stage_0f
01AD71  func  animate_stage_palette_stage_10   # stage 10: animate_palette_stage_10
01AD74  func  animate_stage_palette_stage_11   # stage 11: skip_stage_palette_animation
01AD77  func  animate_stage_palette_stage_12   # stage 12: skip_stage_palette_animation
01B398  func  update_nbg1_scroll_stage_00      # stage 00: skip_nbg1_scroll
01B39B  func  update_nbg1_scroll_stage_01      # stage 01: skip_nbg1_scroll
01B39E  func  update_nbg1_scroll_stage_02      # stage 02: skip_nbg1_scroll
01B3A1  func  update_nbg1_scroll_stage_03      # stage 03: set_nbg1_scroll_to_camera
01B3A4  func  update_nbg1_scroll_stage_04      # stage 04: skip_nbg1_scroll
01B3A7  func  update_nbg1_scroll_stage_05      # stage 05: skip_nbg1_scroll
01B3AA  func  update_nbg1_scroll_stage_06      # stage 06: skip_nbg1_scroll
01B3AD  func  update_nbg1_scroll_stage_07      # stage 07: set_nbg1_scroll_to_camera
01B3B0  func  update_nbg1_scroll_stage_08      # stage 08: set_nbg1_scroll_to_camera
01B3B3  func  update_nbg1_scroll_stage_09      # stage 09: set_nbg1_scroll_to_camera
01B3B6  func  update_nbg1_scroll_stage_0a      # stage 0A: set_nbg1_scroll_to_camera
01B3B9  func  update_nbg1_scroll_stage_0b      # stage 0B: set_nbg1_scroll_to_camera
01B3BC  func  update_nbg1_scroll_stage_0c      # stage 0C: skip_nbg1_scroll
01B3BF  func  update_nbg1_scroll_stage_0d      # stage 0D: skip_nbg1_scroll
01B3C2  func  update_nbg1_scroll_stage_0e      # stage 0E: skip_nbg1_scroll
01B3C5  func  update_nbg1_scroll_stage_0f      # stage 0F: skip_nbg1_scroll
01B3C8  func  update_nbg1_scroll_stage_10      # stage 10: set_nbg1_scroll_to_camera
01B3CB  func  update_nbg1_scroll_stage_11      # stage 11: skip_nbg1_scroll
01B3CE  func  update_nbg1_scroll_stage_12      # stage 12: skip_nbg1_scroll
01B525  func  update_nbg2_scroll_stage_00      # stage 00: skip_nbg2_scroll
01B528  func  update_nbg2_scroll_stage_01      # stage 01: set_nbg2_scroll_to_camera_2
01B52B  func  update_nbg2_scroll_stage_02      # stage 02: set_nbg2_scroll_to_camera_2
01B52E  func  update_nbg2_scroll_stage_03      # stage 03: set_nbg2_scroll_stages_03_07
01B531  func  update_nbg2_scroll_stage_04      # stage 04: set_nbg2_scroll_stage_04
01B534  func  update_nbg2_scroll_stage_05      # stage 05: skip_nbg2_scroll
01B537  func  update_nbg2_scroll_stage_06      # stage 06: skip_nbg2_scroll
01B53A  func  update_nbg2_scroll_stage_07      # stage 07: set_nbg2_scroll_stages_03_07
01B53D  func  update_nbg2_scroll_stage_08      # stage 08: skip_nbg2_scroll
01B540  func  update_nbg2_scroll_stage_09      # stage 09: set_nbg2_scroll_to_camera
01B543  func  update_nbg2_scroll_stage_0a      # stage 0A: set_nbg2_scroll_to_camera
01B546  func  update_nbg2_scroll_stage_0b      # stage 0B: skip_nbg2_scroll
01B549  func  update_nbg2_scroll_stage_0c      # stage 0C: skip_nbg2_scroll
01B54C  func  update_nbg2_scroll_stage_0d      # stage 0D: skip_nbg2_scroll
01B54F  func  update_nbg2_scroll_stage_0e      # stage 0E: skip_nbg2_scroll
01B552  func  update_nbg2_scroll_stage_0f      # stage 0F: skip_nbg2_scroll
01B555  func  update_nbg2_scroll_stage_10      # stage 10: set_nbg2_scroll_to_camera
01B558  func  update_nbg2_scroll_stage_11      # stage 11: skip_nbg2_scroll
01B55B  func  update_nbg2_scroll_stage_12      # stage 12: skip_nbg2_scroll

# ======== More of the stage flow ========
# Game mode 0x0A (stage 5, update_stage_05_event, see the stage events helper): the stage runs until check_stage_clear
# would end it, then run_stage_05_switch (stage 0), Eggman's booth, a shake, explosions, then
# update_stage_00_exit_scene for 1B6 + 12C frames, then game mode 2 (stage 8).
017088  func  update_stage_00_exit_scene       # stage_00_exit_step: the camera climbs at speed 35 (direction y -4000), new tile layer pages, fade to white, then NBG0 and NBG1 zoom (their x and y zoom, 31FF50-31FF56 = 2000000 / stage_00_exit_zoom)
01744C  func  clamp_colour_offsets             # 20F1D0-20F1D4 &= 1F
209077  var   stage_00_exit_step    u8
209078  var   stage_00_exit_delay   u8         # frames between fade steps
209079  var   stage_00_exit_timer   s16  # (TYPE: update_stage_00_exit_scene compares it signed 4 times: >= 8C, A0, AF, D2)
20907B  var   stage_00_exit_zoom    u16        # 200 down to 42
01A6E1  func  escape_alarm_flash               # started by 095587 (game mode 0x1F, stage B): while 20E658 (the escape's seconds) is non-zero the green and blue colour offsets swing between 28 and 3F, sound 19 at each end
001C8F  func  reset_trackball_step             # clears trackball_step_sums[R3], then read_trackball_step (stage select, game mode 8)
001C96  func  read_trackball_step              # adds player R3's trackball x (trackball_N_x, 20F062 + 4n) into trackball_step_sums; R3 = +1 / -1 each 40 counts, else 0
20F0B6  var   trackball_step_sums   u16        # three accumulators (read_trackball_step)
08DC36  func  reset_linked_objects_flag        # 208076 = 0 when a game starts; stage 4 objects share it: type 1 (0138C6) sets 1, type 7's last state (00FFA0) sets FF, 0139B1 waits on it
098E43  func  update_ring_tally_panel_tail     # a lone rsr called at the end of update_ring_tally_panel's case 2
1140AE  table stage_player_entries  u16        # [stage]: non-zero (stages 3, 4, A-10) makes check_stage_entry_path use player state 2A; only tested for zero

# ======== More video registers (MAME segas32_v.cpp register list) ========
31FF8F  io    video_layer_opaque_flags u8      # high byte of 31FF8E, from the shadow 20F2C0 (apply_video_registers); MAME notes say 2 / 8 make NBG1 / NBG3 opaque: probably
20F2C0  var   video_layer_opaque_shadow u8     # shadow of 31FF8F (set_nbg3_page_5_flags sets A)

# ======== struct object fields the stage code relies on ========
field object 6C touch_distance s16  # (TYPE: both compares are signed: catapult_wait_for_rider > 211, check_player_contacts <= 8)
field object 78 wall         u16 object              # check_walls

# ======== Draw list and object removal used by every stage object ========
# The draw list is 100 buckets at 20FA02 (one per screen line, 00-FE from the bottom); the sprite list
# builder walks them every frame, so an object is drawn only on the frames its handler adds it.

# ======== Stage 11 (the ending) and the scripted game modes ========
# (From the stage events helper: renders of s32ref dumps of stage_11 frames 3503-9400, stage_05, stage_06, stage_04, players_2, players_3 and ending.)
# How the ending works (game mode 0x18, stage 11; run_stage_events sets it at scroll 0):
# - update_ending runs every frame: build_ending_scene (step 0 sets up the screen: display off, layer
#   mixing, bitmap layer, vram scripts 171BEB 171C77 17280A 1725B2 1729B2 172A84, sound 94, colour offsets
#   20; step 1 spawns six objects), then floors, restart point, the spawners and the contact checks;
#   joining_closed is held at FF; game over (mode 7) if nobody is in play. start_stage_view clears
#   ending_scene_step for stage 11 instead of running init_stage_view.
# - First scene (frames 3503-4217 in stage_11): the three characters run toward the viewer on a road
#   (scale growing), a tower stands on the horizon behind them; the tower's own timed list
#   (timed_spawns_2, offsets from 01BFC4) fires burning debris arcs from its top, blasts at its base and
#   along the horizon, at 258 the tower collapses (frames 9B00-9B40), a huge explosion and a dust cloud,
#   at 26C the screen flashes to white (second colour offset set up to 1F); then
#   ending_first_scene_done makes the scene objects remove themselves.
# - Credits (4217-8080): ending_credits_fade_in comes back from white and starts the music; the credits
#   spawner (timed_spawns_3, offsets from 01C82F) starts the portrait (Sonic, Ray, Mighty), the name plate
#   with "STARRING" (then Eggman) and seven blocks that scroll up 1 pixel every 2 frames: the team logo
#   and STAFF title, DIRECTOR., PROGRAM., DESIGN., SOUND., VOICE ACTOR., SPECIAL THANKS. (role headings
#   as the renders show them); each block is a sprite plus second-pool parts from its part list.
# - Then the portrait window (clip rectangle 1) opens to the whole screen, fade to black, Eggman flies in
#   with a jet flame and a smoke trail, turns to the viewer, fade, a close-up of his machine flying away
#   into the distance ending in a twinkle, a circle opens (per-line window edges at 30A000/30A400, NBG2
#   pages set), and the three characters run across the screen; 0x78 frames later ending_finished lets the
#   players end the game (player state 2B), game over at 9364 (it prints the "GAME OVER" string record
#   0025D0 instead of 002342 when ending_finished is set).
# The objects keep their own counters in field 30 (byte 0 started, byte 1 a step or done flag, word 32 a
# frame counter) and 60-62 (indexes): per-object scratch, not named.

# ======== Stage 11: scene set-up ========
095177  func  update_ending                     # game mode 0x18 (stage 11): scene_step = FF, next_stage_slot_request 0, joining_closed FF, build_ending_scene, find_nearby_floor_blocks, update_restart_point, the spawners, the contact checks; game mode 7 when player_status is all 0
01BCC6  func  build_ending_scene                # switch on ending_scene_step: 0 display off, set_stage_layer_mixing, clear_sprite_buckets, clear_bitmap_layer, init_video_registers, load_stage_palette_set, queue_sound 94, colour offsets 20, 31FF02 |= 2F, pages and scroll shadows, zoom 200, clip rectangle 1 zero, six run_vram_script calls; 1 allocate_object_outside_loop x6: ending_tower_animate, ending_horizon_show, the three runners, ending_road_scroll; 2/3 nothing
01BF17  func  init_ending_scene_object          # clears field 30 (bytes 30, 31, word 32) of the new object R11 and sets ending_scene_step = 2
20CA45  var   ending_scene_step     u8          # build_ending_scene switches on it (0 screen, 1 objects, 2 done); init_ending_scene_object sets 2; start_stage_view clears it on stage 11
20CA44  var   ending_first_scene_done u8        # build_ending_scene clears it; ending_flash_to_white sets 1 at full white; ending_horizon_show, ending_dust_cloud_rise, ending_road_scroll and animate_ending_runner remove their objects then
20E64F  var   ending_finished       u8          # ending_sonic_run_across sets 1 after 0x78 frames (stage_11 run: frame 9239); start_stage clears it; player state 2B (002CEC, 002FF1) waits for it; game over (run_game_mode 000EE1) then queues the "GAME OVER" string record 0025D0 instead of calling 002342

# ======== Stage 11: first scene (runners, tower, explosions) ========
01BF2D  func  ending_tower_animate              # the tower behind the runners (render s11_3560 object 6, 160,107): first frame run_vram_script 1715D3, NBG3 on, nbg3_pages 0; sprite and palette from ending_tower_frames by its timer (standing until 258, collapse frames 9B00-9B40 to 285), queue_sprite_in_layer (7), runs run_timed_spawns_2 every frame; removed at the end of the list
# 01C458 is run_timed_spawns_2 and 01D328 run_timed_spawns_3 in objects.txt (not proposed again here)
01C4F8  data  ending_tower_frames               # 6-byte records (time, sprite, palette): 0258 9AC8 10F0, then 9B00-9B40 with palette 1260 every 5 frames, FFFF FFFF FFFF ends (ending_tower_animate)
01C53A  func  ending_horizon_show               # sprite 9AC0 (palette 10F0, priority 5) at 9E,BE: the dark hills under the tower across the screen (render alone); at 258 frames spawns ending_dust_cloud_rise once (byte 30 = 2); removed with ending_first_scene_done
01C5BB  func  ending_dust_cloud_rise            # x 9E, palette 10D0, priority 6: every 7 frames the next (y, sprite) of ending_dust_cloud_frames (y A8 then A0): a brown dust cloud along the horizon after the collapse (render frame 4150); removed with ending_first_scene_done
01C636  data  ending_dust_cloud_frames          # (screen y, sprite) pairs, 9A10-9AA8, FFFF ends (ending_dust_cloud_rise)
01C7B0  func  ending_road_scroll                # sprite 9F,D4, priority 2, palette 12B0: the road in perspective under the runners (render alone), next of ending_road_frames every frame (5 frames); removed with ending_first_scene_done
01C825  table ending_road_frames    u16         # 7170 7178 7180 7188 7190 (ending_road_scroll)
01C68A  func  ending_sonic_run_forward          # centre runner (Sonic in the renders), zoom from 40, frames ending_sonic_run_frames, palette 130, y - 1 every 20 frames; animate_ending_runner
01C6E8  func  ending_ray_run_forward            # left runner (Ray), x - 2 and y - 1 every 20 frames, ending_ray_run_frames, palette 140
01C74C  func  ending_mighty_run_forward         # right runner (Mighty), x + 2 and y - 1 every 20 frames, ending_mighty_run_frames, palette 150
01D39A  func  animate_ending_runner             # R0 = frame list entry: removed once ending_first_scene_done; sprite flags 100, queue_sprite_in_layer; zoom + 1 every 4 frames (they come closer), next frame every 2 frames, 12 frames
01E006  data  ending_sonic_run_frames           # 12 sprite words 1B68-1BC0, FFFF
01E020  data  ending_ray_run_frames             # 5280-52D8
01E03A  data  ending_mighty_run_frames          # 36C8-3720
01BFC4  func  ending_debris_1_fly               # timed_spawns_2 at 3C: queue_sound B, then animate_ending_debris with ending_debris_1_path (sprite flags 9): a burning chunk leaving the tower top with a curved smoke trail (renders 3565-3640)
01BFF2  func  ending_debris_2_fly               # at 96, ending_debris_2_path, flags A (left side of the tower)
01C020  func  ending_debris_3_fly               # at F0, ending_debris_3_path, flags 9
01C40D  func  animate_ending_debris             # byte 32 + 1 (an inc.b on a word counter), first 8-byte record whose time is above it: x, y, sprite; palette 10C0, priority 6; removed at the FFFF record
01E078  data  ending_debris_1_path              # 8-byte records (time, screen x, screen y, sprite 9B90-9C18), FFFF x4 ends
01E10E  data  ending_debris_2_path              # sprites 9C20-9C98
01E194  data  ending_debris_3_path              # sprites 9B90-9C18
01C04E  func  ending_tower_blast_1              # queue_sound A, start_ending_blast, at 90,A0 (around the tower base); animate_tower_blast
01C072  func  ending_tower_blast_2              # at A1,98
01C096  func  ending_tower_blast_3              # at AE,A0
01C0BA  func  ending_horizon_blast_1            # queue_sound 9, start_ending_blast, at 35,AA; animate_horizon_blast (renders 3750-4100: fireballs turning into smoke along the horizon)
01C0DE  func  ending_horizon_blast_2            # at 52,A8
01C102  func  ending_horizon_blast_3            # at 73,A5
01C126  func  ending_horizon_blast_4            # at 93,A0
01C14A  func  ending_horizon_blast_5            # at AD,9E
01C16E  func  ending_horizon_blast_6            # at C6,A2
01C192  func  ending_horizon_blast_7            # at CF,AF
01C1B6  func  ending_horizon_blast_8            # at E4,AC
01C1DA  func  ending_horizon_blast_9            # first frame at F8,B0, later frames E4,AC: its "bne" goes to ending_horizon_blast_8's code at 01C1C8 (original code)
01C1FE  func  start_ending_blast                # byte 30 = 1, frame index 60 = 0, timer 32 = 0
01C3B9  func  animate_tower_blast               # frame ending_tower_blast_frames[60] through draw_ending_blast_frame; removed at FFFF
01C365  func  animate_horizon_blast             # the same with ending_horizon_blast_frames
01C3D4  func  draw_ending_blast_frame           # sprite [R10], priority 6, flags 0, zoom 100, palette 10B0, queue_sprite_in_layer; next frame every 6 frames
01E22A  data  ending_tower_blast_frames         # sprite words 98C0-9958, FFFF
01E254  data  ending_horizon_blast_frames       # 9960-9A08, FFFF
01C20E  func  ending_big_explosion              # timed_spawns_2 at 258: queue_sound 7, at A1,62 priority 9 palette 1100 with a second-pool part (field 34); (sprite, flags) from ending_big_explosion_frames and ..._2 every 6 frames: a fire column on the tower that grows into a fireball covering the sky (renders 4105, 4160); part freed and removed at FFFF
01E282  data  ending_big_explosion_frames       # (sprite, sprite flags) pairs 9670-9708, flags 0200 then 0201, FFFF
01E300  data  ending_big_explosion_frames_2     # the second part: 9670-9710, flags 0200 then 0202 (the other half), FFFF
01C2F3  func  ending_flash_to_white             # timed_spawns_2 at 26C: 20F1CE (mixer 61003E) = 80FF, color_offset_2_* 0 then + 1 every 3 frames; at 1F sets ending_first_scene_done, spawns ending_credits_fade_in, removed
20F1D6  var   color_offset_2_red    u16         # the mixer shadow (20F1C0) + 16 -> 610046, the second colour offset set (color_offset_red is 610040): ending_flash_to_white raises it to 1F, ending_credits_fade_in lowers it, the ending fades copy color_offset_red into it; start_test_mode/test_memory clear it
20F1D8  var   color_offset_2_green  u16         # -> 610048 (as color_offset_2_red; the fades copy color_offset_green)
20F1DA  var   color_offset_2_blue   u16         # -> 61004A (the fades copy color_offset_blue)

# ======== Stage 11: credits ========
01C82F  func  ending_credits_fade_in            # first frame: load_credits_palettes, color_offset_2_* = 1F (white), spawns ending_credits_spawner_run, 31FF02 = (x & FF0) | 10A8, 31FF5E |= 200, palette 609400 = 5FD2, NBG1/NBG2 scroll 0, mixer_blend &= 1F00; after 78 frames spawns ending_music_play once and lowers color_offset_2_* by 1 every 3 frames; removed at 0
01C939  func  load_credits_palettes             # sets bit 10 of 31FF00 (tile bank bit), vblank copies off, load_palette_list(1076AE)
01C91D  func  ending_credits_spawner_run        # timer object: word 32 counts frames, byte 61 = entry; run_timed_spawns_3 (timed_spawns_3: portrait 0, name plate 1, the seven credit blocks at 1E 24E 32E 47E 606 756 9F6)
01C968  func  ending_music_play                 # queue_sound 94 then 82 (music group), then ending_music_cues by its timer; removed at FFFF
01C9CA  data  ending_music_cues                 # (time, sound) records: 0E9C 0096 (sound 96 = control 80 01), FFFF FFFF
01C9D2  func  ending_portrait_show              # at E8,4C: portrait sprite and palette by time (ending_portrait_frames, ending_portrait_palettes: Sonic until 398, Ray until 730, Mighty until AC8), a second-pool part with sprite 9880 under it (the checkered backdrop, ending_portrait_backdrop_palettes); part freed and removed at the end (render 4250-6950)
01D2E4  data  ending_portrait_frames            # (time, sprite): 0398 9868, 0730 9870, 0AC8 9878, FFFF FFFF
01D308  data  ending_portrait_palettes          # (time, palette): 1040 1050 1060
01D318  data  ending_portrait_backdrop_palettes # (time, palette): 1080 1070 1090
01CAAC  func  ending_name_plate_show            # first frame clip rectangle 1 = B1,21-120,78 (the portrait window); name plate at E8,A3 from ending_name_plate_frames, a second-pool part "STARRING" (sprite 9800, palette 1030) at E9,18; NBG0 scroll x + 1 every frame; at the end spawns ending_window_open and is removed
01D2F4  data  ending_name_plate_frames          # (time, sprite): 0398 9810 (SONIC THE HEDGEHOG), 0730 9808 (RAY), 0AC8 9820 (MIGHTY), 0E60 9818 (EGGMAN EVIL SCIENTIST), FFFF FFFF
01CB98  func  ending_credits_team_logo_scroll   # credit block: header 9850 (team logo) at 58,F4 plus credits_team_logo_parts ("STAFF" E0 lower); scrolls up (scroll_credits_sprite) until its last part is above -14, then parts freed and removed
01CC4B  func  ending_credits_director_scroll    # header 97C8 "DIRECTOR." plus credits_director_parts (icon, name); scroll_credits_block
01CD17  func  ending_credits_program_scroll     # header 97C0 "PROGRAM." plus 4 parts (fields 38 40 44 48)
01CE15  func  ending_credits_design_scroll      # header 97E8 "DESIGN." plus 5 parts (38-4C)
01CF2C  func  ending_credits_sound_scroll       # header 97E0 "SOUND." plus 4 parts
01D02A  func  ending_credits_voice_actor_scroll # header 97F0 "VOICE ACTOR." plus 5 parts
01D141  func  ending_credits_special_thanks_scroll # header 97D0 "SPECIAL THANKS." plus 8 parts (38-58); the last block
01CCB5  func  scroll_credits_block              # a block with two parts (fields 38, 40): when byte 31 is set frees them and removes the object; else timer + 1, scroll_credits_sprite for the three, wrap_credits_scroll_timer, byte 31 = 1 once the part at 40 is at y -14 or above (the longer blocks call it only on their first frame)
01D2A3  func  scroll_credits_sprite             # queue_sprite_in_layer, screen y - 1 when object_cursor's timer (32) has reached 2; R20 back to object_cursor
01D2B8  func  wrap_credits_scroll_timer         # object_cursor's timer back to 0 at 2: the blocks move 1 pixel every 2 frames
01D2C6  func  init_credits_sprite               # screen x 58, priority 0, flags 0, zoom 100; R20 = object_cursor
01CC3D  data  credits_team_logo_parts           # part lists, 8-byte records (sprite, screen y, palette, field of the block that keeps the part), FFFF ends: here 9848 at 1D4
01CD01  data  credits_director_parts            # icon (palette 10A0) at 110, name at 12C
01CDEF  data  credits_program_parts
01CEFE  data  credits_design_parts
01D004  data  credits_sound_parts
01D113  data  credits_voice_actor_parts
01D25D  data  credits_special_thanks_parts

# ======== Stage 11: after the credits ========
01D3E7  func  ending_window_open                # clip rectangle 1 (video_clip_1_*) grows by 1 pixel a frame on each side to 0,0-140,E0 (byte 31 collects the four done bits), NBG0 scroll x + 1 a frame; then spawns ending_credits_fade_out and is removed (stage_11 frames 7880-8060)
01D49A  func  ending_credits_fade_out           # update_fade with fade_kind 0 (to black), color_offset_2_* follow color_offset_*; when fade_kind < 0 spawns ending_eggman_scene_fade_in
01D4F3  func  ending_eggman_scene_fade_in       # first frame: NBG0/1/3 off, run_vram_script 172653, page shadows 03030303 02020202 08080707 0F0F0F0F, layers on, fade_kind 2 (from black), NBG0 scroll x 0; fades with the copy; then spawns ending_eggman_fly_in
01D59A  func  ending_eggman_fly_in              # Eggman's machine from the side (sprite 9AE0, palette 1250) following ending_eggman_flight_path for 1E0 frames, a jet flame part (9AB0/9AB8, 20 to the left) until x reaches A6, 8 smoke puffs (second pool) shifting along every 10 frames and drawn while the timer > 32, queue_sound 75 at 104; then spawns ending_eggman_turn with its sprite fields, frees the parts, removed (renders 8100-8550)
01D8CA  func  init_eggman_smoke_puff            # second-pool part R11: sprite 9B88, priority 0, screen -10,0, zoom 100, palette 1280
103B76  data  ending_eggman_flight_path         # (screen x, screen y) per frame from FFE0,0070 to A6,66, then A6,66 repeated; once x is A6 ending_eggman_fly_in adds 4 more to its pointer (sub.w #FFFFFFFC, original code) which only skips equal records
01D8F8  func  ending_eggman_turn                # sprite from ending_eggman_turn_frames by its timer: he turns from the side to the viewer and holds; then spawns ending_eggman_scene_fade_out
01D957  data  ending_eggman_turn_frames         # (time, sprite): 2 9AE0, 4 9AE8, 6 9AF0, 8 9AF8, 44 9AF8, FFFF FFFF
01D96F  func  ending_eggman_scene_fade_out      # update_fade kind 0 with the copy; then spawns ending_close_up_fade_in and ending_eggman_fly_away
01D9DC  func  ending_close_up_fade_in           # update_fade kind 2 (from black) with the copy; removed when done
01DA21  func  ending_eggman_fly_away            # close-up of the machine (9AD8 + part 9AD0, zoom 200 shrinking by 3, by 2 below 100: flying off into the distance, renders 8700-8850), cycle_palette on palette_cycle_0 (608BC0 from 108B90); below zoom 20, byte 31 steps: queue_sound 9 and NBG2 pages, a twinkle part (9B50) growing to 110 and shrinking, parts freed, per-line window set-up (31FF04 5015, 31FF06 0400, 31FF48 02020202, 31FF02 2122, 30A000/30A400 filled 1/0), a circle opening around A0,70 until its radius passes E0, then the three runners across; removed
20CA13  var   circle_wipe_left      u16         # ending_eggman_fly_away: A0 - sqrt(radius^2 - dy^2), clamped to 0, written per line to 30A000 (probably the line's window start); init_stage_view sets 82
20CA15  var   circle_wipe_right     u16         # A0 + the same, clamped to 13F, written to 30A400; init_stage_view sets BE
20CA17  var   circle_wipe_line      u16         # the line being computed (0-DF, the centre is line 70); init_stage_view sets 32
20CA1A  var   circle_wipe_radius    s16         # + 3 every other frame from 0 (byte write) until above E0; init_stage_view sets A
01DDF4  func  ending_sonic_run_across           # Sonic running right across the screen (render 9150-9350): body ending_sonic_body_frames + legs part, x + 5 a frame (wraps at 300); after 78 frames sets ending_finished
01DE86  func  ending_ray_run_across             # Ray behind him: zoom F0, priority 3, x + 4
01DF00  func  ending_mighty_run_across          # Mighty last: zoom E0, priority 4, x + 3
01DF7A  func  init_ending_runner_across         # object_cursor started, timers 0; R20 at y 68, x 0, palette | 2000, sprite flags 140
01DFAB  func  animate_ending_runner_across      # body frame from the table at field 4C by (32 & 7) >> 2, legs from the table at 50 by 62 & 3, draw both; at x 300 both go back to 0
01E054  data  ending_sonic_body_frames          # A810 A818 (ending_sonic_run_across keeps it in field 4C)
01E058  data  ending_sonic_leg_frames           # A820 A828 A830 A838 (field 50)
01E060  data  ending_ray_body_frames            # A7E0 A7E8
01E064  data  ending_ray_leg_frames             # A7F0-A808
01E06C  data  ending_mighty_body_frames         # A7B0 A7B8
01E070  data  ending_mighty_leg_frames          # A7C0-A7D8

# ======== Scripted game modes (run_stage_events, cases of run_game_mode) ========
# scene_step (20F0E2, players.txt) is the step of the scripted mode and scene_timer its timer;
# run_stage_events clears both when it switches the mode. Names event_XX_ use the game mode in hex, like
# system.txt's game_mode_event_XX labels. Three modes jump
# through tables of stubs indexed by scene_step (3-byte br for modes 0x0C and 0x0F, 2-byte br for 0x1C); the
# last entry of each table is the step-0 handler itself (09460F, 094888, 095116), reached only if the
# step ran past the end.
20F0E4  var   scene_timer           s16         # timer of the scripted game modes (counts up or down per mode), cleared by run_stage_events; also read by the players (002CEC/002FF1: stage 0 geyser ride while <= 90) and stage 6 objects (084169, 0841DA)

# Not named: 20E5BA (update_stage_05_event step 0 writes 0, nothing reads it) and 20E665 (game mode 0x1F
# step 0 writes 0, nothing reads it); 20E61A is shared (game mode 0x0D, 080160, 0803EB, 084778, 08481D,
# 0890DC, 08924E set or clear it; check_player_jump and player_steer stop the players while it is set):
# probably "player controls locked", left to the players helper.

# Game mode 0x0F: no stage event or code sets it (system.txt); its table at 094606 is dispatched from 0945CE
094606  func  game_mode_event_0f_case_00        # br event_0f_start
094609  func  game_mode_event_0f_case_01        # br event_0f_stop_scroll
09460C  func  game_mode_event_0f_case_02        # br event_0f_show_messages
09460F  func  event_0f_start                    # step 0 (and the table's entry 3): scene_timer 0, scroll mode 0 at speed 3, scene_step + 1
094626  func  event_0f_stop_scroll              # step 1: scene_timer + 1; from 3C scroll mode 1 (stop); at 78 event_0f_message_cursor = event_0f_messages, step 2
094659  func  event_0f_show_messages            # step 2: each record of event_0f_messages whose time has come: spawn_message_box(x, y, kind, frames); at 258 scroll mode 0 speed 5, game mode 3 (never run)
20E5B2  var   event_0f_message_cursor u32 event_0f_message       # pointer into event_0f_messages (event_0f_stop_scroll, event_0f_show_messages only)
0957FA  data  event_0f_messages                 # 10-byte records (time, x, y, message kind, frames): kinds 0-4 at 0, 78, FA, 136, 1EA; 7FFF ends (kinds 1-4 have sprite 0000 in message_box_kinds: unused leftovers)
struct event_0f_message A rom             # an event_0f_messages record: event_0f_show_messages "cmp.h [R10+], 70E4[R25]" + blt (094662: signed), then "mov.h [R10+]" into R5, R6, R7, R8 (09466A-094673) for spawn_message_box; census ROM 0957FA-095829 (0000 90 A0 0 78, 0078 B0 40 1 78, ..., 7FFF)
field event_0f_message 00 show_time s16   # the scene_timer that opens it; 7FFF ends
field event_0f_message 02 screen_x u16
field event_0f_message 04 screen_y u16
field event_0f_message 06 kind u16        # message_box_kinds index
field event_0f_message 08 frames u16      # how long the box shows

# Game mode 0x0C: stage 4 at scroll 0 (dispatched from 09484A)
09487C  func  game_mode_event_0c_case_00        # br event_0c_start
09487F  func  game_mode_event_0c_case_01        # br event_0c_first_quake
094882  func  game_mode_event_0c_case_02        # br event_0c_second_quake
094885  func  game_mode_event_0c_case_03        # br event_0c_finish
094888  func  event_0c_start                    # step 0: scene_timer 3C, 0829CC (four objects with handler 082A94 from the table at 0833EF, kind 4, sprites 5E20-5E58; render stage_04 3506), step + 1, scroll mode 0 at speed 2
0948A7  func  event_0c_first_quake              # step 1: scroll stops once scene_timer <= 1E; at 3C camera_shake_active + sound F; at 0 step 2, scene_timer 3C
0948E3  func  event_0c_second_quake             # step 2: at 3C camera shake + sound F; spawn_walls; at 0 step 3, scene_timer A, sound A
09491C  func  event_0c_finish                   # step 3: after A frames game mode 3, scroll mode 7 (follow the players)

# Game mode 0x0A: stage 5 from scroll 100 (run_game_mode cases 0x0A/0x0B; the stage plays without the spawners)
09492F  func  update_stage_05_event             # switch on scene_step: 0-1 the stage frame (players, scroll, tiles, contacts) with goal_unlocked set every frame until check_stage_clear switches to game mode 0x17 (stage clear): back to 0x0A, step 2, goal_scroll_position FFFF; 2 run_stage_05_switch (stage 0; at its step 2 spawns stage_00_platform_start, at the end load_stage_map_pointers) for B4 frames; 3 spawn_eggman_booth (012B24), at A spawn_message_box(A0, 50, kind 10, B4 frames) + sound 70, spawn_walls, A0 frames; 4 camera shake 3C frames, sounds F; 5 stage_00_geyser_rise (stage_00_geyser) and stage_00_rock_count rock_debris_fly pieces around the platform, sound 6; 6-7 update_stage_00_exit_scene (186 + 12C frames), then game mode 2 (stage_05 run: frames 5416-6610)
094C3F  func  stage_00_platform_start           # waits while stage_05_switch_step is 2, then sprite 6FF8 palette 9040 at x 185, y 1545, z 0, project_object, stage_00_platform = it, handler stage_00_platform_stand (also listed in stage 0's stage_spawns_by_handler at 2F00)
094C93  func  stage_00_platform_stand           # the stone platform the player stands on (render stage_05 5600): queue_sprite_in_bucket(depth + 3C, at most FF) while game_mode < 0x0A or scene_step < 5; at step 5 palette 9030, animation stage_00_platform_break_animation, map x + 4, map y - 13, stage_00_platform_break
094D06  func  stage_00_platform_break           # animate, drawn at depth - 3C; removed once screen y >= 130
094D36  func  stage_00_geyser_rise              # the top of a water spout bursting out of the platform (render 5930, 5960): starts at the platform less 8 in z, animation stage_00_geyser_animation; z - speed_z, speed_z from stage_00_geyser_speeds; every 1C of rise (8 times) allocate_object a stage_00_geyser_column_start; removed below screen y 130. The players ride on it (002CEC reads stage_00_geyser)
094E1E  func  stage_00_geyser_column_start      # a column segment at the platform's map position, z + 8, then stage_00_geyser_column_rise
094E5A  func  stage_00_geyser_column_rise       # z - the top's speed_z (parent 26), sprite stage_00_geyser_column_sprites[top's animation frame], queue_sprite; removed below 130
094E92  func  rock_debris_fly                   # a stone fragment in screen space: map x/y (2A/2C, 1/16 pixel) + speed_x/speed_y, screen = /16, removed off screen (x outside -20..160, y outside -20..100); sprite flags F000 = 0 pieces grow (zoom + 30 up to 600, coming at the viewer); animate, bucket 1. Also spawned by 080C90 (spiked_roller_burst_out), 0828E4 (stage 4 doorway) and 08A89E
20F0E6  var   stage_00_platform     u16 object         # set by stage_00_platform_start; update_stage_05_event places the rock pieces around it, stage_00_geyser_rise and stage_00_geyser_column_start start at it
20E5AC  var   stage_00_geyser       u16         # update_stage_05_event step 5 stores the stage_00_geyser_rise object; the player code (0070F6) puts the player on its z - 20 while scene_timer <= 90
0955A0  table stage_00_rock_count   u16         # 0010: how many rock_debris_fly pieces update_stage_05_event step 5 spawns (stage_00_rock_pieces follows)
0955A2  data  stage_00_rock_pieces              # 12-byte records: sprite flags (0000 growing / 1000), dx, dy from the platform, speed x, speed y, animation offset into rock_debris_animations
095662  data  stage_00_platform_break_animation # last step 2, sprites 6F78 6F80 6F88
09566A  data  stage_00_geyser_animation         # last step 3, sprites 6F38-6F50
095676  table stage_00_geyser_column_sprites u16 # 6F58 6F60 6F68 6F70 by the geyser top's animation frame (stage_00_geyser_column_rise)
09567E  data  rock_debris_animations            # three animations (+0, +A, +14), sprites 5C30-5C88; used with rock_debris_fly by update_stage_05_event, 080C90, 0828E4
09569C  data  stage_00_geyser_speeds            # (frames, rise speed >> 12) pairs: 50 80, 40 D4, 7FFF 90 (stage_00_geyser_rise)

# Message boxes (several objects and scripted modes)
0953BF  func  spawn_message_box                 # R5 screen x, R6 screen y, R7 kind, R8 frames: allocate_object_outside_loop with message_box_kinds[R7] (sprite, palette, full width, height), sized sprite (flags 9000), width 0, handler message_box_show; callers 0140F0, 083D3B, 083E39, 08481D, 08576F, event_0f_show_messages, update_stage_05_event
09542C  func  message_box_show                  # a red caption box with a message (render s03_3560 object 21, stage_05 5800): width + 18 a frame up to its full width while the frames last, then - 18 a frame and removed; queue_sprite_in_bucket(0) (on top)
09571A  data  message_box_kinds                 # 8-byte records: sprite, palette, width, height (24 kinds; kind 10 is the stage 0 message 65E0)
struct message_box_kind 8 rom             # a message_box_kinds record (kind x 8): place_message_box reads it with [R7+] (0953EE "mov.h [R7+], 4[R11]", 0953F3 6[R11], 0953F8 36[R11], 0953FD 18[R11]); census ROM 09571A-0957D9 (5E68 8410 0106 0030, 0000 8410 00F0 0018...)
field message_box_kind 00 sprite_frame u16  # into sprite_frame (04); its sprite_frames record's +6 is the box's sprite_scale
field message_box_kind 02 palette u16       # into sprite_palette (06)
field message_box_kind 04 width u16         # the full width, into speed_x_int (36): message_box_show grows sprite_width to it
field message_box_kind 06 height u16        # into sprite_height (18)

# Game mode 0x10: stage 6 (the opening), steps 2-3 call spawn_stage_06_lightning
09532E  func  spawn_stage_06_lightning          # first call (stage_init_flags bit C) stage_06_lightning_cursor = stage_06_lightning_strikes; when scene_timer reaches the next record: an object at its screen x/y with its animation, palette 9110, handler stage_06_lightning_animate, sound B (render stage_06 3935: a lightning bolt)
0953A0  func  stage_06_lightning_animate        # removed when current_game_mode is no longer 0x10 or at the end of its animation; queue_sprite meanwhile
20E5C0  var   stage_06_lightning_cursor u32 stage_06_lightning_strike     # pointer into stage_06_lightning_strikes (spawn_stage_06_lightning only)
09582E  data  stage_06_lightning_strikes        # 10-byte records (time, screen x, screen y, u32 animation 0958F8-095930), 20 strikes up to 1F4, 7FFF ends
struct stage_06_lightning_strike A rom    # a stage_06_lightning_strikes record: spawn_stage_06_lightning "cmp.h [R10+], 70E4[R25]" + blt (095345: signed), then "mov.h [R10+], E[R11]", "mov.h [R10+], C[R11]", "mov.w [R10+], 48[R11]" (095373-09537D); census ROM 09582E-0958F7 (0008 A0 50 0958F8, 0010 50 00 095906, ..., 7FFF)
field stage_06_lightning_strike 00 strike_time s16  # the scene_timer that strikes; 7FFF ends
field stage_06_lightning_strike 02 screen_x u16
field stage_06_lightning_strike 04 screen_y u16
field stage_06_lightning_strike 06 animation u32    # the bolt's animation (0958F8-095930)

# Game mode 0x0D: stage 8 at scroll 0 (B4 frames held, then scroll mode 0 at 1C until A00; players thrown, state 19)
20E619  var   event_0d_active       u8          # game mode 0x0D sets FF with 20E61A at its start, clears both when scroll_position reaches A00; start_stage clears it; player_place (002BC5) skips setting player field B4 while it is set

# Game mode 0x12: stage 9 at scroll 4C00
20E650  var   event_12_fast_scroll  u8          # set by 011629 together with 20E5F5 (players pulled, state 26); game mode 0x12 then switches scroll mode 7 to scroll mode 0 at speed 20 (stage_09 run of players_3: frame 14888); joining players wait (00D2BA); cleared by the mode at its end and by start_stage

# Game mode 0x16: stage 3 at scroll 1B80 and 8610 (the camera holds: no update_scroll)
20E616  var   scroll_resume_request u8          # game mode 0x16 (the camera held at 1B80 and 8610 in stage 3) waits for it, clears it, scroll mode 7 and game mode 3; set FF by spiked_roller_burst_out (080C90, the kind 1 roller out of the wall) and spiked_roller_crash_into_machine (080ABD, at y <= 0AE0 with a shake and sound 6); start_stage clears it

# Game mode 0x19: stage 10 at scroll C80 (players_2 run: frames 6036-6328)
20E62D  var   barred_gate_closing   u8          # set by game mode 0x19 when a playing player's x passes 17C; barred_gate_update (08D630, a barred gate, render players_2 6100) then plays its animation once with sound 29
20E62C  var   rope_bridge_collapsing u8         # set by game mode 0x19 after the camera has held 78 frames at scroll DA0: scroll mode 0 at speed 1E until 1A00; the players go to state 30 (check_scene_states, 00D2BA) and every rope_bridge_plank_update plank (093ADE) falls (render 6230)

# Game mode 0x1A: stage 8 at scroll 3100 (scroll script, then game mode 3 at 6080)
20E63C  var   event_1a_scroll_mode  u16         # copied into scroll_mode every frame of game mode 0x1A; from event_1a_scroll_script
20E63E  var   event_1a_scroll_speed u16         # copied into scroll_speed every frame of game mode 0x1A
09593E  data  event_1a_scroll_script            # 6-byte records by scene_step (scroll position or FFFF = wait for 20E63A and scene_timer 1E, scroll mode, speed): 3400 0 20, 4C50 1 5, FFFF 0 60, 5200 0 2C, 7FFF 0 28
struct event_1a_scroll_step 6 rom         # an event_1a_scroll_script record (scene_step x 6, game_mode_event_1A 094F86): "cmp.h [R11], 6435[R25]" (bl: scroll_position below it waits) and "test.h [R11]" (bp: FFFF waits for the seesaw), then "mov.h [R11+], 663C[R25]" and "mov.h [R11+], 663E[R25]" (094F97, 094F9D); census ROM 09593E-095961 (0000 7 5, 3400 0 20, 4C50 1 5, FFFF 0 60, 5200 0 2C, 7FFF 0 28)
field event_1a_scroll_step 00 position u16      # the scroll_position that starts the step; FFFF: wait for seesaw_launched and scene_timer instead (its sign is tested)
field event_1a_scroll_step 02 mode u16   # into event_1a_scroll_mode
field event_1a_scroll_step 04 speed u16  # into event_1a_scroll_speed
095940  table event_1a_first_scroll_mode u16    # 0007: event_1a_scroll_mode at step 0 (rom_095940 in run_game_mode)
095942  table event_1a_first_scroll_speed u16   # 0005: event_1a_scroll_speed at step 0

# Game mode 0x1C: stage 8 at scroll 7A8D (players ride across the bridge; dispatched from 095053)
09510E  func  game_mode_event_1c_case_00        # br event_1c_follow (step 0 is never used: the mode sets step 1 itself)
095110  func  game_mode_event_1c_case_01        # br event_1c_follow
095112  func  game_mode_event_1c_case_02        # br event_1c_stop
095114  func  game_mode_event_1c_case_03        # br event_1c_scroll
095116  func  event_1c_follow                   # step 1: follow_base_speed 5, scroll mode 7 (1 when no_player_in_play); at scroll 7E9F step 2, scene_timer 1E, event_1c_ride_phase
09514C  func  event_1c_stop                     # step 2: follow_base_speed 0, scroll mode 1; after 1E frames step 3 and event_1c_riding
09516A  func  event_1c_scroll                   # step 3: scroll mode 0 at speed F (game mode 3 again at scroll 84A0)
20E649  var   event_1c_ride_phase   u8          # set by event_1c_follow at scroll 7E9F; while set check_scene_states gives each player a ride object (spawn_player_ride) before event_1c_riding and state 35 after it, and joining players wait (00D2BA); the bridge (085F80) clears it after its sixth bounce (then a sign, kind 16), start_stage too
20E647  var   event_1c_riding       u8          # set by event_1c_stop; players go to state 35 (riding), the bridge planks (085F80) shake with sound 9; game mode 0x1C clears it at its start, start_stage too

# Game mode 0x1F: stage B at scroll F00, the escape with a time limit (ending run: frames 9743-22297)
095463  func  start_escape_countdown            # once at step 0: two second-pool digit sprites (palette F60, centre 2A = A0, y 80) into escape_tens_digit / escape_units_digit; escape_countdown_frames 0, escape_seconds 14 (20), escape_started 0, escape_countdown_running 0
0954B1  func  update_escape_countdown           # when escape_started: escape_countdown_frames + 1 (only up to 3B until escape_countdown_running), at 3C escape_seconds - 1; the seconds in BCD (convert_to_bcd) drawn on top as two digits from escape_countdown_digits whose zoom grows with the frames (render ending 10300: "59")
095587  func  start_escape_alarm_flash          # allocate_object_outside_loop with handler escape_alarm_flash
095962  table escape_countdown_digits u16       # sprites 9DE8-9E30 for 0-9
20E658  var   escape_seconds        u8          # 14 at the start; when it reaches 0 game mode 0x1F goes to 0x20 ("YOU FAILED"); escape_alarm_flash stops at 0 (ending.txt pokes it to hold the timer)
20E657  var   escape_countdown_frames s8        # frames of the current second (0-3B)
# 20E659 is escape_started (objects by type, code 012309-0158E2; eggman_scene_press_button sets it; ending run frame 10211): from then on the digits are drawn and game mode 0x1F counts escape_alarm_timer
20E66D  var   escape_countdown_running u8       # set FF by the player code (002CEC/002FF1) when a player leaves the stage B sequence (state 38) for running (13) (ending run frame 10469): until then escape_countdown_frames stops at 3B and escape_seconds does not drop; start_escape_countdown clears it
20E65A  var   escape_tens_digit     u16 object         # second-pool sprite of the tens digit (drawn only when not 0, left of the centre)
20E65C  var   escape_units_digit    u16 object         # units digit (centred when the tens are 0)
20E666  var   escape_alarm_timer    u16         # game mode 0x1F counts it from escape_started: at 1 start_escape_alarm_flash, camera shake, sounds F and 7C; at 30C sound 7C again

# Game mode 0x20: "YOU FAILED" (from game mode 0x1F when escape_seconds reaches 0)
20E65E  var   you_failed_fade_red   u16         # + 1 a frame while color_offset_red < 1F, color_offset_red = it >> 2 (fade to white)
20E660  var   you_failed_fade_green u16         # the same for color_offset_green
20E662  var   you_failed_fade_blue  u16         # the same for color_offset_blue
20E664  var   you_failed_done       u8          # set FF by game mode 0x20 at game_mode_timer 12C (after the "YOU FAILED" text at 78); player state 39 then starts the continue countdown

# ======== Stage objects 080072-0857E4: the chasers (spiked roller) and the objects of stages 3-7 ========
# Stage objects 080072-0857E4 (stage_spawns_by_handler handlers of stages 3-8, B-F and the chaser system)
# Renders: s32ref dumps of walk_03, walk_04, stage_06 (stages 7, 6, 5, 0), stage_0E, plus a frame renderer
# (sprite word + palette from a dump) for the sprites named below.
#
# How the chaser works (080160-080E75):
# - A spiked roller (sprites 5E20-5E60: a gear-shaped cross-section that turns over 9 frames) is built by
#   spiked_roller_start from spiked_roller_kinds[kind]: 5 parts side by side across the path (spacing and
#   direction from spiked_roller_layouts), each with handler spiked_roller_update; the first part is the
#   leader (field 86 of every part), the others copy its x or y (spiked_roller_follow_leader). The leader
#   becomes chaser_object. The parts are on the zone list: kind (field 47) 0C (solid: players are pushed
#   out) until its script starts moving, then 18 (players are hurt even while invulnerable; with flags
#   40000000 an attacking player bounces off along its direction: check_player_contacts), and a back zone
#   object (spawn_roller_back_zone) covers 200 units behind it so nobody can stay behind it.
# - spiked_roller_chase runs the leader's script (10-byte records keyed by scroll_position: screen
#   threshold, catch-up factor, base speed, wait frames): on screen past the threshold it moves at the base
#   speed, else at max(camera speed x factor / 100, base) (catch-up). chaser_speed is the speed (1/16 pixel);
#   the animation speed and a rumble sound (B) follow it. While a player drops in, has a mishap or no player
#   is in play it backs off (waits until it is off screen) for up to FF frames or 300 scroll units.
# - spiked_roller_events (by kind, keyed by scroll_position and a coordinate test) switch the leader to the
#   other states (field 80, jump table at 0803BB).
# - Stage 3: kind 0 at the start (players frozen until it moves; it falls off a ledge near 1B80), kind 1 bursts
#   out of a wall (game mode 0x16 holds the camera until it appears), kind 5 crashes into the machine at 8300
#   (wrecks it; its flame becomes the rising fire), kind 2 drops in near 9B00 and in the finale (game mode
#   0x15) sinks into the stone floor blocks, jumps and lands: the blocks fall and goal_unlocked is set.
#   Stage E: kind 4 rolls into the brick wall (brick_wall_wait bursts). Kind 3 is not spawned by any list.

# ======== Stone floor blocks that crumble at the stage 3 finale (nine at 9C00) ========
080072  func  stage_03_floor_block_start       # stage 3 list at 9C00 (3 x 3 at FA62-FAE9 / 0A79-0B8A, field 04 = kind 0-6): stage_03_floor_block_kinds[kind] gives sprite offsets 64/65, a depth offset (38) and the animation (6780/6770/6778/6798: stone blocks seen in the walk_03 finale renders), field 34 = which stage_03_floor_block_falls byte it waits for; z 0
0800FB  func  stage_03_floor_block_update      # waits until its stage_03_floor_block_falls byte is set, then falls (speed_z += 1000) playing its crumble animation once (6780 -> 6788 -> 6790 rubble); removed below screen y 158
082284  table  stage_03_floor_block_kinds stage_03_floor_block_kind  # by kind: offset from 082284 (to 64, 65, depth word, animation) and the fall flag index (kinds 0-2: 0, 3-6: 1)
20E5EE  var   stage_03_floor_block_falls u8    # [2]: stage_03_floor_block_start clears both; spiked_roller_sink_and_jump sets [0] when it starts sinking and [1] when it lands
struct stage_03_floor_block_kind 4 rom    # a record of stage_03_floor_block_kinds (082284), by kind 0-6: stage_03_floor_block_start "movea.w 82284[PC](R0), R10" (08007A), then [R10+]; census ROM 082284-08229F
field stage_03_floor_block_kind 00 shape u16  # "movz.hw [R10+], R11; add.w #82284, R11" (080080, 080083): offset from 082284 of the kind's stage_03_floor_block_shape: 1C, 28, 34, 40, 1C, 34, 34
field stage_03_floor_block_kind 02 fall_flag u16  # "mov.h [R10+], 34[R20]" (08009D): speed_x_fraction, which stage_03_floor_block_falls byte the block waits for: 0 for kinds 0-2, 1 for 3-6
struct stage_03_floor_block_shape 4 rom   # a block's sprite offsets and depth offset; its stage_03_floor_block_animation follows (080099 "mov.w R11, 48[R20]"): stage_03_floor_block_start reads it with [R11+] from 080083 "add.w #82284, R11"; census ROM 0822A0-0822CF (four, 12 bytes apart)
field stage_03_floor_block_shape 00 sprite_offset_x s8  # "mov.b [R11+], 64[R20]" (08008A): 0, 0, 0, 30
field stage_03_floor_block_shape 01 sprite_offset_y s8  # "mov.b [R11+], 65[R20]" (08008F): 0, 30, 30, 50
field stage_03_floor_block_shape 02 depth_offset u16  # "mov.h [R11+], 38[R20]" (080094): speed_y_fraction, taken from screen_y for the depth: 48, 48, 48, 18
struct stage_03_floor_block_animation 8 rom  # a block's animation, after its shape (stage_03_floor_block.animation, 48): last frame 2, then the block's sprite (6780, 6770, 6778, 6798) and its crumbling (6788, 6790; 67A0, 67A8 for the fourth); census ROM 0822A4-0822CF
field stage_03_floor_block_animation 00 last_frame u16  # 2 (animate_step reads it through the object's animation)
field stage_03_floor_block_animation 02 first_frame u16  # read in place: "mov.h 2[48[R20]], 4[R20]" (0800D9), the block's sprite before it falls

# ======== Spiked roller: build and update ========
080160  func  spiked_roller_start              # stage 3 lists (kinds 0, 1, 5, 2 at 0000, 1700, 6B80, 9700) and stage E (kind 4): reads spiked_roller_kinds, allocates the 5 parts (spiked_roller_layouts, starting frames from spiked_roller_part_frames), becomes chaser_object if none, leader state from the record, players_frozen when it is 0, spawn_roller_back_zone; kind 2 (first state E) waits hidden at z 50 with speed_z -100000 and speed_x 8000 (16.16) for spiked_roller_drop_in
080372  func  spiked_roller_update             # every part's handler: jump table on field 80 (states 0-F); then, unless field 9A was set (removed, clearing chaser_object if it is the leader) or flag 1 (hidden), add_to_zone_list and drawn (queue_sprite_at_base, or at the previous floor while falling)
0803BB  func  spiked_roller_update_case_00     # state 0: spiked_roller_chase
0803BE  func  spiked_roller_update_case_01     # state 1: spiked_roller_fall_off_ledge
0803C1  func  spiked_roller_update_case_02     # state 2: spiked_roller_follow_leader
0803C4  func  spiked_roller_update_case_03     # state 3: spiked_roller_drop_in
0803C7  func  spiked_roller_update_case_04     # state 4: spiked_roller_ram (no record or event sets it)
0803CA  func  spiked_roller_update_case_05     # state 5: spiked_roller_remove
0803CD  func  spiked_roller_update_case_06     # state 6: spiked_roller_crash_into_machine
0803D0  func  spiked_roller_update_case_07     # state 7: spiked_roller_sink_and_jump
0803D3  func  spiked_roller_update_case_08     # state 8: spiked_roller_fall
0803D6  func  spiked_roller_update_case_09     # state 9: spiked_roller_burst_out
0803D9  func  spiked_roller_update_case_0a     # state A: spiked_roller_smash_wall
0803DC  func  spiked_roller_update_case_0b     # state B: spiked_roller_wait_for_turn
0803DF  func  spiked_roller_update_case_0c     # state C: spiked_roller_wait_to_burst_out
0803E2  func  spiked_roller_update_case_0d     # state D: spiked_roller_shift_box
0803E5  func  spiked_roller_update_case_0e     # state E: spiked_roller_wait_hidden
0803E8  func  spiked_roller_update_case_0f     # state F: spiked_roller_spawn_fixed_zone
0803EB  func  spiked_roller_chase              # state 0 (leader): script records (field 8A) set threshold 88, factor 8E, base speed 90, wait 82 (a record with no wait sets field 47 (kind) to 18 and clears players_frozen); speed = 90 on screen past 88 else max(camera speed (00AFB3) x 8E >> 8, 90); moves along field 94 (0 -y, 1 +x, 2/4 +y, 3 -x); follows the floor and falls (state 1) when it drops by more than 8 (kinds 0, 1, 3, 4); backs off while player_dropping_in/player_mishap/no_player_in_play; sound B every spiked_roller_rumble_delays frames on screen; spiked_roller_events
080757  func  spiked_roller_fall_off_ledge     # state 1: palette word | 4000 (another priority group), then spiked_roller_fall (walk_03 frames 10160-10213: kind 0 drops out of view near scroll 1B80 and chaser_object is cleared)
08075E  func  spiked_roller_fall               # states 1 and 8: speed_z += 1000, animation speed 1; field 9A = FF (removed) below screen y 130
08078E  func  spiked_roller_follow_leader      # state 2 (the four other parts): copy the leader's (field 86) y and speed_y (or x and speed_x for directions 1/3), kind (47), height, box offsets, z and floors; follow its states 1 and 8; removed when the leader is in state 5
080885  func  spiked_roller_wait_hidden        # state E (kind 2, stage 3 at 9700): hidden (flag 1) until scroll_position 9B00 or machine_fire_done, then state 3
0808A6  func  spiked_roller_drop_in            # state 3: leaps up and comes down (speed_z += 8000, also moving by speed_x/y) onto z 0: camera_shake_active, sound 20, state 0 (walk_03 frame 36880, scroll_position 9B00)
080900  func  spiked_roller_ram                # state 4: jump table on field 98 (080944-08094D); sound B on its own timer. Never runs: no script or event sets state 4
080944  func  spiked_roller_ram_case_00        # spiked_roller_ram_charge
080947  func  spiked_roller_ram_case_01        # spiked_roller_ram_pause
08094A  func  spiked_roller_ram_case_02        # spiked_roller_ram_back_off
08094D  func  spiked_roller_ram_case_03        # spiked_roller_ram_wait
080950  func  spiked_roller_ram_charge         # moves -y 4 a frame (chaser_speed 40) until y <= the event coordinate - 50: camera_shake_active, roller_ram_hit, sound 7; back to state 0 when off screen or after 4 charges (roller_ram_count)
080A05  func  spiked_roller_ram_pause          # stops for 3C frames, then backs off
080A32  func  spiked_roller_ram_back_off       # moves +y 1 a frame (animation reversed) back to the event coordinate
080A7E  func  spiked_roller_ram_wait           # 3C frames (longer while no_player_in_play), then charges again
080AB5  func  spiked_roller_remove             # state 5: field 9A = FF, spiked_roller_update removes the part (and clears chaser_object)
080ABD  func  spiked_roller_crash_into_machine # state 6 (kind 5, event at 8300 when y <= 0C80): rolls -y at chaser_speed 10 (50 once scroll_position >= 8600) until y <= 0AE0: camera_shake_active, sound 6, machine_wrecked and scroll_resume_request; then state 5 once it is below screen y 158 (walk_03: game mode 0x16 at 8610 ends at frame 31661)
080BBC  func  spiked_roller_sink_and_jump      # state 7 (kind 2, event at 9F00 when x >= FA63, stage 3 finale): sound D, stage_03_floor_block_falls[0], sinks 40 into the floor, then jumps (speed_z -6, speed_x 3, gravity 3000) and lands: state 8, stage_03_floor_block_falls[1], camera_shake_active, goal_unlocked, sound D (walk_03 frames 38440-38500)
080C90  func  spiked_roller_burst_out          # state 9: after field 82 frames becomes chaser_object (state 0), scroll_resume_request, sound 7, and roller_wall_fragment_count pieces fly from it (rock_debris_fly, roller_wall_fragments): walk_03 frame 10213, kind 1 bursts out with grey wall pieces
080D50  func  spiked_roller_smash_wall         # state A (kind 4, stage E event at 0DEA when y <= 0640): kind (47) C, chaser_speed 0; stage E sets chaser_stopped (brick_wall_wait bursts: stage_0E frames 5900-5930); state 5 once off screen (margins 68/70)
080D8E  func  spiked_roller_wait_for_turn      # state B (kind 5): waits until chaser_object is free, then becomes it (state 0), height 40
080DB7  func  spiked_roller_wait_to_burst_out  # state C (kind 1): hidden until chaser_object is free (kind 0 fell), then state 9 with 3C frames
080DD6  func  spiked_roller_shift_box          # state D (kind 1 event at 6F00 when x <= F74B): hitbox_offset_y 8A -> DA (the box moves 50 to +y), then spiked_roller_chase
080DE8  func  spiked_roller_spawn_fixed_zone   # state F (the next kind 1 event, same test): an invisible hazard box at F753,114A z 470 (box 100 x F3, kind (47) C, handler roller_fixed_zone_update) where the roller's back zone ends (its limit is F74B); then state 0
080E59  func  roller_fixed_zone_update         # the box from spiked_roller_spawn_fixed_zone: add_to_touchable_list until scroll_position 7600, then removed (walk_03 frames 28050-28650)
082FAC  func  spawn_roller_back_zone           # spiked_roller_start: an object of kind (47) 18, flags 40000002, field 80 = the limit from 08340D record [roller_back_zone_index], parent = the leader
082FED  func  roller_back_zone_update          # copies the leader's box and position and extends it 200 deep behind it (1A2 wide, across all five parts); add_to_zone_list; removed when the leader passes the limit along its direction (kind 0 y >= 10A0, kind 1 x <= F74B, kind 2 x >= FA63, kinds 3/5 y <= 0AF0, kind 4 y <= 0650)
082066  table  spiked_roller_kinds     u16     # 6 offsets from 082066: u8 part count (5), u16 offset into spiked_roller_layouts, u16 first state (0/C/E/0/0/B), u16 back zone record (3-7), then the chase script (10-byte records: scroll position, screen threshold, catch-up factor, base speed, wait; a negative factor word ends)
08219C  table  spiked_roller_layouts   u8      # 16-byte groups at +00/+10/+20/+30: s8 dx, s8 dy between parts, then one part record: sprite flags, hitbox sizes, direction (field 94), flags (field 30: 40000002, 80 = animation backwards), hitbox offsets
0821DC  table  spiked_roller_part_frames u8    # starting animation frame of each part (00 06 03 08 05: the parts do not turn in step)
elements spiked_roller_start_frames u8   # what roller_part_frame_cursor walks (spiked_roller_part_frames, 080228 "movea.b 821DC[PC], 70F2[R25]"): each part's starting frame, "mov.b [70F2[R25]], 46[R20]" (080300), then "inc.w 70F2[R25]" (080307); census ROM 0821DC-0821E0: 00 06 03 08 05
0822D0  data  spiked_roller_animation          # last step 8, sprites 5E20-5E60 (the roller turning); also used by the stage 4 door rollers
0822D2  table  spiked_roller_frames    u16     # the animation's sprite words (spiked_roller_start and spawn_door_rollers index it with field 46)
0822E4  table  spiked_roller_animation_speeds u8 # [min(chaser_speed >> 4, 5)]: 04 04 03 02 01 01 (faster turning at higher speed)
082396  table  spiked_roller_rumble_delays u8  # [chaser_speed >> 4 clamped 0-4]: frames between rumble sounds (B): 46 3C 28 1E 14
082306  table  spiked_roller_events    u16     # 6 offsets from 082306 (field 72), then 8-byte records: scroll position, test (0/4 y <=, 1 x >=, 2 y >=, 3 x <=), coordinate, new state; FFFF never
082302  table  spiked_roller_event_coords u16  # spiked_roller_events - 4: indexed by field 72 after an event fired it reads that event's coordinate (only the unused ram state does). The C reads it as spiked_roller_event [-1].coordinate (event_offset's link); the name marks where the assets cut the table
08234C  table  roller_wall_fragment_count u16  # 6 (spiked_roller_burst_out)
08234E  table  roller_wall_fragments   rock_debris_piece  # 6 x 6 words: sprite flags (0 = growing toward the viewer), dx, dy from the roller's screen position, speed x, y (12.4), animation offset into rock_debris_animations
struct rock_debris_piece C rom           # a piece of rock that flies off as a rock_debris_fly object: roller_wall_fragments (08234E: spiked_roller_burst_out [R12+] 080CDC-080CFD) and ram_target_debris (085ED5: ram_target_burst [R12+] 085606-085627); stage_00_rock_pieces (0955A2) has the same layout; census ROM 08234E-082395, 085ED5-085F04; stage_00_rock_pieces too: update_stage_05_event reads one with [R12+] (094B56)
field rock_debris_piece 00 sprite_flags u16  # "mov.h [R12+], 2[R11]": 1000, or 0 (growing toward the viewer)
field rock_debris_piece 02 offset_x u16   # "add.h [R12+], E[R11]": onto the screen x it starts from (FFC0-0)
field rock_debris_piece 04 offset_y u16   # "add.h [R12+], C[R11]": onto the screen y
field rock_debris_piece 06 speed_x u16    # "mov.h [R12+], 34[R11]": speed_x_fraction (12.4: FF40, FFE0, FF80...)
field rock_debris_piece 08 speed_y u16    # "mov.h [R12+], 38[R11]": speed_y_fraction
field rock_debris_piece 0A animation_offset u16  # "movz.hw [R12+], R0" + 9567E (rock_debris_animations, 080CFD) or 85F05 (ram_target_debris_animations): into 48 (0, A, 14...)
20E5D4  var   chaser_object            u16 object     # the current chaser: the roller leader or tornado_start (stage A), fire_front_init (stages B/C), propeller_machine_init (stage 10); cleared when the roller is removed or falls, by fire_front_chase and start_stage; the player blown away in state 28 (002CEC) flies from it; 00F1F1, 00F5F1, 011259, 014B59, 015756, 084ECE react to it
20E556  var   chaser_speed             s16     # the chaser's current speed in 1/16 pixel a frame (spiked_roller_chase and the other chasers lava_flow_advance, icicle_dropper_move, tornado_move, sand_block_leader_move, biting_head_chase, fire_front_chase, geyser_front_rise); read by the follower parts, boulder_roll, spreading_flame_burn
20E61A  var   players_frozen           u8      # while set check_player_jump returns at once and player_steer zeroes the trackball; set by spiked_roller_start (first state 0) until the first moving script record, by spiked_press_start until its frame C8, by tornado_start (cleared by tornado_move) and game mode 0x0D (with event_0d_active, until scroll A00); stages.txt leaves it to the players helper; start_stage clears it
20E670  var   roller_back_zone_index   u16     # spiked_roller_start: the kind record's back zone index, passed to spawn_roller_back_zone
struct spiked_roller_kind 7 rom          # a roller kind's record (spiked_roller_kinds: an offset from 082066 by kind): spiked_roller_start "movea.b 82066[PC](R11), R10" (080176), then [R10+] (08017C-08018B); its chase script (spiked_roller_chase_step) follows: spiked_roller.path = it + 7 (080191); census ROM 082072-08219B
field spiked_roller_kind 00 part_count u8  # "mov.b [R10+], 70EA[R25]" (08017C): roller_parts_left, 5
field spiked_roller_kind 01 layout u16     # "movz.hw [R10+], R11" (080182), + 8219C (080196): offset of its spiked_roller_layout (0, 10, 20, 30)
field spiked_roller_kind 03 first_state u16  # "mov.h [R10+], 70F6[R25]" (080185): roller_first_state (0, C, E, 0, 0, B)
field spiked_roller_kind 05 back_zone u16  # "mov.h [R10+], 6670[R25]" (08018B): roller_back_zone_index (3-7)
struct spiked_roller_chase_step A rom    # a record of a roller kind's chase script (after its spiked_roller_kind; spiked_roller.path, 8A): spiked_roller_chase_read_script reads it from 08046A "mov.w 8A[R20], R10" and keeps the next (0804B1); census ROM 082079-08219B (FFFF FFFF FFFF ends each)
field spiked_roller_chase_step 00 position u16  # "cmp.h [R10+], 6435[R25]; bl" (08046F): the scroll_position the record waits for (unsigned)
field spiked_roller_chase_step 02 screen_threshold s16  # "mov.h [R10+], 88[R20]" (080499): F0, 140, 168, FF88...
field spiked_roller_chase_step 04 catch_up_factor s16  # "test.h 2[R10]; bp" (080477): a negative one ends the script; "mov.h [R10+], 8E[R20]" (08049F)
field spiked_roller_chase_step 06 base_speed s16  # "mov.h [R10+], 90[R20]" (0804A5)
field spiked_roller_chase_step 08 wait u16  # "mov.h [R10+], 82[R20]" (0804AB): wait_timer (B4, 3C or 0); 0 sets kind 18 and clears players_frozen
struct spiked_roller_layout 10 rom       # a group of spiked_roller_layouts (08219C, by the kind's layout offset 0/10/20/30): the step between the parts, then their part record (spiked_roller_part, kept in roller_part_record): spiked_roller_start 080196 "add.w #8219C, R11"; census ROM 08219C-0821DB
field spiked_roller_layout 00 step_x s8   # "movs.bw [R11+], R0; sha.w #10, R0" (08019D): roller_step_x (D4, 0, 0, 2C)
field spiked_roller_layout 01 step_y s8   # "movs.bw [R11+], R0" (0801A9): roller_step_y (0, 2C, D4, 0)
struct spiked_roller_part E rom          # the part record of a spiked_roller_layout (its + 2, roller_part_record): every part copies it with [R10+] from 0802AD "mov.w 70EC[R25], R10"; census ROM 08219E-0821DB
field spiked_roller_part 00 sprite_flags u16  # -> 2 (0802B2): 1004, 1044
field spiked_roller_part 02 hitbox_size_x u16  # -> 10 (0802B7): 8C, 80, 80, EC
field spiked_roller_part 04 hitbox_size_y u16  # -> 12 (0802BC): 90, B8, B8, 90
field spiked_roller_part 06 path_direction u16  # -> 94 (0802C1): 2, 3, 1, 0
field spiked_roller_part 08 flags u32     # -> 30 (0802C7): 40000002, 40000082 (bit 7: the animation backwards)
field spiked_roller_part 0C hitbox_offset_x s8  # -> 7C (0802CC)
field spiked_roller_part 0D hitbox_offset_y s8  # -> 7D (0802D1)
struct spiked_roller_event 8 rom         # a record of spiked_roller_events (after its 6 offsets; spiked_roller.event_offset from 082306): spiked_roller_chase_check_event 0806FB "add.w #82306, R10", then [R10+]; census ROM 082312-08234B (a position FFFF never comes)
field spiked_roller_event 00 position u16  # "cmp.h [R10+], 6435[R25]; bl" (080702): the scroll_position it waits for
field spiked_roller_event 02 test u16     # "movz.hw [R10+], R0; add.w R0, R0; jmp 8071B[PC](R0)" (080710): 0/4 y at most, 1 x at least, 2 y at least, 3 x at most
field spiked_roller_event 04 coordinate s16  # "cmp.h [R10+], 52[R20]" / "4E[R20]" with ble, bge, bgt (080723-08073E, signed): F74B, 0C80, FA63...; spiked_roller_ram_back_off reads it again from the event before event_offset ([-1])
field spiked_roller_event 06 state u16    # "movt.hb [R10+], 80[R20]" (080745): the roller's new state (its low byte: D, F, 6, 7...)
20E5EB  var   roller_ram_count         u8      # spiked_roller_ram_charge: charges so far (state 4 never runs)
20E5F3  var   roller_ram_hit           u8      # FF when a ram charge hits, cleared by spiked_roller_chase every frame; only unused code reads it (ram_target_check_hits, ram_target_part_update)

# ======== Back zones of the other chasers (object_steps 08340D: limit, handler) ========
082E82  func  spawn_chaser_back_zone           # called by the chasers burning_boulder_start (stage 8), tornado_start (stage A), propeller_machine_init (stage 10) with R1 = record of 08340D: kind (47) 9, flags 40000002, field 80 = the record's scroll position, its handler, parent = the chaser (objects.txt calls it spawn_chaser_back_zone; records 3-7 of the same table are the roller back zone limits)
082EBE  func  chaser_back_zone_check_parent    # kind (47) 14; removed when the parent's field 96 is 3, else chaser_back_zone_update (stage 8 chaser, stage_05 scenario frames 6900-7650)
082ED3  func  chaser_back_zone_update          # removed once scroll_position reaches field 80; else the parent's box extended 200 behind it along its direction (field 24 >> 8) and add_to_zone_list

# ======== Catapults (stage 4, object_pair_templates 082250) ========
080E75  func  catapult_x_start                 # stage 4 at 5100 (three at FDC0, y 1613-169F): template 0 (spoon sprites 6BE0-6C08, kind (47) 6: the player is launched at angle 100, +x); waits on a platform
080E8E  func  catapult_y_start                 # stage 4 at 4C00 (six at y 1500): template 1A (sprites 6C10-6C58 with a separate base 6C18-6C48, kind (47) 7: launched at angle 200, +y)
080EA8  func  catapult_wait_for_platform       # add_to_rider_list until it stands on a platform (flag 8, field 74), then the template: handler, animation, offsets, the base part (catapult_base_update), field 36 = scroll_position + catapult_launch_delays[04] (first state 4 when non-zero, plus a dummy part)
08104E  func  catapult_update                  # removed when gone off screen; jump table on field 34 (0810C5-0810D1); follows its platform's z; add_to_touchable_list; drawn
0810C5  func  catapult_update_case_00          # state 0: catapult_wait_for_rider
0810C8  func  catapult_update_case_01          # state 1: catapult_swing_up
0810CB  func  catapult_update_case_02          # state 2: catapult_hold_up
0810CE  func  catapult_update_case_03          # state 3: catapult_swing_down
0810D1  func  catapult_update_case_04          # state 4: catapult_wait_for_scroll
0810D4  func  catapult_wait_for_rider          # frame 1 (pressed) while touched; touched with touch_distance <= 211: catapult_launch
081102  func  catapult_launch                  # state 1, animation speed 3, sound 1D: in state 1 check_player_contacts (kinds 6/7) puts the touching player in state F (launched)
081126  func  catapult_swing_up                # animate_around_anchor to the last frame (the spoon upright: 6C08 / 6C58), then state 2 for 32 frames
08113F  func  catapult_hold_up                 # wobbles between frames 4 and 5 every 3 frames, the last 1E frames on 4; then state 3 animating backwards; 081190 writes the frame with "mov.b R1, 46[R18]": into the block R18 holds (catapult_update's current_player: the catapult's child, its base), not the catapult's (R20); catapult_base_update overwrites it
0811A2  func  catapult_swing_down              # back to frame 0, state 0, kind (47) restored
0811D0  func  catapult_wait_for_scroll         # state 4: launches by itself when scroll_position reaches field 36 (no stage list uses a kind with a delay)
0811DD  func  catapult_base_update             # the second part: z and frame (parent's - 1) from the parent, animate_around_anchor, drawn if it has a sprite
081216  func  catapult_dummy_update            # handler of the third part catapult_wait_for_platform adds for delayed kinds: an empty function (rsr)
08224A  table  catapult_launch_delays  u16     # by field 04: 0400, 03D0, 0000 (stage 4 uses kind 2: no delay)
0821E2  data  catapult_x_animation             # 6 steps (sprite, dx, dy): 6BE0-6C08, the spoon swinging up (render)
0821FC  data  catapult_y_animation             # 6 steps: 6C10-6C58

081217  func  goal_unlocker_start              # stages B-F lists near their ends (B 5C00, C/E 3D00, D/F 1F00): sets goal_unlocked and removes itself

# ======== Stage 4: arrow boards, pebbles, propped board, boulders ========
08239C  func  arrow_board_start                # stage 4 lists (1D80, 2E00, 5610-7000): sprite 6EB8 (a wooden board with a red arrow, render), then arrow_board_wait_for_platform
0823CA  func  arrow_board_wait_for_platform    # add_to_rider_list until it stands on a platform (flag 8, field 74), then arrow_board_ride
0823E0  func  arrow_board_ride                 # z = its platform's z; grey palette (8400) while 20E59A (a player's +9F) is set; removed when gone off screen; drawn (walk_04 frames 5550-13800)
082434  func  pebble_start                     # stage 4 at 2F00/3300: sprite 6C60 (a small grey rock, walk_04 frame 8000), on its floor
08246E  func  pebble_update                    # drawn until gone off screen
082493  func  propped_board_start              # stage 4 at 8700 (1030,1650, z 230): sprite 6EA8 (a big board with nail heads, 272 x 208 pixels, render) with two parts board_prop_start at x-20, y-70/+70 (sprite 6EC0: wooden props); propped_board_object = it
0825A8  func  propped_board_update             # health A; once group_leaders[1] (the bridge collapse, moving +x there) reaches its x it falls (speed_z += 2000, removed below screen y 180); touched: shakes (screen x +-) and release_board_rings the first time; add_to_touchable_list (walk_04 frames 17250-18000)
082669  func  board_prop_start                 # projects, then board_prop_update
082679  func  board_prop_update                # falls with the board (its speed_z) and copies its shake offset; removed when gone off screen
0826CE  func  release_board_rings              # scatter_rings_fixed 8 times along the board (y - 40 + 20 n, x - 40, z - 90)
082712  func  boulder_start                    # stage 4 at 3600 (two, y 11C9/1109): sprites 6E78-6EA0 at zoom 200 (a rolling boulder, render), kind (47) 9, group_leader_0_held
0827A9  func  boulder_wait                     # until scroll_position 3B00, or 78 frames after 3800, then boulder_roll
0827D3  func  boulder_roll                     # health 7F; x = group_leaders[0].x - 30: it rolls just ahead of the bridge collapse (both moving -x, walk_04 frames 8250-8900), speed_x = -chaser_speed; clears group_leader_0_held; first time x <= 180: camera_shake_active, sound F; falls with no floor; touchable and rider lists; removed below screen y 180
083335  data  boulder_animation                # last step 5: 6E78-6EA0
20E652  var   group_leader_0_held      u8      # new group leaders start in 09177D and wait while it is set (spawn_group_leader), kind 5 groups until scroll 5100 (089F3B); set by boulder_start and tornado_start, cleared by boulder_roll, tornado_move and start_stage

# ======== Stage 4: the door at the start and the rollers behind it (game mode 0x0C) ========
08288A  func  door_wall_start                  # stage 4 at 0000: sprite 6EB0 at F1D2,0F31 (a brick wall with a stone doorway, s04_3560 render)
0828E4  func  door_wall_wait_to_burst          # drawn until scene_step >= 3, then 16 pieces (rock_debris_fly, random spread and speeds, animations from door_fragments) and removed (walk_04 frame 3520)
0829CC  func  spawn_door_rollers               # called by event_0c_start (game mode 0x0C): four roller parts behind the door (door_roller_layout, spiked_roller_animation, palette 82F0, kind (47) 4), handler door_roller_update
082A94  func  door_roller_update               # jump table on field 80 (082AC6-082ACF); removed when field 83 is set; animated; drawn from state 1
082AC6  func  door_roller_update_case_00       # state 0: door_roller_wait
082AC9  func  door_roller_update_case_01       # state 1: door_roller_drop
082ACC  func  door_roller_update_case_02       # state 2: door_roller_roll_out
082ACF  func  door_roller_update_case_03       # state 3: door_roller_ride
082AD2  func  door_roller_wait                 # health 7F; until scene_step >= 3
082AE5  func  door_roller_drop                 # falls (gravity 6000) moving y + 5 a frame out of the doorway; on the floor: camera_shake_active, state 2 (removed if there is no floor); s04_3700 render: the roller on the bridge
082B39  func  door_roller_roll_out             # y + 1 for 8 frames, then flag 400000 (the rock ledge it stands on, crumbling_floor_update 0914D2, falls at once) and state 3
082B52  func  door_roller_ride                 # z = its platform's (the bridge planks) until below screen y 130, then removed (field 83)
0833EF  table  door_roller_layout      door_roller_spawn  # read at 0833EF + 6 n (n = 1-4): x, y (F13C-F1C3, 0F40), starting frame byte
struct door_roller_spawn 6 rom           # a record of door_roller_layout (0833EF, records 1-4: door_rollers_left counts down from 4): spawn_door_rollers "movea.h [R0](R0), R0; movea.h 833EF(R0), R10" (0829E4, 0829E8: + 6 x n), then [R10+]; census ROM 0833F5-08340C
field door_roller_spawn 00 x s16          # "mov.h [R10+], 4E[R20]" (0829F0): x_int F13C, F169, F196, F1C3
field door_roller_spawn 02 y s16          # "mov.h [R10+], 52[R20]" (0829FA): y_int 0F40
field door_roller_spawn 04 first_frame u8  # "mov.b [R10+], 46[R20]" (082A04): animation_frame 5, 8, 3, 6 (the rollers do not turn in step)
struct door_fragment A rom               # a record of door_fragments (08334F; door_wall.fragment, 80): door_wall_wait_to_burst reads only its +8 (082957 "mov.w 80[R20], R10; add.w #8, R10", 08295F) and moves 80 on by A (08296C); its first four words (FFE0 0030 0020 FFF8...) are not read; census ROM 08334F-0833EE
field door_fragment 08 animation u16      # "movz.hw [R10+], R0" (08295F) + 9567E (rock_debris_animations) into the piece's 48: 0, A, 14
08334F  table  door_fragments          u16     # 10-byte records; door_wall_wait_to_burst only uses the word at +8 (animation offset into rock_debris_animations)

# ======== Stage 4 end: bouncing fireballs ========
082B7D  func  fireball_emitter_start           # stage 4 at 9A00: fireball_emitter_lists[04]: x, y, floor height, then the list (field 48); no sprite
082BC7  func  fireball_emitter_update          # while screen y >= -40 moves with the camera: camera_slope_speed along camera_slope_direction for its floor's slope (00B04D, sine_table), as ember_emitter_move; spawns each list record's object when scroll_position reaches it (x, dy, speed_y, speed_z, type from fireball_types), with a shadow (01EF60); removed after the last record
082D36  func  bouncing_fireball_update         # moves, bounces on the floor (speed_z halved, sound 21), kind (47) 17; drawn with its shadow (tail shared with 01F007); removed with the shadow when gone off screen (walk_04 frames 19500-20250: small fireballs, sprites 6EC8-6EE8)
083185  table  fireball_emitter_lists  u16     # by field 04 (only 0): offset from 083185 to x, y, floor height, then 12-byte records (scroll position, x, dy, speed_y, speed_z, type offset); a negative word at +A of the next ends
0832B9  table  fireball_types          u16     # 16-byte types: sprite flags, palette, zoom, height, width, offsets 64/65, bytes 12/13, animation offset from 083329 (the stage uses type 60: zoom 40, sprites 6EC8-6EE8)
struct fireball_emitter_list 6 rom       # the head of a fireball_emitter_lists list (an offset from 083185 by sprite_frame): fireball_emitter_start reads it with [R11+] from 082B89 "add.w #83185, R11"; its shots (fireball_shot) follow (082BAE "mov.w R11, 48[R20]"); census ROM 083187-08318C (the one list)
field fireball_emitter_list 00 x s16      # "mov.h [R11+], 4E[R20]" (082B90): x_int 120C
field fireball_emitter_list 02 y s16      # "mov.h [R11+], 52[R20]" (082B9A): y_int 1718
field fireball_emitter_list 04 floor_z s16  # "mov.h [R11+], 56[R20]" (082BA4): floor_z_int 0187
struct fireball_type 10 rom              # a record of fireball_types (0832B9, by a shot's type offset): fireball_emitter_update reads it with [R12+] from 082CAF "add.w #832B9, R12"; census ROM 0832B9-083328 (7 types)
field fireball_type 00 sprite_flags u16   # "mov.h [R12+], 2[R11]" (082CB6): 0004
field fireball_type 02 sprite_palette u16  # "mov.h [R12+], 6[R11]" (082CBB): 9190, 91C0
field fireball_type 04 sprite_scale u16   # "mov.h [R12+], 8[R11]" (082CC0): 100, 200, 300, 30, 40
field fireball_type 06 hitbox_height u16  # "mov.h [R12+], 66[R11]" (082CC5)
field fireball_type 08 hitbox_size_x u16  # "mov.h [R12+], 10[R11]" (082CCA)
field fireball_type 0A sprite_offset_x s8  # "mov.b [R12+], 64[R11]" (082CCF)
field fireball_type 0B sprite_offset_y s8  # "mov.b [R12+], 65[R11]" (082CD4)
field fireball_type 0C hitbox_centre_x s8  # "mov.b [R12+], 12[R11]" (082CD9)
field fireball_type 0D hitbox_centre_y s8  # "mov.b [R12+], 13[R11]" (082CDE)
field fireball_type 0E animation u16      # "movz.hw [R12+], R10; add.w #83329, R10" (082CE3): offset from blank_fireball_animation into 48 (0, 1A)

# ======== Stage 3: the machine at 8300 and the fire ========
08343E  func  machine_start                    # stage 3 at 8300 (F6DA,0A90): animation machine_animation (68B8-68E0: grey machinery, render), kind (47) C, box E8 x 20; a child with machine_fire_update (small flame 68F0-6908); spawn_sign kind 5 (sprite 6380: a GO! arrow) at F74D,0AED; clears machine_wrecked
083536  func  machine_run                      # animated until machine_wrecked, then frame 68E8 (the machine smashed); add_to_touchable_list; removed when gone off screen
08357F  func  machine_fire_update              # field 7F: 0/8 small flame until the machine is wrecked; 1 becomes an invisible box (F74C,0A90, kind (47) 13), rising_fire_object, fireballs from spawns_with_next_handler and a flame (flame_start); 2 climbs (z) by machine_fire_script like a chaser (screen y threshold 84, factor 86, base 88, waits 90; backs off on player mishaps), sound 9 every 3C frames; 3/4 moves +x 80 frames spawning fireballs in three lanes; 5 machine_fire_done; 6 until off screen; 7 removed
0852D1  data  machine_animation                # last step 5: 68B8-68E0
0852DF  data  machine_flame_animation          # last step 3: 68F0-6908
0852E9  data  machine_fire_script              # 10-byte records: height (when the top z reaches it), screen threshold, factor, base speed, wait: 470/300/1C0 (speeds 8, 10, 18), a negative factor ends at 10
struct machine_fire_script_step A rom    # a record of machine_fire_script (0852E9; machine_fire.path, 80): machine_fire_read_script reads it from 083728 "mov.w 80[R20], R10" and keeps the next (08375D); census ROM 0852E9-08530C: heights 470, 300, 1C0 (threshold 120, factor 10, speeds 8, 10, 18), then 0010 FFFF FFFF
field machine_fire_script_step 00 height s16  # "cmp.h [R10+], R0; bgt" (083735, R0 = z_int - hitbox_height): the fire's top must reach it (signed)
field machine_fire_script_step 02 screen_threshold s16  # "mov.h [R10+], 84[R20]" (083745)
field machine_fire_script_step 04 catch_up_factor s16  # "test.h 2[R10]; bp" (08373A): a negative one ends the climb; "mov.h [R10+], 86[R20]" (08374B)
field machine_fire_script_step 06 base_speed s16  # "mov.h [R10+], 88[R20]" (083751)
field machine_fire_script_step 08 wait u16  # "mov.h [R10+], 90[R20]" (083757): wait_timer (0)
20E5EC  var   machine_wrecked          u8      # set by spiked_roller_crash_into_machine, cleared by machine_start; machine_run switches to the smashed frame
20E5D6  var   rising_fire_object  u16 object.machine_fire  # the invisible climbing fire: machine_fire_update (stage 3) or bridge_fire_start (stage 8); rising_flame_update follows its top and removes itself when its field 7F is 7 (pointer typed: last_names.txt N1)
20E66F  var   machine_fire_done        u8      # machine_fire_update state 5: the stage 3 end roller (spiked_roller_wait_hidden) drops in at once instead of waiting for scroll_position 9B00; cleared by spiked_roller_start
0839E7  func  start_fireball_flipped           # spreading_flame_burn: sprite flags 1044 (x flip), palette 10C0, then init_fireball_flight
0839F7  func  fireball_start                   # handler of the objects machine_fire_update spawns (spawns_with_next_handler, lanes): flags 1004, palette 9010, then init_fireball_flight
083A05  func  init_fireball_flight             # animation fireball_animation; speeds to reach x = field 96, y = field 9A in 36 frames (24 in stage C); sound F; handler fireball_fly
083A99  func  fireball_fly                     # flies along an arc (z = base - sine x 140, field 24 + 9 a frame, E in stage C) with animate_and_move; on arrival plays the rest of its animation, then switches to its next handler (field 9C: a flame) 28 higher (walk_03 frame 31700, walk_0C)
083123  data  fireball_animation               # last step 17, steps of (sprite, dx, dy): 6608-66E0, a flame flying (render)
083B35  func  flame_start                      # animation flame_animation (6698-66B0: a big flame, render), palette 9010, on its floor
083B80  func  flame_burn                       # animated and drawn until gone off screen
083119  data  flame_animation                  # last step 3: 6698-66B0
083BAB  func  rising_flame_update              # jump table on field 7F (083BF7-083C00); removed when rising_fire_object is in state 7 or field 81 is set; animated, drawn above screen y 108 (walk_03 frames 31800-32550)
083BF7  func  rising_flame_update_case_00      # rising_flame_init
083BFA  func  rising_flame_update_case_01      # rising_flame_rise
083BFD  func  rising_flame_update_case_02      # rising_flame_spawn_next
083C00  func  rising_flame_update_case_03      # rising_flame_wait_offscreen
083C03  func  rising_flame_init                # flame_animation, palette 9010 (9260 in stage 8), base z = z
083C57  func  rising_flame_rise                # z - 1 a frame until 28 above its base
083C73  func  rising_flame_spawn_next          # when rising_fire_object (state 2) has its top (z - height) at or above this flame: another rising flame at the fire's x and top; then state 3
083CCE  func  rising_flame_wait_offscreen      # field 81 = FF once below screen y 110 or left of x -40
084E83  func  spreading_flame_start            # stages B/C: the chaser fire_front_init (an invisible fire front) starts four of them 38 apart, the fireballs of spreading_flame_burn the others; flame_animation, flags 1044, palette 10C0, on its floor
084ECE  func  spreading_flame_burn             # once chaser_object reaches its y: start_fireball_flipped from it to y - max(random 0-3F, distance to the chaser) (+40 in stage C when chaser_speed > 8), whose next handler is spreading_flame_start: the fire spreads ahead of the chaser (s0C renders); then only removed when far off screen (also when there is no chaser)
08535E  func  spawn_stage_03_end_pillar        # update_scroll, stage 3 after scroll_end (camera step 2): sprite 6600 (a brick pillar, render) at screen 146,75; never ran in the scenarios
08539F  func  stage_03_end_pillar_show         # drawn until 20E5ED (set by game mode 4), then removed

# ======== Stage 7: how to play ========
083CE8  func  how_to_play_button_hand_start    # stage 7 at 27C2: multi-part sprite (part lists from 085277: a hand and the button), screen 42,B5
083D3B  func  how_to_play_button_hand_update   # stage 7 only: the hand presses the button at the game_mode_timer values of how_to_play_button_presses; at game_mode_timer C8 spawn_message_box kind C at A0,7C (stage_06 frames 2560-3050, s10_5000)
083D95  func  how_to_play_trackball_hand_start # stage 7: sprite 71C8 (a hand) at screen FB,B3 and a second-pool part 71C0 (the trackball)
083E39  func  how_to_play_trackball_hand_update # the hand rubs back and forth (screen x/y +-A, -4) from timer 32 while the ball turns (animation 0852AD: 71B0-71C0); lit palette 8760 from timer A to BE; at timer A spawn_message_box kinds D (the title) and B; scroll mode 0, speed 10 from timer 20
083F99  func  how_to_play_panel_start          # stage 7: sprite 71F0 (the control panel strip) at screen 0,88
083FC3  func  how_to_play_panel_show           # drawn while stage is 7
0852B7  data  how_to_play_button_presses       # (game_mode_timer, frame) pairs: C8 0, E6 1, 140 2, 180 1, 1B8 2, 1F4 FFFF, 7FFF
0852AD  data  how_to_play_trackball_animation  # last step 3: 71B0 71A8 71B8 71C0 (the trackball part of how_to_play_trackball_hand_update)

# ======== Stage 6: the opening scene (game mode 0x10) ========
083FDC  func  opening_ground_start             # stage 6 at 0000: animation opening_ground_animation (7170-7190: the ground rushing toward the viewer, render) at screen A0,B8
084028  func  opening_ground_run               # removed once scene_step >= 2; shakes with stage_06_shake / stage_06_shake_sign; drawn (stage_06 frames 3510-3900)
08406B  func  opening_machine_start            # stage 6 at 0000: field 80 = FF, then opening_machine_update
08407B  func  opening_machine_update           # scene_step 0/1/4: a multi-part sprite (085243: a giant machine head with jaws, s6_3700) driven by field 8A (084157-084166); opening_machine_object; scene_step 2/3: it flies away into the distance (y + 1, zoom 4000 / (82 + 10)) with a shadow part (draw_opening_machine_shadow; s6_4100)
084157  func  opening_machine_update_case_00   # opening_machine_leap_in
08415A  func  opening_machine_update_case_01   # opening_machine_approach
08415D  func  opening_machine_update_case_02   # opening_machine_lunge
084160  func  opening_machine_update_case_03   # opening_machine_show_mouth
084163  func  opening_machine_update_case_04   # opening_machine_engulf
084166  func  opening_machine_update_case_05   # opening_machine_hold
084169  func  opening_machine_leap_in          # after scene_timer >= 3C: rises from below (speed -C, gravity 6000) and lands at y 63: stage_06_shake 800, sound A, next part list
0841DA  func  opening_machine_approach         # the shake decays (stage_06_shake - 80, stage_06_shake_sign flips); zoom grows 0.5 a frame until scene_timer passes C0
084216  func  opening_machine_lunge            # zoom growth accelerates (+1000 a frame), moves up once zoom >= 1B3, until zoom 32B
084250  func  opening_machine_show_mouth       # single sprite 7198 (the open jaws, render), palette 8510, then opening_machine_engulf
084279  func  opening_machine_engulf           # zoom growth +10000 a frame until 5B0, then palette 87A0 (black: the screen goes dark, s6_3900)
0842A7  func  opening_machine_hold             # an empty function (rsr)
08437A  func  draw_opening_machine_shadow      # the shadow part (7078, palette 87A0) at its offsets x zoom under the machine
0851A3  data  opening_ground_animation         # last step 4: 7170-7190
20E5BE  var   opening_machine_object   u16     # opening_machine_update; 006DE9 (the player's part of the scene) copies its zoom and screen position
20E5C8  var   stage_06_shake_sign      s8      # flips every frame while stage_06_shake decays (opening_machine_approach, update_stage_06_background); negative: the shake offset is subtracted (opening sprites, players)

# ======== Player pacing (stages 3, 4, 8) ========
0843C0  func  player_pacer_start               # stage lists (3 at 0900, 4 at 0300/3A00/7B00, 8 at 0A00): field 34 = pacer_end_positions[04], then player_pacer_update
0843D5  func  player_pacer_update              # until scroll_position reaches field 34 (then removed): clears the three speed_scale; with 2 players in play the one ahead along get_pacing_axis gets speed_scale D (13/16), with 3 the first D and the second E
0844FE  func  get_pacing_axis                  # byte 15 of the floor record (floor_blocks) under the first player in play = travel direction (0-4): R5 = coordinate field / 2 (29 = y, 27 = x), R6 = FFFF when it decreases
08519B  table  pacer_end_positions     u16     # 2600, 5000, 8F00, 3400

# ======== Stage 5: the cage, the spiked press, bubbles, the exit ========
084778  func  spiked_press_start               # stage 5 at 0000 (FD9E,02F0): sprite 7070 (a block of spikes, render), kind (47) 9, box E0 x 20, height E0, speed_y 2000; spiked_press_object; scroll mode 1, players_frozen; spawn_eggman_booth
08481D  func  spiked_press_update              # counts frames (current_game_mode 3 or 0x0A): at 14 a message box (kind F/1A/1B by caged_character) and sound 34, at 50 a voice (3F/5C/40 by caged_character); at C8 the BREAK!! sign and arrow (spawn_sign_2 kinds 1/2), players unfrozen, then it creeps +y (sound D every 3C frames; pauses while no_player_in_play or player_dropping_in); at 168 sign 3 (IN and an arrow, sprite 6330) at FE70,0480; stops at y 3D0 (field 84 counts on: check_player_reached_goal), scroll speed 5; removed when stage is not 5
084A02  func  cage_start                       # stage 5 at 0000 (FDA8,03E0): ten bars (cage_bar_start) 18 apart in x, clears cage_bar_hit, removed (r06_4500: the cage)
084ACD  func  cage_bar_start                   # projects, then cage_bar_update
084ADC  func  cage_bar_update                  # health 5; after the signs (spiked press frame C8), each hit by an attacking player bends it (7058 -> 7060, sound 21, cage_bar_hit), the next breaks it (7068 after 8 frames, no longer touchable); removed when stage is not 5 or gone off screen
085171  data  cage_bar_animation               # 7058 straight, 7060 bent, 7068 broken (render)
struct cage_bar_animation 8 rom          # cage_bar_animation (085171), the bar's animation (cage_bar.animation, 48): last frame 2, then the straight, bent and broken sprites 7058, 7060, 7068; census ROM 085171-085178
field cage_bar_animation 00 last_frame u16  # "cmp.h [R10+], R0; blt" (084B11): a hit shows the next frame while it is below this
field cage_bar_animation 06 broken_frame u16  # "mov.h 6[R10], 4[R20]" (084B52): 7068, the broken bar, 8 frames after the last hit
20E5CA  var   spiked_press_object  u16 object.spiked_press  # spiked_press_start; cage_bar_update waits for its frame count (82) to reach C8; check_player_reached_goal (stage 5) on its stopped time (84 >= 258); start_stage clears it (pointer typed: last_names.txt N1)
20E5C9  var   cage_bar_hit             u8      # set when a cage bar is first hit: the arrow over BREAK!! (sign_show_until_cage_bar_hit) and the player's hint sign (0035E5) stop
20E66E  var   caged_character          u8      # spiked_press_update frame 14: field 47 (the character, 0-2) of the first player in play's object; picks the message box and the voice of spiked_press_update
08462A  func  bubble_start                     # lists of stages 5, 8, B, C (field 04 = kind): z = bubble_heights[kind], remembers x/y, animation bubble_animation (6EF0-6F30: a yellow bubble that swells and pops, render), palette 9000 (| 4000 in stage 8, 10B0 in stages B/C)
0846B5  func  bubble_update                    # in stages 5, 8, B, C (current_game_mode not 0x20): draw depth = screen y, bubble_pop; else removed
0846E7  func  bubble_near_parent_update        # handler set by lava_column_become_bubble (091EC5, the tail of lava_column_land and lava_column_land_in_place): removed once its parent's y + B0 reaches its y; draw depth FFF0, bubble_pop
08470B  func  bubble_pop                       # removed when 20E5ED is set or gone off screen; animated; at each pop moves to a random spot 0-F from its base (not kind 6) with a random animation speed 1-8
085179  data  bubble_animation                 # last step 8: 6EF0-6F30
08518D  table  bubble_heights          u16     # z by kind: 0020 0200 04E0 0020 09E0 0550 0550
084558  func  exit_door_start                  # stage 5 at 0090 (FE84,049E): sprite 7348 (an orange door frame, probably), palette 91A0
08458B  func  exit_door_wait_for_switch        # when stage_05_switch_step is 2: sprite 71A0 (a red door panel) at 1E2,154C in the stage 0 area (s5_6800 render)
0845C2  func  exit_door_show                   # drawn until below screen y 130
0845DD  func  stage_05_floor_patch_start       # stage 5 at 0090 (FF40,0400): sprite 7050 (stone floor tiles, 256 x 128), flags 1008, palette B0E0
084610  func  stage_05_floor_patch_show        # drawn while stage is 5

# ======== Eggman's speech scripts (stages 3, 8, A at 0000; stage 9 from game mode 0x14) ========
08574A  func  eggman_speech_start         # field 82 = eggman_speech_scripts[04] (stage 3 kind 1, 8 kind 0, A kind 3), then eggman_speech_run
08576F  func  eggman_speech_run           # not while current_game_mode is 0x11: counts frames (80); at each record's delay runs its command: 0/4 removed, 1 spawn_eggman_booth (Eggman at the top, s03_3560), 2 spawn_message_box (x, y, kind, frames), 3 play_sound, 7 stops
085DE3  table  eggman_speech_scripts     u16     # offsets from 085DE3: (delay, command, arguments) records; e.g. kind 1: 10 spawn_eggman_booth, C message box 14 at A0,40 for 88 frames, sound 3B, end

# ======== Stage objects 0857E5-08A16E: scenery and stages 3, 8, 9, A ========
# Stage objects 0857E5-08A16E (stages 3, 8, 9, A and the shared scenery/script objects), plus 08574A/08576F.
# Renders: s32ref dumps of stage_05 (stage 8),
# a stage 9 walk (gen.py --stage 09 --bias -1 -1 0.8 --seed 2: reaches the end of stage 9), walk_0A and
# walk_03, drawn with render.py and with a per-object renderer that rebuilds each object's sprite entries
# as build_sprite_list does (scratch s89a/objrender.py).
# Touch kinds (field 47) are read through the player's contact switch 00E488: 0x13 and 0x22 (when not
# invulnerable) blow the player away (state 1F), 0x14 hurts with a tumble (state 20), 9/0xC/0x28 hurt
# unless the player is attacking (then he bounces off), 0x1B makes him fall over an edge (state 7, no floor),
# 0x1C sinks him in quicksand (player 114 bit 2), 0x1D is a zone where edges drop at once (player 114 bit 1),
# 0x1E sweeps him away in a swirl (state 28), 0x1F trips him (state 29).
# "Chaser" movement (boulder, lava flow, bridge fire, icicle droppers, tornado, sand block leader): a script
# of records (scroll position and/or a coordinate to reach, then 84 = screen position threshold, 86 = speed
# factor, 88 = minimum speed, 90 = delay); speed = camera ground speed (00AFB3 -> 20E510, or 00B04D ->
# 20E572 with the floor slope) * 86 >> 8, at least 88, and only 88 once the object is past the threshold
# on screen. While no_player_in_play/player_mishap/player_dropping_in (20E599/20E5A3/20E596) are set the
# object holds back (8E = FF, minimum speed 0, threshold moved off screen) for up to 0x300 scroll units.

# ======== Scenery sprites (stages 3, 8, B-10: stage_spawns_by_handler 0857E5, field 04 = kind) ========
0857E5  func  scenery_sprite_start                   # scenery_sprite_kinds[04]: sprite flags, sprite, palette, depth offset (8A), handler; z = floor height (kind D: 8D0000, z_int 8D); 88 = stage. Static sprites: the blue wall blocks at the start of stage 3 (png/s03_3560 objects 10, 16)
08585F  func  scenery_sprite_draw                    # kinds 0, 6, 8, 9, B, C: drawn at screen_y + 8A until 20E5ED is set (the stage's objects go when game mode 4, the map, starts)
085880  func  scenery_sprite_draw_until_offscreen    # kinds 1, 2, 3, 5, 7: drawn at screen_y + 8A; removed once gone off screen (01F0EF)
0858A5  func  scenery_sprite_draw_wide_margin        # kind D (stage 10): the same with a 0x50 margin (01F141)
08584C  func  scenery_sprite_draw_until_you_failed   # kind 4 (stage B): removed 0x3C frames into game mode 0x20 (YOU FAILED, game_mode_timer), else scenery_sprite_draw
0858D1  func  scenery_sprite_cycle_palette_start     # kind A (stage F): palette list scenery_sprite_palettes for animate_sign (84/85 = 0/6), sprite offset y -10
0858FE  func  scenery_sprite_cycle_palette           # animate_sign (00CB15) then drawn like scenery_sprite_draw
# 085D1F is symbols.txt's scenery_sprite_kinds (same layout): the scenery_sprite_start records; a rename to
# scenery_sprite_kinds would say what they are (14 kinds: sprite flags, sprite, palette, depth offset, handler).
085E7F  data  scenery_sprite_palettes                # animate_sign list of kind A: 5 palettes 1120-1170

# ======== Eggman's speech: eggman_speech_start, eggman_speech_run, eggman_speech_scripts (in the chasers section) ========
# What the renders add: at the start of stage 8 (stage_05
# frame 6680) Eggman's portrait drops in at the top of the screen (012B24, handler 012B79) and a red caption
# box with a line of text (0953BF) appears with a voice sound; scripts 0-3 play voice sounds 74, 3B, 7A, 30
# with captions 11, 14, 9, 15. Kind 2 is not a stage start: game mode 0x14 of stage 9 (scroll 1800, scene_step
# 1) spawns it together with the first icicle dropper (088294 kind 0). Commands: 0/4 end, 1 portrait, 2 caption, 3 sound, 7 stop (case 0857A2).

# ======== Stage 3: fire vents (085925 at scroll 8300) ========
# walk_03 frames 31200-36300: wall grilles glowing orange (palette cycle) that blast a burst of fire.
085925  func  fire_vent_spawner_start                # stage 3, scroll 8300: fire_vent_cursor = fire_vent_list, then fire_vent_spawner_update
085936  func  fire_vent_spawner_update               # when scroll_position reaches the next fire_vent_list record: a vent (sprite 6A48, palette 9110, solid box 90 x 48, height 28, touch kind 22) at its x/y/z with its delay in 8A; removed at the end of the list
0859FB  func  fire_vent_start                        # project_object, then fire_vent_update
085A0A  func  fire_vent_update                       # palette cycle (fire_vent_glow_palettes), faster when the delay is <= 1E; the delay counts down while on screen (screen y >= 18); at 0: dark grille (6A50), fire_vent_burst_animate child, sound A; touchable from then on (kind 22 = blows the player away, kind C after the blast)
085B2E  func  fire_vent_burst_animate                # the burst of fire (animation fire_vent_blast_animation, palette per frame from fire_vent_blast_palettes) at map +3A/-31 from the vent; at its end it sets the vent's 88 = FF and is removed
20E5D8  var   fire_vent_cursor  u32 fire_vent_spawn                  # pointer to the next fire_vent_list record (fire_vent_spawner_start / _update only)
085C33  data  fire_vent_list                         # 10-byte records: scroll position, x, y, z, delay (frames on screen before the blast); FFFF ends (12 vents, 859B-930B)
struct fire_vent_spawn A rom             # a record of fire_vent_list (085C33; fire_vent_cursor): fire_vent_spawner_update reads it with [R12+] from 085936 "mov.w 65D8[R25], R12" and moves the cursor on by A (0859F4); census ROM 085C33-085CB4
field fire_vent_spawn 00 position u16     # "cmp.h [R12+], 6435[R25]; bl" (08593B): the scroll_position it waits for (859B-930B; FFFF ends)
field fire_vent_spawn 02 x s16            # "mov.h [R12+], 4E[R11]" (085959): x_int
field fire_vent_spawn 04 y s16            # "mov.h [R12+], 52[R11]" (085963): y_int
field fire_vent_spawn 06 z s16            # "mov.h [R12+], 42[R11]" (08596D): z_int
field fire_vent_spawn 08 blast_delay s16  # "test.h 6[R12]; bp" (085944): a negative one removes the spawner; "mov.h [R12+], 8A[R11]" (085977): fire_vent.blast_delay, 0-78
085CB5  data  fire_vent_blast_animation              # 13 frames 69A8-6A40
085CDF  data  fire_vent_glow_palettes                # animate_sign list: 9110 9150, 9170 9180, ...
085CF7  table fire_vent_blast_palettes  u16          # palette per frame of the blast (90B0-9100)

# ======== Group leader hold (stage 4 at 8B00-9500, stage 9 at 3300) ========
085B75  func  group_leader_hold_start                # group_leader_hold_records[04]: leader number (80), direction (24), coordinate (82), end scroll position (84), which flag (86): sets group_leader_0_held (20E652, 86 = 0) or 20E654 to FF, so spawn_group_leader starts those leaders waiting (09177D)
085BC2  func  group_leader_hold_wait                 # clears the flag and is removed when scroll_position reaches 84 or when group_leaders[80] passes 82 along direction 24 (0/4 y down to it, 1 x up, 2 y up, 3 x down)
085F4D  table group_leader_hold_records  u16         # 5 offsets from 085F4D to 8-byte records (u8 leader, u8 direction, u16 coordinate, end position, flag)

# ======== Stage 8: the collapsing log bridge (spawn_object_groups_2 at 7700: 43 planks, record +048) ========
# stage_05 frames 15500-17600: a bridge of logs; when game mode 0x1C (scroll 7A8D) ends its wait (09514C
# sets event_1c_riding, 20E647) one end comes loose and the bridge swings down to hang as a ladder, the players holding on
# (state 35 with ridden_object); the arrow sign 16 shows; then the bridge fire rises from below.
085F80  func  collapsing_bridge_plank_update         # 6E = state, 6F = plank number (0 = the one fixed at x C17): 0 init (angle 80 = 300.0); 1 a sagging bridge plank (platform list, sag from bridge_0/1 tables like the other bridges) until event_1c_riding (20E647) is set (plank 0: sound 9); 2 its angle (80, 16.16) = the previous plank's (9A) + bridge_swing_angle, not below 200 (straight down), and it hangs 10 units from that plank in that direction (sine/cosine * 40); plank 0 falls from 300 to 200, then swings with decay (sound 20 per swing), after 6 swings stops, spawn_sign 16 at BA0/EA4/660 and clears event_1c_ride_phase (20E649); removed at scroll 9000
20E620  var   bridge_swing_angle  s32                # 16.16 angle added to every plank's angle down the chain (collapsing_bridge_plank_update only)
20E624  var   bridge_swing_speed  s32                # change of bridge_swing_angle per frame; reversed and scaled by 5E/100 at each swing
20E628  var   bridge_head_angle  u16                 # plank 0's angle while it falls; written, nothing reads it
087A91  data  collapsing_bridge_plank_animation      # 7 frames A788-A7A8 (the logs roll while the bridge sags)

# ======== Stage 8: the burning boulders (08626F at scroll 0A00, kinds 0 and 1) ========
# stage_05 frames 6900-9360: two huge orange boulders with flames on top roll down after the players;
# the leader falls off at scroll 3300+, then drops onto the seesaw.
08626F  func  burning_boulder_start                  # burning_boulder_records[04]: x, start delay (98), speed script (80; FFFFFFFF = follower: wider box 120, spawn_chaser_back_zone 2); y = 3E0 - kind * 60; sprite AA58 palette 51B0, solid box 90 x 90, height A0, touch kind 14; the leader is lead_burning_boulder; four burning_boulder_flame parts chained through 28
0863EA  func  burning_boulder_update                 # 96 = state: 0/6 waits 98 frames, falls to the floor (camera shake, sound 7); 1 the leader rolls along y at the chaser speed (script), rolling animation speed from burning_boulder_roll_rates, sound D every 10 * animation speed frames, falls off where no floor after scroll 3300; 2 the follower copies lead_burning_boulder's speed; 3 falls away (x +167D2, y +1638E a frame), flames removed, the follower removed; 4 waits for 20E63B (or 258 frames) and 1E more; 5 drops from (180, EF0, 970) onto the seesaw: camera shake, sound A at z A28, 20E63A = FF at z >= 9E0
086839  func  burning_boulder_flame_start            # burning_boulder_flame_layout[47]: screen offset from the boulder and animation (burning_boulder_flame_animations + offset)
086881  func  burning_boulder_flame_flicker          # while the boulder rolls (its 6E > 0) plays its flame animation at the boulder's screen position + offset, then waits a random 0-3F frames; removed with flag 400
20E672  var   burning_boulder_release_timer  s16     # frames the leader waited in state 4; at 258 it sets 20E63B itself  (TYPE: its one compare is signed)
087B61  data  burning_boulder_records                # 2 x 8 bytes: x, start delay, speed script pointer (FFFFFFFF for the follower)
087B79  data  burning_boulder_roll_rates             # u8 animation speed by rolling speed >> 4 (5 4 3 2 2 2)
087B7F  data  burning_boulder_speed_script           # chaser records of the leading boulder (scroll position, y, 84, 86, 88, 90)
087B03  data  burning_boulder_roll_animation         # 6 frames AA58-AA80
087AA3  data  burning_boulder_flame_layout           # 4 x 6 bytes: screen dx, dy (above the boulder), animation offset from burning_boulder_flame_animations
087ABB  data  burning_boulder_flame_animations

# ======== Stage 8: falling embers (0868F8 at scroll 0000, 0900, 6100, 8C00) ========
0868F8  func  ember_emitter_start                    # ember_emitter_end_positions[04] -> 84; z = its floor height
08692B  func  ember_emitter_move                     # invisible: keeps near screen x 40 by moving along the direction in its floor block's byte +15 (0/4 y-, 1 x+, 2 y+, 3 x-) at the camera ground speed (00B04D), staying on the floor (kind 3 keeps its z); direction 4 moves y- but only while screen x <= 40, as 1 and 2 do, so it would drift away; every random 0-3F frames a falling ember at a random spot; removed at 84
086A9A  func  falling_ember_update                   # 80 = state: 0 moves up to the top of the screen; 1 falls (falling_ember_speeds, animation falling_ember_animation) to the floor (530 where none); 2 bursts into sparks (falling_ember_burst_animation, or the slope one) and is removed. Seen in stage_05 frames 7100-7500: a small falling flame, then a spray of sparks
087CD1  data  ember_emitter_end_positions            # u16 by kind: 3200 0100 8000 AF00
087B11  data  falling_ember_speeds                   # 4 x (speed_z, speed_y) >> 8, picked at random
087B21  data  falling_ember_animation
087B35  data  falling_ember_slope_burst_animation    # used when the floor under it has a slope
087B4B  data  falling_ember_burst_animation          # sparks (ABB0-ABF8)

# ======== Stage 8: game mode 0x1A (scroll 3100-6000): carriers and the seesaw ========
# The players (states 26, 31) are pulled along with scene_object, then the leading boulder drops onto a
# seesaw (frame 9300: a plank with a round pad at each end on a pivot) that tilts and the three carriers
# fly the players up to the metal platform at 5800.
086B6E  func  scene_carrier_start                    # scene_carrier_records[04]: x, y, z, speed x/y/z, end scroll position (80); kind 0 becomes scene_object (20E62E), kinds 1-3 player_carriers[kind - 1] (20E640)
086BD3  func  scene_carrier_move                     # invisible; adds its speeds every frame (kinds 1-3 only once 20E63A is set); removed at 80
086C07  func  seesaw_start                           # stage 8, scroll 4A00: multi-part sprite (seesaw_parts copied to a second-pool block) at (70, F68, 9E0)
086C71  func  seesaw_tilt                            # once 20E63A is set (the boulder landed) plays seesaw_animation once on the plank part; removed at scroll 4D00
087BD9  table scene_carrier_records  u16             # 4 offsets from 087BD9 to 20-byte records: x, y, z, s32 speeds x/y/z, end scroll position
087BAB  data  seesaw_parts                           # multi-part sprite list: 3 parts (B348, B310, B340)
087BC5  data  seesaw_animation                       # frames B310-B330 for the plank part

# ======== Stage 8: the metal platform and the fire wall (scroll 5800) ========
# frame 9470: the players land on a metal platform with railings, trapped (player state 32); 12C frames
# later it flies up off the screen and two pillars of fire burst up next to it.
086CD3  func  metal_platform_base_start              # sprite AFB8 (the platform's frame and supports) at z 530
086D11  func  metal_platform_base_draw               # drawn until off screen (01F18C, margins 40/10); it stays when the platform leaves
086D47  func  metal_platform_start                   # sprite AFC0 (the deck) at z 530, delay 12C; second object: the far railing (AFC8 at 131, F24, state 3)
086DEB  func  metal_platform_update                  # 80 = state: 0/4 waits 12C frames; 1 metal_platform_lifted = FF, spawns the fire wall, rises (speed_z +6000 a frame after A frames) until screen y <= -58; 2 rising; 3 the railing: static until the deck leaves, then rises with it
086F3A  func  fire_wall_start                        # multi-part sprite (fire_wall_parts: two flame pillars) at (100, E47, 610), solid box 5B x F0, height 100, touch kind 14; sound 7
086FF5  func  fire_wall_rise                         # rises to z 550, animates its parts (00E0DE), touch list; removed at scroll 6800
20E648  var   metal_platform_lifted  u8              # FF when the metal platform flies off: starts the lava flow countdown (lava_flow_start clears it)
087C31  data  fire_wall_parts                        # 2 parts, sprite 7030
087D7D  data  fire_wall_animation

# ======== Stage 8: fire trenches (087048 at scroll 6200, 6600, 6A00, 6E00) ========
087048  func  fire_trench_start                      # sprite B200 palette 51F0 (a trench of glowing lava with stone edges), z 530
08708C  func  fire_trench_update                     # every 12C frames spawn_fire_trench_flames; removed once off screen (margin 70)
0870D0  func  spawn_fire_trench_flames               # three flame columns along the trench (y E47, E97, EE7): touch kind 14, box 40 x 80, animation fire_trench_flame_animation; sound 7 if on screen
08718B  func  fire_trench_flame_burn                 # animates once (then removed); its height (66) follows fire_trench_flame_heights[frame]; touch list
087C43  table fire_trench_flame_heights  u16         # collision height per animation frame: 10 18 28 40 58 70 80 A0...
087C89  data  fire_trench_flame_animation            # 22 frames B208-B258

# ======== Stage 8: the lava flow (08750E at scroll 6000) ========
# stage_05 frames 9900-16700: a sheet of lava advancing along x after the players, flames bubbling on
# it, steam puffs at its front edge, more sheets trailing behind.
08750E  func  lava_flow_start                        # multi-part sprite (lava_flow_parts) at z 530 (x and y stay as spawned), solid box 60 x E7, height 50, touch kind 14; script lava_flow_script; clears metal_platform_lifted
0875C9  func  lava_flow_wait                         # waits for metal_platform_lifted, then 78 frames; spawn_lava_steam, lava_flow_advance
0875E2  func  lava_flow_advance                      # chaser along x (lava_flow_script: until x 9CB, removed at scroll 7E00), animates (00E0DE), sound 10 while on screen, spawn_lava_flame, extend_lava_flow, touch list
08778B  func  extend_lava_flow                       # after scroll 6180, while the free lists allow and the object has no follower yet: a lava_flow_segment_follow object behind it (28/26 links, its own copy of the part list)
087803  func  lava_flow_segment_follow               # map position -40/+20 from the object before it (x - 50), copies its frame; also spawn_lava_flame and extend_lava_flow; removed at scroll 7E00 or past screen x -A0
0878AC  func  spawn_lava_flame                       # every random 0-1F frames a flame (lava_flame_animation or _2, at random) at a random spot on the lava
087969  func  lava_flame_burn                        # follows the lava at its offset (14/16), animates once, removed
0879AD  func  spawn_lava_steam                       # lava_steam_puff_count (4) puffs at lava_steam_puff_offsets around the front
087A0E  func  lava_steam_puff_update                 # white steam: plays lava_steam_animation, waits a random 0-3F frames, again; removed at scroll 7E00
087D0F  data  lava_flow_parts                        # 2 parts, sprite 7210, palette D0B0
087D05  data  lava_flow_animation                    # 4 frames 7210-7228 (00E0DE writes them to every part)
087D21  data  lava_flow_script                       # chaser records: x+ from the start, stop at x 9CB, end at scroll 7E00
087D41  data  lava_steam_animation
087D4F  table lava_steam_puff_count  u16             # 4
087D51  data  lava_steam_puff_offsets                # 4 x (x, y) offsets
087D61  data  lava_flame_animation
087D6F  data  lava_flame_animation_2

# ======== Stage 8: game mode 0x1C riders and the bridge fire ========
0871C4  func  ridden_object_start                    # created by spawn_player_ride (00E149) for a player outside x A60-AE0: box 10 x 10, becomes scene_object
0871E5  func  ridden_object_fall                     # in the rider list until it stands on a platform (flag 8, a bridge plank in 74)
0871FB  func  ridden_object_follow                   # takes its plank's x and z every frame (the player hangs on, state 35); removed at scroll 8D00. Seen at stage_05 frame 17000 at the bottom of the hanging bridge
08721F  func  bridge_fire_start                      # stage 8, scroll 8400 (C12, E70) under the hanging bridge: solid box 40 x 80, height 70, touch kind 13 (blows players away), z 868; script bridge_fire_script; three flame parts (handler 083BAB) above it
08730C  func  bridge_fire_rise                       # chaser upwards (z decreases) at the camera speed; 94 moves by a word read at 10E150 + its floor attribute (7A) times the speed and the position follows (00CC8D); sound 9 every 3C frames on screen; at the top (z - 66 <= 534) sets the event bits 208096 to FFFFFFFF once. Seen at stage_05 frame 17000: flames climbing the hanging log bridge
087CD9  data  bridge_fire_script                     # records (z or scroll position to reach, 84, 86, 88, 90): rise, stop at z 534, end at scroll 9000

# ======== Stage 9: ice blocks along the edge (087D88 at scroll 1400 and 2300) ========
087D88  func  ice_block_row_start                    # 0E blocks (sprite 7498 palette 9130, solid box 20 x 5C, height 50, touch kind C) at x FD4E, 5C apart along y, z 78; then removed
087E22  func  ice_block_update                       # 82 = state: 0 projects; 1 touch list until touched: slides along x- at 1.0 (or the toucher's speed if not a player); 2 slides, falls off the edge past x -2BA, removed below screen y 120. Seen in the stage 9 walk at frame 3850: a row of light blue ice blocks
# ======== Stage 9: the moving ice platform (spawn_ice_platform_leader + rows of 087FA5 at scroll C000, D200) ========
087F5B  func  ice_platform_leader_move               # invisible group leader (and moving floor object 20E5F6): x += speed_x, turns back after 88 units (sound F); removed at its end scroll position (8E)
087FA5  func  ice_platform_piece_update              # 6E = state, 6F = piece: 0 init (the last piece uses parts 09356B); 1 x = leader x - piece * 2D, platform list, until an icicle lands on it (92 set): parts 09356B, 2 falls until far off screen. Seen at frame 16250: a floating slab of ice moving back and forth
093559  data  ice_platform_piece_parts               # multi-part sprite of stage_object_records +14A: 2 parts, sprite 7350, palette 9000
09356B  data  ice_platform_piece_parts_2             # the same parts with palette B000 (other mixer priority group): the last piece and falling pieces
# ======== Stage 9: crumbling ice floor (08807F at scroll B700 and C900) ========
08807F  func  ice_floor_start                        # 5 rows of 7 ice cubes (sprite 7490 palette 9120, the first of each row palette bank 2000), 2E apart, on floor block ice_floor_floors[04]; then removed
08815E  func  ice_floor_block_update                 # 80 = state: platform list; 1 when a player stands on it: sound 14, shakes (z +/- 1) for 1E frames (12C while his invulnerable_timer > 0); 2 then palette bank 2000, sound 2A, 3 falls until far off screen. Seen at frame 15200: a grid of ice cubes
0890B9  table ice_floor_floors  u32                  # floor block offset by kind: 040A, 0436

# ======== Stage 9: icicles (088294 kinds 1-9 from scroll 8000; game mode 0x14 spawns kind 0) ========
# Stage 9 walk frames 5080-21900: invisible droppers move along the path; every 400 units of travel three
# big icicles fall side by side (shadows growing under them), stab into the ice floor, sink 70 into it
# (the part below the floor drawn clipped in another palette, cracks spreading), and stay as solid walls.
088294  func  icicle_dropper_start                   # waits for icicle_dropper_ready, clears it; icicle_dropper_records[04]: x, y, z, direction (24), 96/97 (96 = rides the ice platform, 97 = last dropper), script (icicle_dropper_scripts + offset)
088321  func  icicle_dropper_move                    # invisible chaser along its direction (along with 20E5F6 when 96 is set); every 400 units (300 on the platform) of travel drop_icicle_row; at the end sets icicle_dropper_ready and is removed (97: icicle_dropper_finish)
088624  func  icicle_dropper_finish                  # the last dropper: waits until its three icicles have counted goal_unlocked up to 3, then 3 frames, goal_unlocked = FF, removed
088640  func  drop_icicle_row                        # 3 icicles 48 apart across the direction, E0 above the floor, random delay 3C-5B, each with an icicle_shadow_follow; near the script's end coordinate they line up on it (flag 1, 84 = that coordinate)
088832  func  icicle_update                          # 82 = state: 1 waits, falls to the floor; 2 lands: icicle_buried_part_draw, two clip entries, icicle_cracks_spread, sound 29; 3 keeps sinking until 70 below the floor, then set_icicle_wall_box; 4-6 (on the ice platform) moves with it, lands on a piece (sets its 92: the piece falls), then falls with it; 7 the lined-up ones (also clipped at the floor line; 97 counts goal_unlocked up). Touch kind 28
088CA6  func  icicle_buried_part_draw                # copy of the icicle in palette bank 2000, drawn between two clip entries whose top is the floor line (project_point): the part below the floor
088D5D  func  icicle_cracks_spread                   # cracks in the ice where the icicle stabbed in (sprite 74A8): one animation step (00DF72) every 1C units the icicle sinks
088DD2  func  icicle_shadow_follow                   # shadow (sprite 5D90, palette 1FFE) on the floor, zoom growing by 9 to 21C as the icicle falls; blinks and falls with a falling platform piece
088EAA  func  set_icicle_wall_box                    # flags |= 2 (solid box) with box size and offsets by direction (70 x 80 or 80 x 70)
20E651  var   icicle_dropper_ready  u8               # FF when the next icicle dropper may start (start_stage sets FF; icicle_dropper_start clears it, icicle_dropper_move sets it at its end)
20E566  var   current_icicle_dropper  u16            # icicle_dropper_start stores itself; nothing reads it
088F35  table icicle_dropper_records  u16            # 10 offsets from 088F35 to 12-byte records: x, y, z, direction, 96/97 flags, script offset
088FC1  data  icicle_dropper_scripts                 # chaser records (coordinate to reach, scroll position, 84, 86, 88, 90)
struct icicle_dropper_record C rom            # an icicle_dropper_records record, by its offset for sprite_frame (tools/formats/stages.py _ICICLE_DROPPER): icicle_dropper_start 0882AB "movea.b 88F35[PC](R10), R10", six [R10+] (0882B1-0882E0); census ROM 088F49-088FC0
field icicle_dropper_record 00 x s16          # into x_int (4E)
field icicle_dropper_record 02 y s16          # into y_int (52)
field icicle_dropper_record 04 z s16          # into z_int (42) (and floor_z)
field icicle_dropper_record 06 direction u16  # into direction (24): 100, 200, 300
field icicle_dropper_record 08 flags u16      # "mov.h [R10+], 96[R20]" (0882DA): its bytes are icicle_dropper.rides_platform (96: 00/FF) and last_dropper (97)
field icicle_dropper_record 0A script u16     # offset from icicle_dropper_scripts (088FC1) of its first icicle_dropper_step (0882E0 "movz.hw [R10+], R10; movea.b 88FC1[PC](R10), R10" into path, 80): 2, 22, 42...
struct icicle_dropper_step C rom              # a step of an icicle_dropper_scripts script as the dropper walks it: its path (80) points at the step's scroll position; icicle_dropper_check_scroll_position reads +0 ("cmp.h [R10+], 6435[R25]" 0883F9), icicle_dropper_load_record the end test at +4 ("test.h 2[R10]" 088401, R10 = step + 2) and +2-+8 ([R10+] 08841A-08842C), path = the next step (+ C, 088432-088435). A step's end coordinate is its last word, read through the next step's pointer: "cmp.h -2[R10], 52[R20]" / 4E (icicle_dropper_move 0883CE-0883EC), drop_icicle_row (path - 2, 088722); the script's first word is the one before its first step (7FFF). tools/formats/stages.py frames the same words from the coordinate: (coordinate, at, threshold, factor, speed, wait), the droppers pointing 2 bytes in; census ROM 088FC1-0890B7
field icicle_dropper_step 00 position u16     # the step loads once scroll_position reaches it ("bl", unsigned); FFFF never
field icicle_dropper_step 02 screen_threshold s16  # into 84: FFB0, 140
field icicle_dropper_step 04 catch_up_factor s16   # into 86: 0, 8, 20; negative ends the script (icicle_dropper_ready, removed or icicle_dropper_finish)
field icicle_dropper_step 06 base_speed s16   # into 88: 0, 8, 18
field icicle_dropper_step 08 wait u16         # into 90 (wait_timer): 0, 90, B4
field icicle_dropper_step 0A end_coordinate s16  # where the step ends and the next loads: compared signed with y_int / x_int by direction (0883CE-0883EC) or, riding the ice platform, with platform_offset_int; drop_icicle_row lines its icicles up on the current step's (088722); 7FFF / 7F00 never
0890C1  data  icicle_crack_animation                 # 00DF72 steps: frame, dx, dy

# ======== Stage A: the tornado (0890DC at scroll 0000 kind 0, 9A00 kind 1) ========
# walk_0A: five brown twisters circling a centre move along the path after the players; touching it
# (kind 1E) sweeps the player away; it throws small whirlwinds at the players.
0890DC  func  tornado_start                          # waits while sand_block_leader_active is set (kind 1 not after scroll 9C00); tornado_records[04]: x, y, hazard list (48), whirlwind list (04-07), script (80); solid, touch kind 1E, height 70; becomes chaser_object (20E5D4) and 20807F; group_leader_0_held (20E652) = FF; kind 0: players_frozen (20E61A) = FF and spawn_chaser_back_zone 1, kind 1: spawn_tornado_rear_zone; five tornado_column_circle parts (in 96-9F)
08924E  func  tornado_move                           # chaser along its direction (24 >> 8: 0 y-, 1 x+, 2/4 y+, 3 x-; each script record gives a new one and turns its F0 x 80 box), 4 times faster once every player finished, on the floor + 8, rotation angle 94 -= 4, obstacle list; tornado_throw_whirlwinds; spawn_tornado_hazard at the listed points; sound 12 every 28 frames on screen; clears group_leader_0_held when it loads its last moving record (80 = 08A72E); at the end goal_unlocked = FF if the end record says so; removed with its columns when 20E5ED
089685  func  tornado_column_circle                  # one twister: at the tornado's x/y + polar offset (angle = tornado's 94 + own offset, radius bouncing between 1000 and 6800 at a random rate), animates, on the floor; a second-pool top E0 higher (tornado_column_top_frames[frame])
08976B  func  spawn_tornado_hazard                   # an invisible solid box (F0 x 80 turned with the direction, height 50, touch kind C) behind the tornado, from the edge of its floor block
0897EF  func  tornado_hazard_update                  # touch list while near the screen (margins F0), else removed
08981A  func  tornado_throw_whirlwinds               # at each scroll position of its whirlwind list (every 400 for kind 0) throws a small whirlwind at a player (012171: handler 0121C9, kind 1F)
089833  func  spawn_tornado_rear_zone                # kind 1: object of kind 9 (flags 40000002, height 160) for tornado_rear_zone_follow (kind 0 uses spawn_chaser_back_zone 1 instead)
089868  func  tornado_rear_zone_follow               # the tornado's box made 200 long and moved 40 behind it (against its direction), z + 60, obstacle list; removed at scroll E500
20E653  var   sand_block_leader_active  u8           # spawn_sand_block_leader sets FF; the leader clears it when its y reaches A2B; tornado kind 1 waits while it is set (start_stage clears it)
08A6C0  table tornado_records  u16                   # 2 offsets from 08A6C0: x, y, hazard list offset (08A79E), whirlwind list offset (08A7E4), then 7-word chaser records (scroll position, coordinate, 84, 86, 88, 90, direction)
08A79E  data  tornado_hazard_lists                   # (scroll position, coordinate) pairs, FFFF ends
08A7E4  data  tornado_whirlwind_lists                # scroll positions, FFFF ends
08A6A4  data  tornado_column_animation               # frames 79A8-79D0
08A6B4  table tornado_column_top_frames  u16         # top sprite by column frame (79D8-7A00)

# ======== Stage A: quicksand, sinkholes, sand patches ========
089927  func  quicksand_start                        # stage A scroll 2600, 3600, C700: sprite 74E0, touch kind 1C (sinks the player), z = floor + 30, palette cycle quicksand_palettes, multi-part sprite (quicksand_parts) in second-pool blocks; field 04 != 0 (the C700 one): flag 1, never drawn
0899EE  func  quicksand_swirl                        # touch list; palette cycle (animate_sign), steps quicksand_animation on every part; removed with its parts off screen (margin 70). Seen at walk_0A frame 8100: a funnel of sand
089AB4  func  sinkhole_start                         # stage A rows from 0300: sinkhole_growth_records[04 & 7FFF]: zoom step (80), delay (84 = delay + scroll position); bit 15 flips it; zoom 1, touch kind 1B (falls in), animation sinkhole_animation
089B72  func  sinkhole_open                          # from scroll position 84: sound F, zoom += 80 up to 180, sprite offsets and box (8C * zoom) grow with it; touch list; removed off screen (margin 68). Seen at walk_0A frames 4060-4200: a hole opening in the sand
089C84  func  rimmed_sinkhole_start                  # the spawned object stays as rimmed_sinkhole_edge_zone_update (touch kind 1D, box 70); a new one (sprite 7518, touch kind 1B, growth from sinkhole_growth_records) opens with a second-pool rim (sprite 7538)
089DBB  func  rimmed_sinkhole_open                   # like sinkhole_open up to zoom 100, the rim with it; removed off screen (it removes the edge zone too)
089E88  func  rimmed_sinkhole_edge_zone_update       # copies the hole's zoom and depth; touch list (kind 1D: edges drop at once); removed with flag 400
089C0B  func  sand_patch_start                       # stage A (many, with most rimmed sinkholes): sprite 7530 palette 9020 (a triangle of sand), bit 15 of 04 flips it, on the floor
089C51  func  sand_patch_draw                        # drawn at screen_y - 38 until off screen (01F18C, 38/10)
08A65E  data  sinkhole_animation                     # frames 7500-7510
08A666  data  rimmed_sinkhole_animation              # frames 7518-7528
08A66E  data  sinkhole_growth_records                # 4 bytes by kind: zoom step, delay in scroll units (2/180, 4/200, 6/0, 6/280, 6/100, 1/180)
08A686  data  quicksand_palettes                     # animate_sign list, 0D palettes 1030-11D0
08A7BA  data  quicksand_parts                        # 4 parts, sprite 74E8
08A7DC  data  quicksand_animation                    # frames 74E8-74F8

# ======== Stage A: the sand block leader (spawn_sand_block_leader at scroll 4C00) ========
# Leader (group_leaders[0], parent 26 of the sand blocks 08A16E): walk_0A frames 12500-36100.
089F3B  func  sand_block_leader_wait                 # while scroll_position < 5100 and group_leader_0_held (20E652) is set (the stage A tornado clears it), then sand_block_leader_move
089F55  func  sand_block_leader_move                 # invisible chaser (group_leader_data script) along y or x, kept in the middle of its floor block, rider list; each sand block (08A16E) starts its own sequence (effects with clip entries, sound 11) when the leader's y passes it; clears sand_block_leader_active once y <= A2B

# ======== Shared variables seen here (used by other code too) ========
# Named elsewhere: chaser_object 20E5D4, chaser_speed 20E556, players_frozen 20E61A, group_leader_0_held 20E652,
# rising_fire_object 20E5D6 (the chasers section); event_1c_riding 20E647, event_1c_ride_phase 20E649
# (the scripted game modes section); scene_object 20E62E, player_carriers 20E640 (players.txt); goal_unlocked 20E5E3.
# Named here (shared with the player code):
20E63A  var   seesaw_launched       u8         # FF when the stage 8 burning boulder lands on the seesaw (burning_boulder_update state 5, at z >= 9E0); seesaw_tilt and the scene carriers (kinds 1-3, scene_carrier_move) wait for it, the player handlers (002CEC/002FF1) test it; 000652 clears it
20E63B  var   burning_boulder_released u8      # FF from the player handlers (002CEC/002FF1) or from burning_boulder_update once burning_boulder_release_timer reaches 258: the leading boulder leaves state 4 and drops onto the seesaw; burning_boulder_start clears it
20E654  var   group_leaders_1_3_held u8        # group_leader_wait (09177D) keeps the group leaders whose field 04 is not 0 waiting while it is set (group_leader_0_held does the same for leader 0); spawn_group_leader tests both; group_leader_hold_start sets it when its record's flag is 1; start_stage clears it
20E5F6  var   moving_floor_object  u16 object.ice_platform_leader  # the stage 9 ice platform leader (spawn_ice_platform_leader stores it): apply_moving_floor (00CF08), icicle_dropper_move, icicle_update and icicle_shadow_follow add its speed_x (pointer typed: last_names.txt N1)
# 20807F is tornado_object (objects by type, 00EC9E-012308) and 20E5ED map_screen_active (stage flow).

# ======== Stage objects 08A16E-08DC36 and the group rows 090E49-093E63: stages A-10 ========
# Stage objects 08A16E-08DC36 and row/group objects 090E49-093E63
#
# Common pattern of the chasers and group leaders below (fire front, biting head, rising saws, geyser front,
# waterfall, propeller machine, group_leader_move): a path script of entries (start scroll position or an
# x/y/z limit, a screen-x or screen-y threshold, a catch-up factor, a speed, a delay); while the object is on
# its side of the threshold it moves by the entry's speed, when it falls behind it moves by
# max(speed, camera speed * factor >> 8) (20E510 from 00AFB3, or 20E572 from 00B04D), so it keeps up with
# the screen. The step of the frame is left in chaser_speed (20E556). After a player mishap / a player dropping in / no
# player in play (player_mishap, player_dropping_in, no_player_in_play) it holds just off the screen edge for
# up to FF frames or 300 scroll units.
# Renders: s32ref dumps of tests/controls/walk_0C.txt (stages C, D, E), walk_0A.txt (stage A), and scratch
# copies of walk_0C with the stage poke changed to 10 and 08 and extended to 60000 frames (sb10/walk_10x.txt,
# sb10/walk_08x.txt); sprite frames drawn on their own with sb10/frames.py.

# ======== Stage A: collapsing sand blocks (members of spawn_object_groups, leader sand_block_leader_move 089F55 from spawn_sand_block_leader) ========
08A16E  func  sand_block_update                # stage_object_records +176/+18C/+1A2 (stage A, 4C00-9200, one block per record): platform; state 1 waits until the leader's y is inside it (y + hitbox_size_y - 10), state 2 builds the cut (sand_face_update, sand_streams_update, two clip entries from the second pool chained after it, sound 11), state 3 shrinks hitbox_size_y to leader y - y and moves the clip line to the leader's projected position; at 0 the face falls (flag 400), clips freed, removed. Render walk_0A frame 13500: big sand blocks eaten from one side
08A412  func  sand_face_update                 # the moving cut face of a sand_block (2-part sprite 73D0-73E8, palettes copied from the block): y/z follow the group leader, animates; flag 400: falls (gravity 6000, sand_face_fall_animation) and at screen y 150 frees its part list, sets flag 400 on its streams and is removed
08A492  func  sand_streams_update              # 6-part sprite of falling sand at the face's screen position; every part's frame = sand_stream_frames[face animation frame]; removed with flag 400
08A5FC  data  sand_face_animation              # 3, frames 73D0 73D8 73E0 73E8 (sand_block_update gives it to sand_face_update)
08A606  data  sand_face_fall_animation         # 3, frames 78C0-78D8 (sand_face_update when the block is gone)
08A612  table sand_stream_frames     u16       # 73F0 73F8 7400 7408 by the face's animation frame (sand_streams_update); the word before it is 3
08A61A  data  sand_face_parts                  # 2-part list copied to a second-pool block for sand_face_update
08A62C  data  sand_stream_parts                # 6-part list (frame 73F0) copied into the stream object's own fields 40-71
093483  data  sand_block_parts                 # part lists of the sand blocks: 093483 (record +18C), 093495 (+176), 0934A7 (+1A2, with sand columns 73C8 under it); record fields 04/06 point to them (multi-part sprites D002)

# ======== Stage A: the swirl around a player sinking in quicksand (the player code spawns it) ========
08A4ED  func  spawn_sink_swirl                 # called by 00E488 when a player reaches the centre of a kind 1C object (quicksand_start 089927, render walk_0A frame 8700): player mishap: two objects at the player (z + 8) from sink_swirl_effects, each with a take_free_object part (palette 1230)
08A593  func  sink_swirl_start                 # project_object, then sink_swirl_animate
08A5A2  func  sink_swirl_animate               # animates the streak (frames BE90-BF28, palette 1210) and its sand puff (BF30-BF58) once at the same screen position, then frees the puff and is removed
08A80E  table sink_swirl_effects     sink_swirl_effect  # 2 x (depth offset, streak animation 08A822/08A838, puff animation 08A84E/08A856)
struct sink_swirl_effect A rom               # a record of sink_swirl_effects (08A80E): spawn_sink_swirl 08A4F6 "mov.w #8A80E, R12", then [R12+] (08A51E, 08A52D, 08A575) for each of its 2 swirls; census ROM 08A80E-08A821
field sink_swirl_effect 00 depth_offset s16   # "mov.h [R12+], 96[R11]" (sink_swirl depth_offset): 8, FFF0
field sink_swirl_effect 02 streak_animation u32  # "mov.w [R12+], 48[R11]": the swirl's animation (08A822, 08A838: frames BE90-BF28)
field sink_swirl_effect 06 puff_animation u32    # "mov.w [R12+], 48[R11]" of its take_free_object part: the sand puff's (08A84E, 08A856: BF30-BF58)

# ======== Stages C and E: the brick wall that bursts when the chaser stops ========
08A85E  func  brick_wall_init                  # stage C/E at 0C00 (x 0823, y 0509): frame 7D90 palette 10D0 (a brick wall, frames.py), z 1D8
08A89E  func  brick_wall_wait                  # drawn until chaser_stopped (20E655), then 14 bricks (rock_debris_fly) from brick_wall_bricks and 5 dust puffs (brick_dust_animate) from brick_wall_dust; removed. Render walk_0C frame 7700
08A9B9  func  brick_dust_animate               # waits field 80 frames, plays brick_dust_animation once (a cloud that thins out), removed
08BED5  data  brick_animations                 # palette, then an animation: 10F0 + frames 7DE0-7E08, 1100 + frames 7E10-7E68 (tumbling bricks)
08BF01  table brick_wall_bricks      u16       # 0E, then 6 words a brick: sprite flags, screen dx, dy, speed x, speed y, offset into brick_animations
08BFAB  table brick_wall_dust        u16       # 5, then 4 words a puff: sprite flags, screen dx, dy, delay (field 80)
08BFD5  data  brick_dust_animation             # 8, frames 7D98-7DD8 (palette 10E0)

# ======== Stages C and E: the biting head (a big metal head with toothed jaws, frames 7A48-7A98) ========
08A9DC  func  biting_head_init                 # stage C/E at 0C00: path biting_head_paths[04]; 6-part sprite in a take_free_object block with two clip entries; kind 9 (hurts the players), a second take_free_object block as a second zone box; biting_head = it, biting_head_done = 0
08AAFE  func  biting_head_wait                 # until chaser_stopped, or scroll_position 1200 (then sets chaser_stopped itself)
08AB1F  func  biting_head_chase                # moves along its path (direction 3: -x) as the chasers do; both boxes in the zone list; parts 0 and 4 cycle biting_head_frames while it moves, the jaw (part 3) bites through biting_head_bite_steps and the head shakes (biting_head_bounce); clip line at the projected (0821, 05EC); x <= -355: box heights 50; x <= -431: biting_head_done. Render walk_0C frame 12500
08BE25  table biting_head_paths      u16       # offsets from 08BE25: x, y, then 7-word entries (position, x/y limit, screen threshold, factor, speed, delay, direction)
08BE5F  table biting_head_frames     u16       # last index 3, then frames 7A78 7A80 7A88 7A90 written into parts 0 and 4
08BE69  table biting_head_bite_steps u16       # last index 5, then (jaw frame, frames to hold) for part 3; step 5 starts biting_head_bounce
08BE83  data  biting_head_parts                # 6-part list copied by biting_head_init
08BEB5  table biting_head_bounce     s8        # added to screen y after a bite: 0 -8 0 -8 0 -8 0 -4 0 -4 0, 7F ends
08BEC1  data  biting_head_clip_entry           # a 5000 clip entry, copied twice behind the part list

# ======== Stages B and C: the fire front (the chaser) ========
08AEDD  func  fire_front_init                  # stage B (04 = 1) and C (04 = 0) at 0000: path fire_front_paths[04]; zone box 132 x 200, kind 14 (players hurt and tumbling, state 20); chaser_object = it; 4 spreading_flame_start objects (084E83) 38 apart along x
08AFAA  func  fire_front_chase                 # invisible: moves along its direction (24 >> 8: 0 -y, 1 +x, 2/4 +y, 3 -x) as the chasers do, zone list; on the entry whose factor is 0 (its stop: y 05EC in stage C, 04F0 in B) box width F2 and chaser_stopped = FF; the last entry clears chaser_object and removes it. Renders s0B_3700 / s0C_3900: the fire
08BDA1  table fire_front_paths       u16       # offsets from 08BDA1: x, y, floor height, direction, then 6-word entries (position or x/y limit, screen threshold, factor, speed, delay)

# ======== Stages D and F: falling grates ========
08B216  func  falling_grate_update             # stage_object_records +1B8/+1CE (frame 7840, metal grating; render walk_0C frame 23000), rows from spawn_object_groups led by group_leader_move: platform; within 60 of the leader rattles (z +-1 every animation_speed frames); when the leader passes: sound D, falls (gravity 2000, falling_grate_animation), still a platform for its first frames, removed below the screen
0920DB  data  falling_grate_rattle_steps       # the records' field 48: frame 7840 with dy 0/1/0/1; never played (falling_grate_update moves z instead)
0920ED  data  falling_grate_animation          # 4, frames 7848-7868

# ======== Stages C and E: rising saws, the grate steps they cut ========
08B3A7  func  rising_saw_init                  # stage C/E at 3500 (04 = 0-2): waits for scroll_position 3780 or biting_head_done, then path rising_saw_paths[04]; rising_saws[04] = it; box hitbox 50 x 40, kind 26 (knocks players off the stage, state 36); spawns its blade (saw_blade_update) and sparks (saw_sparks_update)
08B560  func  rising_saw_rise                  # rises (z decreases) along the floor slope as the chasers move (moving projected_y and x/y with it); touchable list
08B6ED  func  saw_blade_update                 # the spiked wheel (frames 7F98-7FB0): 0 two clip entries, sound A; 1 slides out (+2 y a frame) to the saw, clipped at its line; 2 frees the clips; 3 follows the saw, leaves a shaft piece every 1D of rise, spins, sound 8 every 60 frames. Render walk_0C frame 19700
08B8F6  func  saw_sparks_update                # sparks (8378-83A0) at the blade's screen position + offsets, drawn just in front of it (depth + 1) while the saw moves (its speed field 88 is not 0)
08B94A  func  saw_shaft_update                 # probably the saw's shaft: a piece left under the blade that steps through saw_shaft_animation (7FB8 -> 7FE8, taller) for every 5 units the saw rises, then stays until far off screen
08BC91  func  build_saw_shaft_base             # stage C/E at 3500 (x FC20/FC64/FCB0): waits like rising_saw_init; three pieces down the column (7FE8, 7FB8, 7FE8 flipped in y), then removed
08BD80  func  saw_shaft_base_draw              # draws the piece (depth FFF7) until it has gone off screen
08C001  table rising_saw_paths       u16       # offsets from 08C001: x, y, z, then 6-word entries (position or z limit, screen-y threshold, factor, speed, delay)
08BFE9  data  saw_blade_animation              # 3, frames 7F98-7FB0
08BFF3  data  saw_sparks_animation             # 5, frames 8378-83A0
08C0CD  data  saw_shaft_animation              # 6, frames 7FB8-7FE8
08B9B9  func  grate_steps_init                 # stage C/E at 3200: fixed position (FC43, 04F4, z 8), clears rising_saws
08BA1B  func  build_grate_steps                # one row of 3 steps a frame (frame 7FF0, the column index in field 47), each row 1E lower down the slope; 15 rows, then removed. Render walk_0C frame 17600: a 3-wide staircase of grating
08BB09  func  grate_step_update                # when the saw of its column (rising_saws[field 47]) reaches it: sound 28, spawn_grate_step_debris, then the bars disappear (grate_step_cut_animation); removed below the screen
08BB86  func  spawn_grate_step_debris          # grate_step_debris records: map offsets, speeds (+ next_random), animation
08BC4D  func  grate_step_debris_fly            # gravity 6000, moves speed_x in projected x, animates, removed off screen
08C0DD  data  grate_step_cut_animation         # 4, frames 7FF0-8010 (4, 3, 2, 1, 0 bars)
08C0E9  table grate_step_debris      u16       # 5, then 5 words a piece: map dx, dy, speed x, speed z, offset into grate_step_debris_animations
08C11D  data  grate_step_debris_animations     # three 6-frame animations (8048-8070, 8078-80A0, 8018-8040)

# ======== Stage 10: the river, its rafts and the waterfall ========
08C148  func  river_raft_spawner_init          # stage 10 at 7000 (04 = 0) and 8A00 (04 = 1): river_raft_spawners[04]: x, y, floor height, end position, raft list
08C1A4  func  river_raft_spawner_update        # removed at its end position; goal_unlocked at scroll_position 9C00; when the last raft it launched (tracked by its speed in 8A) has drifted to it and the waterfall (group_leaders[0]) is downstream: launches the whole list (per entry: distance behind, speed, floor) as rafts with their foam; 8000 loops the list; drifts upstream with the camera speed
08C332  func  river_raft_update                # a wooden raft (frame 8DF0; render frame 36800 of the stage 10 walk): drifts +y at its speed on its floor (2 lower while carrying someone), platform; when the waterfall reaches its far end and then its middle it tips over the edge (river_raft_fall_animation, palette from river_raft_fall_palettes by frame, gravity 2000), removed below the screen
08C547  func  river_raft_foam_update           # foam around a raft (palette B190, river_raft_foam_animation) at the raft's screen position; removed when the raft sets flag 400
08CD1A  func  waterfall_move                   # group leader made by spawn_waterfall (stage 10 at 6E00 and 8700): a 3-part waterfall sprite (render frame 46400), moves -y with group_leader_data entries as the chasers do (catch-up past screen x threshold or screen y E0); y <= 1480: geyser_front_start; sound 10 every 60 frames
08CEB3  func  river_water_update               # stage_object_records +1E4 (rows 90 apart, leader = the waterfall): river water; frame = river_water_frames[waterfall animation frame]; once the waterfall reaches it, two clip entries cut it at the waterfall's line; removed when the waterfall is 5C past
08D97E  data  waterfall_parts                  # 3-part list (frame 7AF0) copied by spawn_waterfall
08D998  data  waterfall_animation              # 7, frames 7AF0-7B28
08D9AA  table river_raft_spawners    u16       # offsets from 08D9AA: x, y, floor height, end position, then (distance, speed, floor) entries; 8000 loops, FFFF
08DAC6  data  river_raft_fall_animation        # 4, frames 8E28-8E48
08DAD2  data  river_raft_foam_animation        # 2, frames 8E08-8E18
08DAE6  table river_raft_fall_palettes u16     # B1B0-B1F0 by animation frame while it falls
093463  data  river_water_frames               # 7, frames 7AB0-7AE8 (record +1E4's field 48)

# ======== Stage 10: geysers ========
08C6E1  func  geyser_front_init                # stage 10 at 6000 (04 = 0) and 8100 (04 = 1): geyser_fronts[04]: start height, top height (9C), geyser list, path; a take_free_object block as its hazard box (30 x C0, height 40, kind 29: hurts the players); clears geyser_front_start
08C7AC  func  geyser_front_wait                # spawns listed geysers (spawn_listed_geyser); waits for geyser_front_start or scroll_position 6500 / 8400
08C7D5  func  geyser_front_rise                # invisible: rises up the floor slope (z, projected_y and x/y move) as the chasers move; every 30 units of rise a row of 5 geysers 2E apart with delays from geyser_row_delays; at the top height spawn_geyser_line; its hazard box follows A0 below it (touchable list); sound 12 every 50 frames on screen
08CA5D  func  spawn_listed_geyser              # next geyser_lists record (position, x, y, z, start position) once scroll_position reaches it; it waits for its start position
08CAA0  func  spawn_geyser                     # at R12/R13/R14 (x, y, z), field 80 = R15: frame 5DA0 palette 8280, box 20 x 30, kind 27 (hurts the players), geyser_animation
08CB40  func  geyser_wait                      # listed ones wait for their start position, row ones count field 80 down; both hold while the front holds; then geyser_erupt
08CBC4  func  geyser_erupt                     # the ground cracks and a water jet bursts out (geyser_animation, then geyser_jet_animation looping; frames.py), sound A at frame 3, in the touchable list from frame 4, removed below the screen. Render frame 33500 of the stage 10 walk
08D0D1  func  spawn_geyser_line                # when the front reaches its top: a geyser_line_move object at geyser_line_starts[04]
08D13D  func  geyser_line_move                 # invisible: per geyser_line_scripts (position or y limit, speed, delay) moves -y; every 300 units spawn_geyser_burst; at its end one more burst and removed; holds after a player mishap
08D216  func  spawn_geyser_burst               # palette 9130, box 30 x 30, height A0, box hitbox, kind 29, geyser_burst_animation
08D298  func  geyser_burst_start               # project_object, then geyser_burst_erupt
08D2A8  func  geyser_burst_erupt               # removed far off screen; cracks open and bursts (sound A at frame 4), then sprays (geyser_burst_spray_animation) in the touchable list
08D81E  data  geyser_animation                 # 0C, steps (frame, dx, dy) 5DA0-5E00 for animate_around_anchor
08D854  data  geyser_jet_animation             # 2, steps 5E08-5E18
08D862  table geyser_row_patterns    u16       # offsets from 08D862 by front: bytes, one per row, selecting a geyser_row_delays pattern
08D886  data  geyser_row_delays                # 2 x 5 words: 0 14 28 3C 50 and 50 3C 28 14 0 (field 80 of the row's geysers)
08D89A  table geyser_fronts          u16       # offsets from 08D89A: start height, top height, offset into geyser_lists, then 6-word entries (position or z limit, screen-y threshold, factor, speed, delay)
08D902  data  geyser_lists                     # 10-byte records (position, x, y, z, start position), a FFFF word ends; front 0 at 08D902, front 1 at 08D936
08DB10  data  geyser_line_starts               # by front: x, y, z, offset into geyser_line_scripts
08DB20  data  geyser_line_scripts              # 8-byte entries: position, y limit, speed, delay (negative delay ends)
08DB50  data  geyser_burst_animation           # 7, frames 9D28-9D60 (cracks, burst, spray)
08DB62  data  geyser_burst_spray_animation     # 3, frames 9D68-9D80

# ======== Stage 10: other objects ========
08D04C  func  stone_block_init                 # stage 10 at 6DF0/86F0 (04 = 0-3): stone_block_positions[04] (x, y, z, depth offset); frame 8A00 palette 9120, a stone block (frames.py; render frame 36800)
08D0AC  func  stone_block_draw                 # drawn until it has gone off screen
08DAF0  data  stone_block_positions            # 4 x (x, y, z, depth offset)
08D32D  func  propeller_machine_init           # stage 10 at 1800: chaser_object = it; 11-part sprite (propeller_machine_parts) in a take_free_object block; zone box F2 x 80, height F0, box hitbox, kind 18 (hurts the players); spawn_chaser_back_zone (record 0); path propeller_machine_path. Render frame 15200 of the stage 10 walk: a machine with big spinning propellers
08D3E1  func  propeller_machine_chase          # moves +y as the chasers do (catch-up with camera speed 20E572), follows the floor, zone list; spins the propellers (propeller_machine_animation into three parts); y >= 1B00: height 68; y >= 1BB0: geyser_front_start; sound B every 60 frames on screen; the last entry frees its block and removes it
08DB6C  data  propeller_machine_parts          # 11-part list
08DBC6  data  propeller_machine_animation      # 3, then 4 pairs of frames (AF50/AF70 ... AF68/AF88)
08DBD8  data  propeller_machine_path           # 6-word entries (position, y limit, screen threshold, factor, speed, delay)
08D5CE  func  barred_gate_init                 # stage 10 at 0B70: frame 9F00 palette 92F0 (an arched barred gate, frames.py), z 158
08D630  func  barred_gate_update               # removed off screen (margin 50); once barred_gate_closing is set plays barred_gate_animation once, then sound 29
08DC10  data  barred_gate_animation            # 4, frames 9F00-9F20
08D67F  func  scene_anchor_init                # stage 10 at 0B70: scene_anchor_positions[04] (x, y, floor height, end position); scene_object (20E62E) = it
08D6C0  func  scene_anchor_update              # removed at its end position; sets z so that its screen y stays A0 above its x/y: the point game mode 0x19 pulls the players to (player state 30)
08DC1C  data  scene_anchor_positions           # (016C, 050D, 0528, end 1B00)
# the records of stage 10's objects read by name (struct ... rom)
struct object_start_place 8 rom               # where an object starts and the scroll position it is removed at (tools/formats/stages.py _RAFT_SPAWNER, _ANCHOR): river_raft_spawners' records (river_raft_spawner_init 08C152 "add.w #8D9AA, R10", four [R10+] 08C159-08C177; the raft list, river_raft_entry, follows) and scene_anchor_positions (scene_anchor_init 08D686, four [R10+] 08D68D-08D6AB); census ROM 08D9AE-08D9B5, 08DA32-08DA39, 08DC1C-08DC23
field object_start_place 00 x s16             # into x_int (4E): 29C, 3C1, 16C
field object_start_place 02 y s16             # into y_int (52): 1AD0, 13C9, 50D
field object_start_place 04 floor_z s16       # into floor_z_int (56): 1D8, 20, 528
field object_start_place 06 end_position u16  # into river_raft_spawner.end_position (84) / scene_anchor.end_position (80), which their updates compare unsigned with scroll_position (08C1A4, 08D6C0 "cmp.h; bl"): 8500, 9E00, 1B00
08DC1C  table scene_anchor_positions object_start_place  # scene_anchor_init: sprite_frame x 8 + 8DC1C (08D67F-08D686)
struct river_raft_entry 8 rom                 # one raft of a river_raft_spawners list (tools/formats/stages.py _RAFT): river_raft_spawner_update reads the one its entry (80) points at (08C1EE "test.h [80[R20]]", 08C24A "mov.w 80[R20], R10", then three [R10+] 08C255-08C26A) and moves entry past it (08C26F); census ROM 08D9B6-08DA31, 08DA3A-08DABD
field river_raft_entry 00 distance s16        # how far behind the spawner the raft starts: << 16 taken from its y (08C255-08C25C); negative ends the launches ("test.h [80[R20]]; bn" 08C1EE), 8000 loops the list ("cmp.h #8000, [80[R20]]" 08C2FD); B0-34E
field river_raft_entry 02 speed u16           # into the raft's speed_int (70, 08C260): 180
field river_raft_entry 04 floor_block u32     # into its floor_block (5C, 08C26A "mov.w [R10+]"): the offset of its block in floor_blocks, 48E-5C2
struct geyser_front_record 6 rom              # a geyser front's record in geyser_fronts (tools/formats/stages.py _GEYSER_FRONT): geyser_front_init 08C6EB "add.w #8D89A, R12", three [R12+] (08C6F2-08C702); its path (chase_step_with_limit) follows (08C710 "mov.w R12, 80[R20]"); census ROM 08D89E-08D8A3, 08D8D0-08D8D5
field geyser_front_record 00 start_z s16      # into z_int (42): 4C0, 1C6
field geyser_front_record 02 top_height s16   # into geyser_front.top_height (9C): 1E0, 28
field geyser_front_record 04 geyser_list u16  # offset in geyser_lists (08D902) of its listed geysers (08C702 "movz.hw [R12+], R10; add.w #8D902, R10"): 0, 34
struct chase_step_at_scroll A rom             # a chase script entry that loads once scroll_position reaches its position (tools/formats/stages.py _CHASE: spiked_roller_chase, group_leader_move, sand_block_leader_move, waterfall_move): waterfall_read_script reads the one its path (80) points at (08CD80 "mov.w 80[R20], R10", 08CD85-08CDB4) and moves path past it (08CDBA); census ROM 0937D7-0937F0, 0937F1-093812 (group_leader_data: the waterfalls' paths)
field chase_step_at_scroll 00 position u16    # "cmp.h [R10+], 6435[R25]; bl" (08CD85): unsigned
field chase_step_at_scroll 02 screen_threshold s16  # into 84: 140
field chase_step_at_scroll 04 catch_up_factor s16   # into 86: 20; negative ends the path ("test.h 2[R10]; bp" 08CD8D, R10 past the position)
field chase_step_at_scroll 06 base_speed s16  # into 88: 0, 2, 8
field chase_step_at_scroll 08 wait u16        # into 90 (wait_timer): 0
elements geyser_row_delay_choice u8          # geyser_row_patterns' bytes, one per row of geysers (tools/formats/stages.py _ROW_PATTERN): which geyser_row_delays set (x A) the row's five geysers wait by; geyser_front_spawn_geyser_row reads the one row_pattern (94) points at and steps it (08C9F8-08CA00 "movz.bw [R10+], R11"); census ROM 08D866-08D885 (00, 01 in turn)
struct listed_geyser A rom                    # a geyser of a front's geyser_lists list (tools/formats/stages.py _GEYSER): spawn_listed_geyser reads the one next_listed_geyser (68) points at (08CA5D "cmp.h [68[R20]], 6435[R25]", 08CA66 R10 = it + 2, four [R10+] 08CA6D-08CA82) and moves 68 past it (08CA85); a FFFF position ends a list; census ROM 08D902-08D933, 08D936-08D97F
field listed_geyser 00 position u16           # spawned once scroll_position reaches it ("bl", unsigned)
field listed_geyser 02 x u16                  # << 16 into spawn_geyser's x (R12, 08CA6D)
field listed_geyser 04 y u16                  # << 16 into its y (R13)
field listed_geyser 06 z u16                  # << 16 into its height (R14)
field listed_geyser 08 erupt_position u16     # spawn_geyser's R15 (geyser_wait.erupt_delay): the scroll position it erupts at
struct stone_block_position 8 rom             # stone_block_positions by sprite_frame (tools/formats/stages.py _STONE_BLOCK): stone_block_init 08D053 "movea.w 8DAF0[PC](R0), R10" (R0 = sprite_frame x 2: 8 bytes a block), four [R10+] 08D059-08D077; census ROM 08DAF0-08DB0F
field stone_block_position 00 x s16           # into x_int (4E): 212, 33C
field stone_block_position 02 y s16           # into y_int (52)
field stone_block_position 04 z s16           # into z_int (42): 1B8, 0
field stone_block_position 06 depth_offset s16  # into stone_block.depth_offset (96): 8, 30
08DAF0  table stone_block_positions stone_block_position  # stone_block_init
struct geyser_line_start 8 rom                # geyser_line_starts by front (tools/formats/stages.py _LINE_START): spawn_geyser_line 08D0DB-08D0E2 "shl.w #3, R10; add.w #8DB10, R10", four [R10+] 08D0E9-08D10D; census ROM 08DB10-08DB1F
field geyser_line_start 00 x s16              # into the line's x_int (4E): 1F1, 317
field geyser_line_start 02 y s16              # into its y_int (52)
field geyser_line_start 04 z s16              # into its z_int (42): 1B8, 0
field geyser_line_start 06 script u16         # offset of its geyser_line_scripts entries (08D10D "movz.hw [R10+], R10; add.w #8DB20, R10"): 0, 18
08DB10  table geyser_line_starts geyser_line_start  # spawn_geyser_line
struct geyser_line_step 8 rom                 # a geyser_line_scripts entry (tools/formats/stages.py _LINE_ENTRY): geyser_line_move reads the one its path (80) points at (08D17C "mov.w 80[R20], R10", 08D181-08D1AA) and moves path past it (08D1B0); census ROM 08DB20-08DB4F
field geyser_line_step 00 position u16        # "cmp.h [R10+], 6435[R25]; bl" (08D181): the entry loads once scroll_position reaches it
field geyser_line_step 02 y_limit s16         # "cmp.h [R10+], 52[R20]; bgt" (08D18E): or once the line's y_int is at or above it (it moves -y)
field geyser_line_step 04 speed s16           # into geyser_line.speed (88): 0, 10
field geyser_line_step 06 wait s16            # into geyser_line.wait_timer (90): 78, 0; negative ends the script ("test.h 2[R10]; bp" 08D196, R10 past the two keys)
struct propeller_machine_animation_step 4 rom  # a step of propeller_machine_animation after its last-step word (frame_animation): propeller_machine_chase_step 08D53C "movea.w 2[R10](R1), R11" (R10 = the animation, R1 = the step: 4 bytes a step), two [R11+] (08D541, 08D544); census ROM 08DBC8-08DBD7
field propeller_machine_animation_step 00 parts_6_9_frame u16  # into the sprite frame of parts 6 and 9 (part_list + 32, + 4A: 08D54C, 08D551): AF50-AF68
field propeller_machine_animation_step 02 part_4_frame u16     # into part 4's (part_list + 22: 08D547): AF70-AF88
struct rope_bridge_rope_offset 4 rom          # where a rope piece hangs from its plank: rope_bridge_rope_offsets (094001) and wide_rope_bridge_rope_offsets (094009), two pieces each; rope_bridge_plank_update 093B17 / 093B24 "movea.b 94001[PC], R13" / 94009, then [R13+] into the piece's 2A and 2C (093B44, 093B49); census ROM 094001-094010
field rope_bridge_rope_offset 00 dx s16       # into the piece's projected_x (2A): -1C, 20 / 62
field rope_bridge_rope_offset 02 dy s16       # into its 2C (the C's projected_y_fraction): -10, F / 30
08D707  func  railing_spawner_init             # stage 10 at 1800 (x 00E2, y 0200): z 520
08D720  func  railing_spawner_update           # removed at scroll_position 5000; every 800 scroll units (with 10 free objects) lays 10 railing segments B8 apart in y, 10 x B8 further each time
08D7C7  func  railing_segment_start            # project_object, then railing_segment_draw
08D7D7  func  railing_segment_draw             # removed at scroll_position 6700 or once gone off screen (margin 88); drawn while on screen (railing_parts: a handrail with posts, render frame 15200)
08DC24  data  railing_parts                    # 2-part list (B350, B978) palette 9290

# ======== Walls (spawn_walls) ========
090E49  func  wall_update                      # spawn_walls' handler: frees its parts and removed in game mode 4 (20E5ED); removed when scroll_position reaches field 82, or (82 = 0) once check_wall_left_screen; joins the wall list; field 80 = no sprite (collision only); else draws its parts from its screen position stepping by each part's offsets (12/13), skipping those off screen, each with a shadow part (palette 9FFE, bucket FF) when field 81 is 0
090F49  func  check_wall_left_screen           # speed_y bits 0/1 (start seen / gone) and 2/3 (far end, at the screen offset kept in speed_z); R8 = FFFFFFFF once both ends were on screen and have left

# ======== Rows led by a group leader ========
0914D2  func  crumbling_floor_update           # stage_object_records rows in stages 4 (rock ledges) and 9 (ice blocks) (frames.py: 6CB0..., 7350...): platform; within 60 of the leader plays its cracking steps (animate_with_screen_offset) and holds; when the leader passes (or at once when its rider has flag 400000, set by 082B39): falls (gravity 2000, crumbling steps, removed below the screen), sound D when on screen unless the row is kind 6 (field 91); while 20E59A (001A34) its palette is 83A0 and it does nothing else; restored from field 98 after
091FA9  data  crumbling_floor_animations       # the records' field 48: 7 steps (frame, dx, dy): 4 cracking, then crumbling (091FA9, 091FCB, 091FED, 09200F, 092031, 092053, 092075, 092097, 0920B9)
09177D  func  group_leader_wait                # spawn_group_leader's handler while group_leader_0_held (20E652; leader 0) or 20E654 (leaders 1-3) is set; then group_leader_move
09179A  func  group_leader_move                # invisible marker moving along its row (24: 0/4 +y, 1 -y, 2 +x, 3 -x) per group_leader_data entries (position, screen-x threshold, factor, speed, delay) as the chasers do; does not move while 20E59A; removed at the end entry; members fall when it passes them (crumbling floors, grates, lava rocks, rope bridge planks). Also holds in stage 8 during game modes 0x17 and 0x1D
091939  func  lava_rock_update                 # stage 8 rows (stage_object_records +108/+11E; rock slabs over lava, frames 7000-7028, palette cycling lava_rock_glow_palettes): platform; when the leader comes within 5A the rock bobs and rises (up to 10, 20 within 30); when the leader passes, or its group slot (group_slots[field 8A]) was triggered and its delay ran out, a lava column erupts under it (spawn_lava_column, sound 7) and the rock flies up and breaks (lava_rock_break_animation); removed above the screen. Render frame 18300 of the stage 8 walk
091C6B  func  spawn_lava_column                # a lava column (2-part sprite 7030-7048 palette 5060, frames.py) E0 below the rock: box 74 x 30, height E0, kind 14 (players hurt and tumbling); field 8C = 1 right of x C97; for rocks of a triggered row it is thrown instead (lava_column_toss_start)
091D6B  func  lava_column_start                # project_object, then lava_column_rise
091D7A  func  lava_column_rise                 # rises (speed_z - 6000 a frame) to z 570, then stays as lava_columns[field 8C]; removed when another column takes its side, in game mode 4 or off the left of the screen; touchable list
091E30  func  lava_column_toss_start           # project_object, then lava_column_toss
091E3F  func  lava_column_toss                 # thrown up (speed_z -E0000) and falling back (gravity 6000), touchable list; at z 660 frees its part list and lava_column_land
091EB5  func  lava_column_land                 # turns into a lava bubble: handler bubble_near_parent_update (0846E7), bubble_animation (085179: swells and bursts), palette D000, z 540
093475  data  lava_rock_break_animation        # 5, frames 7000-7028
0934F9  data  lava_rock_parts                  # 2-part list (record +11E, multi-part D002) copied to a second-pool block
09350B  data  lava_column_animation            # 3, frames 7030-7048
093515  data  lava_column_parts                # 2-part list copied by spawn_lava_column
093527  data  lava_rock_glow_palettes          # 7, palettes 5050-50A0 (animate_sign style cycling through field 80)
20E5DC  var   lava_columns           u16       # two (field 8C: left / right of x C97): the standing lava column; a newer one makes the older remove itself (lava_column_rise)
091F63  func  group_slot_trigger_update        # spawn_group_slot_trigger's object: per group_slot_triggers entry (position, slot, delay) once scroll_position reaches it and the slot is taken: group_slots[slot] = 8000 | delay (the row then erupts after the delay); slot 8000: goal_unlocked = FF; FFFF: removed

# ======== Stage 10: the rope bridge (spawn_object_groups_2) ========
093ADE  func  rope_bridge_plank_update         # stage_object_records_2 rows (stage 10 at 0000 and 0280: tied planks hanging from ropes, frames.py 9CA0; render frame 4100 of the stage 10 walk): every 4th plank gets two rope parts; sags: z eases to its floor height + bridge_0_sag/bridge_1_sag[plank number] (1/4 a frame, sag under 1 ignored); platform; falls when the leader passes or rope_bridge_collapsing is set (sound 28 on screen; the bridge's last plank clears its plank count); a branch for kind F riders (field 92 negative, not used by the stage 10 records) turns the plank into record 0's normal plank on the rider's half
093E63  func  draw_rope_bridge_ropes           # draws the plank's two rope parts (9D18, palette 8290; 9D20 slack while falling) at its screen position + their offsets
093FB9  data  rope_bridge_plank_animations     # two tumbling animations (last index 7: 9CA0-9CD8 and 9CA0, 9CE0-9D10; 093FB9, 093FCB) for falling planks
093FDD  data  wide_rope_bridge_plank_parts     # two 2-part lists for the double-width record +030
094001  table rope_bridge_rope_offsets s16     # screen offsets of the two ropes: (-1C, -10), (20, F) for a plank, (-1C, -10), (62, 30) at 094009 for a wide one
20E5A4  var   bridge_0_last_plank    u16       # the last plank spawn_object_groups_2 made for bridge 0; when it falls bridge_0_plank_count = 0 (no more sag) (rope_bridge_plank_update)
20E5A6  var   bridge_1_last_plank    u16
209450  var   bridge_0_sag           u16       # per plank number (0 and count + 1 are the anchored ends): sag in 8.8 written by compute_bridge_sag; read by rope_bridge_plank_update and 085F80 (collapsing_bridge_plank_update) (checked in a stage 10 dump: loaded plank 11 of 33 -> 2000, linear to 0 at both ends)
209550  var   bridge_1_sag           u16       # the same for the planks with field 95 set

# ======== Variables of the stage C/E and stage 10 objects ========
20E60E  var   biting_head            u16 object       # biting_head_init; the spike gate pillars (015544, 015630) are smashed (spawn_debris) when within 20 of it
20E632  var   rising_saws            u16       # three: rising_saw_init stores itself at [04]; grate_steps_init sets FFFF; grate_step_update reads [column]
20E64E  var   biting_head_done       u8        # biting_head_chase sets FF at x -431 (its stop); rising_saw_init and build_saw_shaft_base start on it (or at scroll_position 3780)
20E66C  var   geyser_front_start     u8        # set by waterfall_move (y <= 1480) and propeller_machine_chase (y >= 1BB0), cleared by geyser_front_init; geyser_front_wait starts the front on it

# ======== Objects by type (spawn_objects_by_type), code 00EC9E-012308 ========
# Stage objects of object_types 0, 2, 3, 5-19 and 28 (code 00EC9E-012308).
# Renders: s32ref dumps of walk_03/04/0A, stage_05 and two
# scratch walks (stage 9 to its end; stage 4 with vitality refilled every 6 frames until the end machine
# breaks), sprites only (render.py, plus a copy with a larger canvas that can draw only chosen objects).
# "kind" is field 47 (players.txt describes the player states each kind leads to in check_player_contacts).

# ======== Type 2: vine with a bar (stage 4, three at 2700, 92 = 2002, 94 = 2007) ========
# The anchor object draws byte 93 vine segments (sprite 6E68, second pool) hanging from it in the x/z plane;
# a separate main-list object is the bar at the tip (6E70). Render (walk_04 frames 6300-7100): three green
# vines with wooden bars over a pit, a player hanging from one, "CATCH!!" over the bars.
00EC9E  func  vine_init                         # type 2: byte 93 segments (add_pieces_to_current_object), then the bar (spawn_object_with_pieces 2: vine_bar_update, kind A, its piece 5D90 is its shadow); phase step = byte 94 << 16 (field 6E), anchor x kept in field 14, floor_z = z
00EDA9  func  vine_swing                        # phase (3C/3E) += field 6E, curl (speed_y) = sine_table[phase] * byte 95; places the segments 6 apart (18 x sine_table) along an angle that grows by the curl per segment (x and z), queue_sprite_at_depth each (bucket 0 while the tip points up, angle 100-2FF); field 80 = 1 except at phase 9F-110 while vines_hold_players is 0; deleted (vines_hold_players = FF) once screen_x is outside +-200
00EED2  func  vine_bar_update                   # the bar: x/y/z of the anchor after vine_swing's loop (the tip), kind A (players jumping into it are held: state 15, which lets go when this field 80 is 0), touchable_list and rider_list; shadow piece at floor_z while on a platform (flag 8); "CATCH!!" (spawn_sign kind 7, 2 frames) every frame while field 80 = 1 and a floor is under it (render: "CATCH!!" over the three bars)
208079  var   vines_hold_players    u8         # non-zero: vine_swing keeps field 80 = 1, so held players never let go; vine_scroll_stop_init sets 1, vine_scroll_stop_end 0, vine_swing FF when a vine leaves the screen; nothing else uses it

# ======== Type 3: scroll stop at the vines (stage 4, 2700, 92 = 2900, 94 = 2C80) ========
# walk_04: scroll_position stays at 29xx from frame 6253 to 7038 (scroll_mode 1), then moves at speed 8 once
# the only player hangs from a bar (frame 7100: player state 15, flag 10000).
00EFCC  func  vine_scroll_stop_init             # type 3 (invisible): vines_hold_players = 1, then vine_scroll_stop_wait
00EFD8  func  vine_scroll_stop_wait             # scroll_position >= field 92: scroll_mode 1 (stop), saved_scroll_mode 1, then vine_scroll_stop_hold; before that position it already runs the hold check every frame
00EFFA  func  vine_scroll_stop_hold             # every player object (vine_scroll_stop_player_blocks) out (flag 400), held (flag 10000, player state 15) or at x >= -120: scroll_mode 0, saved_scroll_mode 0, scroll_speed 8, then vine_scroll_stop_end
00F054  func  vine_scroll_stop_end              # scroll_position past field 94: scroll_mode 7, saved_scroll_mode 7, vines_hold_players = 0, delete_object_and_children

# ======== Type 5: spiked panel that flips up (stage 3, 13 from 2700 to 6800) ========
# Render (walk_03 12800-13600): a grey slab with hinge blocks lying flat over a plate with a grid of holes,
# then standing up as a spiked wall facing the players; the stage 3 chaser (spiked_roller_update) behind it.
00F087  func  spike_panel_init                  # spawn_objects 4: parts 1-2 (spike_panel_part_idle, kind C), part 3 the hitbox (spike_panel_hitbox_update), part 4 the plate (sprite 6B58, spike_panel_base_draw); head: start step = byte 93, rest time = field 94, 86 = 1E, kind C, box hitbox
00F1A9  func  spike_panel_hold                  # keeps the start step (byte 93) while no player is within 40 in x (find_nearest_player_x); while one is (from the start when byte 93 is 0) counts 1E frames, then clears byte 93 and goes to spike_panel_update
00F1F1  func  spike_panel_update                # face_object(chaser_object): field 24 between 100 and 378 -> spawn_debris 15 chunks and deleted; else steps spike_panel_frame_sequence (spike_panel_step_durations, step 0 lasts field 94) and places and queues the head and parts 1-2 by the frame (frame 5 widens the hitboxes); dead code: sound 25 for frame 5 sits in the frame < 3 branch
00F3F2  func  spike_panel_base_draw             # part 4, the plate with the holes (6B58): delete_if_off_screen, queue_sprite_in_bucket FF (behind everything)
00F412  func  spike_panel_hitbox_update         # part 3, only while the head's frame >= 4 (raised): hitbox 8D x BC at part 1's place (projected_x - D8), kind 12 (18: player state 2F, stunned) while rising (step 4), C at the top (step 5); touchable_list
00F46A  func  spike_panel_part_idle             # rsr: handler of parts 1 and 2, which spike_panel_update moves and queues
00F46B  table spike_panel_frame_sequence u8     # field 81 -> frame: 0 1 2 3 4 5 3 2 1 (flat, rising, up, lowering)
00F474  table spike_panel_step_durations u16    # frames per step: -, 3, 3, 2, 2, F (up), 3, 3, 3
00F486  table spike_panel_head_offsets spike_panel_screen_offset   # (screen dx, dy) of the head by frame
00F49E  table spike_panel_head_sprites u16      # 66F8-6768 by frame
00F4DA  table spike_panel_part_1_offsets spike_panel_part_offset   # (projected dx, dy and dz) of part 1 from the head by frame, 6-byte records
00F4FE  table spike_panel_part_1_sprite_flags u16  # sprite_flags of part 1 by frame
00F50A  table spike_panel_part_1_sprites u16    # 66E8-6758 by frame
00F516  table spike_panel_part_2_offsets spike_panel_screen_offset   # (screen dx, dy) of part 2 by frame (not drawn at frame 3)
00F52E  table spike_panel_part_2_sprites u16    # 66F0-6760 by frame
struct spike_panel_screen_offset 4 rom   # a (screen dx, dy) pair by frame: spike_panel_head_offsets (00F486, the head) and spike_panel_part_2_offsets (00F516, part 2): spike_panel_update "movea.w F486[PC](R0), R1" (00F28B), "movea.w F516[PC](R0), R1" (00F38B), then [R1+], [R1]; census ROM 00F486-00F49D, 00F516-00F52D
field spike_panel_screen_offset 00 dx s16  # "add.h [R1+], E[R20]" (00F291, 00F39F): onto screen_x (0, FFFF, FFF8, FFE5, FFE1; part 2: 0, FFFF, FFE0)
field spike_panel_screen_offset 02 dy s16  # "add.h [R1], C[R20]" (00F296, 00F3A4): onto screen_y (0, 13, 10; part 2: 0, 5, 2)
struct spike_panel_part_offset 6 rom     # a (projected dx, dy, dz) record of spike_panel_part_1_offsets (00F4DA), part 1's place from the head by frame: spike_panel_update "add.w R2, R1" (00F321, R1 = F4DA, R2 = frame x 6); census ROM 00F4DA-00F4FD
field spike_panel_part_offset 00 dx s16    # "add.h [R1+], 2A[R20]" (00F324): onto projected_x (28, 2A, 7, FFEC)
field spike_panel_part_offset 02 dy s16    # "add.h [R1+], 2E[R20]" (00F329): onto projected_y_int (FFF9, FFF2, FFF6, 1C, FFFD, FFF5)
field spike_panel_part_offset 04 dz s16    # "add.h [R1], 42[R20]" (00F32E): onto z_int (FFD0, FFD8, FFE0, 0)

# ======== Type 6: electric pole (stage 3, 1E00-6000) ========
# Render (walk_03 10700-11400): closed metal capsules on bases; near a player they open into a red column
# with cyan sparks (the three pieces).
00F53A  func  electric_pole_init                # 67D8 (closed), kind 5, field 7E = 4 (health against attacks), three spark pieces (palette 83D0)
00F59E  func  electric_pole_wait                # face_nearest_player: within 70 -> electric_pole_rise, else only electric_pole_update
00F5B4  func  electric_pole_rise                # every other frame the next of 10 steps (electric_pole_rise_frames -> electric_pole_sprites); at step A: projected_y - B (queued B lower to make up for it, field 84), then electric_pole_update
00F5F1  func  electric_pole_update              # chaser_object within F in x, or touched with field 7E under 4 (attacks): sprite_scale 200 and the small explosion at 08E43B; touched with 7E >= 4: kind 15 (21: players hurt, state 0D step 7); queues the pole and the sparks (front spark while rising, front and side sparks when open)
00F736  table electric_pole_rise_frames u8      # step -> sprite index: 0 1 2 3 4 5 7 5 7 6 7
00F741  table electric_pole_sprites u16         # 67D8 (closed) to 6810 (open)
00F751  table electric_pole_front_spark_sprites u16  # 6818-6848, the spark in front of the column
00F75F  table electric_pole_side_spark_sprites u16   # 6850-68B0, the sparks beside the column
00F779  table electric_pole_side_spark_offsets_x s16 # -D, +D

# ======== Type 0: metal wall that rises and breaks (stage 7 at 3C00, the "how to play" demo) ========
# Render (walk_04 2884-2920, the attract demo of stage 7): a riveted metal wall with a face painted on it
# rises out of cracked ground with white dust when Sonic comes near; a spin jump breaks it, a ring pops out.
00F77D  func  metal_wall_init                   # 8 objects: right half, piece 1 left half, piece 2 shadow, piece 3 floor cracks, pieces 4-7 dust; kind 5, field 7E = 4 (hits), box hitbox; byte 94 set: flipped (sprite_flags 40) with another hitbox
00F878  func  metal_wall_wait                   # face_nearest_player from projected position + (10, 10): within 80 -> field 84 = 14 (dust frames) and metal_wall_rise
00F8B2  func  metal_wall_rise                   # frames 0-4 every 4 frames (rising out of the floor), then 81 = 7 and metal_wall_update
00F8E1  func  metal_wall_update                 # field 7E dropped (hits): frame = metal_wall_damage_frames[7E], a hop if it lost more than 1, screen_x + 2 (move_object_projected rewrites it the same frame: no shake shows), (4 - 7E) * 2 debris chunks; 7E = 0: scatter_rings_random byte 95 rings and metal_wall_broken; moves, fall_to_floor, touchable_list while hits remain
00F97A  func  metal_wall_broken                 # frame 7 (rubble), only positioned and queued (bucket FE)
00F98C  func  metal_wall_draw                   # sprite by frame (metal_wall_sprites), pieces 1-3 from metal_wall_part_tables (+10 when flipped), dust pieces 4-7 on alternate frames while field 84 counts down
00FB20  table metal_wall_sprites u16            # 5838-57F8 rising (frames 0-4), 5858 5868 hit, 5848 broken
00FB30  data  metal_wall_half_sprites            # piece 1 by frame (5840-5850)
00FB40  data  metal_wall_half_offsets            # s8 (dx, dy) by frame, then the flipped set
00FB60  data  metal_wall_shadow_sprites          # piece 2 (67D0-67B0)
00FB70  data  metal_wall_shadow_offsets
00FB90  data  metal_wall_crack_sprites           # piece 3, the cracked floor (6E40-6E60)
00FBA0  data  metal_wall_crack_offsets
00FBC0  table metal_wall_dust_sprites u16       # 5878-58F0 (two sets of 8)
00FBE0  table metal_wall_damage_frames u8       # by hits left 0-3: 7 6 6 5

# ======== Type 7: the machine at the end of stage 4 (A400) ========
# Render (scratch walk with vitality refills, frames 23100-27120, larger canvas): an open-topped red armored
# box with a glowing yellow energy capsule in front, a back wall that slides on two rods, two green arms that
# swing spiked discs over and drop them to roll at the players, two grey cylinders shooting long flames.
# It breaks after 28 hits or 0E10 frames with players in play (here at frame 26675): explosions, then a
# wreck; goal_unlocked = FF opens the goal (the type 13 sign at A700 is kind 1, "BREAK!!").
00FBE4  func  flame_machine_init                # flame_machine_flags = 0; box (6258, kind C, field 7E = 28 hits, 86 = 0E10 frames) with its floor piece (6260), capsule pieces (6B20 animated, 6B18) and shadow; back wall (flame_machine_back_wall_update) with two rods; two arms (flame_machine_arm_update, fields 92/94 offset, 24 = 0001/01FF: index and side); two cannons (flame_machine_cannon_update); all one chain through field 28
00FE46  func  flame_machine_update              # time (86) runs down while active_player_count; a hit (7E changed): sound 21, palette flash (flame_machine_flash_step, sound 1E per step); queues the box pieces, animates the capsule; out of hits or time: flame_machine_destroyed
00FF6F  code  flame_machine_destroyed           # (in flame_machine_update) goal_unlocked = FF, flags bit 14; every 8 frames an explosion object at a random place (flame_machine_explosion_start) until field 8C reaches 130, then flame_machine_remove and spawn_flame_machine_wreck
00FFA0  func  flame_machine_remove              # electric_fence_state (208076) = FF: the type 1 fence posts at A400 delete themselves; then delete_object_and_children
01000B  func  flame_machine_explosion_start     # explosion object: field 6E = 20, 24 = 80, spawn_debris 8 chunks, explode_object
010815  code  spawn_flame_machine_wreck         # reached by a branch from flame_machine_update (right after the type 9 handler's rsr): 3 objects at projected 1B2F,14F, handler flame_machine_wreck_draw
010870  func  flame_machine_wreck_draw          # scorched ground (8360) and two wrecked pieces (8370, 8368) in buckets FE/FD/FC; deleted when 20E5ED is set (render frame 27060)
01004D  func  flame_machine_back_wall_update    # every 3 frames (the first step after 4) the next of flame_machine_back_wall_frames: the wall slides by flame_machine_back_wall_offsets from the box, the rods by flame_machine_rod_sprites; at frame 5 starts the arms (flags 4/5) and waits for them; at the cycle end pauses 1E frames and sets flags 2/3 (cannons fire) and A/B (arms take a disc), clears 9
0101C2  func  flame_machine_arm_update          # arm n (byte 25): flag A+n -> flame_machine_arm_take_disc; flag 4+n: every other frame the next of flame_machine_arm_frames (flame_machine_arm_sprites / _part_sprites), frame C sets flag 6+n and it sways (field 81 through flame_machine_arm_sway_offsets) until field 85 passes 26, then flag 9 (discs dropped) and flame_machine_arm_rest; frame D ends (clears 4+n)
010240  func  flame_machine_arm_rest            # 1E frames, then clears flag 6+n and back to flame_machine_arm_update
0102C5  func  flame_machine_arm_draw            # position = back wall + (92, 94) + sway offset * side (byte 24); queued at the second arm's depth, its piece in bucket n * 4
010614  func  flame_machine_arm_take_disc       # a spiked disc (6278, sprite_scale C0, kind 24: 36, players hurt) held by arm n (field 92), clears flag A+n; shadow piece 6988
0103F0  func  flame_machine_cannon_update       # grey cylinder (kind C, box hitbox), flashes with the box (flame_machine_cannon_hit_palettes); flag 2+n (field 92): a flame (6910, kind 17: 23, player state 20 hurt and tumbling) at flame_machine_flame_offsets[n], direction flame_machine_flame_directions[n]
0104C6  func  flame_machine_flame_burn          # every other frame the next of flame_machine_flame_frames (two bursts, flame_machine_flame_sprites; deleted at frame B), touchable_list from frame 2, shadow on the floor at frames 2-4, drawn 20 higher; explode_object when the machine is destroyed
01069D  func  spiked_disc_hold                  # copy_position_to_cursor from its arm + spiked_disc_hold_offsets[arm frame] (an arc over the top); flag 9 -> spiked_disc_drop
0106D4  func  spiked_disc_drop                  # fall_to_floor, then spiked_disc_roll
0106E5  func  spiked_disc_draw_held             # moves, queue_sprite_in_bucket (arm index + 1) * 2; shadow while the arm frame is 8 or more, a halfword compare that takes spin_toggle's low byte (1 after the first frame) as its high byte, so in practice always; then spiked_disc_spin
010705  func  spiked_disc_roll                  # rolls at angle 154, speed 300, falls, delete_if_off_screen (render: discs rolling toward the players)
010748  func  spiked_disc_draw_shadow           # shadow piece at floor height + 28
010762  func  spiked_disc_spin                  # machine destroyed: explode_object; else 4-frame spin (spiked_disc_sprites), touchable_list and rider_list, a step every frame after the first (spin_toggle is set back to 1 each step)
01002B  table flame_machine_capsule_sprites u16 # 6B20-6B50, the energy capsule's animation
010045  table flame_machine_hit_palettes u16    # box sprite_palette by flash step 0-3 (11F0 0760 1080 10C0)
010196  table flame_machine_rod_sprites u16     # 6230-6250 by back wall frame
0101A0  table flame_machine_back_wall_offsets flame_machine_offset  # (projected dx, dy) of the back wall from the box, frames 0-5
0101B8  table flame_machine_back_wall_frames u8 # 0 1 2 3 4 5 4 3 2 1
010351  table flame_machine_arm_sprites u16     # by arm frame 0-C
01036B  table flame_machine_arm_part_sprites u16
010385  table flame_machine_arm_frames u8       # 1-C, B-1, 0, D (end)
01039E  table flame_machine_arm_sway_offsets flame_machine_arm_sway # 29 (dx, dy) pairs, times the side
01042D  table flame_machine_cannon_hit_palettes u16
010575  table flame_machine_flame_offsets flame_machine_offset   # (projected dx, dy) from cannons 0 and 1
01057D  table flame_machine_flame_frames u8     # flicker 0/1, burn 2-4 repeated, fade 5-A, twice, then B (end)
0105F4  table flame_machine_flame_sprites u16   # 6910-6960
01060A  table flame_machine_flame_shadow_sprites u16  # 6990-69A0
010610  table flame_machine_flame_directions u16      # 0100, 0300 (field 24 of the flames)
0107AF  table spiked_disc_sprites u16           # 6278-6290
0107B7  table spiked_disc_hold_offsets spiked_disc_hold_offset      # (projected dx, dz) by arm frame 0-C
struct flame_machine_offset 4 rom        # a (projected dx, dy) pair of the flame machine: flame_machine_back_wall_offsets (0101A0, the back wall from the box by its frame: flame_machine_back_wall_update "movea.w 101A0[PC](R1), R2" 0100E3) and flame_machine_flame_offsets (010575, a flame from its cannon: flame_machine_cannon_update "movea.w 10575[PC](R0), R1" 010450); census ROM 0101A0-0101B7, 010575-01057C
field flame_machine_offset 00 dx s16      # "add.h [R2+], 2A[R23]" (0100F9), "add.h [R1+], 2A[R3]" (01045E): onto projected_x (FFB2-FFF9; 56)
field flame_machine_offset 02 dy s16      # "add.h [R2], 2E[R23]" (0100FE), "add.h [R1], 2E[R3]" (010463): onto projected_y_int (FFD1-FFF4; 8C, 40)
struct flame_machine_arm_sway 2 rom      # a step of flame_machine_arm_sway_offsets (01039E, 29 steps by sway_step): flame_machine_arm_draw "movea.h 1039E[PC](R0), R1" (0102EB), each byte times the arm's side; census ROM 01039E-0103D7
field flame_machine_arm_sway 00 dx s8     # "movs.bh [R1+], R3; mul.h R2, R3" (0102F5): onto projected_x (F6 up to 1E and back)
field flame_machine_arm_sway 01 dy s8     # "movs.bh [R1], R4; mul.h R2, R4" (0102F8): onto projected_y_int (05 down to F2 and back)
struct spiked_disc_hold_offset 4 rom     # a record of spiked_disc_hold_offsets (0107B7, by its arm's frame 0-C): spiked_disc_hold "movea.w 107B7[PC](R0), R1" (0106BF); census ROM 0107B7-0107EA
field spiked_disc_hold_offset 00 dx s16   # "add.h [R1+], 2A[R23]" (0106C5): onto projected_x (FFBD up to 48)
field spiked_disc_hold_offset 02 dz s16   # "add.h [R1], 42[R23]" (0106CA): onto z_int (an arc over the top: FF96 to FF74 and back to FFD4)
20807A  var   flame_machine_flags   u32        # bit numbers in hex as in the listing: 1 built, 2/3 cannon n fires, 4/5 arm n active, 6/7 arm n swaying, 9 discs dropped, A/B arm n takes a disc, C hit flash (1000), 14 destroyed (100000; also read by type 8)
20807E  var   flame_machine_flash_step s8      # 4 after a hit, down by 1 a frame: palette of the box and the cannons; negative when done

# ======== Type 8 (in no spawn list): scroll stop until the machine breaks ========
0107EB  func  flame_machine_scroll_stop_wait    # scroll_position >= field 92: scroll_mode 1, then flame_machine_scroll_stop_hold (never runs)
010802  func  flame_machine_scroll_stop_hold    # flame_machine_flags bit 14 (destroyed): scroll_mode 7 (every frame from then on)

# ======== Types 9 and 17 (in no spawn list) ========
010814  func  object_type_9_idle                # object_types[9] handler (4 objects): rsr; spawn_flame_machine_wreck follows it
011F1F  func  object_type_17_idle               # object_types[17] handler (5 objects): rsr

# ======== Type 10: treadmill that raises a gate (stage 8, 6000 and 7400) ========
# Render (stage_05 scenario in stage 8, frames 9600-10350): a portcullis hanging on chains across the way,
# a wooden treadmill with a toothed roller and an arrow plate beside it.
0108D4  func  treadmill_init                    # belt (72B0, kind 16: players inside it are in state 22), roller (treadmill_roller_update, +27,+18), gate half (treadmill_gate_update, +73,+31) and the other half (treadmill_gate_half_update, +C3,+59), each with a piece
010AD7  func  treadmill_update                  # sums the speed (field 6E) of the players in state 22 (treadmill_player_blocks); above field 92: field 3C = treadmill_lift_forces[speed] (the gate's lift) and the belt animates faster; queue_sprite_in_bucket FF; add_to_zone_list until the gate is up (its flags bit 0)
010BDC  func  treadmill_roller_update           # roller and its piece animated with the belt (treadmill_roller_sprites / _part_sprites by field 80 >> 5)
010C53  func  treadmill_gate_update             # gate half: fall_to_floor against the treadmill's lift, up to 28 above its floor (then sets the treadmill's flags bit 0); sounds C/1E while it moves; piece (chain top) 63 above in bucket 0; kind C, box hitbox
010D74  func  treadmill_gate_half_update        # the other half: z of the first half
010B84  table treadmill_belt_sprites u16        # 72B0-72E8
010B94  table treadmill_lift_forces u32         # 0, 6010 ... 6028 (gravity is 6000)
010C43  table treadmill_roller_sprites u16      # 72F0-7308
010C4B  table treadmill_roller_part_sprites u16 # 7310-7328

# ======== Type 11: flame jet (stage 8, five at 8800) ========
# Render (stage_05 frame 17850, larger canvas): a row of five green machines on a wall, each with a small
# flame (the switch flags were 0).
010DD3  func  flame_jet_init                    # nozzle (flame_jet_nozzle_sprites[92], kind 14: 20, player state 20) with a shadow piece; flame (6910, kind 14, flame_jet_pilot) with 9 trailing pieces, sprite_scale 180 down by 18, direction 158 (350 when field 92)
010F59  func  flame_jet_nozzle_update           # field 92 = 0 (all five in stage 8): queued, touchable_list, its hitbox_size_y reaching the flame front (1.5 x the distance); field 92 set: bucket 0 and the flame carries the hitbox instead
010FE2  func  flame_jet_pilot                   # until a bit of stage_trigger_bits (208096) is set (bridge_fire_climb_step, under bridge_fire_rise, sets them all): a short flame flickering in and out (field 6E 0-666); then sprite_scale 180 and flame_jet_ignite
011031  func  flame_jet_ignite                  # the flame grows (field 86 = 380) until 6E passes F000, then flame_jet_burn
011049  func  flame_jet_burn                    # flame front at the nozzle + direction * 6E, 5-frame flicker (flame_jet_flame_sprites); after ignition the 9 pieces fill the line back to the nozzle
010FDA  table flame_jet_nozzle_sprites u16      # 7330, 7338 by field 92
0111A5  table flame_jet_flame_sprites u16       # 91E8-9208

# ======== Type 12: crate pushed to a post (stage 7 at 3300, stage 3 at 7300/7980) ========
# Render: a wooden crate and a post with chevrons (walk_03 29700); in the stage 7 demo Sonic spin-jumps on
# the crate (walk_04 2760).
0111AF  func  push_crate_init                   # crate (6968, kind 20, box hitbox) and a post (spawn_object_with_pieces 2, push_crate_stop_post_update) set back by (22, 11) * byte 93 + (11, 1D); overwrites field 28, which held the piece spawn_objects_by_type gave type 12 (count 2)
011259  func  push_crate_update                 # chaser_object within 3 in y, or touched by an attacking player (flag 40): push_crate_break; touched by an object moving in quadrant byte 92 (its field 24): moves at push_crate_push_angles[92] with that toucher's speed (direction_to_speed)
0112D8  func  push_crate_break                  # every other frame push_crate_break_sprites; at the last: field 94 rings (push_crate_release_all_rings)
01130E  func  push_crate_move                   # moves, delete_if_off_screen; broken: bucket FF; else touchable_list, queued; while pushed one ring every 11 frames (timer 10 counted below 0; field 94 counts them)
011378  func  push_crate_stop_post_update       # crate within 33 in y: stops it for good (crate field 81); post and its top piece queued
0113CC  func  push_crate_release_all_rings      # rings_to_release = field 94, then push_crate_release_rings
0113D3  func  push_crate_release_rings          # released_ring_angle = push_crate_push_angles[(92 + 2) & 3] (behind the crate), drop_rings
0113F9  table push_crate_break_sprites u16      # 6A90 6A98 6AA0
0113FF  table push_crate_push_angles u32        # 348, 048, 148, 248

# ======== Type 13 ========
01140F  func  sign_start_from_type              # type 13: becomes sign kind [92] (place_sign) for [94] frames: stage 4 A700 kind 1 ("BREAK!!" until goal_unlocked), stage 9 4400 kind 6 ("CATCH!!", B4 frames, before the handle rods)

# ======== Type 14: handle rod between two posts (stage 9, 4480, 4880, 4A00) ========
# Render (scratch stage 9 walk, frames 15800-18400): a row of orange balls with handles on a rod from a
# purple post with an orange arm to a second post; at 17970 the near post fell and the rod swung down from
# its far end and hung straight down.
011437  func  handle_rod_init                   # pivot (73B8) at the far end, raised 12 per handle; spawn_objects byte 92: handles (handle_rod_handle_init) 12 apart along angle 300, the first becomes the near post (74C8), the one next to it the grip (kind 1A: 26, player state 27 climbs hand over hand to field 94 = the pivot's x while the rod is level); one more object: the far post
0115FC  func  handle_rod_handle_init            # a shadow piece (5D80, sprite_scale 80), then handle_rod_handle_update
011629  func  handle_rod_wait                   # until the near post falls (its field 81): byte 93 set -> 20E60C = 1 for a frame (players in state 27 at x <= 450 drop), else 20E5F5 = 1 (players state 26) and event_12_fast_scroll = 1; queued, rider_list
011662  func  handle_rod_start_swing            # clears 20E60C and 20E5F5, then handle_rod_swing
011675  func  handle_rod_swing                  # pendulum: angle 82 += high byte of the speed 84, 84 += 86, 86 flips at the bottom; angle 0 at low speed: handle_rod_hang; scroll_position past field 94: handle_rod_remove
011767  func  handle_rod_hang                   # hangs straight down, falls when no floor; scroll_position past field 94: handle_rod_remove
0117B2  func  handle_rod_remove                 # delete_object_and_children
0117B9  func  handle_rod_handle_update          # handle i at the pivot's angle, 12 * i away (through the speed fields); deleted with the pivot (94 = FFFF); the grip joins touchable_list and rider_list
0118D0  func  handle_rod_post_init              # near post 74C8 with its arm piece 74D0; far post (field 81) 74D8
01191D  func  handle_rod_post_update            # no floor under it (floor_z 800, not on a platform; it happens once the view has left it behind): field 81 = 1, handle_rod_post_fall; arm piece queued in front
011989  func  handle_rod_post_fall              # falls, delete_if_off_screen

# ======== Types 15 and 28: spear trap (stage 9, 5400-8D00 and A600-CF00) ========
# Render (frames 19560-25700): long spears with blue tips through pink rings; the renderer ignores the
# clip-rectangle entries (sprite command 1, sprite_flags 5000) that hide them below the floor line.
0119A7  func  spear_trap_init_100               # type 15: field 24 = 100 (the direction a player it hurts is knocked, state 0D), then spear_trap_init
0119B0  func  spear_trap_init_300               # type 28: field 24 = 300
0119B7  func  spear_trap_init                   # 10 objects: three clip rectangles (sprite_flags 5000) at the floor line, spear sprites 73A0/7390/7388/7398/73A8; first wait field 94, then period field 92
011AF3  func  spear_trap_wait                   # counts field 82 down, then kind 4 and spear_trap_rise
011B1E  func  spear_trap_rise                   # speed_z -= 10F00 a frame for C frames; then sound 1B, kind C, spear_trap_stay_up
011B54  func  spear_trap_stay_up                # 3C frames, then spear_trap_retract
011B61  func  spear_trap_retract                # fall_to_floor back down; then field 82 = field 92 and spear_trap_wait
011B81  func  spear_trap_draw_touchable         # touchable_list, then spear_trap_draw
011B87  func  spear_trap_draw                   # z from field 84, moves; pieces queued by the floor's screen y (clip rectangle bottoms floor - 5 / floor + 20); keeps z in 84 and leaves z = floor_z

# ======== Type 16: ice cube launcher (stage 9, 0700-2EB8) ========
# Render (scratch stage 9 walk, frames 3650-7700): a thin ice wall pushing out ice cubes that slide away.
011CB5  func  ice_launcher_init                 # type 16: 7420, kind 5, box hitbox, handler ice_launcher_update
011CE7  func  ice_launcher_update               # deleted once scroll_position passes field 94; steps ice_launcher_frames (durations ice_launcher_frame_durations[byte 93]) with a growing hitbox; at step C an ice cube (spawn_object_with_pieces 2: ice_cube_slide, kind 19: 25, players pushed along) with speed ice_cube_speeds[byte 92]
011EAB  func  ice_cube_slide                    # slides at angle 2B6, falls, delete_if_off_screen; shadow piece; hitter_list
011E18  table ice_launcher_sprites u16          # 7420-7488
011E34  table ice_launcher_frame_durations u16  # 3-7 frames per step
011E3E  table ice_cube_speeds u16               # 100-500
011E48  table ice_launcher_frames u8            # step -> frame
011E57  table ice_launcher_hitbox_sizes hitbox_size_pair     # (hitbox_size_x, hitbox_size_y) by frame
011E8F  table ice_launcher_hitbox_offsets hitbox_offset_pair    # (field 7C, 7D) by frame
struct hitbox_size_pair 4 rom                 # an object's (hitbox_size_x, hitbox_size_y) words: ice_launcher_hitbox_sizes by frame (ice_launcher_update 011D3B "movea.w 11E57[PC](R0), R1": 4 bytes a frame; "mov.h [R1+], 10[R20]; mov.h [R1], 12[R20]") and rope_plank_hitbox_size (stage_object_records_2 record 0 +12: rope_bridge_plank_update 093D4F); census ROM 011E57-011E8E, 093F6B-093F6E
field hitbox_size_pair 00 size_x u16          # into hitbox_size_x (10): 0, 18-25
field hitbox_size_pair 02 size_y u16          # into hitbox_size_y (12): 0, 37-5C
struct hitbox_offset_pair 2 rom               # an object's (hitbox_offset_x, hitbox_offset_y) bytes: ice_launcher_hitbox_offsets by frame (ice_launcher_update 011D4B "movea.h 11E8F[PC](R0), R2": 2 bytes a frame; "mov.b [R2+], 7C[R20]; mov.b [R2], 7D[R20]"); census ROM 011E8F-011EAA
field hitbox_offset_pair 00 x s8              # into hitbox_offset_x (7C): 0, 80-99
field hitbox_offset_pair 01 y s8              # into hitbox_offset_y (7D): 0, 13-17

# ======== Type 18: padded wall (stage 9, 6000) ========
# Render (frame 19700): a purple wall with four orange riveted pads.
011F20  func  padded_wall_init                  # wall (77D0) and four pads (spawn_objects 4: 77A8, kind B: 11, players knocked back without damage, state 0D step 1; jumping ones bounce off)
011FDE  func  padded_wall_update                # deleted once scroll_position passes field 92
011FFC  func  wall_pad_update                   # touched: one round of wall_pad_frames (wall_pad_sprites, wall_pad_frame_durations); touchable_list
01205B  table wall_pad_frames u8                # 0 1 2 3 2 3 2 3 2 4 1
012066  table wall_pad_sprites u16              # 77A8 77C8 77B0 77B8 77C0
012070  table wall_pad_frame_durations u16

# ======== Type 19: cactus, and the tornado's whirlwinds (stage A) ========
# Renders (walk_0A 3700, 4300; the stages helper's s0A_4200): green cacti; a small whirlwind leaving the big
# dust storm, the stage A chaser (tornado_start / tornado_move).
012086  func  cactus_init                       # 77D8, flipped when field 92; shadow piece
0120C2  func  cactus_update                     # tornado_object within 60: sprite 77E0, rises spiralling (angle -10, radius +18, speed_z up by C00 a frame); dead code: the "z -= 3" branch after fall_to_floor needs a distance test that already failed
012171  func  spawn_whirlwind                   # called by tornado_throw_whirlwinds (08981A): a whirlwind at the tornado (kind 1F: 31, player state 29 tripped), spin step from whirlwind_spin_step
0121C9  func  whirlwind_aim                     # face_nearest_player; heads for the farthest player (field 24) at 3 x the nearest distance (field 6E); for player 3 it takes player 2's angle (20801E, not 208020)
012220  func  whirlwind_move                    # for F0 frames or until touched: heading plus a circle (angle 82 += 86, radius 100), 4-frame spin (whirlwind_sprites); then whirlwind_vanish
01224A  func  whirlwind_vanish                  # whirlwind_vanish_sprites every other frame, then deleted
0122F1  table whirlwind_sprites u16             # 7988-79A0
0122F9  table whirlwind_vanish_sprites u16      # 80A8-80E0
20807F  var   tornado_object        u32        # tornado_start (0890DC, the stage A chaser: a big dust storm in the renders) stores itself here and in chaser_object; only cactus_update reads it
20808B  var   whirlwind_spin_step   u16        # spawn_whirlwind adds B (mod 40) and gives it to the new whirlwind (field 86)

# ======== Objects by type (spawn_objects_by_type), code 012309-0158E2 ========
# Stage objects started by spawn_objects_by_type, code in 012309-0158E2 (object_types 1, 4, 20-27, 29-44;
# type 28 = 0119B0 is type 15's code at 0119B7 facing 300 instead of 100, outside this range) plus the
# Eggman booth spawned from elsewhere (012B24).
#
# How to read the evidence: "sNN" = stage NN, positions are stage_spawns_by_type scroll positions, 92/94 are
# the two spawn words (object fields 92/94). Sprites (field 04) were drawn with the frame records at 130000
# and the stage's palette from s32ref dumps. Renders: s32ref dumps of tests/controls walk_0A, walk_0C,
# ending, and scratch scripts that start a stage at a later camera section (poke 20F00A = 1 and 20E5CE for
# one frame so start_stage takes the attract-demo start section): stage 0A section 40, stage 10 sections
# 0/9, stage 9 section 13, stage 8 section 22, stage 4 section 20.
# Touch classes (field 47) as check_player_contacts (00E488) uses them: 4 hazard (ignored while the
# player is invulnerable, a jumping player hits it), 5 solid (ignored while invulnerable), 0C solid, both
# hit by a jumping player; 0D hurts even an invulnerable player unless he jumps on it; 0E always hurts;
# 23 the player gets stuck (state 2C); 24 hurts (unless invulnerable) and cannot be hit.

# ======== Type 20: centipede (s0A at 5050 and 7D80; 92 = scroll position where it leaves, 94 = direction) ========
# An insect head (8 directions), nine legged round body segments and a spiked tail, each with a shadow part.
# It hops toward the nearest player in long arcs over the sand; the segments replay the head's path from
# centipede_path 5 frames apart each; a sand puff is left where each hop starts. Seen in walk_0A 12400-13000.
012309  func  centipede_init                   # type 20 entry: head (sprite 7940, palette 9270, kind 24, height 32) + shadow; nine segments (7980) and the tail (7A08) from spawn_object_with_pieces(2), field 82 = 5, A, F .. 32 frames of delay, shadows 5D80 zoom A0; tail 92 = the segment ahead; centipede_command = 0
0123CC  func  centipede_chase                  # head: until scroll_position passes field 92 aims at the nearest player (only while not falling: add_to_touchable_list + face_nearest_player) and centipede_move; no active players: stays on its last path point, centipede_command = 1 (body holds); then centipede_leave
0123E6  func  centipede_leave                  # head after scroll_position passed 92: heads in direction field 94 (nearest_player_distance forced to 1F); delete_if_off_screen -> centipede_command = 9 (the body removes itself)
01249F  func  centipede_move                   # head step: sprite centipede_head_frames[(24 / 92 + 1) & 7]; speed ((2 * nearest_player_distance) & 3FF + 100) * 2 along field 24 plus half of it along a wobble angle (field 82, -10 a frame); writes projected_x/projected_y into centipede_path[81] (81 wraps after 32); centipede_hop, puff, draw
01254E  func  centipede_segment_wait           # body segment: counts its delay (82) down, then centipede_segment_follow
012561  func  centipede_segment_follow         # body: delete_object_and_children on centipede_command 9; otherwise centipede_follow_path (not while 1); hop and draw like the head
012595  func  centipede_tail_wait              # the tail's delay (32 frames)
0125A8  func  centipede_tail_follow            # like the segment; also direction_to_object toward the segment ahead (92) for centipede_tail_frames[(24 / 92 + 5) & 7]
012600  func  centipede_follow_path            # projected_x/projected_y = centipede_path[82], 82 + 1 (wraps after 32)
01262E  func  centipede_hop                    # field 86 > 0: rising, speed_z -= 600 a frame; 86 = 0: fall_to_floor with 4000 taken back (gravity 2000); back at the floor (32-bit compare of z with 8): 86 = 5A (traced in walk_0A: 113 decimal pixels up and back in about 150 frames)
012669  func  draw_centipede_piece             # queue_sprite_at_floor, add_to_rider_list, then the shadow part at its floor (queue_sprite_at_depth screen_y - A)
01268D  func  spawn_centipede_sand_puff        # on the frame a piece lands (86 = 5A): a 1-part object at its spot (projected_y + 6), sprite 75A8 palette 1020 zoom 180, 82 = 2D frames, handler sand_puff_start
0126CD  func  sand_puff_start                  # unproject_object, add_to_rider_list, then sand_puff_animate
0126E1  func  sand_puff_animate                # cycles sand_puff_frames every frame for 2D frames, removed early over a pit (floor height >= 800); sand-coloured puffs in walk_0A 12700
012726  table centipede_head_frames u16        # 8 head sprites (7958 .. 7950) by (direction / 92 + 1) & 7: seven sectors of 92 use entries 1-7, entry 0 only for directions 3FE-3FF
012736  table sand_puff_frames      u16        # 75A8 75B0 75B8 (sand_puff_animate)
01273C  table centipede_tail_frames u16        # 8 spiked tail sprites 7A08-7A40 by direction
20D0EA  var   centipede_path        u16        # 33 records of (projected_x, projected_y): the head writes [81] each frame, segments read [82]; only type 20 uses it
20808D  var   centipede_command     u8         # set by the head for its segments: 0 follow the path, 1 hold (no active players), 9 remove yourselves (head gone)

# ======== Type 21 (no stage list spawns it): crawler ========
# Purple box-shaped segments with claws (78E0-7918), a pincer head (7920/7928) and a hooked tail
# (7930/7938), each with a shadow; 92 low byte picks one of two facings; 94 is the direction it walks.
01274C  func  crawler_init                     # type 21 entry (never spawned): eight 2-part segments stepped by crawler_segment_offsets (kind 4, box 29 x 2F, handler crawler_segment_animate), the last one is the tail (kind D), the spawned object the head (kind D, handler crawler_head_update)
0128EA  func  crawler_head_update              # find_nearest_player_x: within 28 (35 for kind 1) sets field 8C (go); touchable and rider lists; draws itself and its shadow (falling with 92 high set: depth from field 88 of the object in 74); crawler_step
01298C  func  crawler_segment_animate          # every 4 frames the next of the kind's 6 walking frames (crawler_walk_steps -> crawler_segment_frames), then crawler_segment_update
0129CE  func  crawler_segment_update           # move_object_projected, fall_to_floor, lists, draw with shadow, crawler_step (segments and the tail)
012A72  func  crawler_step                     # once the head's 8C is set: every 5 frames (86) toggles 81; while 81 is set the piece moves at speed 200 along field 94, otherwise stands still (stop-and-go)
012AD0  table crawler_head_frames   u16        # [kind]: 7920 7928
012AD4  table crawler_tail_frames   u16        # [kind]: 7930 7938
012AD8  table crawler_walk_steps    crawler_walk_row  # 6 frame indexes per kind (0 1 2 3 2 1 / 4 5 6 7 6 5)
struct crawler_walk_row 6 rom   # a crawler variant's six walking frames: crawler_segment_animate reads crawler_walk_steps + variant x 6 + walk_step (one sum in R0)
field crawler_walk_row 00 steps u8[6]   # the walking frame indexes into crawler_segment_frames, one every 4 frames (0 1 2 3 2 1 / 4 5 6 7 6 5)
012AE4  table crawler_segment_frames u16       # 78E0-7918
012AF4  table crawler_collision_boxes hitbox_size_and_offset  # [variant] the box of the head and the tail: (32 x 30, offsets FC, CF), (3E x 40, FC, D2)
struct hitbox_size_and_offset 6 rom          # an object's box: hitbox_size_x, hitbox_size_y words, then the hitbox_offset_x, _y bytes, by kind: crawler_collision_boxes (crawler_init 01283E / 01289E "movea.b 12AF4[PC](R0), R0", R0 = variant x 6, then [R0+] 012844-012853, 0128A4-0128B3), wall_piece_boxes (wall_piece_init 012CAB "movea.b 12D08[PC](R0), R1", 012CB1-012CC0) and shaking_block_boxes (shaking_block_init 012D47, 012D4D-012D5C); census ROM 012AF4-012AFF, 012D08-012D2B, 012E48-012E53
field hitbox_size_and_offset 00 size_x u16     # "mov.h [R0+], 10[R20]": hitbox_size_x (14-61)
field hitbox_size_and_offset 02 size_y u16     # "mov.h [R0+], 12[R20]": hitbox_size_y (19-86)
field hitbox_size_and_offset 04 offset_x s8    # "mov.b [R0+], 7C[R20]": hitbox_offset_x (E1-2B)
field hitbox_size_and_offset 05 offset_y s8    # "mov.b [R0], 7D[R20]": hitbox_offset_y (82-F1, 06)
012B00  table crawler_segment_offsets crawler_piece_offset  # [variant] (dx, dy) of projected_x / projected_y between segments: (10, -8), (-10, 8); nothing reads the 12 bytes after them (0045 0039 CE05, 0052 0043 C8F0)
struct crawler_piece_offset 4 rom            # a (projected dx, dy) pair of the crawler by variant: crawler_segment_offsets (crawler_init 012771 "movea.w 12B00[PC](R13), R0": the step from one segment to the next, "add.h [R0+], R17; add.h [R0], R18"), crawler_tail_offsets (012819, the tail's extra step: "add.h [R0+], 2A[R20]; add.h [R0], 2E[R20]") and crawler_head_offsets (012874, the head's); census ROM 012B00-012B07, 012B14-012B23
field crawler_piece_offset 00 dx s16          # onto projected_x (2A): 10, -10; tail 4, -12; head -18, B
field crawler_piece_offset 02 dy s16          # onto projected_y_int (2E): -8, 8; tail -10, 0; head 0, -7
012B14  table crawler_tail_offsets  crawler_piece_offset  # [variant] extra (dx, dy) of the tail: (4, -10), (-12, 0)
012B1C  table crawler_head_offsets  crawler_piece_offset  # [variant] extra (dx, dy) of the head: (-18, 0), (B, -7)

# ======== Eggman's booth (not an object type): spawned by 012B24 ========
# Eggman at a console with two levers (15 decimal frames, 5550-56E8) over a 320-pixel backdrop (5718), fixed at
# screen x 9F, sliding down from the top of the screen and back. Seen in png/s03_3560 (object 20, with the
# message box 09542C), stage 9 frame 4600 (game mode 0x14), stage 5/0 in game mode 0x0A.
012B24  func  spawn_eggman_booth               # 2 parts (spawn_object_with_pieces), sprite flag 100, palette 8200, screen x 9F, first frame held 2F frames; backdrop sprite 5718 palette 8210; handler eggman_booth_slide_in; called by 084778 (stage 5), the message script 08576F (command 1) and 09492F (game mode 0x0A)
012B79  func  eggman_booth_slide_in            # field 84 (both parts' screen y) += 4 up to 2F, then eggman_booth_animate
012B9E  func  eggman_booth_animate             # 1D steps: frame eggman_booth_steps[80] for eggman_booth_frame_durations frames; then 84 -= 4 a frame and delete_object_and_children
012BDA  func  draw_eggman_booth                # both parts at screen y = 84, queue_sprite_in_bucket 0 (on top); sound 41 when frame B shows (not in stages 0 and 5)
012C35  table eggman_booth_frames   u16        # 15 decimal sprites 5550-56E8
012C53  table eggman_booth_frame_durations u8  # [frame]
012C62  table eggman_booth_steps    u8         # 1E frame indexes: 0 1 2 five times, then 3-E

# ======== Type 4 ========
012C80  func  object_type_4_idle               # object_types entry 4 (5 parts): a lone rsr; no stage list uses type 4

# ======== Type 22: wall pieces (s0A C000-D080; 92 = shape 0-5) ========
012C81  func  wall_piece_init                  # sprite wall_piece_frames[92] (green stone wall: straight, corner, slope), palette 9080; box from wall_piece_boxes but it never joins a touch list
012CC6  func  wall_piece_draw                  # unproject_object, delete_if_off_screen, queue_sprite_at_depth screen_y + 14 (EE at most) while screen_y <= E0: sorted so it covers what passes behind it (a40 renders 3750-4600)
012CFC  table wall_piece_frames     u16        # 7578-75A0
012D08  table wall_piece_boxes      hitbox_size_and_offset  # 6 x (hitbox_size_x, hitbox_size_y, offset bytes); unused at run time

# ======== Type 23: shaking blocks (s0A D080-DF01; 92 = kind 0/1) ========
012D2C  func  shaking_block_init               # palette 9050, box from shaking_block_boxes (unused); one more part: kind 1 sprite 7AA0, kind 0 its shadow strip 7570 palette 1060; handler shaking_block_animate
012D9C  func  shaking_block_animate            # every 3 frames the next of its kind's 3 frames (the block shifts a pixel or two); kind 0 drawn in bucket FF (under everything) with the shadow at (-10, +7) under it, kind 1 in bucket 0 (over everything) with its part at (-28, +20) (a40 renders 4500-5600)
012E3C  table shaking_block_frames  u16        # kind 0: 7540 7548 7550 (rounded block), kind 1: 7558 7560 7568 (tall block)
012E48  table shaking_block_boxes   hitbox_size_and_offset  # 2 x (hitbox_size_x, hitbox_size_y, offset bytes); unused at run time

# ======== Type 24: sand rock (s0A, many; 92 = rings, 94 = their angle) ========
012E54  func  sand_rock_init                   # sprite 7BA8 palette 9010 (a sandstone boulder), box 22 x 21, height 20, kind 5; handler sand_rock_update
012EA9  func  sand_rock_update                 # fall_to_floor; on any touch (flag 4) drop_rings(92, angle 94) and crumbles to dust: sand_rock_crumble_frames every 2 frames, then delete_object_and_children; touchable until the first step
012F9D  table sand_rock_crumble_frames u16     # 7BA8-7C10 (14 decimal entries)

# ======== Type 25: snow mound (s9 3400-4700, 9D00; 92 = rings + random flag in the high byte, 94 = angle) ========
012FB9  func  snow_mound_init                  # projected_y + 20, sprite 7B30 palette 91B0, box 22 x 21, kind 5; part 3 x-flipped, part 4 y-flipped with palette 11C0|2000 (its reflection on the ice); handler snow_mound_update
013056  func  snow_mound_update                # on any touch drop_rings (92 low, angle 94, released_rings_random = 92 high) and bursts into snow: main and part 2 (two frames ahead) every 2 frames, part 3 every 3 (a mirrored burst 16 higher); part 4 = the main sprite upside down 15 below in bucket FF; removed at the end (s09a renders 4250, 4550)
0131E9  table snow_mound_burst_frames u16      # 7B30-7BA0 (15 decimal entries)

# ======== Type 26: rock wall (s8 9100-A840; 92 = rings, 94 = angle) ========
013207  func  rock_wall_init                   # sprite 7C18 palette 87C0 (a cobblestone block), box 18 x 4D, height B4, kind C; two sparkle parts palette 8A10; handler rock_wall_update
013263  func  rock_wall_update                 # touched by an attacking object (flag 40 of touched_by): crumbles open to a dark hole (rock_wall_break_frames, 3 frames each), drop_rings(92, angle 94) at the 5th; until then every 1E frames a twinkle (part 2, 1A frames at +8,-15) and a glint (part 3 at +5,-2F)
0133AF  table rock_wall_break_frames u16       # 7C20-7C88 (14 decimal entries)
0133CB  table rock_wall_sparkle_frames u16     # 8B08-8BD0 (26 decimal entries)
0133FF  table rock_wall_glint_steps u8         # 20 decimal indexes into rock_wall_glint_frames (1..A..0)
013413  table rock_wall_glint_frames u16       # 8AB0-8AF8

# ======== Type 27 (no stage list spawns it): Eggman teleporting ========
01343C  func  eggman_teleport_init             # zoom 180, sprite flag 100, palette 8220; part 2 = a tall beam (79A8, 128 x 224); handler eggman_teleport_animate (only the unused spawn_eggman_teleport spawns it)
013464  func  eggman_teleport_animate          # frames by eggman_teleport_steps (Eggman builds up in stripes, stands, works a remote, fades out; FF ends: delete_object_and_children); the beam drawn every other frame at its floor (+8), Eggman 3 frames out of 4
0134E5  table eggman_teleport_steps u8         # 5B frame indexes, FF ends
013541  table eggman_teleport_frames u16       # 32 decimal sprites 55A8-5630
013581  table eggman_teleport_durations u16    # read by frame index (0-1F); the 5B words there look like per-step durations

# ======== Type 41: sand arch (s0A 6A80-7400; 92 low = direction / 40, 92 high = balls, 94 = phase spread) ========
# A row of purple spotted balls rising out of the sand one after another and diving back (a sine in height
# with a phase step per ball), with sand spraying where they cross the surface. walk_0A 15300-17000.
013637  func  sand_arch_init                   # leader: field 24 = 92 low * 40; 92-high balls (2 parts: A1C0 palette 9200 / shadow 87A0, radius 10, height 10, kind 24), 81 = 6, C, 12 ... (distance), 82 = phase -n * (94 / (count + 1)); handler sand_arch_update
01370E  func  sand_arch_update                 # leader: its own sprite is never drawn, only its dark part at its floor in bucket FF; delete_object_and_children once field 80 (balls gone) reaches the count
01374A  func  sand_arch_ball_init              # one move of 200 * 81 along 24 (each ball C pixels further), then sand_arch_ball_delay
013782  func  sand_arch_ball_delay             # 82 += 2 until it passes 3FF (balls start one after another), then sand_arch_ball_move
0137A9  func  sand_arch_ball_move              # lists; sand_arch_ball_bob; not drawn 10 or more below its floor, drawn when more than 8 above it; within 10 of the surface part 2 shows sand spray (sand_arch_spray_frames), else the shadow at the floor; over a pit falls, and at z >= 800 the leader's 80 + 1 and sand_arch_ball_gone
013899  func  sand_arch_ball_gone              # rsr: a ball that fell out of the stage waits for the leader to remove the row
01389A  func  sand_arch_ball_bob               # 82 += C, speed_z = -sine[82] * C
0138BD  table sand_arch_spray_frames u16       # BBE8 BBF0 BBF8 BC00 (palette 9020): sand thrown up where a ball crosses the surface

# ======== Type 1: electric fence (s4 A400, the type 7 machine's area; 92 = posts - 1, 94 low = direction 0-3, 94 high = arc start) ========
0138C6  func  fence_post_init                  # electric_fence_state = 1; a row of 1 + 92 posts (spawn_object_with_pieces 2) stepped by fence_post_offsets: post 6CA8 palette 9060, height 64, kind E, box from fence_post_boxes; part 2 = the arc 6C88 palette 9030 (flags fence_arc_flags); handler fence_post_start
01399D  func  fence_post_start                 # add_to_rider_list, unproject_object, then fence_post_update
0139B1  func  fence_post_update                # electric_fence_state 1: the yellow arc to the next post (drawn every frame, next fence_arc_frames every other frame: the part's 1C word is the toggle); FF: delete_object_and_children; anything else: a plain post (kind 5, box 1B x 19) and fence_post_draw (s04a render 4250)
013A35  func  fence_post_draw                  # ground, fall_to_floor, delete_if_off_screen, queue_sprite; health (7E) 7F; touchable and rider lists
013A65  table fence_post_offsets    fence_offset        # [direction] (dx, dy) of projected_x/projected_y to the next post
013A75  table fence_post_boxes      fence_post_box        # [direction] (hitbox_size_x, hitbox_size_y)
013A85  table fence_post_box_offsets fence_post_box_offset        # [direction] (hitbox_offset_x, hitbox_offset_y)
013A8D  table fence_arc_flags       u16        # [direction] the arc's sprite_flags (flips)
013A95  table fence_arc_offsets     fence_offset        # [direction] the arc's screen offset from its post
struct fence_offset 4 rom                # a (dx, dy) pair of the electric fence by row_direction: fence_post_offsets (013A65, the next post's projected place: fence_post_init "movea.w 13A65[PC](R15), R0" 013986) and fence_arc_offsets (013A95, the arc's screen offset from its post: fence_post_update "movea.w 13A95[PC](R1), R0" 0139F5); census ROM 013A65-013A74, 013A95-013AA4
field fence_offset 00 dx u16              # "add.h [R0+], 2A[R3]" (01398C, projected_x: 20, 20, FFE0, FFE0); "mov.h [R0+], R8" (0139FB, place_sprite_near_cursor's offset_x: 10, 10, FFF0, FFF0)
field fence_offset 02 dy u16              # "add.h [R0], 2E[R3]" (013991, projected_y_int: FFF0, 10, 10, FFF0); "mov.h [R0], R9" (0139FE, offset_y: FFE8, FFF5, FFF5, FFE8)
struct fence_post_box 4 rom              # a record of fence_post_boxes (013A75, by row_direction): fence_post_init "movea.w 13A75[PC](R15), R0" (0138FC); census ROM 013A75-013A84
field fence_post_box 00 size_x u16        # "mov.h [R0+], 10[R20]" (013902): hitbox_size_x 2D, 1B, 2D, 14
field fence_post_box 02 size_y u16        # "mov.h [R0], 12[R20]" (013907): hitbox_size_y 1B, 2D, 1B, 2E
struct fence_post_box_offset 2 rom       # a record of fence_post_box_offsets (013A85, by row_direction): fence_post_init "movea.h 13A85[PC](R15), R0" (01390C); census ROM 013A85-013A8C
field fence_post_box_offset 00 x s8       # "mov.b [R0+], 7C[R20]" (013912): hitbox_offset_x F7, F7, E9, F6
field fence_post_box_offset 01 y s8       # "mov.b [R0], 7D[R20]" (013917): hitbox_offset_y ED, ED, E7, D7
013AA5  table fence_arc_frames      u16        # 6C88 6C90 6C98 6CA0
208076  var   electric_fence_state  u8         # fence_post_init sets 1, the type 7 machine's last state (00FFA0) FF (posts removed), reset_linked_objects_flag 0 at a new game; fence_post_update switches on it

# ======== Type 29 (no stage list spawns it) ========
013ADD  func  bubble_column_init               # palette 1060, sound 24, handler bubble_column_animate (never spawned; the unused spawn_bubble_column sets up the same)
013AF9  func  bubble_column_animate            # every 3 frames the next of 14 (hex) frames from mine_bubble_frames + 92 * 13, looping; never removed
013B3F  table mine_bubble_frames    u16        # 80E8-8180: white dots rising (the sinking mine's parts 3 and 5; bubble_column_animate)
013B67  table mine_bubble_frames_2  u16        # 8188-8220 (the sinking mine's part 4)

# ======== Type 30: mine (s10, many; 92 = the water surface height 4C8, 94 = 1 for a half-size one) ========
# A black ball with red spikes floating 20 above the water; with a player within 80 it drops in (splash),
# sinks flashing with bubbles and explodes on the bottom. Seen in s10a 8800-10300.
013B8F  func  mine_init                        # sprite 8610, radius 8, height 1E, kind 24 (hazard), zoom 80 if 94; parts: shadow 5D90 palette 7A0|4000, three bubble parts palette 1060 (phases 0/1/2, the last x-flipped); handler mine_float
013C2A  func  mine_float                       # face_nearest_player; bobs (speed_z = sine[82 += 10]); nearest_player_distance below 80: mine_fall; runs mine_fall's body every frame
013C66  func  mine_fall                        # move, delete_if_off_screen, fall_to_floor; above z 92: flash_mine_palette, draw at floor depth, touch list, shadow every other frame; at 92: spawn_mine_splash, sound 16, z + 4, mine_sink
013CDD  func  mine_sink                        # speed_z -= 5800 (net 800 a frame), animate_sinking_mine; on the bottom: sound 20, palette 1070, shadow off, mine_explode
013D26  func  mine_explode                     # underwater_explosion_frames with underwater_explosion_durations (16 hex frames shown; the 17th step reads past the table on the frame it is removed), then sound 24 and delete_object_and_children
013D70  func  animate_sinking_mine             # flash_mine_palette (sinking variant), mine_sink_steps -> mine_frames, then draw_mine_with_bubbles
013D93  func  draw_mine_with_bubbles           # queue_sprite_at_floor, touch list; shadow every other frame; the three bubble parts at (0,-A), (5,-14), (A,-14) stepping mine_bubble_frames / mine_bubble_frames_2 every other frame
013E8B  func  flash_mine_palette               # every 5 frames palette = mine_flash_palettes[mine_flash_steps[81]]; with R2 set (sinking) bit F flipped and bit E set
013F33  func  spawn_mine_splash                # palette 1070; a 2-part object at the mine (projected_y + 1), palette 90A0, handler mine_splash_animate; part 2 sprite 8570 palette 1080|4000
013F7D  func  mine_splash_animate              # a water column (mine_splash_frames, 1A steps of 3 frames, then removed) and a spray (mine_splash_spray_frames) 7 below it
013EC7  table mine_sink_steps       u8         # 0 1 2 1 0 3 4 3
013ECF  table mine_frames           u16        # 8610 8618 8620 8628 8630: five frames
013ED9  table underwater_explosion_frames u16  # 84C0-8568: a white cloud breaking up (mine_explode; eel_piece_explode too)
013F05  table underwater_explosion_durations u8 # [step]
013F1B  table mine_flash_steps      u8         # 0-5-0 (12 decimal entries)
013F27  table mine_flash_palettes   u16        # 9310 9090 9320 9330 9340 9350
013FF8  table mine_splash_frames    u16        # 8638-8700 (26 decimal entries)
01402C  table mine_splash_spray_frames u16     # 8570-8608 (20 decimal entries)

# ======== Type 31: Eggman's scene in s0B (0C00) and the button box ========
# Seen in ending 9450-10500: the scroll stops, Eggman talks (three message boxes), a box rises out of the
# floor and opens, he presses its button (the escape countdown starts), then runs off and the scroll goes on.
014054  func  eggman_scene_init                # Eggman sprite 88A8 palette 81F0 flag 100, 82 = 3C; part 2 = his shadow AA50 (palette 250 overwritten by 9FFE); the button box (1 part) at map (-1E, +12), handler button_box_init; handler eggman_scene_wait
0140B8  func  eggman_scene_wait                # until scroll_position passes FC0; then scroll_mode = 2 and stage_trigger_bits = 1 (the players' stage B sequence waits for it to be set, then cleared)
0140D6  func  eggman_scene_pause               # 3C frames, 81 = 1, then eggman_scene_talk
0140F0  func  eggman_scene_talk                # three message boxes (0953BF at A0,80: eggman_scene_texts for eggman_scene_text_durations frames) with eggman_scene_sounds; talking frames every 7 frames
014177  func  eggman_scene_press_button        # 7 frames (eggman_press_frames / durations); at the 5th 81 = 2 (the box's button goes down) and escape_started = 1; then eggman_scene_run_off
0141C9  func  eggman_scene_run_off             # 3C frames, then runs (eggman_run_frames every 3 frames), speed + direction_to_speed(20, 2A0) each step
014208  func  eggman_scene_move                # move, fall_to_floor, queue_sprite_in_bucket 0; far off screen: scroll_mode = 7, stage_trigger_bits = 0 (removed); shadow every other frame
0142A0  func  button_box_init                  # sprite 8928 palette 11D0, 82 = B4; handler button_box_wait
0142BE  func  button_box_wait                  # until Eggman's field 80 is 1 (his first talking step), then button_box_rise
0142D4  func  button_box_rise                  # button_box_steps 0-12: the box rises out of the floor and its lid opens on a lit button; waits for Eggman's 81 = 2, then button_box_press
0142F3  func  button_box_press                 # steps 13-22: the button held down 3C frames, the lid closes, the box sinks back; then draw_button_box
01432B  func  draw_button_box                  # ground, delete_if_off_screen, queue_sprite_in_bucket FF (also on the frame it was removed)
014255  table eggman_talk_steps     u8         # 10 decimal indexes into eggman_talk_frames
01425F  table eggman_press_durations u16       # 7 entries
01426D  table eggman_talk_frames    u16        # 88A8 88B0 88B8
014273  table eggman_press_frames   u16        # 88C0-88E0, 88A8
014281  table eggman_run_frames     u16        # 88E8-8920
014291  table eggman_scene_texts    u8         # message numbers 16 17 18 (0953BF R7)
014294  table eggman_scene_text_durations u16  # 78 each
01429A  table eggman_scene_sounds   u16        # by direction: sound ids 003E 0032 0030 (read as halfwords, the low byte played)
014345  table button_box_steps      u8         # 23 frame indexes
014368  table button_box_durations  u16        # 23 (3C on the pressed step)
0143AE  table button_box_frames     u16        # 8928-8998 (15 decimal entries)

# ======== Type 32: sliding door (s0B 0900) ========
# A wall of white and red panels with a handle (sprite 7830, 176 x 176) that slides away once the players
# arrive (ending 9450-9700); two sprite clip entries keep it cut off at map x 3FF as it slides.
0143CC  func  sliding_door_init                # main = a sprite clip entry (flags 5000), box E6 x 21, height 100, kind C, 82 = A; part 2 = the door 7830 palette 9070; part 3 = a second clip entry; handler sliding_door_wait
01444E  func  sliding_door_wait                # every playing player (player_status negative) has player field 140 set (stage B sequence, state 38) and at least one plays: A frames later sliding_door_open
014490  func  sliding_door_open                # moves along angle B4 at 100 until projected_x reaches 3EF, then stops and sliding_door_opened = FF; sliding_door_draw
0144CD  func  sliding_door_draw                # move; clip entry, door and second clip entry (06 = screen_x, 08 = 3FF - projected_x + screen_x) at screen_y - 50; touch list
20E656  var   sliding_door_opened   u8         # sliding_door_open sets FF; the players' stage B sequence (state 38 step 2) waits for it, then restarts the scroll; start_stage clears it

# ======== Type 33: lid trap (s10 2A80-2D00) ========
01453E  func  lid_trap_init                    # a round pad 8840 palette 10F0|4000, box 26 x 26, height 0, kind 23 (a player touching it is stuck: state 2C); part 2 = the open red lid 8850; handler lid_trap_update
01459E  func  lid_trap_update                  # touched: kind 5, pad 8848, lid palette 1110, sound 11, 81 = 1, and the lid snaps shut (lid_trap_close_steps every 4 frames); s10a 11600: player 1 in state 2C under a closed lid
014647  table lid_trap_close_steps  u8         # 0 2 1 2
01464B  table lid_trap_lid_frames   u16        # 8858 8860 8868

# ======== Type 34: turtle (s10 1D00, 4B80, 5180) ========
014651  func  turtle_init                      # a green sea turtle in its shell (7F28 palette 1040|4000), box 26 x 26, height 0, kind C; part 2 = head 7F70, part 3 = shadow 5D88; handler turtle_wait (spawned at z 528, 60 below the height 4C8 where the mines hit the water)
0146C9  func  turtle_wait                      # until touched: queue_sprite_at_depth screen_y - 20, touch list (the head/shadow code after it needs field 80 = 4 and never runs); touched: turtle_surface
014780  func  turtle_surface                   # speed_z alternates -8000 / 0 until 20 above its floor, then turtle_drop_rings
0147B5  func  turtle_drop_rings                # every 30 frames (92) one ring (angle 154, flip 2); after 5 (94) only turtle_swim
0147F2  func  turtle_swim                      # flipper strokes (turtle_swim_frames every 3 frames, then 3C frames gliding), speed (turtle_swim_speeds - 3 a frame) + E0 along angle 14C; head part animated at (B, -13); the shadow is its current frame in black, zoom 100 - height; removed far off screen
014917  table turtle_swim_frames    u16        # 16 decimal entries from 7F10 (7EA8-7F08 in between)
014937  table turtle_head_steps     u8         # 16 decimal indexes 1 2 3 4 3 2 1 0 ...
014947  table turtle_head_frames    u16        # 7F70-7F90
014951  table turtle_swim_speeds    u16        # field 6E per stroke step: D0 B0 .. 10 .. 100

# ======== Type 35: eel (s10 3500, 3580, 4600, 5A00, 5A10; 92 high = its den's number, 92 low = facing, 94 low = pieces, 94 high = wave step) ========
# A dark blue segmented body with a whiskered head that stretches out of its den and back; when the den
# with the same number collapses the pieces explode one after another (s10b 3800-6400).
014971  func  eel_init                         # anchor ball at the den (A898 palette 1210|4000, kind 24, 81 = 10, 6E = 480, 24 = 34A or 14A), then 94-low pieces of 4 parts (kind 24, 92 = wave amplitude, 8C = 4000 * amplitude, 94 = the anchor's 92); the last is the head AA38 (eel_head_move), the rest eel_segment_move (amplitudes 8, 10, 18 ... with 94 high = 8, seen in s10b 3900)
014B46  func  eel_anchor_update                # bit (92 high) of stage_trigger_bits set: eel_anchor_wait_explode; else draws, touch list, and stretches field 6E by 81 (+10, later +28, out to 1111; -18 back to 480; 3C-frame pauses at both ends); piece n gets 24, 81 and 6E * n
014B59  func  eel_anchor_wait_explode          # delete_object_and_children once its count (94 low) of pieces reaches 0; its own chaser test only sets the bit already set
014C5A  func  eel_head_move                    # place_eel_piece, head frames every 4 frames (60-frame pause per cycle), move, draw with shadow; bit set: delay = (6E / anchor 6E) * B frames, eel_head_explode_delay
014D64  func  eel_segment_move                 # the same for a body piece (no frames of its own)
014E52  func  place_eel_piece                  # map position = the anchor's, speed from (24, 6E) so the next move puts it out along 24; z = anchor's - 8C + sine[82 += 8] * 92 (speed_y gets twice that): a wave growing toward the head
014ED5  func  eel_head_explode_delay           # waits its delay (82), still drawn, then eel_piece_start_explosion
014EF8  func  eel_segment_explode_delay  # A body piece of an eel waiting its turn to explode: counts explode_delay down, still drawn at its floor with its shadow; then...
014F23  func  eel_piece_start_explosion        # projected_y + 18, palette 1070|4000, flags 4, sound 24, eel_piece_explode
014F5D  func  eel_piece_explode                # underwater_explosion_frames 2-15 every 3 frames; then the anchor's count - 1, sound 24, eel_piece_gone
014FB7  func  eel_piece_gone                   # rsr: waits for the anchor to remove the eel
014FB8  table eel_head_frames       u16        # facing 0: AA38 AA40 AA48
014FBE  table eel_head_retract_frames u16      # facing 1, going back in: AA08 AA10 AA18 AA00
014FC6  table eel_head_extend_frames u16       # facing 1, going out: AA20 AA28 AA30
014DC2  func  draw_eel_piece_parts             # unused: nothing refers to it (it follows eel_segment_move's rsr): eel_segment_move's ending with the piece's parts 2 and 3 drawn: R20 = the piece's child (part 2): every 3 frames (frame_timer) its sprite = eel_part_frames_2[eel_part_steps[frame_step]], at the piece's (R23) screen position, queued at its floor depth (projected_y + floor_z - view_y + 1); the next child (part 3) likewise with eel_part_frames (copy_screen_position_from_cursor), at the same depth; the next one (the shadow) at the piece's floor in bucket FF. The live ending skips parts 2/3 (three child links) and queues only the shadow, so they are never drawn; eel_init seeds part 3's 2D for it
014FCC  table eel_part_frames       u16        # A8A0-A990 (31 decimal entries); read only by draw_eel_piece_parts
01500A  table eel_part_steps        u8         # 0-C-1 (24 decimal entries); read only by draw_eel_piece_parts
015022  table eel_part_frames_2     u16        # A998-A9F8 (13 decimal entries); read only by draw_eel_piece_parts

# ======== Type 36: breakable panel (s3 0001-0400; 92 = ring angle, 94 = rings) ========
01503C  func  breakable_panel_init             # blue stone panel 9278 palette 91F0, box 7 x 40, height 64, kind 5; part 2 = an arch outline 9308 whose palette cycles; handler breakable_panel_wait
01508D  func  breakable_panel_wait             # touched by an attacking object (flag 40 of touched_by): drop_rings(94, angle 92, random) and breakable_panel_break
0150C7  func  breakable_panel_break            # breakable_panel_break_frames 0-10 every 3 frames (it cracks open to an arched doorway), then draw_breakable_panel
0150F4  func  draw_breakable_panel             # ground, remove when far, queue_sprite_at_depth screen_y - 20, touch list (still solid when open); unbroken: the outline's palette steps breakable_panel_glow_steps (blue to gold and back, pauses F/1E) at screen_y - 19
015180  table breakable_panel_break_frames u16 # 9280-9300 (17 decimal entries)
0151A2  table breakable_panel_glow_steps u8    # 1D indexes into breakable_panel_glow_palettes
0151BF  table breakable_panel_glow_palettes u16 # 9200-92E0

# ======== Types 37-40: s0C puffs, s0B pad, static triangle, railing ========
0151DD  func  white_puff_init                  # palette 2000 (the 90A0 written before is overwritten); handler white_puff_animate (s0C, in pairs 30 apart)
0151F3  func  white_puff_animate               # a white burst rising and settling (white_puff_frames, 4 frames each), the last and faintest held 5A frames, then again; png/s0C_4200 objects 17, 19
015239  table white_puff_frames     u16        # 7870-78B8
01524D  func  checker_pad_init                 # palette 9040; handler checker_pad_animate (s0B 0C00, next to Eggman's scene)
01525C  func  checker_pad_animate              # update_screen_position_no_offset (no floor); a yellow checkered pad with a red lamp whose squares light in turn (checker_pad_frames, 9 frames each)
0152A0  table checker_pad_frames    u16        # 77E8-7808
0152AA  func  static_triangle_init             # palette 9050; part 2 = a red triangle 7828 palette 9060; handler static_triangle_animate (s0B 0C00)
0152CB  func  static_triangle_animate          # a triangle of white static (static_triangle_frames every 3 frames); the red triangle drawn every other frame 68 to its right
01533B  table static_triangle_frames u16       # 7810 7818 7820
015341  func  railing_init                     # a V-shaped railing 7838 palette 9080: box F x 57, height 100, kind C; a second object (spawn_objects) linked as its child for the other side, box 56 x 11, handler railing_side_update
0153C6  func  railing_draw                     # ground; unless removed far off screen: queue_sprite, touch list
0153E5  func  railing_side_update              # the invisible second side: ground, touch list (removed with the railing)

# ======== Type 42: spike gate (s0C 0F00-2E00, s0E 0F00-2E00; 94 = scroll position where it breaks up) ========
# Two green pillars and a spiked beam between them that rises and slams down (walk_0C 7300-16000).
0153F2  func  spike_gate_init                  # left pillar A200 palette 91F0 (box 2D x 21, height 100, kind C, spike_gate_pillar_update); right pillar A208 at map (+94, +4A); the beam (4 parts: A1E8, A1F0, A1F8 palette 91E0, shadow A788 zoom 180) at (+1A, +14), box 2D x DA, height 28, spike_gate_beam_wait
015544  func  spike_gate_pillar_update         # the object 08A9DC keeps in 20E60E (stages C/E) within 20 in x, or scroll_position past 94: spawn_debris 5 (from map -90, -40) and removed; else queue_sprite_at_depth screen_y - 8, touch list
01559C  func  spike_gate_beam_rise             # z - 3 a frame; kind 24 (hurts) once 20 above its floor; at 50 above: spike_gate_beam_hold
0155CB  func  spike_gate_beam_hold             # 8 frames at the top
0155D8  func  spike_gate_beam_drop             # speed_z + 2000 on top of gravity; landing: kind C, bounces up 6 pixels, after the third z + 3, sound 7, spike_gate_beam_wait
015623  func  spike_gate_beam_wait             # A frames on the ground, then spike_gate_beam_rise
015630  func  spike_gate_beam_update           # move; breaks up like the pillars; draws its three sections at (0,0), (30,19), (60,2D) by cursor_floor_screen_y, touch list, shadow in bucket FF
0156F4  func  spawn_eel_den                    # unused: nothing refers to it (it follows an rsr): spawn_object_with_pieces(1), R20 = the new object, falls into eel_den_init (whose closing R20 = R23 only matters on this path)

# ======== Type 43: eel den (s10 3500, 3580, 4600, 5A00, 5A10; 92 = its number) ========
015706  func  eel_den_init                     # a stone arch AC70 palette 1300|4000, box 47 x 42, height 28, kind C, health (7E) 1; handler eel_den_wait
015756  func  eel_den_wait                     # collapses when hit with no health left or when the chaser (20E5D4) is within 35 in y: sets bit 92 of stage_trigger_bits (its eel explodes), 3 rings (angle 144), eel_den_collapse
0157C0  func  eel_den_collapse                 # eel_den_collapse_frames every 3 frames (it crumbles), then eel_den_draw
0157ED  func  eel_den_draw                     # ground, remove when far, queue_sprite_at_depth screen_y - 18, touch list
015814  table eel_den_collapse_frames u16      # AC70-ADD0 (2D entries)

# ======== Type 44: stepped wall (s0A C000) ========
01586E  func  stepped_wall_init                # a long green wall of five stepped panels B670 palette 8D60|2000, sprite flags 6, box 1D x DC, height 80, kind C; handler stepped_wall_draw (a40 render 3750, object 13)
0158BB  func  stepped_wall_draw                # ground; unless removed: queue_sprite_in_bucket FF (under everything), touch list

# ======== Shared variables these objects set (also used elsewhere) ========
208096  var   stage_trigger_bits    u32        # bit array cleared by start_spawns_by_type at each stage: s0B Eggman's scene sets 1 / clears it (the players' state 38 waits), s10 eel dens set bit n for eel n, s8 08730C sets all (010FE2/011049 react)
20E659  var   escape_started        u8         # eggman_scene_press_button sets 1: game mode 0x1F counts 20E666 from it (quake, sound F, sound 7C at 30C) and 0954B1 draws the 20-second countdown 20E658; 095463 clears it

# board addresses the code reaches directly
609400  io  backdrop_color         u16   # palette entry 0x4A00: load_palette_set_and_backdrop and the stage 0 exit scene write the colour behind every layer
60C3A0  io  palette_color_61D0     u16   # update_stage_06_background compares it with 0x7B0 to see where its colour cycle is
608000  io  palette_color_4000     u16   # set_nbg3_page_5_flags writes 0xF70
30CA50  io  memory_test_video_word u32   # test_work_ram sets bits 5 and 6 of this word of video RAM
D00005  io  irq_control_5          u8    # init_system clears it with the other interrupt controller registers (MAME: unused)

# ======== remaining (from build/naming/remaining.txt) ========
# (tables the code at 09Fxxx reads through the 0FFxxx mirror, named at their own copy)
09F08E  table rom_checksum_ic8          u16         # rom_checksums[1] through the mirror
09FFCE  table start_prompt_positions_3p u16         # [player] text map offsets of the start prompts (31FC02, 31FC1C, 31FC36): draw_player_start_prompt, player_hud_update, draw_attract_credits
09FFD0  table start_prompt_position_p2  u16         # [1] of start_prompt_positions_3p (draw_attract_credits)
09FFD2  table start_prompt_position_p3  u16         # [2]
09FFD4  table start_prompt_positions_2p u16         # 2-player cabinets (31FC02, 31FC36)
09FFD8  table common_credit_position    u16         # 31F99E: credit count with common chutes (draw_attract_credits)
09FFDA  table credit_positions_3p       u16         # [player] credit counts (31FD06, 31FD20, 31FD38): draw_attract_credits, the continue countdown (state 23)
09FFDC  table credit_position_p2        u16         # [1]
09FFDE  table credit_position_p3        u16         # [2]
09FFE0  table credit_positions_2p       u16         # 2 players (31FD06, 31FD38)
# Remaining names: proposals for what build/naming/unnamed.txt (12-10-2026) still lists.
# Syntax as in symbols.txt, one per line, each with "# evidence". Renders: s32ref dumps of
# tests/controls/attract.txt (frames 640-2200) drawn with the stages helper's render.py / frames.py.
#
# Characters: object field 47 (kind) of a player's object is the character it plays: 0 Sonic, 1 Ray,
# 2 Mighty (ranking records, character_name_sprites B728 SONIC / B738 RAY / B730 MIGHTY, the title).
#
# How the pieces named here work (summary; details in the comments):
# - SEGA logo and title (attract system_mode 3): attract_title runs run_title_screen every frame. Until
#   sega_logo_step reaches 7, run_sega_logo plays the SEGA logo (sprite 53D0 + 8n on a temporary second-pool
#   block: fade in from black, zoom in, 1E frames of animation, hold, fade out, 3C frames); then the title scene is
#   built once (title_scene_built): the emblem with Sonic rising out of it and Ray and Mighty popping
#   up (title_emblem_animate), a strip of land sliding in (title_land_slide), clouds drifting up-left and
#   growing (three groups, started by the timed spawner at 097419), a small craft crossing the sky with a
#   smoke trail, and a waving background (per-line scroll of NBG2, palette cycling). title_done after 258 frames.
# - Ranking (attract system_mode 2): start_ranking_display's object scrolls the "TOP 15" screen down,
#   holds 12C frames, then ranking_done. build_ranking_display makes 15 rows of 12 sprites (rank, initials,
#   portrait, name, four score digits, stage reached or ALL CLEAR) and the spinning "TOP15" title.
#   Records (8 bytes): rings (the score), character, stages reached (8 = ALL CLEAR), three letter codes, 0.
#   Letter codes: 00-19 a-z, 20-29 0-9, 2B space (ranking_letter_sprites).
# - Ranking entry (player state 2E): start_ranking_entry checks the player's rings against the lowest
#   record and sets up a static block (ranking_entry_blocks, outside the object pools) that
#   enter_ranking_initials runs: 3 letters chosen with the trackball, 20-second countdown, or OUT OF RANK!
# - Character select at the start of a game (game mode 0x1B, player state 34): start_character_chooser
#   makes the PLAYER SELECT heading with its countdown (character_select_countdown_update) and one chooser
#   per player: the character turns with the trackball x (character_chooser_turn), the button or the end of
#   character_select_timer confirms (character_chooser_confirm: voice, the character goes into the player
#   object's kind). Joining later (player_select_character): start_join_character_chooser shows a small
#   portrait with a 9-second countdown (join_character_chooser_update). characters_taken holds one bit per
#   character already playing.
# - Camera speed helpers for the chasers: compute_camera_ground_speed (flat) and
#   compute_camera_slope_speed (R8 = the floor slope factor) turn camera_velocity_x/_y into a ground speed
#   and direction, recomputed only when the velocity (or slope) changed.
# - Grey screen: update_grey_screen (every stage frame) greys the four tile layers (mixer 3E bits 1-4:
#   MAME's grayscale colour offset mode) while any player block's 9F is set, and some objects switch to
#   grey palettes (arrow_board_ride, crumbling_floor_update) or stop (group_leader_move, spiked_roller_chase). Nothing ever sets
#   player field 9F: the effect is dormant.
#
# Mirrors: rom_0FF08C/0FF08E and rom_0FFFCE-0FFFE0 are reads of 09F08C/09FFCE-09FFE0 through the 0FFxxx
# mirror (the code at 09Fxxx runs there). 09F08C is already rom_checksums: the names below are only needed if
# table reads are not folded to 09Fxxx (the decompiler does not fold them today).

# ---------------- functions ----------------
001A34  func  update_grey_screen                # run_game_mode (stage frames): any player block with 9F set -> grey_screen_active = FF and mixer_offset_select |= 1E; when none is set any more: grey_screen_ended = FF for that frame, grey_screen_active 0, bits 1-4 cleared
002342  func  draw_game_over_banner             # game mode 7: static sprite block 209FC0 = frame 89A0 (render: "GAME OVER" with Eggman behind), palette 8970, flags 1000, at A0,6C; queue_sprite_on_top
002370  func  blink_demo_prompt                 # draw_attract_credits (common chutes): every 0x20 frames demo_prompt_shown toggles; while shown and current_screen is 4/5 (the demos) the 209FC0 sprite 89B8 "PUSH START BUTTON" (89A8 "INSERT COIN(S)" without enough credits, renders) at A0,64 on top; port D bits 2-3 cleared otherwise
00AFB3  func  compute_camera_ground_speed       # 11 chasers: when camera_velocity_x/_y changed: camera_ground_speed = sqrt((vx>>5)^2 + (vy>>4)^2) << 13, camera_ground_direction = angle(vx>>9, vy>>8) + 80, camera_map_direction = angle(vx>>8, vy>>8)
00B04D  func  compute_camera_slope_speed        # the same for a floor slope: R8 = floor_slope_factors[slope] (0 = flat); vy scaled by 1 / (1 + |R8|): camera_slope_speed, camera_slope_direction, camera_scroll_direction; keep_player_on_screen and 7 chasers
00CB58  func  cycle_part_palettes               # lava_rock_update: animate_sign's palette step (list at 80, index 84, delay 85, timer 86) written into the palette word (+6) of every part of the object's multi-part sprite (sprite_frame -> count, 8-byte parts)
00CC75  func  screen_point_to_ground            # R8, R9 = screen x, y and R7 = height: adds view_x/view_y, takes the height off, doubles y, then unproject_point -> R8 = x, R9 = y (aim_at_screen_centre, place_player_at_restart_point)
00CC8D  func  unproject_point                   # R8, R9 = projected x, 2 x projected y (16.16): R8 = x = ((px - py) >> 8) * B5, R9 = y = ((px + py) >> 8) * B5 (the inverse of project_object; 9 callers)
00E07B  func  animate_part_list                 # opening_machine_update: animation_timer, then animate_part_list_step
00E08C  func  animate_part_list_step            # animate_step for multi-part sprites: the step's word is an offset from the animation, sprite_frame (32-bit) = that part list (how_to_play_button_hand_update)
00E0DE  func  animate_all_parts                 # animate (timer, step, R8 low byte FF on the last step) writing the step's frame into every part of the multi-part sprite (fire_wall_rise, lava_flow_advance, lava_rock_update, waterfall_move...)
01EF60  func  create_child_shadow               # a pool 2 child for the current object: frame 5D90, palette 87A0, sprite_scale = R8, parent = the object (fireball_emitter_update); R18 used as a scratch register
08EAC9  func  start_ranking_entry               # player state 2E (game over), once: rings below every one of the 15 ranking records -> "OUT OF RANK!" for 78 frames; else the player's ranking_entry_blocks block (5 parts: 3 letters, cursor) with rank, rings, character, stages_reached, 4B0-frame countdown; bit in ranking_entry_players
08F169  func  copy_ranking_records              # R2 8-byte records from R0 to R1, then ranking_checksum = compute_crc16 of ranking (78 bytes); entered from load_ranking and reset_ranking
08F194  func  ascii_to_ranking_letter           # reset_ranking: 'a'-'z' -> 00-19, ' ' -> 2B, '3' -> 23; any other character loops forever (bne to itself at 08F1AE, original code)
08F263  func  build_ranking_display             # start_ranking_display's handler, once: 15 rows (row number in 92, 0x17 pixels apart) of 12 sprites from ranking (rank, initials, portrait, name, score digits, STAGE n / ALL CLEAR: render att_1300) and the 5-letter "TOP15" title (ranking_title_spin_in); then ranking_display_scroll_in
08F4FD  func  ranking_title_spin_in             # the TOP15 letters spin around (direction + 8) while growing to 200, then ranking_title_pause
08F551  func  ranking_title_pause               # 1C frames (field 82)
08F55E  func  ranking_title_rise                # screen y - 1 a frame up to 23, then draw_ranking_title only
08F578  func  draw_ranking_title                # the 5 letters around the centre at (+-speed_x, +-speed_y) and twice that for the outer two (along x only when ranking_title_layout is 0)
08F649  func  ranking_display_scroll_in         # the screen comes down 1 pixel a frame from -1A4 to 24, then rows animate (80 = 1) and ranking_display_hold
08F662  func  ranking_display_hold              # 12C frames (84), then the rows remove themselves (81) and ranking_display_end
08F67D  func  drift_ranking_background          # the NBG0 picture drifts: direction (speed_z) swings between 180 and 280, speed_x/speed_y += direction_to_speed, nbg0 scroll = their high words
08F6F8  func  ranking_display_end               # ranking_done = 1, delete_object_and_children
08F704  func  ranking_row_update                # one ranking row: y = the display's + row * 17, its 12 sprites on top; the portrait animates (ranking_portrait_frames, +14 frames for the top row) once the display's 80 is set; removed with its 81
08F9AB  func  start_character_chooser           # player state 34 in game mode 0x1B: the first one builds the PLAYER SELECT heading (B708) and its countdown (character_select_countdown_update); character_select_timer = 258 (again for every chooser); an 8-part chooser for the player: portrait of the first free character, the two others smaller at the sides, name, Player n label, panel B740
08FBB4  func  character_chooser_wait            # chooser handler: trackball x turns it (character_chooser_turn); its button, or only one free character, or character_select_timer < 1E: character_chooser_confirm; idle portrait animation (chooser_idle_steps)
08FBD8  func  character_chooser_confirm         # takes the first free character from 81: kind of the player object, characters_taken bit, free_character_count - 1, players_choosing_character - 1 (at 0 the timer drops to 3C); chosen pose, a random voice of the character (09036F) and sound 20
08FD84  func  draw_character_chooser_labels     # the name (character_name_sprites, refreshed when 13 is set), player label and panel parts
08FDBA  func  character_chooser_turn            # 18 frames of the portraits sliding and scaling toward the side the trackball went, then the next free character (taken ones dimmed with palette 87A0)
08FF9A  func  character_chooser_chosen          # the chosen portrait and its labels until character_select_timer runs out, then removed
08FFE3  func  character_select_countdown_update # the PLAYER SELECT heading: character_select_timer - 1, its seconds as a growing digit (ranking_letter_sprites 20+), the heading's palette bouncing through player_select_title_palettes; removed when nobody is choosing and the timer is out
090096  func  start_join_character_chooser      # player_select_character (joining during a game): if a character is free, the player's block in join_chooser_blocks: small portrait of the first free character, its name, countdown 21C; the player's bit in players_in_join_chooser
090161  func  join_character_chooser_update     # trackball x steps through the free characters; the button, joining_closed or the end of the countdown takes it (characters_taken, the player object's kind) and clears the player's bit
0902F9  func  count_free_characters             # free_character_count = characters whose characters_taken bit (character_taken_bits) is clear
096E24  func  run_title_screen                  # attract_title every frame: run_sega_logo; once sega_logo_step is 7 and title_scene_built is 0: palettes 1076AE, vram scripts 172D11/172D41, NBG2/NBG3 set-up and four objects (title_land_slide, title_emblem_animate, the timed spawner 0973B4 with list 097419, title_sky_wave)
096F9F  func  title_emblem_animate              # the emblem in four 8 bpp quarters (52E0/52E8 top, 52F0/52F8 bottom), Sonic rising (5300 + 8 n, height title_sonic_rise_heights, then a loop of frames at 096E02), Ray (title_ray_frames) and Mighty (title_mighty_frames) popping up; title_done after 258 frames (render att_2200)
0972F5  func  draw_title_emblem_part            # palette 0, sprite_flags 200 (8 bits per pixel), zoom 100, queue_sprite_in_layer
09734A  func  title_land_slide                  # sprite B6D0 (green hills on a strip of land, render) at y 7A sliding left from x 190 at 1/2 pixel a frame until title_done
097435  func  title_flying_craft_move           # sprite BB58 (a small craft, frames.py) crossing from x 154 one pixel a frame; every B frames a title_smoke_puff_animate behind it
0974BA  func  title_smoke_puff_animate          # brown puffs BB60-BBA0 (list 097538) every 3 frames, then removed
09756E  func  title_clouds_1_drift              # 12 white clouds (B6D8/B6E0/B6F0, palette D80, second-pool blocks) moved by drift_title_cloud every 4 frames
097976  func  title_clouds_2_drift              # 7 more: itself and 6 second-pool blocks (timed spawn at C8 and 172), drift_title_cloud_2
097BDA  func  title_clouds_3_drift              # 7 more coming from the right: itself and 6 second-pool blocks (timed spawn at 3C and 12C)
097E57  func  drift_title_cloud                 # screen y - 1, x - 3, zoom + 2 up to 140
097E76  func  drift_title_cloud_2               # the same, zoom set to 140 once above 120
097E95  func  title_sky_wave                    # per-line x scroll of NBG2 (31FF04 = 401: MAME row scroll of NBG2, table at 300800): 80 lines moved by summed sine_table values each frame; NBG3 x scroll + 1/4 a frame; palette cycle at 608F60 from 097F69
09816A  func  run_sega_logo                     # sega_logo_step 0 set-up (bitmap layer 8 bpp, colour offsets 20 = black), 1 fade in (offsets + 1 every 2 frames up to 3F), 2 zoom in (sega_logo_scale + 18 a frame), 3 animation (sega_logo_frame up to 1E), 4 69 frames, 5 fade out, 6 3C frames, 7 done; the sprite is a pool 2 block allocated and freed every frame (render att_1500: SEGA)
0983C0  func  set_sega_logo_sprite              # screen A0,70, palette 420, zoom 100, frame 53D0 + 8 x sega_logo_frame
0998B8  func  vram_script_end                   # command AA (the entry after A7 in the table at 09988E): "add.w #4, SP; rsr" returns from run_vram_script
099A76  func  draw_hex_digits                   # draw_hex_2/4/8: R2 digits of R0 leftwards from R11 + 2 x R2, character '0'-'9'/'A'-'F' with attribute R1, blanks (R0 = 0) once the rest is 0
099AA3  func  draw_credit_digits                # draw_credit_count_now: two BCD digits of R0, each written as nibble + 1, leading zero blank
09A4DF  func  load_palette_list_case_00         # kind 0: br copy_palettes_with_pen_0
09A4E1  func  load_palette_list_case_01         # kind 2 (the jump table's offsets are the kinds): br copy_palette_block
09A4E3  func  load_palette_list_case_02         # kind 4: br repeat_palette_block
09A943  func  fill_span_8bpp                    # R3 + 1 bytes of colour R4 from R1 (32-bit stores where it can): draw_line's horizontal lines and fill_rect_unclipped
09A9C0  func  draw_vertical_span_8bpp           # R5 + 1 pixels of colour R4 down from R1, R2 apart (200: one 8 bpp row)
09AAF1  func  fill_span_4bpp                    # the horizontal line for 4 bits per pixel (two pixels a byte); not run in the scenarios
09AB9C  func  draw_vertical_span_4bpp           # the vertical line for 4 bits per pixel (nibble mask by x parity); not run
09AF1F  func  fill_rect_unclipped               # fill_rect after clip_rect_to_window, and clear_bitmap_layer: rows of fill_span_* (8 or 4 bpp), single rows/columns through the span routines
09C3D2  func  clip_line_to_window_case_00       # outcode 0 (inside): clip_line_end_inside
09C3D5  func  clip_line_to_window_case_01       # 1 (left): clip_line_to_left_edge
09C3D8  func  clip_line_to_window_case_02       # 2 (right): clip_line_to_right_edge
09C3DB  func  clip_line_to_window_case_03       # 3 (cannot happen): clip_line_end_inside
09C3DE  func  clip_line_to_window_case_04       # 4 (above): clip_line_to_top_edge
09C3E1  func  clip_line_to_window_case_05       # 5 (above, left): clip_line_to_top_edge
09C3E4  func  clip_line_to_window_case_06       # 6 (above, right): clip_line_to_top_edge
09C3E7  func  clip_line_to_window_case_07       # 7: clip_line_end_inside
09C3EA  func  clip_line_to_window_case_08       # 8 (below): clip_line_to_bottom_edge
09C3ED  func  clip_line_to_window_case_09       # 9 (below, left): clip_line_to_bottom_edge
09C3F0  func  clip_line_to_window_case_0a       # A (below, right): clip_line_to_bottom_edge
09C3F3  func  clip_line_end_inside              # rsr (also the table's entry B)
09C3F4  func  clip_line_to_left_edge            # R5 = bitmap_window_left - x0, then clip_line_at_x
09C3FE  func  clip_line_to_right_edge           # R5 = bitmap_window_right - x0, then clip_line_at_x
09C406  func  clip_line_at_x                    # x0 += R5, y0 += (y1 - y0) * R5 / (x1 - x0) (y0 kept when x1 = x0)
09C42D  func  clip_line_to_top_edge             # R5 = bitmap_window_top - y0, then clip_line_at_y
09C437  func  clip_line_to_bottom_edge          # R5 = bitmap_window_bottom - y0, then clip_line_at_y
09C43F  func  clip_line_at_y                    # y0 += R5, x0 += (x1 - x0) * R5 / (y1 - y0)
09CA0E  func  run_test_mode_case_00             # test_screen 0: br test_menu
09CA11  func  run_test_mode_case_01             # 1: toggle_test_continue_mode
09CA14  func  run_test_mode_case_02             # 2: test_memory
09CA17  func  run_test_mode_case_03             # 3: test_inputs
09CA1A  func  run_test_mode_case_04             # 4: test_sound
09CA1D  func  run_test_mode_case_05             # 5: test_crt
09CA20  func  run_test_mode_case_06             # 6: test_game_assignments
09CA23  func  run_test_mode_case_07             # 7: test_coin_assignments
09CA26  func  run_test_mode_case_08             # 8: test_bookkeeping
09CA29  func  run_test_mode_case_09             # 9: test_backup_clear
09CA2C  func  run_test_mode_case_0a             # A: test_outputs
09CA2F  func  run_test_mode_case_0b             # B: exit_test_mode
09E6FF  func  draw_test_hex_4                   # R1 = 200 (text attribute), draw_hex_4 (test_bookkeeping)
09E70B  func  draw_test_hex_2                   # R1 = 200, draw_hex_2 (test_bookkeeping)
09ECC7  func  draw_blinking_test_cursor         # test_cursor_blink - 1: below 1 the blank (115CED) instead of [R10]; back to 1E at -F; writes the character with attribute 200 at test_cursor_column/_row (blink_test_cursor, erase_test_cursor)

# ---------------- work RAM variables ----------------
# Only written, never read: named "unused_..." so the C says so; the writer is in the comment.
208078  var   unused_type_spawn_byte    u8          # start_spawns_by_type clears it at every stage; nothing else uses it
20803E  var   escape_alarm_stray_flags  u32         # escape_alarm_flash: "set1 #E, 3E[R25]" right after setting bit 14 of the mixer shadows 71C2-71CA: probably meant 71CE (mixer_offset_select); nothing reads it
209008  var   unused_title_word         u16         # run_title_screen writes 58; nothing reads it
20900B  var   unused_title_byte_1       u8          # attract_title clears it; nothing else
209015  var   unused_title_byte_2       u8          # attract_title clears it; nothing else
20902B  var   scroll_skip_open          u8          # attract_title and init_stage_view clear it; only the unused scroll skip mode (run_scroll_skip_mode) reads it: the panel is open while it is set
209070  var   unused_view_word          u16         # init_stage_view clears it (with the stage 3 end variables); nothing reads it
20CA19  var   unused_circle_wipe_byte   u8          # init_stage_view clears it, between circle_wipe_line and circle_wipe_radius; nothing reads it
20CA36  var   debug_sprite_x       u16         # init_stage_view writes 30; the unused debug sprite mover (debug_sprite_mover_update) moves it and prints it
20CA38  var   debug_sprite_y       u16         # init_stage_view writes 30; the unused debug sprite mover (debug_sprite_mover_update) moves it and prints it
20CA3A  var   debug_sprite_spawned       u8          # init_stage_view clears it; the unused spawn_debug_sprite_mover spawns its mover only while it is 0, and adds 1
20E42A  var   unused_scroll_offset      s16         # init_stage_view writes FFF6 (neg.h #A); nothing reads it
20E42E  var   unused_scroll_flag        u8          # start_stage_view sets 1, init_stage_view clears it right after; nothing reads it
20E544  var   unused_group_spawn_word   u16         # spawn_object_groups_2 clears it at its stage set-up; nothing else
20E57A  var   unused_camera_speed_word  u16         # cleared with the camera speed caches (run_game_mode twice, start_stage); nothing else writes or reads it
20E5B1  var   unused_summary_byte       u8          # update_player_summary clears it once per stage (stage_init_flags bit B); nothing reads it
20E5BA  var   unused_stage_05_event_long u32        # update_stage_05_event step 0 writes 0 (mov.w); nothing reads it
20E5D2  var   unused_title_byte_3       u8          # attract_title clears it; nothing else
20E5EA  var   unused_stage_byte         u8          # start_stage clears it; nothing else
20E612  var   unused_player_flags       u8          # [3]: player_reset_joined clears [p], start_stage the four bytes (32-bit); nothing reads them
20E665  var   unused_escape_byte        u8          # game mode 0x1F step 0 clears it; nothing reads it
20F0A4  var   unused_group_spawn_word_2 u16         # spawn_object_groups_2's stage set-up writes 0; nothing reads it
20F0A6  var   unused_group_spawn_word_3 u16         # spawn_object_groups_2's stage set-up writes 1; nothing reads it
20F0A9  var   unused_game_byte          u8          # run_game_mode (twice) and start_stage clear it; nothing reads it
20F0E9  var   unused_player_start_byte  u8          # player_start writes 3; nothing reads it
20F405  var   unused_coin_byte          u8          # attract_warning clears it (between shared_coin_counter and coin_counter_1_on); only the unused set_port_d_bit_3_if_sound_allowed reads it
20F03E  var   credit_text_position      u32         # draw_credit_count_now stores the text map address it draws at (R11); nothing reads it
20F104  var   sprite_jump_init          u16         # init_sprites writes A400 (the entry 1 jump of sprite_buffer_swaps' second half); nothing reads it
20E514  var   camera_map_direction      u16         # compute_camera_ground_speed: angle(camera_velocity_x >> 8, camera_velocity_y >> 8), the camera's direction on the screen plane; nothing reads it
20E51E  var   camera_ground_direction   u16         # compute_camera_ground_speed: angle(vx >> 9, vy >> 8) + 80 (ground axes); nothing reads it
20E488  var   section_unused_cursor     u32 rom     # init_stage_view loads stage_section_unused[stage], advance_scroll adds 2 per section; nothing reads it
20E668  var   stage_b_sound_done        u8          # player state 38 (stage B sequence), after its path: the first player to get there sets FF and plays sound 94 and its character's voice; start_stage clears it

# Character select and ranking (game_08)
20808F  var   players_choosing_character u8         # start_character_chooser + 1, character_chooser_confirm - 1 (at 0 character_select_timer = 3C); character_select_countdown_update goes away when it is 0 and the timer ran out
208090  var   characters_taken          u32         # bit character_taken_bits[c] (bits 0-2: Sonic, Ray, Mighty) per character in use: the choosers skip them, game over frees the player's (005FD6), game mode 0x1B clears it (00109E: a 32-bit store that also clears 208091-208093); bit tests are 32-bit (test1/set1/clr1)
208091  var   players_in_join_chooser   u32         # bit per player while start_join_character_chooser's chooser runs (set1/test1/clr1 at 91[R25]); player_select_character waits for it to clear
208092  var   free_character_count      u8          # count_free_characters; character_chooser_confirm - 1; with 1 left the chooser confirms at once, with 0 there is no join chooser
208095  var   ranking_entry_players     u32         # bit per player whose ranking entry runs (start_ranking_entry sets it, enter_ranking_initials clears it)
208037  var   ranking_title_layout      u8          # build_ranking_display toggles 0/1 at each ranking screen; draw_ranking_title moves the outer two TOP15 letters along x only (0) or along x and y (1)

# Title and SEGA logo (209000-2090FF is reused by several screens)
209000  var   title_scene_built         u8          # run_sega_logo sets 1 at its start and 0 at its end; run_title_screen builds the title scene when it is 0 (after the logo) and sets 1
209001  var   title_sonic_step          s8          # title_emblem_animate: Sonic's rise step 0-13 (height title_sonic_rise_heights), then 14+ loops through the frame list at 096E02
209002  var   screen_sprite_block       u16         # a second-pool block a screen draws one sprite with: the SEGA logo (run_sega_logo allocates and frees it every frame), draw_map_trail_dot, the input test's cursors (test_inputs, move_input_test_cursors)
209004  var   title_step_timer          s16         # title_emblem_animate: + 1 a frame, the next Sonic step when it reaches 0 (FFFE, FFFC, FFC4 before Ray and Mighty, FFFD)
209006  var   title_timer               s16         # title_emblem_animate: frames; title_done at 258
209083  var   sega_logo_step            u8          # run_sega_logo switches on it (0-6), 7 = done (run_title_screen waits for it); attract_title clears it
209084  var   sega_logo_frame           s8          # run_sega_logo: animation frame 0-1E; set_sega_logo_sprite shows 53D0 + 8 x it
209085  var   sega_logo_timer           s8          # run_sega_logo: frames within a step (fades every 2 frames, 69 hold, 3C wait)
209086  var   sega_logo_scale           s16         # run_sega_logo step 2: the logo's zoom, + 18 a frame up to FF
20CA40  var   title_ray_mighty_started  u8          # title_emblem_animate sets 1 at Sonic's step 13: Ray and Mighty then pop up
20CA41  var   title_ray_mighty_frame    u8          # index into title_ray_frames / title_mighty_frames (stays on the last before FFFF)
20CA42  var   title_ray_mighty_delay    u8          # frames per Ray/Mighty frame (3, then 2)
20CA43  var   title_ray_mighty_wait     u8          # 28 frames after title_ray_mighty_started before they appear

# Input test and the stage 3 end share 20906B-20906D (different screens)
209056  var   input_test_switch_pressed u8          # test_inputs: 1 while the test switch (bits 1/13) is pressed after input_test_switch_released; its release leaves INPUT TEST
209059  var   input_test_switch_released u8         # test_inputs: 0 until the test switch held since entering the screen has been released
20906B  var   stage_03_end_started      u8          # update_scroll after stage 3's scroll_end: its set-up (vram scripts 145A46/146DD1, NBG0 pages, speed-up) runs while this is <= 0, then + 1; init_stage_view clears it. INPUT TEST uses the byte for trackball 1's quadrant bits (F = GOOD)
20906C  var   stage_03_end_step         u8          # stage 3 end: 0 speed up, 1 climb 78 frames, 2 slow down (then all_players_path_request 23, spawn_stage_03_end_pillar), 3 stopped. INPUT TEST: trackball 2's quadrant bits
20906D  var   stage_03_end_timer        s8          # stage 3 end step 1: 78 frames. INPUT TEST: trackball 3's quadrant bits
20906E  var   stage_03_end_frames       u16         # stage 3 end: + 1 every frame of steps 0-2; nothing reads it
209055  var   map_stage_09_sound_count  u8          # map_eggman_update (stage 9 only): + 1 when its third sound (stage_map_sounds_3) plays; at 5A it would also queue sound 95, but it only reaches 1
20905D  var   camera_shake_phase        u8          # update_camera_with_shake: 0 on the first shaking frame, then 1; cleared when the shake ends and by init_stage_view
20909C  var   palette_cycle_set_0_frame u8          # byte +C (current frame) of the cycle_palette_set state at 209090 (cycle_palette_set_0): update_stage_06_background step 6 keeps both sets cycling while it is not 0
2090AC  var   palette_cycle_set_1_frame u8          # byte +C of the state at 2090A0 (cycle_palette_set_1): with both at 0, a 3/16 chance per try (two a frame) starts set 0
2090DE  var   window_wipe_frames        u16         # start_window_wipe: the record's frame count (the edge speeds are distances / it)
2090E0  var   window_wipe_left_speed    s32         # 16.16 per frame (negated when window_wipe_closing)
2090E4  var   window_wipe_right_speed   s32
2090E8  var   window_wipe_top_speed     s32
2090EC  var   window_wipe_bottom_speed  s32
2090F0  var   window_wipe_left_edge     s32         # 16.16 current edge: update_screen_wipe moves it toward window_wipe_left and writes it to video_bitmap_clip_left
2090F4  var   window_wipe_right_edge    s32         # -> video_bitmap_clip_right
2090F8  var   window_wipe_top_edge      s32         # -> video_bitmap_clip_top
2090FC  var   window_wipe_bottom_edge   s32         # -> video_bitmap_clip_bottom

# Objects and stages
209401  var   bridge_0_load_first       u8          # bridge_0_load entry 1: check_platforms writes 0 (the anchored first end) every frame
209402  var   bridge_0_load_last        u8          # bridge_0_load entry 2: bridge_0_plank_count (the anchored last end)
209501  var   bridge_1_load_first       u8          # the same for bridge_1_load
209502  var   bridge_1_load_last        u8
209FC2  var   banner_sprite_flags       u16         # static sprite block at 209FC0 (draw_game_over_banner, blink_demo_prompt): sprite_flags (1000)
209FC4  var   banner_sprite_frame       u16         # 89A0 GAME OVER, 89A8 INSERT COIN(S), 89B8 PUSH START BUTTON
209FC6  var   banner_sprite_palette     u16
209FCC  var   banner_sprite_y           u16         # screen y (6C, 64)
209FCE  var   banner_sprite_x           u16         # screen x (A0)
20CA01  var   palette_cycle_0_frames    u8          # byte 1 of the palette_cycle_0 state: frames in the cycle (cycle_palette wraps its frame index there); every user sets it before calling
20CA06  var   palette_cycle_1_frames    u8          # the same for palette_cycle_1
20E510  var   camera_ground_speed       u32         # compute_camera_ground_speed: sqrt((vx>>5)^2 + (vy>>4)^2) << 13; the chasers use its high word x their catch-up factor >> 8, river_raft_spawner_update moves by it >> 7
20E512  var   camera_ground_speed_int   u16         # the high word of camera_ground_speed
20E516  var   camera_ground_speed_vx    u32         # camera_velocity_x camera_ground_speed was computed for (recomputed when it changes)
20E51A  var   camera_ground_speed_vy    u32         # camera_velocity_y likewise
20E520  var   stage_link_from_block     u16         # continue_to_next_stage_slot (never runs): offset of this stage's link floor block (stage_link_points)
20E522  var   stage_link_from_x         s32         # its x min (+0)
20E526  var   stage_link_from_y         s32         # its y min (+8)
20E52A  var   stage_link_from_height    u16         # its height (+10)
20E52C  var   stage_link_to_block       u16         # the next stage's link block
20E52E  var   stage_link_to_x           s32
20E532  var   stage_link_to_y           s32
20E536  var   stage_link_to_height      u16
20E539  var   group_depth_step          s8          # spawn_object_groups: F8 for type 2 records, 0 else; each member's depth offset (96) gets this x the members still to spawn
20E564  var   group_leader_1            u16 object  # group_leaders[1]: propped_board_update falls when it reaches the board's x
20E56A  var   camera_slope_speed_vx     u32         # camera_velocity_x camera_slope_speed was computed for
20E56E  var   camera_slope_speed_vy     u32
20E572  var   camera_slope_speed        u32         # compute_camera_slope_speed: the camera's ground speed on the slope (<< 13); keep_player_on_screen, ember_emitter_move, fireball_emitter_update and the chasers on slopes (tornado, biting head, icicle droppers...) use it
20E574  var   camera_slope_speed_int    u16         # the high word of camera_slope_speed
20E576  var   camera_slope_direction    u16         # its ground direction (+ 80); keep_player_on_screen, fireball_emitter_update
20E5F8  var   camera_slope_factor       u16         # the slope factor (R8) camera_slope_speed was computed for
20E591  var   player_2_status           u8          # player_status[1]: run_game_mode and player_pacer_update read it directly
20E592  var   player_3_status           u8          # player_status[2]
20E593  var   player_status_spare       u8          # 4th byte after player_status: only cleared (player_place, the 32-bit stores at 000711/000C3F); check_stage_clear needs it 0 and the game over tests read the four bytes as one word
20E59A  var   grey_screen_active        u8          # update_grey_screen: FF while a player's 9F is set: spiked_roller_chase and group_leader_move stop moving, arrow_board_ride and crumbling_floor_update switch to grey palettes
20E59B  var   grey_screen_ended         u8          # update_grey_screen: FF for the frame the grey screen ends: arrow_board_ride and crumbling_floor_update put their palettes back
20E5A0  var   camera_scroll_direction   u16         # angle(camera_velocity_x >> 5, camera_velocity_y >> 5), written by compute_camera_slope_speed and compute_scroll_drift; keep_player_on_screen reads it
20E5A2  var   all_players_path_request  u8          # follow_player_path gives every player this path (then clears it): update_scroll sets 23 at stage 3's end; push_player_out_of_wall and check_player_contacts do nothing while it is set
20E5A8  var   group_takes_slot          u8          # spawn_object_groups(_2): the record's flag byte: the group takes a free entry of group_slots (its members' 8A); copied to the members' 92
20E5AA  var   group_slot_index          u16         # the group_slots entry taken (members' field 8A)
20E5EF  var   stage_03_floor_block_falls_1 u8       # stage_03_floor_block_falls[1]: spiked_roller_sink_and_jump sets FF when it lands
20E5F2  var   per_player_view_mode      u8          # only cleared (start_stage): while set the player code skips find_floor and projects with project_object_2, which from scroll_position A5F0 uses each player's own_view_y (player DE) instead of view_y; shadow_update too. Never set
20E5F4  var   sign_stop_flag            u8          # sign_show_until_gate_closed (never run) removes its sign once it is set; only the unused exit gate (exit_gate_start, exit_gate_update) writes it
20E5F5  var   players_pulled            u8          # while set check_scene_states puts every player (not falling: 6/8/9) into state 26 (pulled toward scene_object); handle_rod_wait sets 1, handle_rod_start_swing clears it; run_game_mode clears it with a 16-bit store that also clears the low byte of moving_floor_object
20E5FA  var   scroll_drift_block        u32 floor_block  # compute_scroll_drift: the floor block (address) the drift was computed for; its travel_direction (+15) << 8 is scroll_drift_direction on a slope (00B1F7 "mov.w 65FA[R25], R10; movz.bh 15[R10], R1")
20E5FE  var   scroll_drift_vx           u32         # camera_velocity_x it was computed for
20E602  var   scroll_drift_vy           u32
20E606  var   scroll_drift_speed        u32         # compute_scroll_drift: the camera's speed on that block (<< 13); player_move_with_scroll moves players by it >> 14 (states 25/26)
20E60A  var   scroll_drift_direction    u16         # its direction: from the camera velocity, or the block's byte +15 << 8 on a sloped block; push_player_out_of_wall turns it along a wall; 300 while a pulled player's drift_turned_back is set
20E60C  var   handle_rod_drop_players   u8          # handle_rod_wait (rod with byte 93 set) sets 1 when the near post falls: players climbing the rod (state 27) at x <= 450 drop; handle_rod_start_swing clears it
20E617  var   demo_prompt_shown         u8          # blink_demo_prompt toggles it every 0x20 frames (the big prompt over the demos); init_game_settings and run_game_mode clear it
20E634  var   rising_saw_1              u16 object  # rising_saws[1] (grate_steps_init sets FFFF)
20E636  var   rising_saw_2              u16 object  # rising_saws[2]
20F094  var   group_member_count        s16         # spawn_object_groups(_2): members still to spawn (the record's count, checked against free_object_stack)
20F096  var   group_step_x              u16         # added to group_member_x per member
20F098  var   group_step_y              u16
20F09A  var   group_member_x            u16         # x of the next member
20F09C  var   group_member_y            u16
20F09E  var   group_record              u32 rom     # the stage_object_records(_2) record the members are made from
20F0A8  var   group_member_number       u8          # member index, into the member's 6F (spawn_object_groups_2: odd ones take the record's other animation)
20F0DE  var   stage_event_cursor        u32 stage_event     # run_stage_events: the next (scroll position, game mode) record of stage_events[stage]
20F0FC  var   random_position           u32         # next_random: offset into the random number area at D80000 (moved on by each value read, & 7FFFF); init_system clears it

# Temporaries: 20F0EA-20F0F7 and 20E500-20E50F are scratch shared by many routines (each sets and uses them
# within one call): generic names on purpose.
20F0EA  var   temp_0                    u16         # 45 routines: loop counts, saved bytes, 32-bit pointers (also read as AS32); scatter_rings and reset_objects_from_list take a parameter in it
20F0EC  var   temp_1                    u32         # 22 routines (reset_objects_from_list walks the player blocks in its upper half)
20F0F0  var   temp_2                    u8          # spawn_object_groups_2: bridge number (member 95); geyser_front_rise: a 32-bit pointer
20F0F2  var   temp_3                    u16         # spawn_object_groups(_2): the record's direction (member 24); spiked_roller_start: a 32-bit pointer
20F0F4  var   temp_4                    u16         # spawn_object_groups(_2): the group's leader (member 26)
20F0F6  var   temp_5                    u16         # spawn_object_groups: FF for type 6 records (member 91); spawn_object_groups_2: the previous member (9A); spiked_roller_start
20E500  var   scratch_0                 u32         # check_player_contacts saves x here; spawn_ring_rows: count and template offset; spiked_roller_start: x of the next part
20E504  var   scratch_1                 u16         # check_player_contacts: saved y (32-bit); spawn_ring_rows: x of the next ring; spiked_roller_start: x step (32-bit)
20E506  var   scratch_2                 u16         # spawn_ring_rows: y of the next ring
20E508  var   scratch_3                 u32         # spawn_ring_rows: dx/dy (two words); spiked_roller_start: y of the next part
20E50C  var   scratch_4                 u16         # spawn_ring_rows: the next ring's 80 (+ 20 each); spiked_roller_start: y step (32-bit)

# Mixer and video register shadows (MAME segas32_v mix_all_layers)
20F1C4  var   mixer_nbg1_blend          u16         # -> 610034 (NBG1 blend control: bits 0-5 sprite blend, 6-13 layer blend mask, 14 colour offset set); the code sets and clears bit 14 with 32-bit set1/clr1 on each of these words
20F1C6  var   mixer_nbg2_blend          u16         # -> 610036 (NBG2)
20F1C8  var   mixer_nbg3_blend          u16         # -> 610038 (NBG3)
20F1CA  var   mixer_bitmap_blend        u16         # -> 61003A (the bitmap layer)
20F1CE  var   mixer_offset_select       u16         # -> 61003E: bit n (0 text, 1-4 NBG0-3, 5 bitmap, 6 sprites, 8 backdrop) and bit 15 pick each layer's colour offset mode (MAME compute_color_offsets; bit n with bit 15 clear = the grayscale mode): update_grey_screen sets bits 1-4, ending_flash_to_white writes 80FF
20F1DC  var   mixer_sprite_control      u32         # -> 61004C: sprite grouping, shadow (bit 2), the sprites' colour offset set (bit 15: the fades set it with the layers' bit 14); the 32-bit word includes mixer_blend (61004E)
20F2A2  var   nbg0_center_y             u16         # -> 31FF32 (MAME: NBG0 y centre for zoom); update_stage_00_exit_scene sets 90, init_stage_view and build_ending_scene 0
20F2A4  var   nbg1_center_x             u16         # -> 31FF34 (A0 in the stage 0 exit zoom)
20F2A6  var   nbg1_center_y             u16         # -> 31FF36 (90)
20F300  var   vram_script_dictionary    u16         # run_vram_script: script bytes 00-7F write word [byte] of it; command 95 fills it. Its first two words overlap the end of video_registers_latched (20F2C2 + 0x42), rewritten every VBLANK
20F302  var   video_layer_opaque_latched u8         # the latched copy of video_layer_opaque_shadow (last word of video_registers_latched): apply_video_registers writes it to video_layer_opaque_flags (31FF8F)
20F627  var   setting_small_large       u8          # EEPROM setting byte (760E-7627): GAME ASSIGNMENTS item 8 toggles it 0/1 and prints SMALL / LARGE (small_large_texts); the menu's cursor list (115BBB) has only 8 items, so the item is never reached

# ---------------- ROM tables ----------------
0005DE  table mixer_default_nbg0        u16         # mixer_defaults entry 11 (610022): update_stage_06_background raises it to | F and restores it
0005E4  table mixer_default_nbg3        u16         # mixer_defaults entry 14 (610028), the same; mixer_defaults ends at 00060C (attract_mode_names)
0050AF  table floor_4_slow_run_steps    u8          # [animation_frame] FF FF 00 00 FF FF FF 00 00 FF: on floor type 4 player_animate_run_cycle needs 4 x the walk_cycle (1800 instead of 600) before leaving these steps
00CD12  table hud_name_positions_3p     u16         # [player] text map offsets (31FC08, 31FC20, 31FC3C) where state 24 erases the character name ("MIGHTY", the longest, at 00CD2F); 3-player cabinets
00CD18  table hud_name_positions_2p     u16         # the same for 2 players (31FC08, 31FC3C)
00E453  table sink_depth_limits         s16         # [floor_attribute] of a floor type A block: 2100, 0800, 0100: update_sinking_floor pushes sink_depth back by A0 past it (2100: past the 2000 that costs 8 health)
08E2E7  table direction_to_player_offsets u16       # 1C 1E 20: R25 offsets of direction_to_player_1..3 (face_nearest_player stores through it; it also adds [nearest_player] to the angle it computed last: see the report)
08EF7F  table ranking_initial_fields    u16         # 38 39 3A: the entry block fields enter_ranking_initials writes the chosen letter into, by letter index
08EF85  table ranking_letter_sprites    u16         # [letter code 0-2B]: A238... (a-z, then 0-9 from 20, 2B blank): ranking rows, entry letters, character select countdown digits
08EFDD  table ranking_entry_x           u16         # [player] screen x of the entry: 3 players 10 78 E0, then [player + 3] for 2 players 10 E0 E0
08EFF5  table ranking_entry_buttons     u8          # [player] input bit of the trackball button (10 18 12) -> entry field 80
08F0CF  table ranking_entry_blocks u32 object.ranking_entry  # [player] static entry blocks outside the pools: 20DB00, 20DC60, 20DDC0 (one object + 4 second-pool-size parts, 160 bytes each) (pointer typed: last_names.txt N1)
08F876  table ranking_portrait_frames   u32 ranking_portrait_frame_list  # [character] pointer to 28 (hex) portrait frames: 14 for the rows, 14 more for the top row (ranking_row_update)
elements ranking_portrait_frame_list u16  # the frame list a ranking_portrait_frames entry points at (08F882, 08F8D2, 08F922): 14 sprite words for the rows, 14 for the top row; ranking_row_update "mov.w 8F876[PC](R0), R1" (08F7BA), then the word at R1 + frame_index * 2; census ROM 08F882-08F971
08F972  table ranking_portrait_palettes u16         # [character] 8B00 8AF0 8AE0
08F978  table ranking_name_sprites      u16         # [character] A750 A758 A760 (SONIC, RAY, MIGHTY in the render)
08F984  table ranking_stage_sprites     u16         # [stages_reached % 9 - 1]: STAGE 1 ... STAGE 7, ALL CLEAR (8) (render att_1300)
09031D  table chooser_idle_steps        u8          # 0 1 2 1: idle portrait step (character_chooser_wait, field 80)
090321  table chooser_portrait_frames   u16         # [character x 4 + step]: B2F0-B308 Sonic, BF60-BF78 Ray, 8F20-8F38 Mighty; step 3 is the chosen pose
090339  table chooser_portrait_palettes u16         # [character] 8130 8140 8150
09033F  table chooser_x_by_player       u16         # [player] screen x of a chooser: 38 A0 108 (3 players), [player + 3] 50 F0 F0 (2 players)
09034B  table chooser_trackball_offsets u16         # [player] 7062 7066 706A: R25 offsets of trackball_N_x (the C shows GAME_VARIABLES + it)
090351  table chooser_button_bits       u8          # [player] 10 18 12: the trackball button's input bit
090354  table character_taken_bits      character_bit_numbers  # [character] 0 1 2: bit number in characters_taken
elements character_bit_numbers u8     # character_taken_bits' bytes, by character: its bit in characters_taken ("movz.bw 90354[PC](R2), R3" 090304, then "test1 R3, 90[R25]"); census ROM 090354-090356 (00 01 02)
090357  table chooser_player_labels     u16         # [player] B710 B718 B720 ("Player 1/2/3", render)
09035D  table character_name_sprites    u16         # [character] B728 SONIC, B738 RAY, B730 MIGHTY (render)
090363  table join_chooser_portraits    u16         # [character] 90C8 90D8 90D0: small head portraits (render)
090369  table join_chooser_palettes     u16         # [character] 8F00 8F20 8F10
09037B  table player_select_title_palettes u16      # palettes 8DD0-8E90 the PLAYER SELECT heading steps through (character_select_countdown_update)
090395  table join_chooser_blocks       u32 object.join_character_chooser  # [player] 20CE7A 20CF4A 20D01A: static blocks of the join choosers
093F5B  table rope_plank_frame          u16         # stage_object_records_2 record 0 +2: rope_bridge_plank_update's kind F branch turns a plank into this one
093F5D  table rope_plank_palette        u16         # record 0 +4
093F63  table rope_plank_animation      u32 rom     # record 0 +A (field 48)
093F67  table rope_plank_animation_2    u32 rom     # record 0 +E (the alternative animation)
094003  table rope_bridge_rope_1_dy     s16         # rope_bridge_rope_offsets[1]: -10
094005  table rope_bridge_rope_2_dx     s16         # [2]: 20
094007  table rope_bridge_rope_2_dy     s16         # [3]: F
0967FB  table window_wipe_default_left  u16         # start_window_wipe when the record's left is <= 0: "mov.h 967FB[PC], R1" reads its own instruction bytes (1B 21 = 211B); probably meant 0
096804  table window_wipe_default_top   u16         # the same for the top: reads 1B 22 (221B)
096E0F  table title_sonic_rise_heights  u8          # [title_sonic_step 0-13]: Sonic's height above y 70 as he rises out of the emblem (04 10 1A 21 ...)
097316  table title_ray_frames          u16         # Ray's frames on the title (left, x 66, palette 830): 8EB0..., FFFF ends
097330  table title_mighty_frames       u16         # Mighty's frames (right, x DA, palette 840): B678-B6C8, FFFF ends
101C32  table sine_of_300               s16         # sine_table[300]: handle_rod_init steps the handles along angle 300
101E32  table cosine_of_300             s16         # cosine_table[300]
10DF46  table floor_slope_factors       u32         # floor_slopes + 2, read as [slope x 2] (8-byte records): the slope factor (the R8 of compute_camera_slope_speed; compute_scroll_drift)
10DF4A  table floor_slope_direction_tables u16      # floor_slopes + 6, read as [slope x 4]: offset from 10DFCC of the slope's (speed factor, direction) pairs by direction >> 6 (player_steer, compute_ground_speed)
1139E8  table sonic_bounce_records      u16         # [bounce_direction] 6-byte records for character 0 (113A28 Ray, 113A68 Mighty): direction, sprite_flags (40 = x flip), animation offset from the table (state E); push_player_out_of_wall takes the direction word by the wall's side
113CD1  table ledge_recover_headings    u16         # [ledge_side] 180 280 380 080: heading after pulling back from a ledge (states 5/7)
113EC0  table heading_speed_factors     u16         # [(heading + 20) >> 6]: 200 1AA 143 10F 100 ...: ground_speed = speed >> 8 x it (player_steer, compute_ground_speed)
113EF2  table max_health_by_setting     u16         # [joined_health_setting x 2]: 1E 1E 18 18 (4-byte steps): check_player_contacts caps health there, give_result_bonus too
113F4A  table floor_1_facing_bases      u8          # [floor_attribute 1-4] 00 04 08 0C: on floor type 1 a facing within 5 of it switches player_animate_run_cycle to the 111ACC animations (attributes 2/3 always, 1/4 while alternate_run_toggle is 0-7F)
113F9F  table hop_speeds_by_speed       s16         # [object speed >> 24]: FFFC-FFF8: the hop's speed_z (high word) on floor type 6 (state 17)
113FB7  table hud_prompt_positions_3p   u16         # [player] where blink_player_hud_message blinks "Insert Coins" / "Push Start" (row FC)
113FBD  table hud_prompt_positions_2p   u16
113FEB  table hud_text_positions_3p     u16         # [player] (31FB82, 31FB9C, 31FBB6): the HUD message lines (erase_text_lines of blank lines, player_hud_start/update)
113FF1  table hud_text_positions_2p     u16
114074  table player_state_stops_move   u8          # [player state]: FF for 3, 6-9, D-F, 15, 1B, 1D, 20, 27-29, 2C, 2D, 2F, 36: player_move stops after check_player_contacts when the player is (now) in one of them
114122  table hud_continue_positions_3p u16         # [player] "CONTINUE ?" (11412C) and the seconds (+14)
114128  table hud_continue_positions_2p u16
114158  table hud_game_over_positions_3p u16        # [player] " GAME OVER" (114162), states 24/2E
11415E  table hud_game_over_positions_2p u16
114190  table hud_ring_count_positions_3p u16       # [player] the 4-digit ring count (player_hud_update) and its blank (114183)
114196  table hud_ring_count_positions_2p u16
1141A0  table stage_goal_exit_paths     u8          # read as [stage_goals[stage]]: byte +6 of the stage's goal record (the word load_stage_goal skips): the path_request of the stage exit (state 21)
1142AE  table tumble_effect_y_offsets   u16         # [the player's animation_frame]: screen y added to the tumble effect (tumble_effect_update)
1142C4  table tumble_effect_depth_offsets s16       # [facing]: added to the player's draw_depth - 10 for the effect's depth (F8 = -8 for half the directions)
115528  table player_path_offsets       u16         # [path + player number]: offset from 115528 to the path's 8-byte records (follow_player_path)
115B92  table starting_health_by_vitality u8        # [setting_initial_vitality & 3]: 09 10 12 18 (apply_settings)
115F2E  table cabinet_type_texts        u32 rom     # [setting_cabinet_type] text lists "2P" / "3P" (test_game_assignments)
115FC0  table vitality_setting_numbers  u16         # [setting_initial_vitality] 1-4, drawn by draw_vitality_setting
1161DD  table credits_to_start_texts    u32 rom     # [setting_credits_to_start >> 1] "1 CREDIT" / "2 CREDITS"
116422  table coins_per_credit_texts    u32 rom     # [setting_coins_per_credit] (manual coin setting page)
1164BF  table bonus_adder_texts         u32 rom     # [setting_bonus_adder]
116711  table coin_setting_texts        u32 rom     # [setting_coin_number] (common chutes): the coin/credit setting lines
116779  table coin_setting_texts_individual u32 rom # [setting_coin_number_individual]
1167B5  table individual_coin_setting_numbers u8    # [setting_coin_number_individual 0-E]: 01 06 08 09 0A 0B 0C 0F 11 12 13 15 16 18 1A, the settings allowed with individual chutes
11786D  table small_large_texts         u32 rom     # [setting_small_large] "SMALL" / "LARGE" (test_game_assignments item 8)
# Four more rom_ reads the C regenerated at 06:53 shows (not in unnamed.txt):
090A84  table wall_kind_second_sprites  s16         # wall_kinds + 12, read at the kind's offset (spawn_walls): the second part's sprite of each step; >= 0 doubles the pool 2 blocks a wall needs. The C reads it as the kind's wall_kind_parts second_sprite (sprite_frame's link); the name marks where the assets cut wall_kinds
096C21  table map_number_badge_sprites  u16         # map_marker_layouts records 7-D (+6): the numbered badges 64A8 + 8 n (render: 1, 2, 3, 4); read at 096C21 + 12 x stage_map_markers[stage], blink_current_map_marker blinks the part showing the current stage's badge
10E150  table floor_attribute_slopes    s16         # floor_types + 4, read at floor_attribute (a byte offset): x moved per unit of rise on that floor (machine_fire_update, bridge_fire_rise x speed >> 4, build_grate_steps x 1E, build_saw_shaft_base x 1D); low byte 1 = the axis of a floor type 7 moving floor (apply_moving_floor). The C reads it as floor_attribute_record's x_per_rise and ride (floor_attribute's link); the name marks where the assets cut floor_types
10E152  table floor_attribute_wall_heights u16      # floor_types + 6 at floor_attribute: the climb height start_climb puts in climb_remaining. The C reads it as floor_attribute_record's height; the name marks where the assets cut floor_types

# ---------------- player fields ----------------
# Not proposed: player 02, 2A and 47 (and the shadow_update uses of 54 and 58) are not player fields:
# shadow_update (00BED5) and draw_attack_effect load the player's OBJECT into R18 ("mov.h 14[R17], R18"), so
# the C showed PLAYER(current_player)->unk_02 / unk_2A / unk_47 / unk_54 / unk_58 (and path_pointer,
# climb_remaining, step_accumulator, previous_x...) where the code reads the object's sprite_flags,
# projected_x, kind, floor_z, previous_floor_z (floor_block, projected_y, flags, floor_type...).
field player 54 drift_turned_back u8        # state 26 (pulled, then carried with the scroll): 0 when the carried phase starts, FF once x >= CD0; then the drift turns to 300 (scroll_drift_direction) and the phase ends back at x <= 630 (player_state_pulled 008E31-008EB0, player_move_with_scroll)
field player 55 drift_done u8               # state 26: 0 at the carried phase's start, FF when it ends (with drift_turned_back, x <= 630); game mode 0x12 (094410) counts the players in play whose drift is not done
field player 56 pull_distance_factor u16    # state 26 start: (1200 - pull_start_distance) >> 5; nothing reads it
field player 73 alternate_run_toggle u8     # player_animate_run_cycle: + 2 a frame on floor type 1 attributes 1/4 while facing along floor_1_facing_bases: the 111ACC run animation while 0-7F, the normal one while 80-FF (40 frames each)
field player 9F grey_screen_request u8      # update_grey_screen greys the tile layers while any player's is set; only ever cleared (player_reset 002B1A, state 1B 005157): never set
field player DE own_view_y u16              # project_object_2 and shadow_update subtract it instead of view_y once scroll_position >= A5F0 with per_player_view_mode; never written
field player E6 pull_angle u16              # state 26: 300 at the start, then a copy of scene_object's direction (24) every frame; the pull ends (player thrown) once it is 1D0 or less
field player E8 pull_start_distance u16     # state 26 start: (5D0 - x) >> 12
field player 12F sequence_bounced u8        # state 25 (floor type 8 sequence): 0 at steps 0/11, FF when the player lands and bounces (speed_z reversed and halved, speeds halved); game mode 0x14 tests it (094288)
field player 140 stage_b_path_done u8       # state 38 (stage B sequence): FF once the player has walked its path and stands facing direction 0; sliding_door_wait opens the door when every playing player has it; player_reset clears it
field player 14A hud_gauge_address u16      # gauge record +08 (sprite_scale, the address word of a sized frame): player_hud_init copies word +6 of frame 89E0's record
field player 15A hud_gauge_height u16       # gauge record +18 (sprite_height of a sized frame): 8
field player 167 hud_gauge_frame u8         # gauge record +25: index into its palette list (animate_hud_palette); player_hud_init clears it
field player 18A hud_portrait_height u16    # portrait record +18: player_hud_init writes A; its sprite (flags 100A, unscaled) does not use it
field player 1C9 hud_ring_effect_anim_timer u8  # ring effect record +27: frames before the next tile (animate_hud_tiles reloads it from +26)
# Only written (player_reset, player_start, player_hud_init, state 4): no reader.
field player 39 unused_hud_byte u8          # player_hud_init clears it
field player 58 unused_hud_word u16         # player_hud_init writes FFFF (16-bit)
field player 11A unused_reset_word u16      # player_reset clears it
field player 11C unused_jump_byte u8        # state 4 (jump) start clears it
field player 12C stage_rings_shown u16      # player_hud_init writes FFFF; the unused draw_player_stage_rings keeps the stage_rings it last drew here
field player 136 unused_start_byte u8       # player_start clears it
field player B3 unused_reset_byte u8        # player_reset writes 78

# ---------------- scoped object fields (7E-9F, per handler family) ----------------
# Format: "# scoped object FAMILY_PREFIX OFFSET NAME TYPE  # evidence". The names describe the fields of the
# object the family's code runs on (OBJECT(current_object)); inside these functions some lines touch other
# objects' fields at the same offsets (children being set up, the touched object, the parent), which these
# names do not describe: e.g. handle_rod_init sets its handles' 80-94, machine_fire_update its fireballs'
# 7F/96/9C, player_stuck_wait_release reads the holder's 80. Where a family's parts share the prefix with a
# different layout, the parts get a longer prefix that should win (burning_boulder_flame over
# burning_boulder, handle_rod_handle / handle_rod_post over handle_rod, group_leader_move / group_leader_hold,
# icicle_update / icicle_cracks / icicle_shadow instead of icicle, which would also take icicle_dropper).
# Most used families first. Not done (meanings change between the family's own handlers, or too little
# use): eel, snow_mound, sand_rock, rock_wall, spike_panel, saw_blade, opening_machine, flame_machine_*,
# eggman_scene, ember_emitter, crawler, centipede, whirlwind and the small effect objects.
#
# The chaser movement (fire front, biting head, sand block leader, propeller machine, waterfall, rising saws,
# lava flow, geyser front, bridge fire, machine fire, icicle droppers, burning boulder): one routine copied
# into each handler. A path script record gives a position or coordinate to reach, then (threshold, catch-up
# factor, base speed, wait); on its side of the threshold the object moves at the base speed, behind it at
# max(base, camera_ground_speed (or camera_slope_speed) x factor >> 8); chaser_speed gets the step. After a
# player mishap / a player dropping in / no player in play it holds back (threshold moved off screen, base
# speed 0) for FF frames or 300 scroll units.
# fire_front (fire_front_init, fire_front_chase): the chaser movement
scoped object fire_front 80 path u32 chase_step_with_limit  # next record of its path script (the record's words are loaded into 84/86/88/90 when its position or coordinate is reached)
scoped object fire_front 84 screen_threshold s16  # screen coordinate it compares its own with: on its side it moves at base_speed, behind it at max(base_speed, camera speed x catch_up_factor >> 8)
scoped object fire_front 86 catch_up_factor u16  # multiplies the camera's ground speed (>> 8) when it falls behind
scoped object fire_front 88 base_speed s16  # speed of the current record (chaser_speed when it is ahead)
scoped object fire_front 8A saved_threshold s16  # screen_threshold kept while holding back (a player mishap / dropping in / no player in play)
scoped object fire_front 8C saved_base_speed s16  # base_speed kept while holding back (base_speed is 0 meanwhile)
scoped object fire_front 8E holding_back u8  # FF while it holds back
scoped object fire_front 8F hold_timer u8  # FF frames of holding back at most (always FF without players in play)
scoped object fire_front 90 wait_timer s16  # frames of the record's wait: it does not move until 0
scoped object fire_front 92 hold_start_position u16  # scroll_position when it started holding back: it gives up after 300 units

# biting_head (biting_head_init, biting_head_wait, biting_head_chase): the chaser movement
scoped object biting_head 80 path u32 biting_head_path_entry  # as fire_front 80 (the same chaser code)
scoped object biting_head 84 screen_threshold s16  # as fire_front 84 (the same chaser code)
scoped object biting_head 86 catch_up_factor u16  # as fire_front 86 (the same chaser code)
scoped object biting_head 88 base_speed s16  # as fire_front 88 (the same chaser code)
scoped object biting_head 8A saved_threshold s16  # as fire_front 8A (the same chaser code)
scoped object biting_head 8C saved_base_speed s16  # as fire_front 8C (the same chaser code)
scoped object biting_head 8E holding_back u8  # as fire_front 8E (the same chaser code)
scoped object biting_head 8F hold_timer u8  # as fire_front 8F (the same chaser code)
scoped object biting_head 90 wait_timer s16  # as fire_front 90 (the same chaser code)
scoped object biting_head 92 hold_start_position u16  # as fire_front 92 (the same chaser code)

# sand_block_leader (spawn_sand_block_leader (80), sand_block_leader_wait, sand_block_leader_move): the chaser movement
scoped object sand_block_leader 80 path u32 chase_step_at_scroll  # as fire_front 80 (the same chaser code); sand_block_leader_read_script reads the entry through R10 (089FC9 "mov.w 80[R20], R10", 089FCE "cmp.h [R10+], 6435[R25]", 089FD6 "test.h 2[R10]", 089FE1-089FF3 [R10+]) and moves path past it (089FF9); census ROM group_leader_data (09357D + the leader_spawn's path)
scoped object sand_block_leader 84 screen_threshold s16  # as fire_front 84 (the same chaser code)
scoped object sand_block_leader 86 catch_up_factor u16  # as fire_front 86 (the same chaser code)
scoped object sand_block_leader 88 base_speed s16  # as fire_front 88 (the same chaser code)
scoped object sand_block_leader 8A saved_threshold s16  # as fire_front 8A (the same chaser code)
scoped object sand_block_leader 8C saved_base_speed s16  # as fire_front 8C (the same chaser code)
scoped object sand_block_leader 8E holding_back u8  # as fire_front 8E (the same chaser code)
scoped object sand_block_leader 8F hold_timer u8  # as fire_front 8F (the same chaser code)
scoped object sand_block_leader 90 wait_timer s16  # as fire_front 90 (the same chaser code)
scoped object sand_block_leader 92 hold_start_position u16  # as fire_front 92 (the same chaser code)

# propeller_machine (propeller_machine_init, propeller_machine_chase): the chaser movement
scoped object propeller_machine 80 path u32 chase_step_with_limit  # as fire_front 80 (the same chaser code); propeller_machine_read_script reads the entry through R10 (08D449 "mov.w 80[R20], R10")
scoped object propeller_machine 84 screen_threshold s16  # as fire_front 84 (the same chaser code)
scoped object propeller_machine 86 catch_up_factor u16  # as fire_front 86 (the same chaser code)
scoped object propeller_machine 88 base_speed s16  # as fire_front 88 (the same chaser code)
scoped object propeller_machine 8A saved_threshold s16  # as fire_front 8A (the same chaser code)
scoped object propeller_machine 8C saved_base_speed s16  # as fire_front 8C (the same chaser code)
scoped object propeller_machine 8E holding_back u8  # as fire_front 8E (the same chaser code)
scoped object propeller_machine 8F hold_timer u8  # as fire_front 8F (the same chaser code)
scoped object propeller_machine 90 wait_timer s16  # as fire_front 90 (the same chaser code)
scoped object propeller_machine 92 hold_start_position u16  # as fire_front 92 (the same chaser code)
scoped object propeller_machine 94 sound_timer u16  # 3C frames between sounds B while on screen

# waterfall (spawn_waterfall (80, 94: write them for the new leader), waterfall_move): the chaser movement
scoped object waterfall 80 path u32 chase_step_at_scroll  # as fire_front 80 (the same chaser code); waterfall_read_script reads the entry through R10 (08CD80 "mov.w 80[R20], R10")
scoped object waterfall 84 screen_threshold s16  # as fire_front 84 (the same chaser code)
scoped object waterfall 86 catch_up_factor u16  # as fire_front 86 (the same chaser code)
scoped object waterfall 88 base_speed s16  # as fire_front 88 (the same chaser code)
scoped object waterfall 8A saved_threshold s16  # as fire_front 8A (the same chaser code)
scoped object waterfall 8C saved_base_speed s16  # as fire_front 8C (the same chaser code)
scoped object waterfall 8E holding_back u8  # as fire_front 8E (the same chaser code)
scoped object waterfall 8F hold_timer u8  # as fire_front 8F (the same chaser code)
scoped object waterfall 90 wait_timer s16  # as fire_front 90 (the same chaser code)
scoped object waterfall 92 hold_start_position u16  # as fire_front 92 (the same chaser code)
scoped object waterfall 94 sound_timer u16  # 3C frames between sounds 10

# rising_saw (rising_saw_init, rising_saw_rise (rising_saw_init also writes the blade's 80/83: see saw_blade)): the chaser movement
scoped object rising_saw 80 path u32 chase_step_with_limit  # as fire_front 80 (the same chaser code)
scoped object rising_saw 84 screen_threshold s16  # as fire_front 84 (the same chaser code)
scoped object rising_saw 86 catch_up_factor u16  # as fire_front 86 (the same chaser code)
scoped object rising_saw 88 base_speed s16  # as fire_front 88 (the same chaser code)
scoped object rising_saw 8A saved_threshold s16  # as fire_front 8A (the same chaser code)
scoped object rising_saw 8C saved_base_speed s16  # as fire_front 8C (the same chaser code)
scoped object rising_saw 8E holding_back u8  # as fire_front 8E (the same chaser code)
scoped object rising_saw 8F hold_timer u8  # as fire_front 8F (the same chaser code)
scoped object rising_saw 90 wait_timer s16  # as fire_front 90 (the same chaser code)
scoped object rising_saw 92 hold_start_position u16  # as fire_front 92 (the same chaser code)
scoped object rising_saw 94 fixed_projected_x s32  # 16.16 projected x moved along the floor slope as it rises; its high word (96) is copied to projected_x and x/y follow through unproject_point (00CC8D)
scoped object rising_saw 96 projected_x_whole s16  # high word of fixed_projected_x (rising_saw_init sets it to projected_x, 94 to 0)

# lava_flow (lava_flow_start, lava_flow_wait, lava_flow_advance): the chaser movement
scoped object lava_flow 80 path u32 chase_step_with_limit  # as fire_front 80 (the same chaser code)
scoped object lava_flow 84 screen_threshold s16  # as fire_front 84 (the same chaser code)
scoped object lava_flow 86 catch_up_factor u16  # as fire_front 86 (the same chaser code)
scoped object lava_flow 88 base_speed s16  # as fire_front 88 (the same chaser code)
scoped object lava_flow 8A saved_threshold s16  # as fire_front 8A (the same chaser code)
scoped object lava_flow 8C saved_base_speed s16  # as fire_front 8C (the same chaser code)
scoped object lava_flow 8E holding_back u8  # as fire_front 8E (the same chaser code)
scoped object lava_flow 8F hold_timer u8  # as fire_front 8F (the same chaser code)
scoped object lava_flow 90 wait_timer s16  # as fire_front 90 (the same chaser code)
scoped object lava_flow 92 hold_start_position u16  # as fire_front 92 (the same chaser code)
scoped object lava_flow 98 sound_timer u8  # frames between sounds 10 (40); FF while off the left of the screen. The code clears it twice where bridge_fire clears its off-screen flag and its timer (original code), every frame on screen, so the 40 never counts down: sound 10 every frame

# geyser_front (geyser_front_init, geyser_front_wait, geyser_front_rise): the chaser movement
scoped object geyser_front 80 path u32 chase_step_with_limit  # as fire_front 80 (the same chaser code); geyser_front_read_script reads the entry through R10 (08C835 "mov.w 80[R20], R10")
scoped object geyser_front 84 screen_threshold s16  # as fire_front 84 (the same chaser code)
scoped object geyser_front 86 catch_up_factor u16  # as fire_front 86 (the same chaser code)
scoped object geyser_front 88 base_speed s16  # as fire_front 88 (the same chaser code)
scoped object geyser_front 8A saved_threshold s16  # as fire_front 8A (the same chaser code)
scoped object geyser_front 8C saved_base_speed s16  # as fire_front 8C (the same chaser code)
scoped object geyser_front 8E holding_back u8  # as fire_front 8E (the same chaser code)
scoped object geyser_front 8F hold_timer u8  # as fire_front 8F (the same chaser code)
scoped object geyser_front 90 wait_timer s16  # as fire_front 90 (the same chaser code)
scoped object geyser_front 92 hold_start_position u16  # as fire_front 92 (the same chaser code)
scoped object geyser_front 94 row_pattern u32 geyser_row_delay_choice  # pointer into geyser_row_patterns: one byte per row of geysers (which geyser_row_delays set); geyser_front_spawn_geyser_row 08C9F8 "mov.w 94[R20], R10; movz.bw [R10+], R11"
scoped object geyser_front 98 fixed_projected_x s32  # 16.16 projected x moved along the slope as it rises; its high word (9A, the object's player byte and the next) is copied to projected_x, x/y follow through unproject_point (00CC8D)
scoped object geyser_front 9C top_height s16  # geyser_fronts record: the height where spawn_geyser_line starts (then 0)

# bridge_fire (bridge_fire_start, bridge_fire_rise): the chaser movement
scoped object bridge_fire 7F state u8  # 2 while it climbs, 7 at the top: rising_flame_update (its flames, via rising_fire_object) removes itself at 7
scoped object bridge_fire 80 path u32 bridge_fire_script_entry  # as fire_front 80 (the same chaser code)
scoped object bridge_fire 84 screen_threshold s16  # as fire_front 84 (the same chaser code)
scoped object bridge_fire 86 catch_up_factor u16  # as fire_front 86 (the same chaser code)
scoped object bridge_fire 88 base_speed s16  # as fire_front 88 (the same chaser code)
scoped object bridge_fire 8A saved_threshold s16  # as fire_front 8A (the same chaser code)
scoped object bridge_fire 8C saved_base_speed s16  # as fire_front 8C (the same chaser code)
scoped object bridge_fire 8E holding_back u8  # as fire_front 8E (the same chaser code)
scoped object bridge_fire 8F hold_timer u8  # as fire_front 8F (the same chaser code)
scoped object bridge_fire 90 wait_timer s16  # as fire_front 90 (the same chaser code)
scoped object bridge_fire 92 hold_start_position u16  # as fire_front 92 (the same chaser code)
scoped object bridge_fire 94 fixed_projected_x s32  # 16.16 projected x moved by the floor slope (10E150 by floor_attribute) as it climbs; its high word (96) is copied to projected_x, x/y follow through unproject_point (00CC8D)
scoped object bridge_fire 96 projected_x_whole s16  # high word of fixed_projected_x (bridge_fire_start: projected_x, 94 = 0)
scoped object bridge_fire 98 off_screen u8  # FF while below the screen (y > 160); coming back clears it and restarts the sound timer
scoped object bridge_fire 99 sound_timer u8  # 3C frames between sounds 9

# machine_fire (machine_fire_update (also writes its fireballs' 7F/96/9C and flames' 7F)): the chaser movement
scoped object machine_fire 7F state u8  # jump table 0-7 (small flame, becomes the fire, climbs, moves +x, done...); rising flames read it (7 = remove)
scoped object machine_fire 80 path u32 machine_fire_script_step  # as fire_front 80 (the same chaser code)
scoped object machine_fire 84 screen_threshold s16  # as fire_front 84 (the same chaser code)
scoped object machine_fire 86 catch_up_factor u16  # as fire_front 86 (the same chaser code)
scoped object machine_fire 88 base_speed s16  # as fire_front 88 (the same chaser code)
scoped object machine_fire 8A saved_threshold s16  # as fire_front 8A (the same chaser code)
scoped object machine_fire 8C saved_base_speed s16  # as fire_front 8C (the same chaser code)
scoped object machine_fire 8E holding_back u8  # as fire_front 8E (the same chaser code)
scoped object machine_fire 8F hold_timer u8  # as fire_front 8F (the same chaser code)
scoped object machine_fire 90 wait_timer s16  # as fire_front 90 (the same chaser code)
scoped object machine_fire 92 fixed_projected_x s32  # 16.16 projected x moved by the floor slope while it climbs (state 2); its high word (94) is copied to projected_x, x/y follow through unproject_point
scoped object machine_fire 94 projected_x_whole s16  # high word of fixed_projected_x (set to projected_x, 92 to 0, when it becomes the fire)
scoped object machine_fire 98 hold_start_position u16  # scroll_position when it started holding back (300 units at most)

# icicle_dropper (icicle_dropper_start, icicle_dropper_move, icicle_dropper_finish): the chaser movement
scoped object icicle_dropper 80 path u32 icicle_dropper_step  # as fire_front 80 (the same chaser code); the step to load next (icicle_dropper_step: its scroll position; the running step's end coordinate is the word before it)
scoped object icicle_dropper 84 screen_threshold s16  # as fire_front 84 (the same chaser code)
scoped object icicle_dropper 86 catch_up_factor u16  # as fire_front 86 (the same chaser code)
scoped object icicle_dropper 88 base_speed s16  # as fire_front 88 (the same chaser code)
scoped object icicle_dropper 8A saved_threshold s16  # as fire_front 8A (the same chaser code)
scoped object icicle_dropper 8C saved_base_speed s16  # as fire_front 8C (the same chaser code)
scoped object icicle_dropper 8E holding_back u8  # as fire_front 8E (the same chaser code)
scoped object icicle_dropper 8F hold_timer u8  # as fire_front 8F (the same chaser code)
scoped object icicle_dropper 90 wait_timer s16  # as fire_front 90 (the same chaser code)
scoped object icicle_dropper 92 hold_start_position u16  # as fire_front 92 (the same chaser code)
scoped object icicle_dropper 94 travel s16  # distance moved since the last row: a row of icicles every 400 (300 on the platform), then - that
scoped object icicle_dropper 96 rides_platform u8  # record flag: moves with moving_floor_object (the ice platform)
scoped object icicle_dropper 97 last_dropper u8  # record flag: the last dropper (icicle_dropper_finish, waits for all players)
scoped object icicle_dropper 98 platform_offset s32  # the ice platform's movement summed (subtracted from x while riding)
scoped object icicle_dropper 9C finish_timer u16  # icicle_dropper_finish: 3 frames before goal_unlocked = FF

# burning_boulder (burning_boulder_start, burning_boulder_update (not burning_boulder_flame: its own prefix below)): the chaser movement
scoped object burning_boulder 80 path u32 chase_step_with_limit  # burning_boulder_speed_script record (FFFFFFFF for the follower)
scoped object burning_boulder 84 screen_threshold s16  # as fire_front 84 (the same chaser code)
scoped object burning_boulder 86 catch_up_factor u16  # as fire_front 86 (the same chaser code)
scoped object burning_boulder 88 base_speed s16  # as fire_front 88 (the same chaser code)
scoped object burning_boulder 8A saved_threshold s16  # as fire_front 8A (the same chaser code)
scoped object burning_boulder 8C saved_base_speed s16  # as fire_front 8C (the same chaser code)
scoped object burning_boulder 8E holding_back u8  # as fire_front 8E (the same chaser code)
scoped object burning_boulder 8F hold_timer u8  # as fire_front 8F (the same chaser code)
scoped object burning_boulder 90 wait_timer s16  # as fire_front 90 (the same chaser code)
scoped object burning_boulder 92 hold_start_position u16  # as fire_front 92 (the same chaser code)
scoped object burning_boulder 96 state u8  # jump table 0-6 (wait, roll, follow, fall away, wait for the release, drop onto the seesaw)
scoped object burning_boulder 98 start_delay s16  # burning_boulder_records: frames before it drops and rolls; 1E before the seesaw drop
scoped object burning_boulder 9C depth_offset u16  # taken off screen_y for its draw depth (20)
scoped object burning_boulder 9E slow_timer s16  # 78 frames at speed 10 after it stops holding back

# tornado (tornado_start, tornado_move): most of its chaser fields are read through mem_read16(current_object + 0x84...) in the C; the
# ->unk_ ones:
scoped object tornado 80 path u32 tornado_path_step  # tornado_records chaser records (7 words); tornado_move compares it with 08A72E (its last moving record) to clear group_leader_0_held
scoped object tornado 94 rotation_angle u16  # - 4 a frame (& 3FF): tornado_column_circle places each twister at this + its own offset
scoped object tornado 96 columns u16 object  # tornado_start stores its five tornado_column_circle objects in 96-9F

# geyser_line (spawn_geyser_line writes 80/8E/90/94 for the new object, geyser_line_move): chaser-like, moves -y
scoped object geyser_line 80 path u32 geyser_line_step  # geyser_line_scripts entries (position, y limit, speed, delay); geyser_line_move reads the entry through R10 (08D17C "mov.w 80[R20], R10")
scoped object geyser_line 88 speed s16  # the entry's speed; added to travel every frame
scoped object geyser_line 8E holding_back u8  # FF while it holds back after a player mishap
scoped object geyser_line 8F hold_timer u8  # FF frames at most
scoped object geyser_line 90 wait_timer s16  # the entry's delay
scoped object geyser_line 92 hold_start_position u16  # scroll_position when it started holding back (300 units at most)
scoped object geyser_line 94 travel s16  # distance since the last burst: spawn_geyser_burst every 300

# group_leader_move (spawn_group_leader starts 80 at 1): the row leaders keep their chaser values in speed_x/speed_y/speed_z halves
scoped object group_leader_move 80 wait_timer s16  # group_leader_data entry's delay: it does not move while > 0 (or while grey_screen_active)
scoped object group_leader_move 82 hold_start_position u16  # scroll_position when it started holding back (gives up after 300)
scoped object group_leader_move 84 hold_timer u8  # FF frames of holding back (FF again while no_player_in_play)
scoped object spawn_group_leader 80 wait_timer s16  # the new leader's (1: its first entry is read next frame)

# group_leader_hold (group_leader_hold_start, group_leader_hold_wait): group_leader_hold_records
scoped object group_leader_hold 80 leader u8  # index into group_leaders of the leader it watches
scoped object group_leader_hold 82 coordinate s16  # the x or y (by direction 24) the leader must pass
scoped object group_leader_hold 84 end_position u16  # scroll_position where it gives up and clears the flag
scoped object group_leader_hold 86 which_flag u16  # 0: group_leader_0_held, else group_leaders_1_3_held

# spiked_roller (all spiked_roller_* handlers): offsets of its own, not the common chaser ones
scoped object spiked_roller 80 state u8  # spiked_roller_update's jump table (0 chase ... F spawn fixed zone); the follower parts copy the leader's 1/8
scoped object spiked_roller 81 holding_back u8  # FF while the leader holds back (mishap / dropping in / no player in play)
scoped object spiked_roller 82 wait_timer s16  # the script record's wait (frames, counted down to 0); spiked_roller_burst_out's delay (3C)
scoped object spiked_roller 84 saved_base_speed s16  # base_speed kept while holding back
scoped object spiked_roller 86 leader u16 object.spiked_roller  # the first part (every part's 86): spiked_roller_follow_leader copies it (pointer typed: last_names.txt N1)
scoped object spiked_roller 88 screen_threshold s16  # the record's screen threshold (160 or FFE0 while holding back)
scoped object spiked_roller 8A path u32 spiked_roller_chase_step  # next record of the kind's chase script (spiked_roller_kinds)
scoped object spiked_roller 8E catch_up_factor u16  # the record's factor (camera ground speed x it >> 8)
scoped object spiked_roller 90 base_speed s16  # the record's speed
scoped object spiked_roller 92 saved_threshold s16  # screen_threshold kept while holding back
scoped object spiked_roller 94 path_direction u16  # 0 -y, 1 +x, 2/4 +y, 3 -x (direction = it << 8)
scoped object spiked_roller 96 rumble_timer u8  # frames to the next rumble sound B (spiked_roller_rumble_delays by speed)
scoped object spiked_roller 97 rumble_off_screen u8  # FF while off screen: back on screen the rumble timer restarts
scoped object spiked_roller 98 substate u8  # step inside states 4 (ram), 6 (crash into the machine) and 7 (sink and jump)
scoped object spiked_roller 99 pause_timer u8  # ram states: 3C-frame pauses
scoped object spiked_roller 9C hold_start_position u16  # scroll_position when it started holding back
scoped object spiked_roller 9E hold_timer u8  # FF frames of holding back

# player (player_update, player_animate, player_reset and the other player_* code on the player's own object)
scoped object player 80 palette_list u32 player_palette_list  # player_reset: the character's palette list (113D59 table); the normal palette is its word +2 (put back after every flash)
scoped object player 84 hurt_flash_on u8  # flash_hurt_palette toggles it: on = palette 83B0
scoped object player 85 hurt_flash_period u8  # frames between flash toggles (3); FF = not flashing (the states test it signed)
scoped object player 86 hurt_flash_timer u8  # flash_hurt_palette's countdown (3; 7F)
scoped object player 9C block u32 player  # player_start: the player block (current_player) the object belongs to

# palette animation helpers (field layout shared by animate_sign, cycle_part_palettes and flash_hurt_palette users)
scoped object animate_sign 80 palette_list u32 sprite_palette_cycle  # a word (last index), then the palettes
struct sprite_palette_cycle 2 rom            # a palette cycle animate_sign steps through (animate_sign 80 palette_list): the last index, then the palettes, a word each; animate_sign 00CB29 "mov.w 80[R20], R10", 00CB2E "mov.h [R10+], R0" (last_index), 00CB51 "mov.h [R10](R1), 6[R20]" (palettes[R1] into sprite_palette); census ROM 08514D-085170 (sign_palette_lists: 5 0460-0490..., 3 84A0-84D0, 4 04E0-0500), 085CDF (fire_vent_glow_palettes), 085E7F (scenery_sprite_palettes), 08A686 (quicksand_palettes: D 1030-11D0)
field sprite_palette_cycle 00 last_index u16  # the last entry's index (1-D): animate_sign compares its low byte with palette_index + 1 (cmp.b R0, R1; blt/bgt): past it the index wraps to 0
field sprite_palette_cycle 02 palettes u16[]  # the palettes after last_index, a word each: palettes[palette_index] into sprite_palette (6)
elements sprite_cycle_palettes u16           # the palettes of a sprite_palette_cycle, after its last_index word: animate_sign reads palette_list + 2 + palette_index x 2 (00CB2E moves R10 past last_index, 00CB51 "mov.h [R10](R1), 6[R20]")
scoped object animate_sign 84 palette_index u8  # current entry
scoped object animate_sign 85 palette_delay u8  # frames per entry (reload of 86)
scoped object animate_sign 86 palette_timer u8  # countdown to the next entry
scoped object cycle_part_palettes 80 palette_list u32 rom  # as animate_sign
scoped object cycle_part_palettes 84 palette_index u8  # as animate_sign 84
scoped object cycle_part_palettes 85 palette_delay u8  # as animate_sign 85
scoped object cycle_part_palettes 86 palette_timer u8  # as animate_sign 86
scoped object flash_hurt_palette 80 palette_list u32 player_palette_list  # as player 80
scoped object flash_hurt_palette 84 flash_on u8  # toggled each period; when set the palette is 83B0, else word +2 of the list
scoped object flash_hurt_palette 85 flash_period u8  # reload of the timer
scoped object flash_hurt_palette 86 flash_timer u8  # counted down every call

# fire_vent (fire_vent_spawner_update writes 80-8A of each vent, fire_vent_update, fire_vent_burst_animate)
scoped object fire_vent 80 palette_list u32 rom  # fire_vent_glow_palettes (animate_sign)
scoped object fire_vent 84 palette_index u8  # FF after the blast: no more glowing
scoped object fire_vent 85 palette_delay u8  # 8, 2 once the delay is <= 1E (it glows faster before blasting)
scoped object fire_vent 86 palette_timer u8  # animate_sign's countdown
scoped object fire_vent 88 burst_state u8  # 0 waiting, 1 bursting (child fire_vent_burst_animate), FF burst over (set by the burst)
scoped object fire_vent 8A blast_delay s16  # fire_vent_list: frames on screen before the blast, FFFF after

# lava_rock (lava_rock_update; spawn_lava_column passes 96): a group member (spawn_object_groups)
scoped object lava_rock 80 palette_list u32 rom  # lava_rock_glow_palettes (cycle_part_palettes)
scoped object lava_rock 84 palette_index u8  # cycle_part_palettes' entry
scoped object lava_rock 85 palette_delay u8  # random 4-13 (next_random & F + 4)
scoped object lava_rock 86 palette_timer u8  # cycle_part_palettes' countdown
scoped object lava_rock 88 draw_depth u16  # its floor's screen y + depth_offset; the player standing on it draws relative to it
scoped object lava_rock 8A group_slot u16  # its group_slots entry (spawn_object_groups); a triggered slot makes it erupt
scoped object lava_rock 8E rise_limit u16  # how far it bobs up as the leader approaches (10, 20 within 30)
scoped object lava_rock 92 takes_slot u8  # group_takes_slot of its record (1 once it set its slot)
scoped object lava_rock 96 depth_offset s16  # the record's word +14 (stage_object_records; +16 in stage_object_records_2) (plus group_depth_step for type 2 rows)

# other group members of spawn_object_groups (the leader in 26): draw depth and depth offset
scoped object crumbling_floor 88 draw_depth u16  # floor screen y + depth_offset (the player reads a platform's 88 for his own depth)
scoped object crumbling_floor 91 silent_row u8  # FF for type 6 rows: no sound D when it falls
scoped object crumbling_floor 96 depth_offset s16  # the record's word +14 (stage_object_records; +16 in stage_object_records_2) (+ group_depth_step)
scoped object crumbling_floor 98 normal_palette u16  # its palette, put back when grey_screen_ended
scoped object falling_grate 80 rattle_step s16  # +-1 added to z and floor_z when it rattles near the leader
scoped object falling_grate 88 draw_depth u16  # floor screen y + depth_offset, as crumbling_floor
scoped object falling_grate 96 depth_offset s16  # the record's word +14 (stage_object_records; +16 in stage_object_records_2)
scoped object river_water 88 draw_depth u16  # floor screen y + depth_offset
scoped object river_water 96 depth_offset s16  # the record's word +14 (stage_object_records; +16 in stage_object_records_2)
scoped object sand_block 88 draw_depth u16  # floor screen y + depth_offset; its stream and face parts read it
scoped object sand_block 8A face_object u16 object  # the sand_face_update object it made
scoped object sand_block 96 depth_offset s16  # the record's word +14 (stage_object_records; +16 in stage_object_records_2)
scoped object rope_bridge_plank 88 draw_depth u16  # floor screen y + depth_offset
scoped object rope_bridge_plank 8A group_slot u16  # group_slots entry (used as bytes by the planks)
scoped object rope_bridge_plank 95 bridge u8  # 0 or 1 (spawn_object_groups_2): which bridge_N_load / bridge_N_sag it uses
scoped object rope_bridge_plank 96 depth_offset s16  # doubled, - 28 for the wide plank of record 0

# collapsing_bridge_plank (085F80; 6E state, 6F plank number, 9A the previous plank)
scoped object collapsing_bridge_plank 80 angle u32  # 16.16 angle (82 = whole part, 0-3FF): 300 at the start; = the previous plank's + bridge_swing_angle, at least 200
scoped object collapsing_bridge_plank 84 angle_speed s32  # plank 0 while it falls from 300 to 200: - 3000 a frame
scoped object collapsing_bridge_plank 88 draw_depth u16  # floor screen y + 96 while it is a bridge plank
scoped object collapsing_bridge_plank 8A swing_count u8  # swings of the hanging bridge (copied from the previous plank); at 6 the swinging stops
scoped object collapsing_bridge_plank 8C roll_frames u16  # plank number x 2: frames it keeps rolling after the third swing
scoped object collapsing_bridge_plank 8E roll_timer u16  # frames rolled so far

# icicle_update (one falling icicle; drop_icicle_row sets its fields)
scoped object icicle_update 80 timer s16  # fall delay (3C-5B random), then the sinking count
scoped object icicle_update 82 state u8  # 1 waits/falls ... 7 lined up (see icicle_update)
scoped object icicle_update 84 line_coordinate u16  # the dropper script's end coordinate the lined-up icicles snap to (x or y)
scoped object icicle_update 88 draw_depth u16  # floor screen y (the parts and the shadow read it)
scoped object icicle_update 96 rides_platform u8  # copied from its dropper: moves with the ice platform (states 4-6)
scoped object icicle_update 97 counts_goal u8  # the last dropper's icicles: count goal_unlocked up when they land
scoped object icicle_cracks 80 next_depth s16  # icicle_cracks_spread: depth of the next crack step (+ 1C)
scoped object icicle_shadow 86 blink_timer u8  # icicle_shadow_follow: 2-frame blink while its piece falls

# handle_rod (the pivot: handle_rod_init, handle_rod_wait, handle_rod_swing, handle_rod_hang); handles and posts have their own prefixes
scoped object handle_rod 81 swing_side u8  # flips at each end of the swing (1 / 0)
scoped object handle_rod 82 angle u16  # rod angle (& 3FF): 322 at the start, 300 level; += high byte of angle_speed
scoped object handle_rod 84 angle_speed s16  # += swing_accel every frame, halved and reversed at the ends
scoped object handle_rod 86 swing_accel s16  # 60, sign flipped at each end
scoped object handle_rod 92 handle_count u8  # spawn word 92 low byte: handles on the rod
scoped object handle_rod 93 drops_players u8  # spawn word 92 high byte: when the near post falls, handle_rod_drop_players instead of players_pulled
scoped object handle_rod 94 end_position u16  # spawn word 94: scroll_position where it is removed (then FFFF: the handles go too)
scoped object handle_rod_handle 80 index u8  # handle number i: 12 x i from the pivot
scoped object handle_rod_handle 81 grip u8  # 1 for the grip (players climb along it)
scoped object handle_rod_post 81 fallen u8  # far post: set; near post: set once no floor is under it (handle_rod_post_fall)

# spiked_press (spiked_press_start, spiked_press_update)
scoped object spiked_press 80 sound_timer s16  # 3C frames between sounds D while it creeps
scoped object spiked_press 82 frame_count s16  # frames since the start: message box at 14, voice at 50, the signs at C8, sign 3 at 168 (cage_bar_update waits for C8)
scoped object spiked_press 84 stopped_frames s16  # frames since it stopped at y 3D0 (check_player_reached_goal waits for 258)
scoped object spiked_press 8E holding_back u8  # FF while it pauses (no_player_in_play / player_dropping_in)
scoped object spiked_press 8F hold_timer u8  # FF frames of pausing at most

# metal_platform (metal_platform_start, metal_platform_update; the railing copy starts in state 3)
scoped object metal_platform 80 state u8  # 0/4 wait, 1 lift off, 2 rising, 3 the railing
scoped object metal_platform 82 timer s16  # 12C frames before lifting, A-frame steps
scoped object metal_platform 84 frame u16  # sprite frame to show (AFD0, AFE8)

# ice_floor_block (ice_floor_start, ice_floor_block_update)
scoped object ice_floor_block 80 state u8  # 0 platform, 1 shaking, 2 grey, 3 falling
scoped object ice_floor_block 81 shake_timer u8  # 2 frames per shake step
scoped object ice_floor_block 82 shake_duration s16  # 1E frames (12C while the player is invulnerable)
scoped object ice_floor_block 84 shake_offset s16  # +-1 added to z at each shake step
scoped object ice_floor_block 88 draw_depth u16  # floor screen y; the player standing on it reads it

# river_raft and its spawner (stage 10)
scoped object river_raft_spawner 80 entry u32 river_raft_entry  # next river_raft_spawners entry (distance, speed, floor); 8000 loops to list_start; river_raft_spawner_update reads it in place (08C1EE "test.h [80[R20]]", 08C2FD) and through R10 (08C24A)
scoped object river_raft_spawner 84 end_position u16  # scroll_position where it is removed
scoped object river_raft_spawner 86 list_start u32 rom  # first entry of its list
scoped object river_raft_spawner 8A last_raft_y s32  # y of the last raft it launched (moved on by its speed)
scoped object river_raft_spawner 8E last_raft_speed u32  # that raft's speed
scoped object river_raft_update 84 state u8  # drifting, tipping over the waterfall's edge, falling
scoped object river_raft_update 88 draw_depth u16  # floor screen y (its foam part reads it)

# burning_boulder_flame (burning_boulder_flame_start, burning_boulder_flame_flicker): longer prefix than burning_boulder
scoped object burning_boulder_flame 80 offset_x s16  # burning_boulder_flame_layout: screen x offset from the boulder
scoped object burning_boulder_flame 82 offset_y s16  # screen y offset

# scene_object (scene_object_start, scene_object_update)
scoped object scene_object 80 end_position u16  # 5C00: scroll_position where it is removed
scoped object scene_object 82 radius_x4 u16  # F90 at the start, + 4 a frame: four times the radius of the circle it turns on in (x, z) (scene_object_update shifts it right 2)

# trap (trap_start, trap_update: the second object of player state 32)
scoped object trap 80 player_block u32 player  # the trapped player's block (its substate 3 = broke free)

# The new families named in this file
scoped object character_chooser 80 idle_step u8  # index into chooser_idle_steps (& 3)
scoped object character_chooser 81 character u8  # character shown (0 Sonic, 1 Ray, 2 Mighty)
scoped object character_chooser 82 step_timer s16  # 3 frames per idle step
scoped object character_chooser 84 turn_timer u16  # 18 frames of the turn animation
scoped object character_chooser 92 right_count u16  # + 8 a frame while the trackball goes right: each change of its high byte turns once
scoped object character_chooser 94 left_count u16  # the same to the left
scoped object ranking_row 80 animate u8  # copied from the display object every frame: the portraits animate once it is set
scoped object ranking_row 81 remove u8  # read on the parent (the display's 81) by ranking_row_update: the row deletes itself
scoped object ranking_row 92 row u16  # 1-F: row number (rank 1 at the top), screen y = display + 17 x it
scoped object ranking_display 80 rows_animate u8  # set when the screen has scrolled in
scoped object ranking_display 81 rows_remove u8  # set after the hold
scoped object ranking_display 84 hold_timer s16  # counts to 12C
scoped object ranking_title 82 pause_timer s16  # 1C frames between the spin and the rise

# ======== parameters and results (param lines) ========
param spawn_message_box R5 screen_x
param spawn_message_box R6 screen_y
param spawn_message_box R7 kind
param spawn_message_box R8 frames
param convert_to_bcd R0 value
param copy_blocks R1 count
param copy_blocks R10 source
param copy_blocks R11 dest
param spawn_banner_sparkle R0 offset_x               # added to the banner's screen_x for the sparkle (0E); stage_clear_banner_reveal passes var_80, += 0x10 per sparkle
param apply_stage_layer_blending R2 layer_bit        # "set1 R2, [R11]": the bit set in the layer's mixer shadow (12: blend with sprites); out = the last record's bit, in = passed through when the stage has no records
param apply_stage_layer_blending R8 count            # loop counter (7 shadows, then the record count); always 0 on return
param check_stage_clear R8 count                     # dbr counter over the 3 players; in/out only because the early return leaves it alone
param check_stage_clear R10 finished_flag            # walks players_finished (20E5E0), set to FF for the players not in play
param check_stage_clear R11 status                   # walks player_status (20E590): 0, 82, 3, 4, 2 = not in play
param clear_player_blocks R8 count                   # dbr counter, 0x30 rounds of 0x20 bytes; always 0 on return
param update_grey_screen R3 count                    # dbe counter over the 3 player blocks (test of 9F); 0 when none is set
param update_player_summary R3 in_play_count         # number of player_status bytes with bit 7 set, stored to players_in_play (low byte)
param update_player_summary R8 count                 # dbr/dbne counter over the 3 players
param update_player_summary R10 cursor               # walks player_status (20E590), then the player blocks (208400 + 0x200), then player_status again
param update_player_summary R11 player_object        # mov.h 14[R10], R11: the player's object (test of its 30 bit A)
param continue_to_next_stage_slot R1 table_offset    # what load_stage_map_pointers hands back (offset in the bounds table); also the offset read from stage_link_points (folded in the C)
param continue_to_next_stage_slot R11 block          # points at the "from" and then the "to" floor block record (x at +0, y at +8, height at +10)
param load_stage_map_pointers R1 table_offset        # stage_floor_block_bounds[stage]: offset of the stage's list from 10E29C
param load_stage_map_pointers R10 bounds_list        # 10E29C + the offset, also stored to floor_block_bounds
param run_stage_events R1 table_offset               # stage_events[stage] (offset from 002492), set only on the stage's first call
param run_stage_events R10 event                     # stage_event_cursor: the (scroll position, game mode) record, + 2 once reached
param reset_trackball_step R3 player_number          # clears trackball_step_sums[R3]; run_game_mode passes 0; kept for read_trackball_step
param read_trackball_step R3 player_number           # in: the player (20F062 + 4n, 20F0B6 + 2n); out (low half): the step +1 / -1 / 0 that run_game_mode adds to stage
param read_trackball_step R10 trackball              # 20F062 + 4n: the player's trackball count it adds up
param update_restart_point R4 position               # mov.h 6435[R25], R4: scroll_position, compared with each 8-byte record
param start_player_objects_alt R3 player_number      # reset_objects_from_list's R3, handed back
param start_player_objects_alt R11 object            # passed to reset_objects_from_list
param start_player_objects R10 handler_cursor        # reset_objects_from_list's R10, handed back
param start_player_objects R11 object                # passed to reset_objects_from_list
param reset_objects_from_list R3 player_number       # 1 or 2 (from the player block at 20F0EE): player_status[R3]; else what reset_objects left
param reset_objects_from_list R10 handler_cursor     # walks the list at 20F0EA (count, then handlers), 4 bytes per handler; out = past the last one read
param reset_objects_from_list R11 object             # the object being started (allocate_object_or_start_list / allocate_object); the incoming value is only kept when no object is free
param check_hits R11 previous                        # the anchor before check_hit's first object: the hitter (its 68 = next) or 20F018 (its 68 = touchable_list)
param check_hit R10 other                            # walks the list it tests against (hit_list_next, 68)
param check_hit R11 previous                         # the object before R10 in that list (mov.h R10, R11 before advancing)
param check_hit R17 hitter                           # the object tested; both get flags bit 2 and touched_by on a hit
param check_platforms R8 load_count                  # the bridge load list's count after inc.b [R11], used as the index to append the platform's 6F
param check_platforms R10 platform                   # walks platform_list (platform_list_next, 72); out = the last one tested
param check_platforms R11 load_list                  # 209400 or 209500 (by the platform's 95): the bridge load list it appends to
param compute_bridge_sag R1 middle                   # R19 >> 1: plank numbers past it count from the far end (min(n, count - n))
param compute_bridge_sag R2 plank                    # the entry's plank number (movz.bw [R10+], R2)
param compute_bridge_sag R4 sag                      # bridge_sag_curve[...] << 8, then + R5 per plank, written to the sag table
param compute_bridge_sag R5 sag_step                 # (next sag - sag) / planks between the entries (div.h R3, R5)
param compute_bridge_sag R8 count                    # dbr counter: entries - 1, or (planks >> 1) + 1 words to clear
param compute_bridge_sag R9 passes_left              # outer counter of the sort of the load list
param compute_bridge_sag R10 load_entry              # walks the load list (the sort's inner cursor, then the entries)
param compute_bridge_sag R19 plank_count             # bridge_0/1_plank_count from check_platforms
param blink_demo_prompt R1 prompt_frame              # mov.h #89B8 / #89A8, R1: the sprite frame ("PUSH START BUTTON" / "INSERT COIN(S)") written to 04 of 209FC0; low half only
param blink_demo_prompt R12 credit_block             # movz.bh 1[R12]: the credits compared with credits_per_start; draw_attract_credits passes 20F412
param player_animate_run_cycle R10 animation_table   # per-facing offsets table of the run animation (110A4C, 110FCC, 110D0C, 11128C...); replaced by 111ACC/11204C/111D8C on some floors
param player_stuck_begin R11 object                  # allocate_object's object (the stuck effect, handler stuck_effect_update), then spawn_sign_2's sign; the incoming value is a leftover
param spawn_climb_hand R11 hand                      # allocate_object's object (handler climb_hand_update); the incoming value is a leftover kept when no object is free
param spawn_player_effect R11 effect object.player_effect  # allocate_object's object (handler player_effect_start); player_animate changes its palette and 96 after the call (pointer typed: last_names.txt N1)
param spawn_struggle_marker R11 marker               # allocate_object's object (tile 0x77A0, handler struggle_marker_update)
param player_stuck_on_object_begin R11 sign          # spawn_sign_2's object (type 0xD); the incoming value is a leftover
param spawn_skid_dust R11 dust                       # allocate_object's object (animation 113D13, handler skid_dust_update)
param player_hud_update R11 text_position            # 31xxxx: hud_text_positions_*, hud_ring_count_positions_* for erase_text_lines/print_text_lines/draw_hex_4, and blink_player_hud_message's / draw_player_start_prompt's
param draw_player_start_prompt R11 text_position     # 310000 | start_prompt_positions_*[player], as blink_start_prompt leaves it
param animate_hud_palette R1 list_index              # +25 + 1: the entry of the +28 list copied to 06; 0 when it wraps
param animate_hud_palette R8 wrapped                 # 0, or FFFFFFFF when the index passed the list's count
param animate_hud_tiles R1 list_index                # the same index, the entry goes to the sprite frame (04)
param animate_hud_tiles R8 wrapped                   # 0, or FFFFFFFF when the list wrapped
param blink_player_hud_message R10 text              # the text lines record printed or erased (player_hud_update passes 113FDD)
param blink_player_hud_message R11 text_position     # 31xxxx: hud_prompt_positions_*[player] (tables 113FB7/113FBD), as print_text_lines/erase_text_lines leave it; handed back unchanged between blinks
param compute_camera_slope_speed R8 slope_factor     # floor_slope_factors[floor_slope] (0 = flat), compared with camera_slope_factor; vy is divided by 0x10000 + |R8|
param get_ledge_side R3 side                         # 3 x < left, 1 x > right, 0 y < top, 2 y > bottom, 4 inside the previous floor block
param get_ledge_side R11 block                       # floor_blocks + 2 + previous_floor_block: the block record tested
param draw_attack_effect R17 player                  # the player block (attack_power 72, state, facing 01); here R18 holds the player's object (shadow_update)
param side_of_box R3 sides                           # bits: 8 before the box in x, 4 past it, 2 before it in y, 1 past it; check_player_contacts tests them (low half)
param side_of_box R10 other                          # the object whose box (4E/52 + 7C/7D, size 10/12) is tested
param polar_to_xy R5 angle                           # in: the angle (movs.hw R5); out: dx = sine * length >> 6
param polar_to_xy R6 length                          # in: the length (movz.hw R6); out: dy = -cosine * length >> 6
param project_point R7 point_x                       # in: the point's x (16.16); out (low half): its screen x (point_to_screen)
param project_point R8 point_y                       # in: the point's y (16.16); out (low half): its screen y
param project_point R9 z                             # height added to the projected y (callers pass z or floor_z)
param point_to_screen R0 projected_x                 # whole part: R7 = R0 - view_x
param point_to_screen R1 projected_y                 # in: projected y (16.16); out: (R1 + R9) >> 16 - view_y, the screen y
param point_to_screen R7 screen_x                    # out (low half): R0 - view_x
param point_to_screen R8 screen_y                    # out (low half): the same as R1
param point_to_screen R9 z                           # height added to the projected y
param find_nearby_floor_blocks R1 corner_y           # a corner's y from the bounds record, minus the top edge (low half)
param find_nearby_floor_blocks R3 floor_block        # offset of the block in floor_blocks (+0x16 per block): what goes into nearby_floor_blocks (and later floor_block)
param find_nearby_floor_blocks R4 bounds_offset      # offset of the block's record in floor_block_bounds (+0x10 per block)
param find_nearby_floor_blocks R5 nearby_count       # blocks listed; stored to nearby_floor_block_count (low half)
param find_nearby_floor_blocks R8 corners_left       # dbnl counter over the 4 corners
param find_nearby_floor_blocks R10 left              # view_x - 0x80 (low half)
param find_nearby_floor_blocks R11 right             # view_x + 0x1C0 (low half)
param find_floor R3 slope_height                     # the slope's part of floor_z (added to it); on a platform the whole floor_z it stores; 0 on flat floor
param get_floor_block_side R3 side                   # 3 x < left, 1 x > right, 0 y < top, 2 otherwise
param get_floor_block_side R10 floor_block           # in: the block's offset (a floor_block value), turned into floor_blocks + 2 + offset
param get_floor_block_side R11 x                     # the point tested (callers pass the object's x)
param get_floor_block_side R12 y                     # the point tested (callers pass the object's y)
param place_player_at_restart_point R0 player_offset # x offset by player number added to x_int (shift_signed_by, 2 x player, or player << 17)
param screen_point_to_ground R7 z                    # the height taken off the screen y (aim_at_screen_centre passes z)
param screen_point_to_ground R8 point_x              # in: screen x; out: the ground x (unproject_point)
param screen_point_to_ground R9 point_y              # in: screen y; out: the ground y
param unproject_point R8 point_x                     # in: projected x (16.16); out: x = ((px - py) >> 8) * B5; 9 callers store it to x
param unproject_point R9 point_y                     # in: 2 x projected y; out: y = ((px + py) >> 8) * B5
param aim_at_screen_centre R5 dx                     # x speed 0x200 toward screen point (A0, 70) (polar_to_xy); check_player_contacts stores it to push_out_x
param aim_at_screen_centre R6 dy                     # y speed, to push_out_y
param spawn_tumble_effect R11 effect                 # allocate_object's object (animation 1142F6, handler tumble_effect_update)
param apply_speed_scale R0 target_speed              # the speed player_steer aims at (16.16); x speed_scale / 16 when speed_scale is set
param try_continue R3 cost                           # credits a continue takes (player_animate passes 1)
param try_continue R8 joined                         # 0 = not paid, FFFFFFFF = paid (or no game in progress); 0 for state 0x37
param try_join R8 joined                             # the same; the cost is credits_per_start
param pay_and_join R3 cost                           # credits to take (0 with free play), compared with the credit block's count
param pay_and_join R8 joined                         # callers pass 0; FFFFFFFF when the credits were taken
param spawn_slip_piece R11 piece                     # allocate_object's object (handler slip_piece_update); handed back, unchanged when no object is free
param spawn_slip_pieces R11 piece                    # allocate_object's object, two of them (handler slip_pieces_update); free_object_stack is checked first
param follow_player_path R0 target_speed  # returns path_speed << 16 (0 while waiting at a record); player_steer runs it through apply_speed_scale and moves speed toward it; returned unchanged with no path; also the dx/angle temporary for angle_of_vector
param animate_with_flags R1 frame  # animate_step_with_flags' R1 (the new animation_frame x 2); no caller reads it
param animate_with_flags R8 at_last_step  # low byte 0, FF when animate_step_with_flags reached the last step
param animate_step_with_flags R1 frame  # the new animation_frame, then doubled (add.w R1,R1 at 00DE85) to index the 4-byte steps; no caller reads it
param animate_step_with_flags R8 at_last_step  # low byte FF when animation_frame + 1 equals the step count (00DE74)
param animate R1 frame  # animate_step's new animation_frame
param animate R7 wrapped  # low byte 0, FF when animate_step wrapped (banner_sparkle_animate deletes itself on it)
param animate R8 at_last_step  # low byte 0, FF on the last step (callers change handler or set animation_speed FF)
param animate_step R1 frame  # animation_frame + 1 (- 1 with flags bit 7); sprite_frame = the step word at frame
param animate_step R7 wrapped  # FF when it went past the end and restarted at 0 (backwards: below 0, restarted at the end)
param animate_step R8 at_last_step  # FF when it reached the last step (backwards: step 0)
param animate_and_move R8 at_last_step  # low byte cleared, | FF on the last step; returned (the upper bytes pass through)
param animate_around_anchor R8 at_last_step  # 0, FFFFFFFF on the last step (catapult_swing_up tests it < 0)
param set_step_around_anchor R1 frame  # the step index; callers store it in animation_frame first (catapult_wait_for_rider: 0 or 1)
param set_step_around_anchor R10 steps  # animation + 2, the first step (frame word, dx, dy: 4 bytes each); step = steps + frame x 4
param animate_with_screen_offset R8 at_last_step  # low byte cleared, | FF on the last step; returned
param add_step_screen_offset R10 step_offset  # points at the step's dx, dy bytes (animation + 4 + frame x 4); screen_x += dx, screen_y += dy
param animate_all_parts R8 at_last_step  # low byte FF on the last step (lava_rock_update resets animation_frame on it, the rope bridge tests < 0)
param animate_all_parts R11 part  # walks the part list sprite_frame points at (count word, 8-byte parts) writing the step's frame word; out: past the last part, no caller reads it
param spawn_player_ride R11 object  # allocate_object's object (x A60/AE0, y EB2, z 530, handler ridden_object_start), stored in ridden_object; returned unchanged when nothing is spawned
param player_select_character R0 player_number  # player_join passes the object's player (it also indexes player_status); the end recomputes 0/1/2 for the players_in_join_chooser bit
param spawn_climb_zone_effect R11 object  # allocate_object's new object (handler climb_zone_effect_start, parent = R18); in: leftover
param spawn_intro_marker R11 object  # allocate_object's new object (tile A0C0, animation E46D, handler intro_marker_update); in: leftover
param flame_machine_arm_take_disc R3 disc  # spawn_object_with_pieces' object: the spiked disc (sprite 6278, kind 24, handler spiked_disc_hold); in: unproject_object's leftover
param spawn_whirlwind R0 spawned  # spawn_object_with_pieces' result: low byte 0 when nothing was spawned; in: leftover (only its upper bytes survive)
param spawn_whirlwind R3 whirlwind  # spawn_object_with_pieces' object: current_object = it, copy_position_to, kind 1F, handler whirlwind_aim
param spawn_whirlwind R11 object  # the block allocate_object handed back (the whirlwind itself, spawn_count 1); returned; tornado_throw_whirlwinds hands it on to spawn_tornado_hazard, which overwrites it
param centipede_follow_path R0 path_entry  # 20D0EA + field 82 x 4, the centipede_path entry it copies to projected_x/projected_y; out: the address after it; callers pass it on unused
param spawn_centipede_sand_puff R0 spawned  # spawn_object_with_pieces' result: low byte 0 when nothing was spawned; in: leftover
param spawn_centipede_sand_puff R3 puff  # spawn_object_with_pieces' object: sprite 75A8, palette 1020, zoom 180, 82 = 2D, handler sand_puff_start
param spawn_eggman_booth R0 spawned  # spawn_object_with_pieces' result: low byte 0 when nothing was spawned; in: leftover
param spawn_eggman_booth R3 booth  # spawn_object_with_pieces' object: palette 8200, screen x 9F, 82 = 2F, handler eggman_booth_slide_in (its child is the 5718 backdrop)
param flash_mine_palette R2 sinking  # animate_sinking_mine passes 1, mine_fall 0: when set, palette bit F is flipped and bit E set
param spawn_mine_splash R0 spawned  # spawn_object_with_pieces' result: low byte 0 when nothing was spawned; in: leftover from mine_fall
param spawn_mine_splash R3 splash  # spawn_object_with_pieces' object: projected_y + 1, palette 90A0, handler mine_splash_animate
param init_stage_view R5 stage_offset  # stage x 2: what load_tile_set hands back and the index of the 975A/9780/97A6/97CC word tables; handed back, unused
param update_stage_tile_layers R9 cycle  # passed through animate_stage_palette (the palette cycle state it last ran)
param update_stage_tile_layers R11 dest  # the write address: palette RAM for animate_stage_palette, video RAM for reload_changed_tile_set's scripts
param reload_changed_tile_set R10 script  # passed to load_tile_set: the vram script it last ran
param reload_changed_tile_set R11 dest  # passed to load_tile_set: the video RAM write address
param load_tile_set R5 stage_offset  # get_tile_set_list's stage x 2, handed back unused
param load_tile_set R7 tile_set  # the set number: init_stage_view reads it from stage_initial_tile_sets, reload_changed_tile_set from a byte of wanted_tile_sets; indexes the stage's list (get_tile_set_list)
param load_tile_set R10 script  # each entry of the set's list (after get_tile_set_script_bank) run by run_vram_script; out: the FFFFFFFF end marker
param load_tile_set R11 dest  # run_vram_script's video RAM write address, carried from script to script
param get_tile_set_script_bank R10 script  # a vram script address from the list; + 100000 for stages 3-0x10 and 0x13 (the second half of the ROM)
param get_tile_set_list_bank R6 script_list  # the set's list of script addresses; + 100000 for stages 3-0x10 and 0x13
param get_tile_set_list R5 stage_offset  # stage x 2, the switch index, handed back
param get_tile_set_list R6 script_list  # in: stage_tile_sets[stage] (the stage's per-set table); out: the set's list (u16 offset + 170602, or a u32 pointer)
param get_tile_set_list R7 tile_set  # the set number: index x 2 or x 4 into the stage's table
param update_scroll R3 speed  # the speed handed to advance_scroll/set_camera_velocity: scroll_speed, scroll_ramp_speed, follow_base_speed (+ the push term, modes 7/8), 25 in stage 3's end, 0 in modes 1/2
param is_player_pushing_edge R0 pushing  # low byte 1 when the player is at a leading edge of push_edge_case; 0 at a trailing edge (which sets byte 2 of players_facing_scroll instead); upper bytes pass through
param is_player_pushing_edge R2 object  # the player's object (update_scroll passes players 1-3's objects); the edge tests read its screen_y (0C) and screen_x (0E)
param is_player_at_diagonal_edge_30 R0 at_edge  # low byte 1 when the object is on the edge side of the line (a second test, 10 further in, sets byte 3 of players_facing_scroll)
param is_player_at_diagonal_edge_30 R2 object  # the player's object: screen_y (0C) against screen_x (0E) / 2 + edge_offset
param is_player_at_diagonal_edge_30 R5 edge_offset  # the line's offset: is_player_pushing_edge passes 30; - A for the second test
param is_player_at_diagonal_edge_f0 R0 at_edge  # low byte 1 on the edge side of the line (second test sets byte 3 of players_facing_scroll)
param is_player_at_diagonal_edge_f0 R2 object  # the player's object: screen_y against edge_offset - screen_x / 2
param is_player_at_diagonal_edge_f0 R5 edge_offset  # the line's offset: is_player_pushing_edge passes F0; - A for the second test
param is_player_at_diagonal_edge_c0 R0 at_edge  # low byte 1 on the edge side of the line (second test sets byte 3 of players_facing_scroll)
param is_player_at_diagonal_edge_c0 R2 object  # the player's object (screen_y 0C, screen_x 0E)
param is_player_at_diagonal_edge_c0 R5 edge_offset  # the line's offset: is_player_pushing_edge passes C0; - A for the second test
param is_player_at_diagonal_edge_60 R0 at_edge  # low byte 1 on the edge side of the line (second test sets byte 3 of players_facing_scroll)
param is_player_at_diagonal_edge_60 R2 object  # the player's object (screen_y 0C, screen_x 0E)
param is_player_at_diagonal_edge_60 R5 edge_offset  # the line's offset: is_player_pushing_edge passes 60; - A for the second test
param is_player_at_right_edge R0 at_edge  # low byte 1 when screen_x >= push_edge_right (also the edge + A temporary)
param is_player_at_right_edge R2 object  # the player's object: screen_x (0E)
param is_player_at_left_edge R0 at_edge  # low byte 1 when screen_x <= push_edge_left (also the edge - A temporary)
param is_player_at_left_edge R2 object  # the player's object: screen_x (0E)
param is_player_at_top_edge R0 at_edge  # low byte 1 when screen_y <= push_edge_top (also the edge - A temporary)
param is_player_at_top_edge R2 object  # the player's object: screen_y (0C)
param is_player_at_bottom_edge R0 at_edge  # low byte 1 when screen_y >= push_edge_bottom (also the edge + A temporary)
param is_player_at_bottom_edge R2 object  # the player's object: screen_y (0C)
param set_screen_push_edges R0 quadrant_bits  # update_scroll passes 0; | 1 always, | 2 / 4 / 8 by the signs of section_direction_x/_y: 1, 3, 7 or F, the switch that sets the push edges and push_edge_case
param set_screen_push_edges R4 count  # 100: the sine_table entries of a quadrant, for is_direction_in_quadrant
param set_screen_push_edges R11 sine_entry  # sine_table (101632) + 200 per quadrant step: the quadrant's first entry
param is_player_facing_scroll R0 facing  # low byte 1 when heading is within the window around the scroll direction's quadrant
param is_player_facing_scroll R1 heading  # update_scroll passes each player block's target_heading (field 28); doubled to compare with the sine_table offset
param is_player_facing_scroll R11 sine_entry  # where is_direction_in_quadrant stopped: (it - sine_table) / 2 is the scroll direction's angle; only the low 16 bits are used
param is_direction_in_quadrant R0 found  # low byte 1 when section_direction_y lies between two consecutive entries (or the direction is straight: 4000/C000), 0 after count entries
param is_direction_in_quadrant R4 count  # entries left to check (set_screen_push_edges gives 100)
param scroll_slowing_down R3 speed  # out: the speed scroll_at_scroll_speed used (scroll_speed after the decrement)
param scroll_speeding_up R3 speed  # scroll_ramp_speed handed to advance_scroll_unless_ended; on the frame the ramp ends, set_camera_velocity gets the incoming R3 unchanged (0169FA)
param scroll_at_scroll_speed R3 speed  # scroll_speed, handed to advance_scroll_unless_ended
param advance_scroll_unless_ended R3 speed  # passed to advance_scroll (nothing when scroll_ended < 0)
param advance_scroll R3 speed  # move_camera_along_path's step, then set_camera_velocity's
param advance_scroll R5 next_scroll_position  # what move_camera_along_path returns, handed to set_camera_velocity, unused
param set_camera_velocity R3 speed  # camera_velocity_x/_y = section_direction_x/_y x (u16) speed; then update_background_scroll
param update_background_scroll R3 speed  # set_camera_velocity's speed, passed to update_nbg3_scroll/update_nbg2_scroll (stages 3/7 return scroll_position in it)
param update_background_scroll R10 range  # walks stage_fast_scroll_ranges[stage] (u16 positions, FFFF ends) to the first one past the previous section's end; out: the cursor, unused
param start_stage_view R5 stage_offset  # init_stage_view's stage x 2, handed back unused
param run_stage_05_switch R11 dest  # run_vram_script's video RAM write address over the eight scripts of step 2
param update_fade R1 delta  # set at the first step: fade_speed (fade_kind 2/3) or - fade_speed (0/1) in the low half, also stored in fade_delta; otherwise unchanged; callers ignore it
param update_stage_00_exit_scene R10 script  # the vram scripts of steps 0-1 and 8; on the other steps update_background_scroll leaves its range cursor in it
param update_stage_00_exit_scene R11 dest  # run_vram_script's video RAM write address
param set_nbg0_scroll_from_camera R2 scroll_x  # camera_x_pixels (mov.h: only the upper half of the incoming value survives), written to NBG0's x scroll (& 3FF) and tile_block_column; returned, unused
param update_camera_view_y R2 y  # in: a 16.16 camera y (camera_y from update_camera_view, the shaken y from update_camera_with_shake) -> camera_y_pixels; out: view_y (camera_y_pixels - view_offset_y)
param move_camera_along_path R3 speed  # camera_x/_y += section_direction_x/_y x speed and scroll_position += speed, as camera_limits_reached allows
param move_camera_along_path R5 next_scroll_position  # returned: scroll_position + speed, compared with the section end (camera_limits_reached bit 0)
param advance_camera_scroll_x_y R3 speed  # advance_scroll_position, advance_camera_x, advance_camera_y with it
param advance_camera_x_y R3 speed  # advance_camera_x, advance_camera_y with it
param advance_camera_y_scroll R3 speed  # advance_camera_y, advance_scroll_position with it
param advance_camera_y R3 speed  # camera_y += section_direction_y x speed (update_scroll and update_stage_00_exit_scene pass their own speeds)
param advance_camera_x_scroll R3 speed  # advance_camera_x, advance_scroll_position with it
param advance_camera_x R3 speed  # camera_x += section_direction_x x speed
param advance_scroll_position R3 speed  # scroll_position += speed (16 bits)
param update_stage_06_background R11 dest  # load_default_palettes' and load_palette_set_and_backdrop's palette RAM address, then run_vram_script's video RAM address
param move_camera_to_start_section R3 section_offset  # load_camera_start_section's, handed back unused
param move_camera_to_start_section R5 stage_index  # load_camera_start_section's, handed back unused
param load_camera_start_section R3 section_offset  # stage_start_section x 2: offset into the per-section u16 lists (x 1/2 for the byte lists, x 2 for the scroll modes); handed back
param load_camera_start_section R5 stage_index  # stage: index of the stage_section_* tables; handed back
param animate_stage_palette R4 palette_count  # passed through the stage's animate_palette_stage_ function (cycle_palette's count)
param animate_stage_palette R9 cycle  # passed through (the palette cycle state the stage function ran)
param animate_stage_palette R10 source  # passed through (cycle_palette's source)
param animate_stage_palette R11 dest  # passed through (cycle_palette's palette RAM address)
param animate_palette_stage_0c R4 palette_count  # set before each cycle_palette call
param animate_palette_stage_0c R9 cycle  # palette_cycle_0 (20CA00) / palette_cycle_1 (20CA05) state
param animate_palette_stage_0c R10 source  # the colour frames in ROM
param animate_palette_stage_0c R11 dest  # the first palette in palette RAM (6088xx)
param animate_palette_stage_0d_0e R4 palette_count  # 1
param animate_palette_stage_0d_0e R9 cycle  # palette_cycle_0 (20CA00)
param animate_palette_stage_0d_0e R10 source  # 108710
param animate_palette_stage_0d_0e R11 dest  # 6088C0
param animate_palette_stage_0f R4 palette_count  # 3, then 1
param animate_palette_stage_0f R9 cycle  # 20CA00, then 20CA05
param animate_palette_stage_0f R10 source  # 108810, then 1088D0
param animate_palette_stage_0f R11 dest  # 608920, then 608900
param animate_palette_stage_0a R4 palette_count  # 1, then 9
param animate_palette_stage_0a R9 cycle  # 20CA00, then 20CA05
param animate_palette_stage_0a R10 source  # 108550, then 108410
param animate_palette_stage_0a R11 dest  # 608A40, then 608A60
param animate_palette_stage_09 R4 palette_count  # 2
param animate_palette_stage_09 R9 cycle  # 20CA00
param animate_palette_stage_09 R10 source  # 1080B0
param animate_palette_stage_09 R11 dest  # 6088A0
param animate_palette_stage_04 R4 palette_count  # 2
param animate_palette_stage_04 R9 cycle  # 20CA00
param animate_palette_stage_04 R10 source  # 107FB0
param animate_palette_stage_04 R11 dest  # 608500
param cycle_palette R4 palette_count  # how many consecutive 16-colour palettes get the same frame (r11 + 20 each); 0 after the copy
param cycle_palette R10 source  # the frames in ROM; + frame x 20: the frame queue_vblank_copy copies
param cycle_palette R11 dest  # the first palette in palette RAM; + 20 per palette
param cycle_palette R12 halfwords  # colours per copy (callers pass 10), queue_vblank_copy's size
param cycle_palette_set_0 R10 source  # cycle_palette_set's, with palette_cycle_set = 209090
param cycle_palette_set_1 R10 source  # cycle_palette_set's, with palette_cycle_set = 2090A0
param cycle_palette_set R10 source  # when the set's timer runs out: frames (set + 0) + frame x 20, queue_vblank_copy's source; otherwise returned unchanged
param load_stage_palette_set R3 tile_bank_bits  # load_palette_set_and_backdrop's, handed back (init_stage_view overwrites it)
param load_stage_palette_set R11 dest  # load_palette_set_and_backdrop's palette RAM address, handed back
param load_palette_set_and_backdrop R1 stage_index  # which stage's entries: stage_tile_bank_bits[it] and stage_palette_sets[it]; load_stage_palette_set passes stage, update_stage_06_background 1 (the backdrop colour still goes by stage)
param load_palette_set_and_backdrop R3 tile_bank_bits  # stage_tile_bank_bits[stage_index] << 10, ORed into 31FF00 (bit 10); handed back
param load_palette_set_and_backdrop R10 source  # load_palette_list's, handed back
param load_palette_set_and_backdrop R11 dest  # load_palette_list's palette RAM address, handed back
param init_nbg0_picture_screen R11 dest  # load_palette_list's palette RAM address after palette set 1076AE; returned, show_nbg0_picture hands it to run_vram_script
param show_nbg0_picture R2 copy_entry          # out only: queue_vblank_copy's R2 handed back (the vblank_copies entry it wrote, past its end); callers only hand it on
param show_nbg0_picture R4 rows                # palette rows to queue (1), counted down: 0 when it returns
param show_nbg0_picture R10 source             # palette row 1B154 queued with queue_vblank_copy (its source); before that the vram script 17280A (run_vram_script's script)
param show_nbg0_picture R11 dest               # palette RAM 608BE0 + 20 per row (queue_vblank_copy destination); before that init_nbg0_picture_screen/run_vram_script's write position
param set_stage_layer_mixing R4 count          # the 4 mixer words 610022-610028, counted down: always 0 when it returns (init_stage_view and build_ending_scene only hand it on)
param init_nbg1_layer R11 dest                 # run_vram_script's write position (video RAM) through the four scripts 170722-17076A; init_stage_view chains it init_nbg2_layer -> init_nbg1_layer -> init_nbg3_layer
param init_nbg2_layer R11 dest                 # as init_nbg1_layer: run_vram_script's destination through 1706C2-17070A, handed back
param update_nbg2_scroll R3 position           # stages 3 and 7: scroll_position from set_nbg2_scroll_stages_03_07; the other stages hand back what the caller had
param set_nbg2_scroll_stages_03_07 R3 position # scroll_position, compared with 9159 (past it the NBG2 scroll is the camera + 10)
param init_nbg3_layer R11 dest                 # run_vram_script's destination (the scripts set it themselves), then set_nbg3_page_5_flags' cell pointer
param set_nbg3_page_5_flags R1 count           # 400 cells, counted down: 0 at the end
param set_nbg3_page_5_flags R11 dest           # the NBG3 cell it sets bit 13 of, from 301400 on (+2 each): 301C00 at the end
param update_nbg3_scroll R2 scroll_y           # the NBG3 scroll y it computes (view_y / 2, / 4, * 3/4) before nbg3_scroll_y = it (+ 80); after select_nbg3_pages that one's block_pages; stages without parallax hand back the caller's value
param update_nbg3_scroll R3 quadrants          # only select_nbg3_pages' R3 handed back (stages 0, 3, 5, 6, 7, A); else unchanged
param update_nbg3_scroll R11 quadrant_cursor   # only select_nbg3_pages' R11 handed back; else unchanged
param select_nbg3_pages R2 block_pages         # the four page bytes of the 2 x 2 blocks under the NBG3 scroll (stage_nbg3_block_maps at row/column from get_nbg3_block), packed one per byte
param select_nbg3_pages R3 quadrants           # for each of those blocks, which byte of nbg3_pages it goes to (0-3): a 2 x 2 window of the 3 x 3 grid at 1BCBC (00 01 00 / 02 03 02 / 00 01 00) picked by the row/column parity
param select_nbg3_pages R11 quadrant_cursor    # pointer into the 1BCBC table (+ 3 x row parity + column parity), past the four bytes read
param build_ending_scene R3 tile_bank_bits     # step 0: load_stage_palette_set's R3 handed back (stage_tile_bank_bits << 10); unchanged in the other steps
param build_ending_scene R9 blocks_per_row     # step 0: clear_bitmap_layer's R9 handed back (fill_rect_unclipped's); unchanged in the other steps
param init_ending_scene_object R11 object      # the object build_ending_scene just allocated: clears its flags (30-33)
param run_timed_spawns_2 R11 object            # allocate_object_outside_loop's object: the spawned one gets the timed_spawns_2 handler; what callers pass only stays when none is free
param load_credits_palettes R11 dest           # load_palette_list's R11 handed back: its palette RAM write position after list 1076AE
param init_eggman_smoke_puff R11 piece         # a pool 2 block (allocate_object_2 in ending_eggman_fly_in) set up as a smoke puff sprite
param spawn_ring_rows R3 slope_height          # find_floor's R3 for the last ring (the slope's part of floor_z); unchanged when no row spawned
param spawn_ring_rows R11 object object.carried_ring  # each ring (allocate_object_outside_loop, filled from stage_object_templates), then its pool 2 shadow (allocate_object_2): the last shadow when it returns (pointer typed: last_names.txt N1)
param scatter_rings_fixed R3 random_value      # scatter_rings' R3 handed back (20F0EA = FFFF: no random speed or jump, but odd counts still draw the direction)
param scatter_rings_fixed R8 count             # rings to throw (release_board_rings passes 1)
param scatter_rings_fixed R11 allocated            # scatter_rings' R11: the last block it allocated
param scatter_rings_random R3 random_value     # passed to scatter_rings, where next_random replaces it
param scatter_rings_random R8 count            # rings to throw (metal_wall_update: spawn word 94 high byte; drop_rings: 1)
param scatter_rings_random R11 allocated           # scatter_rings' R11: the last block it allocated
param scatter_rings_random R17 ring            # scatter_rings' R17: the last ring (drop_rings goes on with it: copy_position_to, its fields)
param scatter_rings R3 random_value            # next_random's result, masked into speed_z (& 1FFFF + 80000), speed (& 7FFFFF + 800000) and direction (& FF + 180); unchanged when nothing is drawn
param scatter_rings R8 count                   # rings to throw, at most A; counted down (odd counts pick a random direction, even ones mirror the last)
param scatter_rings R11 allocated                  # each ring (allocate_object), then its shadow (allocate_object_2): the last shadow at the end
param scatter_rings R17 ring                   # the ring just made (r17 = R11 before the shadow is allocated; the shadow's parent); the last one at the end
param spawn_ring_sparkle R11 sparkle           # the sparkle object it allocates (animation 01F5CE) and hands to init_sparkle_object; nothing comes back
param spawn_ring_glint R11 sparkle             # the same with animation 01F5AC (a glint)
param init_sparkle_object R11 sparkle          # the new sparkle/glint object: sprite flags 1000, palette 1B0, handler sparkle_start, at the current object
param next_random R3 random_value              # the result: two words read through the random number area (D80000 + offset), swapped into one u32
param next_random R9 words_left                # 2 words to read, counted down: 0 when it returns
param next_random R10 random_offset            # random_position moved on by each word read (& 7FFFF), stored back
param create_child_shadow R8 scale             # the shadow's sprite_scale (fireball_emitter_update: 180, or 30 when hitbox_size_x is 0)
param create_child_shadow R11 shadow           # the pool 2 block allocate_object_2 gives (frame 5D90, palette 87A0), the current object's child
param is_off_screen R8 off_screen              # FFFFFFFF when screen_x + 20 >= 180 or screen_y + 20 >= 120, else 0 (callers delete the object or skip drawing)
param is_off_screen_margin R1 margin_x         # x margin: off when screen_x + it >= 140 + 2 x it (callers 20-68)
param is_off_screen_margin R3 margin_y         # y margin, the same against E0 (callers 50-140)
param is_off_screen_margin R8 off_screen       # as is_off_screen
param check_left_screen R8 off_screen          # FFFFFFFF once an object that has been on screen (flags bit 11) is 20 outside it; 0 while inside or never seen
param check_left_screen_margin R1 margin       # the margin once it has been on screen (0 before): callers 40-70
param check_left_screen_margin R8 off_screen   # as check_left_screen
param check_left_screen_margins R1 margin      # margin once it has been on screen (flags bit 11)
param check_left_screen_margins R2 unseen_margin # margin used instead while it has not been on screen yet ("r1 = r2")
param check_left_screen_margins R8 off_screen  # as check_left_screen
param allocate_object_outside_loop R0 free_count # allocate_object's R0: how many objects were free (0: none, nothing allocated)
param allocate_object_outside_loop R11 object  # allocate_object's R11: the new object (unchanged when none is free)
param delete_object_outside_loop R0 object     # the object to unlink and free (delete_object's R0; spawn_walls passes the current object)
param delete_object_outside_loop R1 free_count # delete_object's R1: the free stack's count after pushing it back
param delete_object_outside_loop R2 previous   # delete_object's R2: the object that was before it in the list
param spawn_objects_by_handler R1 list_offset  # stage_spawns_by_handler[stage] the first time (handler_spawn_cursor = 81224 + it); afterwards the caller's value goes back unchanged
param spawn_objects_by_handler R10 record      # the record handler_spawn_cursor points at (position, x, y, sprite_frame, handler); past the handler once one spawned
param spawn_objects_by_handler R11 object      # allocate_object_outside_loop's new object
param spawn_door_rollers R11 object            # each roller (allocate_object_outside_loop, door_roller_layout), the last one at the end (find_floor gets it as its R11 in between)
param spawn_door_rollers R3 slope_height       # find_floor's R3 for the last roller
param spawn_chaser_back_zone R1 zone_index     # record of object_steps (08340D, 6 bytes: limit, handler): propeller_machine_init 0, tornado_start 1, burning_boulder_start 2
param spawn_chaser_back_zone R11 zone object.chaser_back_zone  # the back zone object (kind 9, flags 40000002, field 80 = the record's limit); unchanged when none is free (pointer typed: last_names.txt N1)
param spawn_roller_back_zone R1 zone_index     # roller_back_zone_index (spiked_roller_start): record of 08340D whose word is the limit
param spawn_roller_back_zone R11 zone          # the back zone object (kind 18, handler roller_back_zone_update)
param get_pacing_axis R10 player               # the player block whose object's floor block (byte 15) gives the travel direction (player_pacer_update: the first player in play, or 208400)
param get_pacing_axis R5 axis_field            # the coordinate along that direction as a halfword index into the object: 29 (y_int, 52) or 27 (x_int, 4E); callers read [object + it x 2]
param get_pacing_axis R6 decreasing            # FFFF when travel goes towards smaller values of that coordinate (directions 0/4 and 3), else 0 (low half only)
param spawn_sign R5 x                          # whole-pixel x (place_sign: x_int)
param spawn_sign R6 y                          # whole-pixel y
param spawn_sign R7 kind                       # index into object_kinds (12-byte records: z, sprite, duration, animation, handler)
param spawn_sign R8 z                          # whole-pixel z
param spawn_sign R11 sign                      # allocate_object_outside_loop's object, set up by place_sign
param spawn_sign_2 R5 x                        # as spawn_sign
param spawn_sign_2 R6 y                        # as spawn_sign
param spawn_sign_2 R7 kind                     # as spawn_sign
param spawn_sign_2 R8 z                        # as spawn_sign
param spawn_sign_2 R11 sign                    # allocate_object's object (handed back; unchanged when none is free)
param place_sign R5 x                          # -> x_int (x fraction 0)
param place_sign R6 y                          # -> y_int
param place_sign R7 kind                       # object_kinds index: init_sign_from_kind gets 84FB7 + 12 x it
param place_sign R8 z                          # -> z_int
param place_sign R11 sign                      # the object it places (sign_start_from_type passes the current object)
param init_sign_from_kind R7 record            # the object_kinds record past its z word (+2): sprite, duration (speed_x), animation offset (+ 8514D into 80), handler
param spawn_stage_03_end_pillar R11 pillar     # allocate_object_outside_loop's object: sprite 6600 at screen 146,75, handler stage_03_end_pillar_show; unchanged when none is free
param extend_lava_flow R11 block               # the follower segment (allocate_object), then the pool 2 block that gets its copy of the part list (allocate_object_2, 9 words from 87D0F)
param spawn_lava_flame R3 random_value         # next_random's result (kept in temp_0): bits 0-4 the next delay, bit 0 the animation, bytes 0/2 the spot on the lava
param spawn_lava_flame R11 flame               # the flame object (allocate_object, handler lava_flame_burn); unchanged when none is free
param spawn_ice_platform_leader R0 spawned     # FFFFFFFF when made, 0 when no object was free (spawn_object_groups stops then)
param spawn_ice_platform_leader R1 travel      # x - the reference word before it: its start offset along the swing, also put in field 8C (low half)
param spawn_ice_platform_leader R11 leader     # the new leader (group_leaders[n & 3], moving_floor_object)
param drop_icicle_row R3 random_value          # next_random's result: & 1F + 3C is each icicle's fall delay (field 80)
param drop_icicle_row R11 block                # each icicle (allocate_object), then its icicle_shadow_follow shadow (allocate_object)
param spawn_tornado_hazard R7 x                # 16.16 x of the box (tornado_move: its floor block's x - 8, or the tornado's own x, - 80 for direction 1)
param spawn_tornado_hazard R8 y                # 16.16 y
param spawn_tornado_hazard R9 z                # the tornado's z
param spawn_tornado_hazard R11 hazard          # the hazard object (handler tornado_hazard_update, touch kind C)
param tornado_throw_whirlwinds R0 spawned      # only handed to spawn_whirlwind (its R0: spawn_object_with_pieces keeps the upper bytes); the result is not handed back
param tornado_throw_whirlwinds R3 whirlwind    # only handed to spawn_whirlwind (its R3, which spawn_object_with_pieces replaces with the whirlwind); not handed back
param tornado_throw_whirlwinds R11 object      # spawn_whirlwind's R11 (spawn_count 1: the whirlwind block); unchanged while the next list position is not reached
param spawn_tornado_rear_zone R11 zone         # the rear zone object (kind 9, height 160, tornado_rear_zone_follow); unchanged when none is free
param spawn_sand_block_leader R0 spawned       # FFFFFFFF when made, 0 when no object was free
param spawn_sand_block_leader R11 leader       # the new leader (group_leaders[n & 3])
param spawn_sink_swirl R11 allocated               # each swirl (allocate_object), then its take_free_object part (palette 1230): the second part at the end
param spawn_listed_geyser R11 geyser           # spawn_geyser's object (flags bit 31: it waits for its start position); unchanged when none was spawned
param spawn_geyser R12 x                       # 16.16 x (geyser_lists word << 16, or the front's x)
param spawn_geyser R13 y                       # 16.16 y
param spawn_geyser R15 wait                    # field 80: listed geysers wait for this scroll position, row geysers count it down (geyser_row_delays)
param spawn_geyser R0 spawned                  # FFFFFFFF when made, 0 when no object was free
param spawn_geyser R11 geyser                  # the new geyser (handler geyser_wait)
param spawn_waterfall R0 spawned               # FFFFFFFF when made, 0 when no object was free; with pool 2 empty it returns allocate_object's count, the new object left unset
param spawn_waterfall R11 block                # the waterfall leader (allocate_object_outside_loop), then the pool 2 block with its part list copy (allocate_object_2)
param spawn_waterfall R2 free_stack            # allocate_object_2's R2 handed back (free_object_stack_2, 20EA10); unchanged when nothing was allocated
param spawn_geyser_line R11 line               # the geyser_line_move object at geyser_line_starts[sprite_frame] (allocate_object)
param spawn_objects_by_type R4 projected_x     # the record's word +2, stored in projected_x of the new object
param spawn_objects_by_type R5 projected_y     # the record's word +4 << 16, stored in projected_y
param spawn_objects_by_type R3 first           # spawn_object_with_pieces' object, set up as current_object; unproject_object's R3 after it
param spawn_objects_by_type R8 object_type     # object_types + 8 x type: +0 spawn_count, then +4 the handler
param count_active_players R1 player_object    # the player block (player_block_offsets), then its object (block +14): the last one checked (player 1's) at the end
param count_active_players R2 player_index     # 2, 1, 0: counted down to FFFFFFFF
param spawn_object_with_pieces R0 spawned      # 1 when made; on failure only its low byte is cleared ("mov.b #0, R0"); callers test (uint8_t)
param spawn_object_with_pieces R1 piece_count  # spawn_count - 1: the pool 2 pieces (low half)
param spawn_object_with_pieces R2 free_count   # free_object_stack's count (16-bit load, tested 32-bit), then pool 2's count less the pieces; after add_object_pieces its R2
param spawn_object_with_pieces R3 first        # the pool 1 object (spawn_first), parent of the pieces: what callers set up
param spawn_object_with_pieces R11 block       # each block it allocates: the object, then the pieces; the last one at the end
param add_pieces_to_current_object R0 spawned  # add_object_pieces' R0
param add_pieces_to_current_object R2 free_count # add_object_pieces' R2
param add_pieces_to_current_object R3 first    # add_object_pieces' R3: spawn_first = object_cursor
param add_pieces_to_current_object R11 block   # object_cursor going in, add_object_pieces' last piece coming out
param add_object_pieces R0 spawned             # 1 when the pieces were made; on failure only the low byte is cleared
param add_object_pieces R1 piece_count         # how many pool 2 blocks to chain (low half; also into spawn_count)
param add_object_pieces R2 free_count          # pool 2's free count less piece_count (low half); after allocating, allocate_object_2's R2
param add_object_pieces R3 first               # spawn_first (the block R11 brought in) when it returns; unchanged on failure
param add_object_pieces R11 block              # the block the pieces hang from (spawn_first/spawn_last), then each piece: the last one at the end
param spawn_objects R0 spawned                 # 1 when made, 0 when the free stack is too short; unchanged when spawn_count <= 0
param spawn_objects R3 first                   # spawn_first: the first object of the chain (spawn_debris walks it through child)
param spawn_objects R11 block                  # each object it allocates: the last one at the end
param init_object R11 object                   # the object to clear (init_object_fields with R12 = 0)
param init_object_2 R11 object                 # the pool 2 block to clear (R12 = 1)
param init_object_fields R11 object            # the object or pool 2 block it clears
param init_object_fields R12 mode              # 0 every field, 1 only 00-2F (a pool 2 block), 2 (08DEE5, unused) all but z and below
param move_object_projected R0 screen_y        # unproject_object's R0 (update_screen_position_no_offset): the new screen_y
param move_object_projected R3 slope_height    # unproject_object's R3 (find_floor's)
param unproject_object R3 slope_height         # find_floor's R3 handed back (the slope's part of floor_z, 0 on flat floor)
param update_screen_position_no_offset R0 screen_y # (projected_y + z) >> 16 - view_y, also stored in screen_y
param is_on_screen R0 on_screen                # 1 while screen_x is in -C0..200 and screen_y in -C0..1A0, else 0
param delete_if_off_screen R0 deleted          # 0 while on screen, 1 when delete_object_and_children ran (callers return on (uint8_t) == 1); screen_x is loaded in it first
param delete_object_and_children R0 deleted    # always 1 when it returns ("this object is done"); in between the child being freed
param copy_position_to R3 object               # the object that gets object_cursor's x, y, projected_x/y, z and floor_z (callers: a part just spawned)
param place_sprite_near_cursor R8 offset_x     # added to object_cursor's screen_x
param place_sprite_near_cursor R9 offset_y     # added to object_cursor's screen_y
param face_nearest_player R3 octant           # angle_of_vector's R3 for the last active player checked (its angle_octants entry, complemented when negative); the caller's when none is in play
param distance_to_object R0 distance           # square_root(dx^2 + dy^2) to the object at R14 (projected), also in nearest_player_distance
param direction_to_object R3 octant           # angle_of_vector's R3 (its angle_octants entry) handed back; the direction itself goes into the object
param distance_xy_to_object R0 distance_x      # |x_int difference| to the object at R14 (also distance_to_player_1); callers compare it with 20
param distance_xy_to_object R1 distance_y      # |y_int difference| (also distance_to_player_2); push_crate_update compares it with 3, eel_anchor_wait_explode with 10
param fall_to_floor R0 height                  # floor_z - z while it is still above the floor after speed_z += 6000 (16.16), 0 once on it (callers: 0 = landed)
param fall_to_floor R1 over_pit                # 1 when floor_z >= 800 (no floor under it), else 0 (low byte)
param fall_to_floor R2 z                       # its z, loaded only while it is still falling; else what the caller had
param place_sprite_at_cursor_floor R0 bucket   # FF: callers queue_sprite_in_bucket with it (drawn first, under everything)
param direction_to_speed R0 direction          # the angle (& 3FF on the way out); callers load a direction field or a constant
param direction_to_speed R1 speed              # the length: sine/cosine x it >> 6 (callers load speed or a constant)
param direction_to_speed R2 speed_x            # sine[direction] x speed >> 6; callers store it in speed_x
param direction_to_speed R3 speed_y            # -cosine[direction] x speed >> 6; callers store it in speed_y
param cursor_floor_screen_y R1 floor_screen_y  # screen_y + object_cursor's floor_z - z (whole pixels, low half): callers queue_sprite_at_depth with it - 10/14
param drop_rings R16 random_value              # next_random's result kept for the spread (& 7F speed, & 3F x 2 - 40 angle) while released_rings_random
param drop_rings R17 ring                      # scatter_rings_random's R17: the ring just thrown (then set up as a released_ring_wait)
param spawn_debris R1 count                    # pieces to throw: spawn_count for spawn_objects (callers 5-F)
param queue_string_record R1 y                 # not used: the record's y replaces it
param queue_string_record R2 palette           # queue_string's R2: the record's palette (low half)
param queue_string_record R3 scale             # not used: the record's scale replaces it
param queue_string_record R4 text              # queue_string's R4: the text after the record's 4 words
param queue_string_at R0 x                     # screen x of the text
param queue_string_at R1 y                     # screen y
param queue_string_at R2 palette               # sprite palette (8440, 8AD0, 8F70)
param queue_string_at R3 scale                 # sprite scale (100 = 1:1, 140)
param queue_string_at R4 text                  # the string (character codes for text_frames, 0 ends)
param queue_string R0 x                        # screen x; with mode 0 the record instead (x, y, palette, scale are read from it and the text follows)
param queue_string R1 y                        # screen y (mode 1)
param queue_string R5 mode                     # 0: everything from the record at R0; 1: from R0-R4
param queue_string R2 palette                  # palette (low half)
param queue_string R3 scale                    # scale (low half)
param queue_string R4 text                     # the string; with mode 0 the record + 8
param draw_queued_strings R19 sprite_entry     # build_sprite_list's R19: where the next 16-byte sprite entry goes; the end command is written there at the end
param draw_queued_strings R4 scale             # each string's scale (word +6 of its string_queue record): the last one at the end
param draw_queued_strings R5 cursor            # string_count, then the string_queue record, then the string's characters: past the last 0 at the end
param draw_queued_strings R8 size              # entry word 1: the first visible character's frame width, packed with its height (source size)
param draw_queued_strings R9 height            # entry word 2: the frame's address bits (high byte) and its height x scale (low half)
param draw_queued_strings R10 width            # entry word 3: the frame's width x scale | its bank bits
param copy_ranking_records R0 source           # 8-byte records read from here (load_ranking: saved_ranking 701F82, reset_ranking: ranking 20CE00); after the copy R0 is reused for compute_crc16's result
param copy_ranking_records R1 dest             # where they go: the other one of ranking / saved_ranking
param copy_ranking_records R2 count            # records to copy (both callers pass F), counted down; then reused as compute_crc16's source (20CE00)
param copy_ranking_records R4 polynomial       # what compute_crc16 leaves in R4 (10210000, the CRC polynomial), handed back through load_ranking/reset_ranking: nobody uses it
param ascii_to_ranking_letter R3 letter        # in: an ASCII character of the default ranking (08F1B5); out: its ranking letter code ('a'-'z' 00-19, ' ' 2B, '3' 23)
param start_ranking_display R4 rows            # show_nbg0_picture's R4 handed back (0); attract_ranking only hands it on
param start_ranking_display R10 source         # show_nbg0_picture's R10 handed back (palette row 1B154)
param spawn_walls R5 second_sprite             # build_wall_parts' R5 handed back (the wall kind's second part sprite); else the caller's value
param spawn_walls R8 point_y                   # the far end's y (y + box y, 16.16) given to project_point, then its screen y minus the wall's: stored in speed_z_int; else the caller's value
param spawn_walls R9 z                         # the new wall's z, given to project_point for its far end; else the caller's value
param spawn_walls R11 wall                     # the wall object (allocate_object_outside_loop, handler wall_update); build_wall_parts leaves its last part here
param check_wall_left_screen R8 off_screen     # FFFFFFFF once both ends of the wall were on screen and have left it (speed_y bits 1 and 3), else 0: wall_update goes on while it is 0 (as check_left_screen)
param build_wall_parts R5 second_sprite        # wall_kinds record word +12 (wall_kind_second_sprites): sprite of the second part of each step (palette 9FFE); negative = none
param build_wall_parts R11 part                # each pool 2 part (allocate_object_2), chained by parent/child: the last one at the end (the wall itself when the kind has no parts)
param spawn_object_groups R8 count             # group_slots search counter (40 slots), also spawn_group_slot_trigger's; nothing reads it after
param spawn_object_groups R11 object object.group_member  # each group member (allocate_object_outside_loop) or leader (spawn_group_leader & co.): the last one at the end; briefly the stage_object_records address before it goes to group_record (pointer typed: last_names.txt N1)
param spawn_group_leader R0 spawned            # FFFFFFFF when made, 0 when no object was free (callers stop then); in between the leader number
param spawn_group_leader R11 leader            # the new leader (group_leaders[n & 3], handler group_leader_move or group_leader_wait)
param spawn_lava_column R11 block              # the lava column (allocate_object), then its pool 2 sprite block (allocate_object_2, 9 words from 93515), handed back; lava_rock_update chains it into the next call
param spawn_group_slot_trigger R0 spawned      # FFFFFFFF when made, 0 when no object was free
param spawn_group_slot_trigger R8 count        # group_slots longwords cleared (20), counted down: 0 at the end
param spawn_group_slot_trigger R11 trigger     # the new object (handler group_slot_trigger_update)
param spawn_object_groups_2 R8 count           # 10 longwords of group_slots cleared at the start, then the slot search counter (40)
param spawn_object_groups_2 R11 object object.group_member  # each group member (allocate_object_outside_loop) or leader (spawn_group_leader): the last one at the end (its address goes to 20E5A4/20E5A6) (pointer typed: last_names.txt N1)
param event_0f_show_messages R8 frames         # spawn_message_box's R8: the record's frame count
param event_0f_show_messages R11 box           # spawn_message_box's R11 handed back
param event_0c_start R11 object                # spawn_door_rollers' R11 handed back: the last roller object
param event_0c_second_quake R8 point_y         # spawn_walls' R8 handed back
param event_0c_second_quake R9 z               # spawn_walls' R9 handed back
param event_0c_second_quake R11 wall           # spawn_walls' R11 handed back
param spawn_stage_06_lightning R10 record      # stage_06_lightning_cursor: the strike record (time, screen x, y, animation); past it once spawned, past the time word before
param spawn_stage_06_lightning R11 bolt        # the new object (palette 9110, handler stage_06_lightning_animate); unchanged when none is free
param spawn_message_box R11 box                # allocate_object_outside_loop's object (handler message_box_show), handed back; unchanged when none is free
param start_escape_countdown R2 free_stack     # allocate_object_2's R2 handed back (20EA10)
param start_escape_countdown R8 count          # 2 digit sprites, counted down: 0 at the end
param start_escape_countdown R11 digit         # each digit sprite (allocate_object_2: palette F60, centre A0, y 80), stored in escape_tens_digit / escape_units_digit: the units one at the end
param start_escape_alarm_flash R11 flash       # allocate_object_outside_loop's object, given handler escape_alarm_flash; what run_game_mode passes only stays when none is free
param run_map_screen R11 object                # step 2: allocate_object_outside_loop's object (handler map_intro_update, window_wipe_left/top in 32/34)
param draw_map_trail_dot R11 object            # the dot's pool 2 sprite block (allocate_object_2, frame 64E0), freed again after queue_sprite_in_layer; callers' value only stays when pool 2 is empty
param update_screen_wipe R5 edges_done         # bits 0-3: the edges that are at their target (low byte rebuilt every call, upper bytes kept); game mode 0x11 and map_screen_update wait for 0F
param start_ring_tally R11 object              # the objects it allocates (up to three panels, percent, ticker, end sound wait): the last one at the end; first player 2's block (copied to R13)
param pick_ring_bonus_players R0 player_1_rings # player_1_stage_rings | 100 (the bonus bit); the high byte is cleared unless player 1 has the most rings
param pick_ring_bonus_players R1 player_2_rings # the same for player 2
param pick_ring_bonus_players R2 player_3_rings # the same for player 3
param build_ring_tally_panel R0 character      # the player object's kind (the character, from the tally object's 6A): index into ring_tally_portraits; then R0 is the record pointer and allocate_object_2's R0
param wait_for_frames R8 count                 # wait_for_frame's R8 handed back (read_controls' loop counter: 0)
param wait_for_frame R2 released               # read_controls' R2 handed back
param wait_for_frame R3 input_cursor           # read_controls' R3 handed back
param wait_for_frame R8 count                  # read_controls' R8 handed back
param wait_for_frame R10 trackball_port        # read_controls' R10 handed back
param wait_for_frame R11 trackball_cursor      # read_controls' R11 handed back
param read_controls R0 inputs                  # the four ports b, a, f, e in one word (play_demo_buttons, then input_ports); inverted for the pressed test, then reused for the trackball bytes
param read_controls R1 pressed                 # the previous input_ports, then the bits just pressed (inputs_pressed)
param read_controls R2 released                # the bits just released (inputs_released)
param read_controls R3 input_cursor            # walks input_ports, input_ports_active, inputs_pressed ("[R3+]"): &inputs_released at the end
param read_controls R8 count                   # 6 trackball axes, then 3 counter resets, counted down: 0 at the end (play_demo_trackballs: the same)
param read_controls R10 trackball_port         # C00040 + 4 per axis (signed counts read), then + 8 per counter reset; play_demo_trackballs replaces it in demos
param read_controls R11 trackball_cursor       # trackball_1_x .. trackball_3_y (20F062 + 2 per axis): 20F06E at the end
param play_demo_buttons R0 inputs              # read_controls' input word: in demos the 3 low bits of [demo_input] replace bits 25-27
param play_demo_trackballs R8 count            # 6 axes, counted down
param play_demo_trackballs R10 demo_cursor     # demo_input: 6 signed bytes read, stored back
local play_demo_trackballs 09900E r10 demo_cursor demo_trackball_deltas  # "mov.w 65CE[R25], R10": demo_input, at the frame's trackball bytes
elements demo_trackball_deltas s8     # a demo frame's six trackball bytes (after its button byte): play_demo_trackballs "movs.bh [R10+], [R11+]" (09901B, sign-extended into trackball_1_x..trackball_3_y); census ROM 117886-118875, 118886-119875 (attract_demo_1/2_inputs' frames)
param play_demo_trackballs R11 trackball_cursor # trackball_1_x .. trackball_3_y written (20F062 + 2 per axis), as read_controls
param square_root R0 value                     # in: the number (callers: dx^2 + dy^2); out: its integer square root (16 rounds)
param angle_of_vector R0 dx                    # in: x difference (16-bit, signed); out: the angle 0-3FF (callers store it in direction / target_heading)
param angle_of_vector R1 dy                    # y difference (16-bit, signed)
param angle_of_vector R3 octant                # the octant (bit 0 dx < 0, bit 1 dy < 0, bit 2 |dy| >= |dx|), then its angle_octants entry (complemented when negative), handed back
param convert_to_bcd R1 digit                  # divux_pair's remainder: each decimal digit; with more than 8 digits the two above them in BCD; callers overwrite it
param reset_objects R0 block                   # reset_object_pool_2's R0 handed back
param reset_objects R3 stack_slot              # reset_object_pool_2's R3 handed back
param take_free_object R0 free_count           # how many objects were free before (0: none, nothing taken)
param take_free_object R11 object              # the object popped from the free stack (0x200000 | entry), not linked; unchanged when none is free
param allocate_object_or_start_list R11 object # the new object (allocate_object, or the first one of an empty list); unchanged when allocate_object finds none free
param allocate_object R0 free_count            # how many objects were free before (0: none, nothing allocated)
param allocate_object R11 object               # the new object, linked last; unchanged when none is free
param delete_object R0 object                  # the object to unlink and push back on the free stack (delete_object_outside_loop, delete_object_and_children: a child, catapult_update)
param delete_object R1 free_count              # the free stack's count after pushing it (in between the object after it)
param delete_object R2 previous                # the object that was before it in the list
param reset_object_pool_2 R0 block             # each pool 2 block address pushed (205000 + 30 each): the one past the last at the end
param reset_object_pool_2 R3 stack_slot        # each free_object_stack_2 entry written (20EA12 + 2 each): the one past the last at the end
param allocate_object_2 R0 free_count          # how many pool 2 blocks were free before (0: none)
param allocate_object_2 R2 free_stack          # always 20EA10 (free_object_stack_2) when it returns
param allocate_object_2 R11 object             # the pool 2 block popped (0x200000 | entry, never linked); unchanged when none is free
param free_object_2 R0 object                  # the pool 2 block pushed back (callers pass the parent's field that holds it)
param free_object_2 R2 free_stack              # 20EA10 (free_object_stack_2), as allocate_object_2
param load_text_font R4 pixels                 # expand_font's R4 handed back
param load_text_font R5 color_index            # expand_font's R5 handed back (1)
param clear_text_layer R0 value                # clear_blocks' R0 handed back (0)
param clear_text_layer R1 count                # clear_blocks' count (E0 blocks of 16 bytes: 31F000-31FDFF): 0 at the end
param clear_text_layer R11 dest                # clear_blocks' write position: 31FE00 at the end
param expand_font R0 color                     # colour of the set pixels (load_text_font: 1; & F is used); then R0 is reused for each font byte
param expand_font R1 count                     # characters to expand (load_text_font: 5B)
param expand_font R4 pixels                    # each 8-pixel row: the set bits as 1 per nibble, then times color_index; the last row at the end
param expand_font R5 color_index               # color & F, the nibble written for each set pixel
param expand_font R11 dest                     # the 4-bit characters (a long per row) in the text character RAM, after the copied ones
param clear_page_0_and_scroll R11 dest         # clear_blocks' write position: 20F300 (past the scroll shadow) at the end
param set_screen_mode R0 mode                  # bit 0 wide screen (416 px), bit 1 flip (xor setting_monitor_flip bit 1): the result is screen_mode
param clear_blocks R0 value                    # what it writes: always 0
param clear_blocks R1 count                    # 16-byte blocks to clear, counted down
param clear_blocks R11 dest                    # where, advanced: past the last block at the end
param fill_halfwords R0 value                  # the halfword written (init_video_registers: 200)
param fill_halfwords R1 count                  # halfwords to write
param fill_halfwords R11 dest                  # where, advanced; handed back past the last one
param draw_fraction R0 attribute               # attribute byte (high byte of both cells): 0 or 2
param draw_fraction R1 numerator               # top digit: character 80 + it (draw_credit_count_now: coins inserted toward a credit); the cell written comes back
param draw_fraction R2 denominator             # bottom digit: character 90 + it (credit_coin_cost / setting_coins_per_credit); the cell written comes back
param draw_fraction R11 text_position          # text map cell of the fraction: the second cell at the end
param print_text_list R11 text_position        # print_text's: the cell after the last character printed
param print_text R11 text_position             # 310000 | the record's word, then the cell after the last character
param print_text_here R0 cell                  # attribute << 8 | character, written to each cell; the attribute with a 0 character at the end (draw_credit_count_now reuses it)
param print_text_here R11 text_position        # the text map cell, + 2 per character
param erase_text_here R0 cell                  # what goes in each cell (callers: 0)
param erase_text_here R11 text_position        # the first cell, + 2 per character
param print_text_lines R11 text_position       # the cell, + 2 per character; each new row is the row start + 80 ("movea.b [R11], R13" copies R11 itself)
param erase_text_lines R11 text_position       # as print_text_lines, writing 0
param run_vram_script R1 count                 # the last command's count (its ops count it down): 0
param run_vram_script R11 dest                 # video RAM write position (command 80 sets it to 300000 + word x 2)
param vram_script_skip R11 dest                # + byte x 2
param vram_script_skip_long R11 dest           # + word x 2
param vram_script_fill R1 count                # the script's count byte, counted down
param vram_script_fill R11 dest                # + 2 per word written
param vram_script_count_up R1 count            # as vram_script_fill
param vram_script_count_up R11 dest            # + 2 per word
param vram_script_count_down R1 count          # as vram_script_fill
param vram_script_count_down R11 dest          # + 2 per word
param vram_script_bytes R1 count               # as vram_script_fill
param vram_script_bytes R11 dest               # + 2 per word
param vram_script_words R1 count               # as vram_script_fill
param vram_script_words R11 dest               # + 2 per word
param vram_script_words_to_dictionary R1 count # as vram_script_fill
param vram_script_words_to_dictionary R11 dest # + 2 per word
param vram_script_words_to_dictionary R12 dictionary_cursor # where each word also goes in vram_script_dictionary (run_vram_script starts it at 20F300 every script; the low byte wraps)
param vram_script_clear_pages R11 dest         # 300000 + word x 2: the first word cleared (count x 200 words with set_halfwords)
param vram_script_fill_down R1 count           # as vram_script_fill
param vram_script_fill_down R11 dest           # + 40 per word (down a column)
param vram_script_fill_up R1 count             # as vram_script_fill
param vram_script_fill_up R11 dest             # - 40 per word (up a column)
param vram_script_fill_down_wide R1 count      # as vram_script_fill
param vram_script_fill_down_wide R11 dest      # + 80 per word (down a column of a 64-cell map)
param vram_script_repeat R1 count              # as vram_script_fill
param vram_script_repeat R11 dest              # written count times without moving (so it is not handed back)
param draw_hex_2 R0 value                      # the number shown in hex (callers often convert_to_bcd's value first); what is left of it after its digits comes back
param draw_hex_2 R1 attribute                  # attribute in the high byte (callers 200, 1200, 1C00...); the low byte becomes each character
param draw_hex_2 R11 text_position             # text map cell of the leftmost digit
param draw_hex_4 R0 value                      # as draw_hex_2, 4 digits
param draw_hex_4 R1 attribute                  # as draw_hex_2
param draw_hex_4 R11 text_position             # as draw_hex_2
param draw_hex_8 R0 value                      # as draw_hex_2, 8 digits
param draw_hex_8 R1 attribute                  # as draw_hex_2
param draw_hex_8 R11 text_position             # as draw_hex_2
param draw_hex_digits R2 digit_count           # 2, 4 or 8: the digits occupy text_position .. + 2 x (it - 1), written right to left
param draw_hex_digits R0 value                 # shifted right 4 per digit; once it is 0 the remaining cells get it (blank)
param draw_hex_digits R1 attribute             # high byte kept, low byte = '0'-'9' / 'A'-'F'
param draw_hex_digits R11 text_position        # leftmost cell (it starts at the right end and comes back here)
param draw_credit_digits R0 value              # the credits as two BCD digits (draw_credit_count_now); each nibble is written + 1, a leading 0 blank
param draw_credit_digits R1 attribute          # high byte kept (print_text_here's attribute), low byte = each digit's character
param draw_credit_digits R11 text_position     # leftmost cell, as draw_hex_digits
param init_sprites R0 end_mark                 # FFFF, the end mark written at 400020/402010/404000/405FF0 (low half); run_sega_logo hands it to clear_bitmap_layer, whose R0 != 0 test then picks the 8 bpp bitmap (set_bitmap_scroll R2 = 1)
param init_sprites R10 source                  # walks sprite_control_init (4 bytes to 500006-50000C): 9A6B6 at the end
param clear_sprite_buckets R11 bucket          # each of the 256 buckets from 20FA02, set to its own address (empty): 20FC02 (past the last) when it returns
param queue_sprite R1 depth  # its low half is set to screen_y (what callers pass is not used); comes back as queue_sprite_at_depth's key: depth + 10 clamped to 0-FE
param queue_sprite_at_depth R0 bucket_head  # out only: the bucket's list head, 20FA02 + (FF - key) x 2, the object was pushed onto
param queue_sprite_at_depth R1 depth  # screen y the object sorts by (callers: draw_depth, fall_draw_depth, draw_depth +- 1, FF); comes back as the key, depth + 10 clamped to 0-FE
param queue_sprite_in_bucket R0 bucket  # bucket number 0-FF (place_sprite_at_cursor_floor's FF: drawn first, under everything; 0 is drawn last)
param queue_sprite_at_floor R1 depth  # the key it queued with: (projected_y + floor_z) >> 16 - view_y + 10, clamped to 0-FE
param queue_sprite_at_previous_floor R1 depth  # the same with previous_floor_z
param queue_sprite_at_base R1 depth  # its low half is set to screen_y - sprite_offset_y (what callers pass is not used); comes back + 10, clamped to 0-FE
param depth_to_bucket R0 bucket  # result: FF - clamp(depth + 10, 0, FE) in the low half (stage_00_platform_break/_stand add or subtract 3C and queue_sprite_in_bucket it); the high half is the caller's leftover
param depth_to_bucket R1 depth  # screen y to sort by (both callers pass screen_y)
param build_sprite_list R4 height  # entry word 2 being built: the frame record's address bits (high byte) | its height (x scale for scaled frames, sprite_height for sized ones); the last entry's at the end (draw_queued_strings R9's meaning)
param build_sprite_list R8 scale_x  # multi-part sprites: sprite_scale (mode 110) or 100 (mode 111), negated when sprite_flags bit 6 (x flip) is set; multiplies each part's s8 dx
param build_sprite_list R9 scale_y  # the same for each part's s8 dy, negated with bit 7 (y flip)
param build_sprite_list R10 sprite_flags  # each object's sprite_flags: written as entry word 0 (& 2FFF for unscaled and sized frames); & F0 for multi-part sprites, XORed into each part's flags; the last object's at the end
param build_sprite_list R19 sprite_entry  # starts at sprite_list_start, + 10 per entry written: past the last entry when it returns (draw_queued_strings R19 goes on from there)
param load_default_palettes R11 dest  # load_palette_list's palette RAM write position after list 104D60, returned
param load_palette_list R10 source  # each entry's source (+0); past the last block copied at the end
param load_palette_list R11 dest  # 600000 | each entry's halfword (+4): palette RAM; past the last block written at the end
param copy_palettes_with_pen_0 R0 pen_0_color  # written as pen 0 of every 16-colour palette: load_palette_list passes the entry's kind (0; kind 6 would give 6)
param copy_palettes_with_pen_0 R1 size  # bytes of source (next entry's source - this one): 1E (pens 1-15) per palette
param copy_palettes_with_pen_0 R11 dest  # palette RAM, 16 halfwords per palette, advanced
param copy_palette_block R1 size  # bytes to copy (next entry's source - this one); halved for the MOVCU count
param copy_palette_block R10 source  # past the block at the end (R28)
param copy_palette_block R11 dest  # palette RAM; past the block at the end (R27)
param repeat_palette_block R1 size  # bytes of the repeat count list (the entry's source up to the long after the entry): one halfword count per 16-colour block
param repeat_palette_block R10 source  # in: the entry's source, here the list of repeat counts; then the palette data read from palette_list; past the last block at the end
param repeat_palette_block R11 dest  # palette RAM: each block written count times, one after the other
param queue_vblank_copy R1 copy_offset  # out only: vblank_copy_count before the + 1, x 5 (halfwords from vblank_copies to the new entry)
param queue_vblank_copy R2 copy_entry  # out only: the vblank_copies entry it wrote (source, dest, halfwords), past its end (show_nbg0_picture R2's name)
param queue_vblank_copy R10 source  # copied every time the entry is run (cycle_palette's frame, show_nbg0_picture's 1B154)
param queue_vblank_copy R11 dest  # palette RAM; its top byte is how many copies run_vblank_copies makes (each continuing where the last ended)
param queue_vblank_copy R12 halfwords  # per copy (callers pass 10: one 16-colour palette; cycle_palette R12's name)
param init_sound R0 sound_hold  # always 0: the value it stores in sound_cpu_hold (the Z80 runs); callers ignore it (init_system hands it to load_settings as a leftover)
param play_sound R0 sound_id  # index into sound_commands (and the bit tested in attract_sound_mask)
param queue_sound R0 sound_id  # into sound_queue[sound_queue_write]
param load_stage_palette R9 palette_record  # stage_palettes + stage x 8 (source, count), left at +4 (the count)
param load_stage_palette R10 source  # the stage's palette data (record +0), copied with move_halfwords
param load_stage_palette R11 dest  # 60B800, palette RAM it copies to
param split_frame_count R0 frames  # low 32 bits of a 48-bit frame count (test_bookkeeping: power-on, play, average, longest, shortest time); divx dividend
param split_frame_count R1 frames_high  # its high bits (the u16 after the u32, or 0)
param split_frame_count R5 seconds  # / 3C
param split_frame_count R6 minutes  # / DFB
param split_frame_count R7 hours  # / 346A7
param split_frame_count R8 days  # / 4E9FA0 (= 24 x 346A7)
param clear_bitmap_layer R0 is_8bpp  # not 0: set_bitmap_scroll's R2 = 1 (8 bits per pixel); callers leave it there (init_sprites' FFFF end mark, clear_text_layer's 0)
param clear_bitmap_layer R1 top  # out only: bitmap_window_top handed to fill_rect_unclipped, which gives back a bitmap address; before that R1 holds 31FF00 (a separate variable)
param clear_bitmap_layer R4 color  # its low byte is cleared: the layer is filled with 0; fill_rect_unclipped's replicated colour comes back
param clear_bitmap_layer R9 blocks_per_row  # fill_rect_unclipped's, handed back
param get_bitmap_address R0 x  # pixel column (halved in 4 bpp)
param get_bitmap_address R1 y  # row: x 200 (8 bpp) or x 100 (4 bpp); bitmap_address = 300000 + both
param set_bitmap_scroll R0 scroll_x  # -> video_bitmap_scroll_x
param set_bitmap_scroll R1 scroll_y  # -> video_bitmap_scroll_y
param set_bitmap_scroll R2 is_8bpp  # bit 0 -> bitmap_8bpp and 31FF00 bit 11 (callers pass 1, clear_bitmap_layer R0 != 0)
param set_bitmap_window R0 left  # swapped with right when right is smaller; -> bitmap_window_left
param set_bitmap_window R1 top  # swapped with bottom when bottom is smaller; -> bitmap_window_top
param set_bitmap_window R2 right  # -> bitmap_window_right
param set_bitmap_window R3 bottom  # -> bitmap_window_bottom
param draw_line R0 start_x  # first end of the line (get_window_outcode/clip_line_to_window swap and clip the ends); -1 when both ends are beyond the same window edge (nothing drawn), scratch otherwise at the end
param draw_line R1 start_y  # first end's y; the bitmap address after the last pixel at the end
param draw_line R2 end_x  # other end
param draw_line R3 end_y  # other end's y
param draw_line R4 color  # pixel value (test_crt: 7E); 4 bpp uses the low nibble (shifted for odd x)
param fill_span_8bpp R0 blocks  # out: 16-byte blocks written (counted down to 0)
param fill_span_8bpp R1 pixel_address  # first byte in the bitmap (y x 200 + x + bitmap_address); the last byte written at the end
param fill_span_8bpp R3 length  # bytes after the first (x2 - x1): length + 1 are written
param fill_span_8bpp R4 color  # byte; x 01010101 for the long stores
param draw_vertical_span_8bpp R1 pixel_address  # top pixel's byte; the last one written at the end
param draw_vertical_span_8bpp R2 row_step  # bytes to the next row: 200 (draw_line), 100 (fill_rect_unclipped's 2-pixel column in 4 bpp)
param draw_vertical_span_8bpp R4 color  # byte written to each row
param draw_vertical_span_8bpp R5 length  # rows after the first: length + 1 are written (two per loop); 0 at the end
param fill_span_4bpp R0 start_x  # one end (the smaller of R0/R2 is used); scratch at the end
param fill_span_4bpp R1 start_y  # row (x 100 + bitmap_address); the bitmap address where it stopped at the end
param fill_span_4bpp R2 end_x  # other end
param fill_span_4bpp R4 color  # low nibble; replicated for the long stores
param fill_span_4bpp R5 length  # out only: |end_x - start_x|, the pixels after the first; what is left of it at the end
param draw_vertical_span_4bpp R0 start_x  # column: picks the nibble (odd x: high)
param draw_vertical_span_4bpp R1 start_y  # first row; the address after the last pixel at the end
param draw_vertical_span_4bpp R4 color  # low nibble (shifted up for odd x)
param draw_vertical_span_4bpp R5 length  # rows after the first (draw_line's |dy|, fill_rect_unclipped's bottom - top); 0 at the end
param draw_vertical_span_4bpp R10 row_step  # 100, or -100 when draw_line goes up
param fill_rect R0 left  # x of one corner (the corners are sorted and clipped); -1 when nothing is left inside the window
param fill_rect R1 top  # y of one corner (sorted and clipped with bottom)
param fill_rect R2 right  # x of the other corner
param fill_rect R3 bottom  # y of the other corner
param fill_rect R4 color  # low byte (8 bpp) or nibble (4 bpp); comes back replicated
param fill_rect R9 blocks_per_row  # fill_rect_unclipped's, handed back
param fill_rect_unclipped R0 left  # first column (from clip_rect_to_window or the whole window); scratch at the end
param fill_rect_unclipped R1 top  # first row; a bitmap address (where the last pass stopped) at the end
param fill_rect_unclipped R2 right  # last column (inclusive)
param fill_rect_unclipped R3 bottom  # last row (inclusive)
param fill_rect_unclipped R4 color  # low byte (8 bpp) or low nibble (4 bpp); replicated to fill longs/halfwords
param fill_rect_unclipped R9 blocks_per_row  # 8 bpp: (width) >> 4, the 16-byte blocks of a row; 4 bpp: the halfwords (4 pixels each) of a row
param get_window_outcode R0 start_x  # swapped with end_x first, then sign-extended: the outcode is of the point that was in R2/R3 (draw_line calls it twice to get both)
param get_window_outcode R1 start_y  # swapped with end_y the same way
param get_window_outcode R2 end_x  # gets start_x
param get_window_outcode R3 end_y  # gets start_y
param get_window_outcode R10 outcode  # result: 1 left of bitmap_window_left, 2 right of _right, 4 above _top, 8 below _bottom
param clip_line_to_window R0 start_x  # clipped by R10's edge, then swapped with end_x and clipped by R11's (the ends stay swapped)
param clip_line_to_window R1 start_y  # start_x's y, clipped and swapped with it
param clip_line_to_window R2 end_x  # the other end (gets the clipped start)
param clip_line_to_window R3 end_y  # the other end's y
param clip_line_to_window R8 unclipped_y  # scratch from clip_line_at_x/_y: the clipped end's y before the clip
param clip_line_to_window R10 outcode  # the start point's (get_window_outcode's), picks the clip routine (table at 09C3D2)
param clip_line_to_window R11 end_outcode  # the end point's: draw_line copies the first get_window_outcode result here
param clip_line_to_right_edge R0 start_x  # moved to bitmap_window_right
param clip_line_to_right_edge R1 start_y  # moved along the line
param clip_line_to_right_edge R2 end_x  # the other end of the line (only read)
param clip_line_to_right_edge R3 end_y  # the other end's y (only read)
param clip_line_to_right_edge R8 unclipped_y  # clip_line_at_x's
param clip_line_at_x R0 start_x  # += dx
param clip_line_at_x R1 start_y  # += (end_y - start_y) x dx / (end_x - start_x); kept when end_x = start_x
param clip_line_at_x R2 end_x  # the other end of the line (only read)
param clip_line_at_x R3 end_y  # the other end's y (only read)
param clip_line_at_x R5 dx  # how far start_x moves: the window edge - start_x (clip_line_to_left_edge/_right_edge)
param clip_line_at_x R8 unclipped_y  # out only: start_y before the clip (the code's temporary)
param clip_line_to_top_edge R0 start_x  # moved along the line
param clip_line_to_top_edge R1 start_y  # moved to bitmap_window_top
param clip_line_to_top_edge R2 end_x  # the other end of the line (only read)
param clip_line_to_top_edge R3 end_y  # the other end's y (only read)
param clip_line_to_top_edge R8 unclipped_y  # clip_line_at_y's
param clip_line_to_bottom_edge R0 start_x  # moved along the line
param clip_line_to_bottom_edge R1 start_y  # moved to bitmap_window_bottom
param clip_line_to_bottom_edge R2 end_x  # the other end of the line (only read)
param clip_line_to_bottom_edge R3 end_y  # the other end's y (only read)
param clip_line_to_bottom_edge R8 unclipped_y  # clip_line_at_y's
param clip_line_at_y R0 start_x  # += (end_x - start_x) x dy / (end_y - start_y); kept when end_y = start_y
param clip_line_at_y R1 start_y  # += dy
param clip_line_at_y R2 end_x  # the other end of the line (only read)
param clip_line_at_y R3 end_y  # the other end's y (only read)
param clip_line_at_y R5 dy  # how far start_y moves: the window edge - start_y (clip_line_to_top_edge/_bottom_edge)
param clip_line_at_y R8 unclipped_y  # out only: start_y before the clip (the code's temporary)
param clip_rect_to_window R0 left  # sorted with right (smaller first), then raised to bitmap_window_left
param clip_rect_to_window R1 top  # sorted with bottom, then raised to bitmap_window_top
param clip_rect_to_window R2 right  # lowered to bitmap_window_right
param clip_rect_to_window R3 bottom  # lowered to bitmap_window_bottom
param clip_rect_to_window R10 unclipped_edges  # result: F with bit 0/1/2/3 cleared when left/right/top/bottom was clipped; 0 when wholly outside (fill_rect then draws nothing; note: also when all four edges were clipped)
param attract_warning R4 pixels  # step 0: load_text_font's R4 handed back (expand_font's last row); other steps leave the caller's; run_frame only hands it on
param attract_warning R10 text  # step 0: the warning's print_text_list records (104BD2), past them at the end (print_text_list R10's name); init_sprites' R10 before that
param load_settings R0 crc  # compute_eeprom_checksum/compute_eeprom_backup_checksum results it compares; what callers pass (init_sound's 0) only goes to eeprom_read_all, where it is shifted out of the words
param eeprom_read_all R0 data  # eeprom_read_word's word, stored at 20F600 + 2n; what is passed in only ends up above bit 16 of the first word and is not stored
param eeprom_save_and_lock R4 command  # eeprom_save's, handed back (80: the write command after the last address); eeprom_lock really leaves 00 in its low byte (see the final message)
param eeprom_save_and_lock R5 words_left  # eeprom_save's word counter: 0
param eeprom_save R4 command  # 93C46 command byte: 30 (write enable), then 40 + address for each of the 40 words; 80 at the end (before it R4 holds compute_eeprom_checksum's polynomial)
param eeprom_save R5 words_left  # 40 words, counted down to 0
param eeprom_read_word R0 data  # out: the word read (18 clocks of port F bit 7 shifted in, >> 9); bits 16 and up are garbage
param eeprom_read_word R2 lines  # port D value written (chip select bit 5, clock bit 6, data in bit 7); handed back, chip select off
param eeprom_read_word R4 command  # read command: 80 + word address
param eeprom_read_word R12 port_f  # C0000A: bit 7 is the EEPROM's data out
param eeprom_read_word R13 port_d  # C00006: where lines go
param eeprom_write_word R0 data  # the word to write, sent high bit first; then the scratch bit and port F reads of the busy wait
param eeprom_write_word R2 lines  # port D value; returned with chip select off
param eeprom_write_word R4 command  # write command: 40 + word address
param eeprom_write_word R12 port_f  # read until bit 7 (ready) is set
param eeprom_write_word R13 port_d  # C00006: where lines go
param eeprom_send_command R2 lines  # port D value: start bit sequence, then each command bit in bit 7 with a clock pulse (bit 6); returned
param eeprom_send_command R4 command  # the 8 command bits after the start bit (00 lock, 30 write enable, 40+ write, 80+ read)
param eeprom_send_command R13 port_d  # C00006: where lines go
param eeprom_select R2 lines  # out: port D's current value (low byte), the base the other routines toggle bits on
param eeprom_select R12 port_f  # out: C0000A
param eeprom_select R13 port_d  # out: C00006
param compute_eeprom_backup_checksum R0 crc  # compute_eeprom_crc of 764A-767F
param compute_eeprom_checksum R0 crc  # compute_eeprom_crc of 760A-763F (load_settings and eeprom_save store it in eeprom_checksum)
param compute_eeprom_checksum R4 polynomial  # compute_eeprom_crc's, handed back
param compute_eeprom_crc R0 crc  # CRC of 36 bytes from R20 (polynomial 1021, seeded DEBDEB), in the low half
param compute_eeprom_crc R4 polynomial  # 10210000, left in R4 (compute_crc16's name)
param eeprom_deselect R2 lines  # bits 5-7 cleared (chip select, clock, data in off), written, returned
param eeprom_deselect R13 port_d  # C00006: where lines go
param eeprom_set_lines R0 data  # not changed ("and.b #FF, R0" only sets the flags): the data word or bit its callers keep in R0, returned as it came
param eeprom_set_lines R2 lines  # written to port D
param eeprom_set_lines R13 port_d  # C00006
param start_test_mode R4 color  # clear_bitmap_layer's (0, replicated); run_test_mode's first frame (test_menu) leaves it
param start_test_mode R9 blocks_per_row  # clear_bitmap_layer's, handed back the same way
param get_trackball_quadrant R1 dx  # a trackball's x count this frame (trackball_n_x)
param get_trackball_quadrant R2 dy  # its y count
param get_trackball_quadrant R3 quadrant  # result in the low byte: 1 (dx >= 0, dy >= 0), 2 (dx >= 0, dy < 0), 4 (dx < 0, dy >= 0), 8 (both < 0); the input test ORs them until F ("GOOD")
param test_crt R4 color  # each colour bar's crt_test_color, then 7E for the crosshatch lines
param test_crt R9 blocks_per_row  # only handed to fill_rect (its leftover)
param test_game_assignments R9 blocks_per_row  # only handed to draw_difficulty_setting/draw_vitality_setting (fill_rect's leftover)
param draw_vitality_setting R4 color  # low byte 17 for the bar, then 49 for its filled part (fill_rect)
param draw_vitality_setting R9 blocks_per_row  # fill_rect's, handed back
param draw_difficulty_setting R4 color  # 17, then 49, as draw_vitality_setting
param draw_difficulty_setting R9 blocks_per_row  # fill_rect's, handed back
param draw_coin_setting_number R0 setting_number  # 1-based COIN/CREDIT SETTING number (setting_coin_number + 1 or individual_coin_setting_numbers[n]): convert_to_bcd, then draw_hex_2 at 31F4BE
param clamp_coin_setting R10 setting  # address of a setting byte (20F60F coins per credit, 20F612 bonus adder, 20F610/20F611 chute multipliers), kept in 1-9
param draw_coin_credit_table R4 coins  # 9 down to 1 for draw_coin_credit_cell: 0 when it returns
param draw_coin_credit_cell R1 multiplier  # the chute's setting_chute_n_multiplier (1 coin counts as n coins); then x coins
param draw_coin_credit_cell R4 coins  # coins inserted (1-9): credits = coins x multiplier (with bonus adder) / coins per credit, max 9
param draw_test_hex_4 R0 value  # draw_hex_4's (test_bookkeeping: convert_to_bcd's value)
param draw_test_hex_4 R11 text_position  # draw_hex_4's
param draw_test_hex_2 R0 value  # draw_hex_2's
param draw_test_hex_2 R11 text_position  # draw_hex_2's
param test_backup_clear R4 polynomial  # only set by reset_ranking (copy_ranking_records' polynomial, handed back) when YES is chosen; otherwise the caller's leftover
param flip_test_coordinates R0 left  # x -> 140 - x (not re-sorted: clip_rect_to_window sorts)
param flip_test_coordinates R1 top  # y -> E0 - y
param flip_test_coordinates R2 right  # x -> 140 - x
param flip_test_coordinates R3 bottom  # y -> E0 - y
param check_moving_seat_dip R5 words_left  # eeprom_save_and_lock's (0) when it saves; otherwise init_system's leftover (load_text_font's R5) comes back
param run_vblank_copies R0 source  # each entry's source (+0)
param run_vblank_copies R2 dest  # each entry's destination (+4; top byte = how many copies), continuing after each copy
param run_vblank_copies R3 copies_left  # vblank_copy_count, counted down: 0 at the end (or the count when it was <= 0)
param run_vblank_copies R4 copy_entry  # walks vblank_copies (20EE02), 10 bytes per entry
param count_frames_and_check_test_switch R4 polynomial  # only set by update_bookkeeping_checksum (every 64 frames); otherwise irq_vblank_start's leftover
param update_coins R4 chute_offset  # add_coin's R4, cleared by "xor.w R4, R4" before use: what is passed in is not used (the xor is why it is a parameter)
param pulse_coin_counter R1 counter_bit  # port D bit of the counter (0, 1)
param pulse_coin_counter R8 service_credits  # update_coins' count of service credits this frame: save_credits when not 0
param pulse_coin_counter R9 coin_credits  # the count of credits the coins gave: save_credits when not 0
param add_service_credit R8 service_credits  # + 1 when the block gets a credit (update_coins adds the total to bookkeeping_service_credits)
param add_service_credit R11 max_credits  # 9: no credit at or above it; reaching it clears coins and bonus coins
param add_coin R4 chute_offset  # 0 or 4: picks the multiplier (chute_1_multiplier + it)
param add_coin R5 pending_offset  # R25 offset of the coin counter's pending coins (7408 or 740C), + 1
param add_coin R7 bookkeeping_chute  # bookkeeping_coin_chute_1/_2/_3 (701F06/08/0A), + 1
param add_coin R9 coin_credits  # + 1 per credit the coins make (update_coins adds the total to bookkeeping_coin_credits)
param add_coin R11 max_credits  # 9, as add_service_credit
param copy_credit_counts R12 dest  # saved_credits (701F2E) or its backup (701F6E): three halfwords
param update_bookkeeping_checksum R4 polynomial  # compute_bookkeeping_crc's, handed back: callers ignore it
param compute_bookkeeping_crc R0 crc  # compute_crc16's result (load_bookkeeping compares it, update_bookkeeping_checksum stores it)
param compute_bookkeeping_crc R2 data  # 701F02 or the backup 701F42; past the 40 bytes at the end (load_bookkeeping uses that)
param compute_bookkeeping_crc R4 polynomial  # compute_crc16's
param compute_crc16 R0 crc  # result in the low half (seed DEBDEB, 8 extra shifts)
param compute_crc16 R2 data  # bytes read from here, advanced past them
param compute_crc16 R3 count  # how many bytes: 0 at the end
param compute_crc16 R4 polynomial  # 10210000 (1021 << 16), left in R4
param compute_crc16_alternate_bytes R0 crc  # result in the low half
param compute_crc16_alternate_bytes R3 count  # bytes read (40000 / 3FFFF)
param draw_attract_credits R11 text_position  # 310000 | each credit_position_* / start_prompt_position_*, then where the last text ended
param blink_start_prompt R2 blink_bit  # frame_counter bit that blinks the prompt: 5 - frames_per_wait, + 1 to alternate the two texts with credits_per_start 2
param blink_start_prompt R10 text  # first which prompt (0 "Push start button", 1 "Insert more coin", 2 "Insert coin", 3 "2 credits start"), then that text; print_text_lines' / erase_text_lines' at the end
param blink_start_prompt R11 text_position  # where the prompt goes (310000 | start_prompt_position_*); after it at the end
param draw_credit_count R2 coins_per_credit  # draw_credit_count_now's, handed back
param draw_credit_count R11 text_position  # 310000 | the block's credit position
param draw_credit_count_now R2 coins_per_credit  # credit_coin_cost: draw_fraction's denominator (no fraction when it is 1)
param draw_credit_count_now R10 text  # "Free play" (FFFC0) or "credit" (FFFAE) for print_text_here; past it at the end
param draw_credit_count_now R11 text_position  # stored in credit_text_position; moved along the text, digits and fraction
param clip_line_to_left_edge R0 start_x  # moved to bitmap_window_left
param clip_line_to_left_edge R1 start_y  # moved along the line
param clip_line_to_left_edge R2 end_x  # the other end of the line (only read)
param clip_line_to_left_edge R3 end_y  # the other end's y (only read)
param clip_line_to_left_edge R8 unclipped_y  # clip_line_at_x's

# ======== fields of each object type (scoped lines, from the helpers) ========
scoped object animate_hud 25 frame u8  # current entry of the list at 28: + 1 a step, back to 0 (R8 = -1) past the list's last index; the player block calls it hud_gauge_frame / hud_portrait_frame / hud_ring_effect_frame
scoped object animate_hud 26 anim_delay u8  # frames per entry: reloads 27
scoped object animate_hud 27 anim_timer s8  # counted down every call; the next entry when it was <= 1 (signed)
scoped object animate_hud 28 anim u32 hud_step_list  # the list: a word (its last index), then palettes (animate_hud_palette -> sprite_palette) or sprite words (animate_hud_tiles -> sprite_frame); 0xCD37, 0xCD4D... (hud_gauge_anim)
struct hud_step_list 2 rom                 # what animate_hud steps through: its last index, then a word a step: the gauge's and the portrait's palettes (hud_portrait_hurt_palettes_*), or the ring effect's sprite frames (animate_hud_tiles); player_hurt_hud reads the first step in place (2[28[R]])
field hud_step_list 00 last_index u16      # the last step's number
field hud_step_list 02 steps u16[]         # the palettes or sprite frames, a word each
scoped object ending 30 started u8  # 0 when spawned (the spawners clear it); set to 1 (or + 1) on the first frame, which sets the object up; ending_horizon_show sets 2 once it has spawned ending_dust_cloud_rise
scoped object ending 31 step u8  # per object: the credit blocks' "scrolled out" flag (1: parts freed, removed), ending_credits_fade_in's "music started", ending_window_open's four clip-edge bits (flags bits 8-11, done at F), the entry of ending_eggman_turn_frames, ending_eggman_fly_away's step (switch 0-7)
scoped object ending 32 timer s16  # frames: cleared on the first frame, + 1 a frame (time tables, timed lists, music cues and the credit scroll compare it signed); ending_eggman_fly_in counts it down from 1E0 (sound 75 at 104, puffs drawn while > 32)
scoped object ending 44 part_3 u32 object  # the third second-pool part it allocated (allocate_object_2, freed with free_object_2): a credit block's third part (its part list stores the parts in 38, 40, 44, 48...), ending_eggman_fly_in's second smoke puff (34 flame, 40-5C puffs)
scoped object ending 60 frame_index u8  # entry of the object's frame table: ending_big_explosion (+ 1 every 6 frames), the forward runners (ending_*_run_frames, animate_ending_runner), the blasts (start_ending_blast clears it)
scoped object ending 61 list_index u8  # entry of its timed list: run_timed_spawns_2/3 (ending_tower_animate and ending_credits_spawner_run clear it), ending_music_cues (ending_music_play)
scoped object ending 62 cycle_count s16  # a second counter that wraps: forward runners at 20 (1 pixel closer), ending_road_scroll after 4 (index into ending_road_frames), ending_credits_fade_in at 3 (one fade step); the across runners' leg counter (& 3, init_ending_runner_across clears it)
scoped object animate_ending 32 timer s16  # as ending 32: animate_ending_debris adds 1 with an inc.b on the word (original code) and shows the first path record whose time is above it; animate_ending_runner zooms in when it & 3 is 2; animate_ending_runner_across (object_cursor) keeps it & 7, body frame by >> 2
scoped object animate_ending 60 frame_index s8  # as ending 60: animate_ending_runner's run frame, + 1 every other frame (timer bit 0), 0 after B (signed)
scoped object animate_ending 62 cycle_count u16  # as ending 62: animate_ending_runner_across's leg frame counter (object_cursor, & 3, legs table at 50)
scoped object animate_horizon_blast 60 frame_index u8  # as ending 60: entry of ending_horizon_blast_frames (removed at FFFF)
scoped object animate_tower_blast 60 frame_index u8  # as ending 60: entry of ending_tower_blast_frames (removed at FFFF)
scoped object draw_ending_blast_frame 32 timer s16  # as ending 32: + 1 a frame, back to 0 at 6 (signed)
scoped object draw_ending_blast_frame 60 frame_index u8  # as ending 60: + 1 every 6 frames
scoped object biting_head 48 bite_step u8  # entry of biting_head_bite_steps (jaw frame for part 3, frames to hold), 0 after the last index (5); step 5 starts the bounce (4B = 0)
scoped object biting_head 49 bite_timer s8  # frames left of the bite step (the step's byte +2); the next step when it was <= 1 (signed)
scoped object biting_head 4A bob_counter u8  # + 1, & 3 every frame while it moves (not holding back, chaser_speed non-zero): at 0 it is drawn 1 pixel higher
scoped object biting_head 4B bounce_index s8  # FF = no bounce (biting_head_init); 0 at bite step 5, then + 1 a frame through biting_head_bounce (added to screen_y) until its 7F end, FF again
scoped object blink_current_map_marker 30 step_timer u16  # the map screen object's step timer (map_screen_update sets 12C before this step): counted down to 0; the stage's marker is drawn while its bit 3 is set (blinks every 8 frames)
scoped object board_prop 80 draw_depth u16  # screen_y - B while the propped board's speed_z is 0, kept once the board falls: its queue_sprite_at_depth depth
scoped object boulder 80 wait_timer s16  # boulder_start 78 (one mov.h): after scroll_position 3800 boulder_wait counts it down and rolls when it was <= 1 (or at once at 3B00)
scoped object brick_dust 80 wait_timer s16  # brick_wall_dust word +3 (brick_wall_wait): frames before the puff plays brick_dust_animation; then 0
scoped object bubble_start 80 base_x u16  # x_int at the start: bubble_pop puts x_int at it + random 0-F after each pop (lava_column_land sets x_int + 40 for lava bubbles)
scoped object bubble_start 82 base_y u16  # y_int at the start: the same in y
scoped object bubble_pop 80 base_x u16  # as bubble_start 80: x_int = it + random 0-F after each pop (not kind 6)
scoped object bubble_pop 82 base_y u16  # as bubble_start 82: y_int = it + the same random
scoped object build_grate_steps 80 rows_left s16  # grate_steps_init F: one row of 3 steps a frame, removed after the last (dec.h, signed)
scoped object build_grate_steps 82 row_projected_y s32  # projected_y (16.16) where the next row starts (grate_steps_init: its own); + half of the row's x shift (the floor attribute's x_per_rise x 1E >> 1) after each row
scoped object build_grate_steps 94 row_projected_x s32  # 16.16 projected x where the next row starts (grate_steps_init: projected_x, 94 = 0); + the floor attribute's x_per_rise x 1E after each row
scoped object build_grate_steps 96 row_projected_x_whole s16  # the whole part of row_projected_x: each row's first step starts at it (projected_x)
scoped object build_saw_shaft_base 94 fixed_projected_x s32  # as rising_saw 94: 16.16 projected x of the next piece (projected_x, 94 = 0), + the floor attribute's x_per_rise x 1D per piece
scoped object build_saw_shaft_base 96 projected_x_whole s16  # the whole part of fixed_projected_x: each piece's projected_x
scoped object build_ring_tally_panel 44 part_3 u32 object  # the third second-pool part (sprite 9E90 at 40,70); the others go to 38, 40 and the digits to 48, 4C, 50
scoped object build_ring_tally_panel 67 gets_bonus u8  # start_ring_tally: 1 when the player gets the ring bonus (pick_ring_bonus_players, stage bonus percentage reached); 0: label sprite 9EF8 instead of 9EF0, update_ring_tally_panel skips the bonus steps
scoped object burning_boulder 97 rumble_timer u8  # frames to the next sound D while the leader rolls (state 1): 3C at the start and while holding back, 0 when the hold ends; counted and reloaded (animation_speed x A) with 16-bit dec.h/mov.h at 97-98, 98 (start_delay) being 0 by then
scoped object burning_boulder 9A draw_depth u16  # screen_y - depth_offset in states 1 and 2: its queue_sprite_at_depth depth; burning_boulder_flame_flicker draws the flames at the parent's 9A + 1
scoped object catapult 34 state u8  # catapult_update's jump table: 0 wait for a rider, 1 swing up, 2 hold up, 3 swing down, 4 wait for scroll (5 = 0); before that catapult_x_start/y_start keep the object_pair_templates offset (0 / 1A) here for catapult_wait_for_platform
scoped object catapult 36 launch_position u16  # scroll_position + catapult_launch_delays[04] (catapult_wait_for_platform): catapult_wait_for_scroll launches once scroll_position reaches it
scoped object catapult 38 launch_kind u8  # the template's kind (6: launches along +x, 7: along +y): put back in kind (47) when it is ready (no delay, or back down); kind is 5 meanwhile
scoped object catapult 39 rides_platform u8  # FF once it stands on a platform (catapult_wait_for_platform): catapult_update copies the platform's z every frame; its dummy part then gets flag 8 and the platform
scoped object centipede_tail 92 segment_ahead u32 object  # centipede_init: the body segment in front of the tail; centipede_tail_follow aims at it (direction_to_object) for centipede_tail_frames
scoped object character_chooser 12 name_changed u8  # 1 when a turn ends on a free character (character_chooser_turn): draw_character_chooser_labels shows that character's name label and clears it
scoped object character_chooser 13 turn_direction s8  # +1 / -1: the trackball's right minus left turns (the high bytes of right_count and left_count, character_chooser_wait); character_chooser_turn adds it to character and moves the portraits by it; start_character_chooser sets 1
scoped object chaser_back_zone 80 end_position u16  # object_steps record (spawn_chaser_back_zone): removed once scroll_position reaches it
scoped object check_player 80 palette_list u32 player_palette_list  # as player 80: check_player_floor_edge and check_player_contacts put its word +2 (the normal palette) back
scoped object checker_pad 80 frame_index s8  # entry of checker_pad_frames: + 1 every 9 frames, 0 after 4 (signed); the frame read uses it as a 16-bit index
scoped object checker_pad 82 frame_timer u16  # counted down; when it was 0 the next frame and 8 again
scoped object collapsing_bridge_plank 6E state u8  # jump table: 0/3 set up (angle 300), 1 sagging bridge plank, 2 hanging from the previous plank (plank 0: falling, then swinging)
scoped object collapsing_bridge_plank 6F plank_index u8  # its place in the bridge (- 1 on the first frame): index into bridge_0/1_sag, roll_frames = it x 2; plank 0 leads (sound 9, falls and swings), the others follow previous_plank
scoped object collapsing_bridge_plank 82 angle_whole s16  # the whole part of angle (& 3FF): held at 200 (straight down) when below it; indexes sine/cosine for the hanging offset; bridge_head_angle copies it while plank 0 falls
scoped object collapsing_bridge_plank 95 bridge u8  # 0 or 1: bridge_0_sag or bridge_1_sag (as rope_bridge_plank 95)
scoped object crawler 92 variant u8  # the record's 92 low byte: 0/1 picks crawler_head/tail_frames, crawler_walk_steps, boxes and offsets; 1: the head notices players from 35 (else 28), the tail draws 8 lower
scoped object crawler 93 on_platform u8  # the record's 92 high byte: set: with no floor under it (fall_to_floor R1) the piece draws at its platform's (74) draw depth (88) - F, without its shadow
scoped object crumbling_floor 6E state u8  # jump table: 0/3 first frame (projects), 1 platform (cracks near the leader, falls when it passes), 2 falling (gravity 2000)
scoped object door_wall 80 fragment u32 door_fragment  # door_fragments record (08334F, movea in door_wall_start): each piece reads its word +8 (animation offset into rock_debris_animations), then + A
scoped object door_wall 84 pieces_left s8  # 10: pieces still to throw when it bursts (signed test)
scoped object door_roller 80 state u8  # door_roller_update's jump table: 0/4 wait, 1 drop, 2 roll out, 3 ride; drawn from state 1
scoped object door_roller 83 off_screen u8  # FF once it is below screen y 130 (door_roller_ride): door_roller_update removes it; spawn_door_rollers clears it (82-83 hold the 8-frame roll-out timer before, high byte 0)
scoped object drop_icicle_row 80 path u32 icicle_dropper_step  # as icicle_dropper 80: the word before it is the script's end coordinate the row's icicles line up on (flag 1, their 84)
scoped object drop_icicle_row 96 rides_platform u8  # as icicle_dropper 96: rows on the ice platform do not line up; the dropper takes palette 87A0
scoped object eel_init 92 facing u8  # the record's 92 low byte (on the anchor): 0 direction 34A, else 14A; the pieces get 92-93 in their 94-95
scoped object eel_init 94 piece_count u8  # the record's 94 low byte: pieces to spawn
scoped object eel_init 95 wave_step u8  # the record's 94 high byte: the first piece's wave amplitude (its 92); each next piece's is that much larger (8, 10, 18...)
scoped object eel_anchor 92 facing u8  # as eel_init 92: only facing 0 eels test the chaser distance in eel_anchor_wait_explode
scoped object eel_anchor 93 den_number u8  # the record's 92 high byte: the stage_trigger_bits bit its den sets (eel_den_wait); once set the eel explodes
scoped object eel_anchor 94 piece_count s8  # pieces left: eel_piece_explode takes 1 off its anchor's; at 0 the eel is removed with its pieces (signed)
scoped object eel_head 94 facing u8  # the anchor's 92: 0 eel_head_frames, else eel_head_retract_frames / eel_head_extend_frames by the stretch direction
scoped object eel_head 95 den_number u8  # the anchor's 93: its stage_trigger_bits bit starts the explosion delay
scoped object eel_segment 95 den_number u8  # as eel_head 95
scoped object eggman_speech 80 frame_count s16  # + 1 a frame (not in game mode 11); the record runs when it reaches the record's delay (signed); 0 after each record
scoped object eggman_speech 82 script u32 eggman_speech_step  # next eggman_speech_scripts record (delay, command, arguments); eggman_speech_start takes it from the table by kind (04)
struct eggman_speech_step 4 rom              # a record of an eggman_speech_scripts script (085DE3 + the offset): a delay and a command word, then the command's words (none for 0, 1, 4, 7; screen x, screen y, message kind, frames for 2; the sound for 3); eggman_speech_run 08577C "mov.w 82[R20], R12", 085781 "cmp.h [R12+], 80[R20]", 085789 "movz.hw [R12+], R0", the arguments with [R12+] (0857B4-0857BD, 0857D1), script = past them (0857A3, 0857C0, 0857D4); census ROM 085DEB-085E4A
field eggman_speech_step 00 delay s16          # the frame_count it runs at ("cmp.h [R12+], 80[R20]; blt", signed): 10, C, 0
field eggman_speech_step 02 command u16        # "movz.hw [R12+], R0", x 2 into the jump table at 085794: 0 end, 1 spawn_eggman_booth, 2 spawn_message_box, 3 play_sound, 4 end, 7 stays
field eggman_speech_step 04 arguments u16[]    # the command's words: 2: screen_x, screen_y, kind, frames (spawn_message_box's R5-R8: A0, 40, 11, 88); 3: the sound (play_sound's R0: 74, 3B, 7A)
elements eggman_speech_arguments u16         # the words after an eggman_speech_step's command (its arguments): eggman_speech_run reads them with [R12+] from 085789 on (R12 past the command word): x, y, kind, frames for command 2 (0857B4-0857BD), the sound for 3 (0857D1)
scoped object escape_alarm_flash 30 started u8  # 0 on its first frame: sets bit 4000 of mixer_nbg0-3_blend, mixer_bitmap_blend and escape_alarm_stray_flags, colour offsets red 0 / blue 40 / green 40, clears 32 and 61; then 1
scoped object escape_alarm_flash 32 step_timer s16  # + 1 every frame; at 2 (cmp.h + blt) back to 0 and the blue and green colour offsets move one step
scoped object escape_alarm_flash 61 counting_up u8  # 0: color_offset_blue and _green count down to 28, then 1: up to 3F, then 0 again; sound 19 at each turn
scoped object falling_ember 5E floor_block_high s16  # high half of floor_block (test.h 5E): negative when find_floor found no block (-1): it then falls to z 530; landing on a block whose slope or type is set picks falling_ember_slope_burst_animation
scoped object falling_grate 6E state u8  # falling_grate_update's jump table (movz.bw / inc.b 6E): 0 start (rattle_step 1), 1 platform rattling while the leader (parent) is within 60, until the leader passes it (sound D, falling_grate_animation), 2 falls (speed_z + 2000 a frame) until screen y 1C0
scoped object fence_post 92 extra_posts u8  # word 92 low byte: fence_post_init lines up this many more posts after this one (dec.w + bl), each fence_post_offsets[row_direction] further on
scoped object fence_post 94 row_direction u8  # word 94 low byte, 0-3: indexes fence_post_offsets, fence_post_boxes, fence_post_box_offsets, fence_arc_flags (fence_post_init) and fence_arc_offsets (fence_post_update, through the cursor); the next posts get a copy of word 94
scoped object fence_post 95 arc_phase u8  # word 94 high byte: the fence_arc_frames step every arc of the row starts at (fence_post_init writes it into each arc piece's direction word)
scoped object fire_trench_start 80 flame_timer s16  # mov.h #0: the first fire_trench_update spawns flames at once
scoped object fire_trench_update 80 flame_timer s16  # frames to the next spawn_fire_trench_flames (dec.h + bgt), then 12C again (the C showed var_80 = 2C, var_81 = 1)
scoped object fireball_fly 24 arc_angle u32  # angle of its hop (400 a turn): + 9 a frame (E in stage C) with add.w (original code; the low half, direction, never carries into parent at 26); z = floor_z - sine_table[low half & 3FF] x 140; init_fireball_flight clears the low half
scoped object flame_machine_arm 24 side s8  # low byte of that word, +1 / -1 (movs.bh): multiplies flame_machine_arm_sway_offsets (flame_machine_arm_draw)
scoped object flame_machine_arm 25 arm_index u8  # high byte of that word, arm 0 or 1: flame_machine_flags bits A+n (take a disc), 4+n (active), 6+n (swaying); the spiked disc's 92; its piece goes in bucket n x 4
scoped object flame_machine_arm 84 sway_toggle u8  # + 1 & 1 every frame while it sways: a sway step every other frame. The word at 84 is also the 1E-frame rest timer (set by flame_machine_arm_update, counted down by flame_machine_arm_rest: 3 uses against 6 of the bytes), which leaves 84-85 at 0 for the next sway
scoped object flame_machine_arm 85 sway_count s8  # + 2 per sway step (the step itself is 81, mod 29): past 26 (signed) flag 9 (discs dropped) and flame_machine_arm_rest
scoped object flame_machine_update 24 previous_health s8  # the box's health (7E) as it last saw it (s8 like health): when they differ (a hit) it takes the new value, sets flame_machine_flags bit C (hit flash), flame_machine_flash_step 4 and plays sound 21 (init_object leaves it 0 while health starts at 28)
scoped object geyser_front 68 next_listed_geyser u32 rom  # geyser_front_init: geyser_lists + the front's offset; spawn_listed_geyser spawns that record (position, x, y, z, start position) once scroll_position reaches its position and steps on (the front itself is never in a hit list: its child box is)
scoped object geyser_front 7E sound_timer s16  # geyser_front_rise: counted down while screen_y <= 130, at 1 sound 12 and reloaded with 32 as two bytes (health = 32, 7F = 0); geyser_front_init clears both bytes
scoped object geyser_front 9A projected_x_whole s16  # high word of fixed_projected_x: geyser_front_init sets it to projected_x (and the low word 98 to 0), geyser_front_rise copies it back to projected_x
scoped object group_leader_move 34 screen_threshold s16  # (whole field) group_leader_data entry word: base_speed while screen_x is on its side (compared signed), else the catch-up speed; FF90 / 1B0 (by direction) while holding back
scoped object group_leader_move 36 catch_up_factor u16  # (whole field) entry word: camera_ground_speed x it >> 8 when behind the threshold
scoped object group_leader_move 38 base_speed s16  # (whole field) entry word: the speed (0 while holding back); chaser_speed gets the larger of it and the catch-up speed
scoped object group_leader_move 3A saved_threshold s16  # (whole field) screen_threshold kept while holding back, put back when it stops
scoped object group_leader_move 3C saved_base_speed s16  # (whole field) base_speed kept while holding back, put back when it stops
scoped object group_leader_move 3E holding_back u8  # FF while it holds back (no_player_in_play / player_mishap / player_dropping_in, and stage 8 in game modes 17/1D): the threshold moves off screen and base_speed is 0, until 300 scroll units or hold_timer run out; spawn_group_leader clears it
scoped object group_leader_move 48 path u32 chase_step_at_scroll  # (whole field) next group_leader_data entry (position, threshold, factor, speed, delay; a negative factor ends it): spawn_group_leader sets it, each entry is read once scroll_position reaches its position
scoped object handle_rod 85 angle_speed_int s8  # whole part (high byte) of angle_speed (8.8): handle_rod_swing adds it to angle every frame of the swing
scoped object icicle_dropper 9A platform_offset_int s16  # whole part of platform_offset: icicle_dropper_move compares it (signed) with the script record's end coordinate while it rides the ice platform
scoped object lava_rock 6E state u8  # lava_rock_update's jump table: 0/4 set up, 1 waits for the leader or its group slot, 2 bobs and rises, 3 the lava column erupts and the rock flies up and breaks (spawn_object_groups clears it)
scoped object lava_rock 6F member u8  # its number in the group (spawn_object_groups' group_member_number): after a slot-triggered eruption (takes_slot 1) only member 0 (with sound 7) and member 2 spawn lava columns
scoped object lava_rock 70 erupt_delay s16  # (whole field, shown as speed_int) group_slots[group_slot] & 7FFF once the slot is triggered; counted down in state 2, at 1 it erupts and takes_slot becomes 1
scoped object machine_fire 48 move_frames s16  # states 3-4: cleared, + 1 a frame while it moves +x one pixel a frame; state 5 at 80
scoped object machine_fire 72 sound_timer s8  # frames to the next sound 9 (reloaded with 3C; compared signed); cleared when it comes back on screen. It is never in a platform list
scoped object machine_fire 73 off_screen u8  # FF while below the screen (screen_y > 160; also set when it becomes the fire): coming back clears it and the sound timer (as bridge_fire 98/99)
scoped object player 5C floor_block_low s16  # low half of floor_block: player_launched_fly lands only while it is >= 0 (test.h 5C: on a floor block)
scoped object player 5E floor_block_high s16  # high half of floor_block: player_animate tests its sign (test.h 5E, three places: >= 0 there is a floor block under the player; find_floor sets -1 for none)
scoped object follow_player_path 80 palette_list u32 player_palette_list  # as player 80: follow_player_path puts back the normal palette (word +2) when a path starts
scoped object geyser_wait 80 erupt_delay u16  # spawn_geyser's R15: for row geysers (geyser_front_rise) a geyser_row_delays count of frames, counted down while the front is not holding back; for listed ones (flags bit 31, spawn_listed_geyser) the scroll_position they erupt at
scoped object grate_steps 82 row_projected_y s32  # 16.16 projected y where build_grate_steps starts the next row (it moves it with the floor slope after each row, the row 1E higher); grate_steps_init sets it to projected_y
scoped object grate_steps 96 row_projected_x s16  # (whole field) projected x where each row starts: grate_steps_init sets projected_x (the low word 94 to 0); build_grate_steps moves the 16.16 value in 94-97 along the slope
scoped object grate_step_debris 94 fixed_projected_x s32  # 16.16 projected x: + speed_x every frame; its high word (96) is copied to projected_x
scoped object grate_step_debris 96 projected_x_whole s16  # (whole field) high word of fixed_projected_x: spawn_grate_step_debris sets the step's projected_x + the record's dx (94 = 0)
scoped object group_slot_trigger 80 next_trigger u32 group_slot_trigger_record  # next group_slot_triggers record (scroll position, slot, delay): fired once scroll_position reaches it and the slot is taken; stepping on rewrites only the low half (one AS16 write, the reads and the spawner use 32 bits)
scoped object how_to_play_button_hand 80 next_press u32 how_to_play_button_press  # next how_to_play_button_presses pair (game_mode_timer, frame): at that timer the hand takes the frame and it steps 4; _start writes it as bytes 80, 81 and the halfword 82 (B7 52 08 00 = 0852B7)
struct how_to_play_button_press 4 rom        # a pair of how_to_play_button_presses (0852B7): how_to_play_button_hand_update 083D6B "cmp.h [80[R20]], 65AE[R25]", 083D75 "movt.hb 2[80[R20]], 46[R20]", then 80 + 4 (083D7E); census ROM 0852B7-0852CE
field how_to_play_button_press 00 press_time u16  # the game_mode_timer value it is pressed at (cmp.h, bne): C8, E6, 140, 180, 1B8, 1F4
field how_to_play_button_press 02 frame u16    # its low byte into animation_frame (movt.hb): the part-list step 0, 1, 2, 1, 2, FF
scoped object how_to_play_trackball_hand 82 pause_timer s8  # 8 frames of pause after the hand rubs out (step 1 of 81), counted down (compared signed), then it moves back
scoped object ice_block_row 80 blocks_left s16  # blocks still to place: E (bytes 80/81 every frame) until free_object_stack has that many free objects, then counted down as it places them 5C apart along y
scoped object ice_block 82 state u8  # ice_block_update's jump table: 0/3 project (then 1), 1 solid box in the touchable list until touched (flags bit 2), 2 slides along -x and falls past x -2BA (ice_block_row_start clears it)
scoped object ice_launcher 92 cube_speed_index u8  # spawn word 92 low byte: the ice cube launched at step C gets speed ice_cube_speeds[it]
scoped object ice_launcher 93 step_duration_index u8  # spawn word 92 high byte: frames per animation step = ice_launcher_frame_durations[it]
scoped object ice_platform_leader 8E end_position u16  # the record's first word (spawn_ice_platform_leader): removed once scroll_position reaches it
scoped object ice_platform_piece 6E state u8  # ice_platform_piece_update's jump table: 0/3 set up (member 3, the last piece, gets parts 09356B), 1 rides with the leader (platform list), 2 falls until off screen (spawn_object_groups clears it)
scoped object ice_platform_piece 6F member u8  # its number in the group (spawn_object_groups' group_member_number): x = the leader's x - member x 2D
scoped object ice_platform_piece 92 icicle_landed u8  # FF once an icicle lands on it (icicle_update state 5): it takes parts 09356B and falls; spawn_object_groups puts group_takes_slot here
scoped object init_fireball_flight 96 target_x s16  # (whole field) where the flight ends: speed_x = (it << 16 - x) / the flight frames (36, 24 in stage C); machine_fire_update sets it in the fireballs it spawns, spreading_flame_burn in itself before start_fireball_flipped
scoped object init_fireball_flight 9A target_y s16  # where the flight ends in y: speed_y = (it << 16 - y) / the flight frames; machine_fire_update and spreading_flame_burn write it as a halfword
scoped object lava_column 80 base_x s16  # lava_column_land: x + 40 (bytes 80/81 = 40, then + x_int), where bubble_pop puts the bubble it turns into (+ random 0-F)
scoped object lava_column 82 base_y s16  # (whole field) lava_column_land: y, bubble_pop's base y
scoped object lava_column 90 standing s8  # FF once it rose to z 570 and became lava_columns[8C]: it stops rising, draws at depth FFF1, and removes itself when a newer column takes that side (spawn_lava_column clears it)
scoped object map_intro 30 timer s16  # 0 from run_map_screen; + 1 in step 0 (map_screen_active = FF from 1E: never, step 0 runs once); step 2 would set 3C, the frames of the icon's flight (move_map_stage_icon counts it down), but steps 1-3 never run
scoped object map_intro 32 base_x s16  # icon x (run_map_screen: window_wipe_left); step 6 passes base + offset to the map_screen_update object
scoped object map_intro 34 base_y s16  # (whole field, shown as speed_x_fraction) icon y (run_map_screen: window_wipe_top)
scoped object map_intro 36 step_x s32  # 16.16 x step a frame: (20 - base_x) << 16 / 3C, the flight to the corner
scoped object map_intro 3A step_y s32  # 16.16 y step a frame: (10 - base_y) << 16 / 3C
scoped object map_intro 3E offset_x s32  # 16.16 x moved so far (run_map_screen clears it)
scoped object map_intro 42 offset_y s32  # 16.16 y moved so far (cleared as z_int, animation_speed and animation_timer)
scoped object move_map_stage_icon 30 timer s16  # frames left of the icon's flight: counted down while it moves, the caller's step advances at 0
scoped object move_map_stage_icon 32 base_x s16  # icon x before the flight: screen_x = (offset_x >> 16) + it
scoped object move_map_stage_icon 34 base_y s16  # (whole field, shown as speed_x_fraction) icon y before the flight
scoped object move_map_stage_icon 36 step_x s32  # 16.16 added to offset_x every frame of the flight
scoped object move_map_stage_icon 3A step_y s32  # 16.16 added to offset_y every frame of the flight
scoped object move_map_stage_icon 3E offset_x s32  # 16.16 x moved so far
scoped object move_map_stage_icon 42 offset_y s32  # 16.16 y moved so far
scoped object move_map_stage_icon 5B blink_phase u8  # toggled 0/1 every frame: at 0 the icon shows sprite 6538 instead of map_stage_icon
scoped object map_screen 30 timer s16  # step timer, counted down (1, 64, 12C, 28, 3C frames; compared signed in step 2); in step 3 blink_current_map_marker counts it to blink the marker; step 1 tests only its low byte
scoped object map_screen 32 base_x s16  # icon or trail x: from map_intro_update (base + offset), 20 in step 5; the trail's segment start (next_map_trail_segment)
scoped object map_screen 34 base_y s16  # (whole field, shown as speed_x_fraction) icon or trail y (10 in step 5)
scoped object map_screen 36 step_x s32  # step 7: (target_x - base_x) << 16 / 3C for the icon's flight to the next stage
scoped object map_screen 3A step_y s32  # step 7: (target_y - base_y) << 16 / 3C
scoped object map_screen 48 blink_frames s16  # 154: frames the stage marker blinks in step 3 once the trail is drawn (trail_done); at 0 the timer gets 28 and step 4
scoped object map_screen 4A trail_done s8  # FF when the trail has ended or the stage has none (start_map_trail / next_map_trail_segment): step 3 blinks the marker only then
scoped object map_screen 4D target_x s16  # step 7: the next stage's icon position (map_stage_positions[stage_map_markers[stage]]); step 9 puts the icon there
scoped object map_screen 4F target_y s16  # step 7: its y
scoped object map_screen 57 marker_blink_count u16  # draw_map_stage_markers counts it up and shows the stage's badge while its bit 3 is set; step 9 tests its low byte where the other steps test blink_phase (test.b 57 in the original)
scoped object map_screen 5B blink_phase s8  # toggled 0/1 every frame (at 0 the icon shows sprite 6538); step 4 cycles it 0-2 and flips the sprite with its bit 1
scoped object next_map_trail_segment 32 base_x s16  # the segment's start x (the trail point read)
scoped object next_map_trail_segment 34 base_y s16  # (whole field, shown as speed_x_fraction) the segment's start y
scoped object next_map_trail_segment 36 step_x s32  # (base_x - target_x) << 16 / segment_frames: draw_map_trail_dot takes it off offset_x every frame
scoped object next_map_trail_segment 3A step_y s32  # (base_y - target_y) << 16 / segment_frames
scoped object next_map_trail_segment 3E offset_x s32  # cleared for each segment (offset_y too, as z_int and the two animation bytes)
scoped object next_map_trail_segment 4A trail_done s8  # set to FF when the next point's x is negative (the end of the trail)
scoped object next_map_trail_segment 4B segment_frames u16  # frames per segment: the first word of the stage_map_trails list (start_map_trail); divides the steps and reloads draw_map_trail_dot's countdown (55)
scoped object next_map_trail_segment 4D target_x s16  # the segment's end x
scoped object next_map_trail_segment 4F target_y s16  # the segment's end y
scoped object next_map_trail_segment 51 next_trail_point u32 rom  # the trail point after the one passed in (this segment's end, x and y words, in a stage_map_trails list): draw_map_trail_dot passes it back when the segment's frames run out, so the next segment starts there
scoped object map_eggman 30 timer u16  # 3C before the hands appear, 186 of gestures (step 2), then 3 frames per pose (step 3)
scoped object map_eggman 48 hold_frames u16  # step 3: 50 frames it holds pose 3 (the first of them queues stage_map_sounds_2); at 0 stage_map_sounds_3 is queued and the poses cycle 4-5
scoped object map_eggman 51 gesture_list u32 map_eggman_gestures  # 096D78: step 2's gesture bytes indexed by kind (47): sprite 5760 + 8 x (byte - 1); 7 also queues sound 21; a negative byte steps kind back
elements map_eggman_gestures s8       # map_eggman_gesture_frames (096D78) as map_eggman_update reads it by kind (47): the gesture byte, sign-extended (a negative one steps kind back; 7 also queues sound 21); census ROM 096D78-096D86 (01-07 06 03-07 06 FF)
scoped object map_eggman 59 unused_step u8  # cleared when the hands appear, then + 1 every 4 frames in step 2, wrapping at 7 (like the hands' frame_step); nothing reads it
scoped object map_eggman 5D sound_step u8  # 0, 1 once stage_map_sounds_2 is queued, 2 once stage_map_sounds_3 is (map_screen_update clears it in the new object)
scoped object map_eggman_hands 30 timer u16  # F0 frames of hand animation (set by map_eggman_update), counted down; nothing drawn at 0
scoped object map_eggman_hands 59 frame_step u8  # sprite 5798 + 8 x it: + 1 every 4 frames, wrapping at 6 (map_eggman_update clears 59-5A)
scoped object metal_wall 92 last_health u8  # the health it last reacted to (metal_wall_init: 4): when 7E drops below it metal_wall_update shows metal_wall_damage_frames[7E], hops if it lost more than 1, copies 7E here, adds 2 to screen_x (rewritten the same frame) and throws (4 - it) x 2 debris. The debris count reads it as a halfword (movz.hw 92): 3 byte uses, 1 halfword
scoped object metal_wall 94 flipped u8  # spawn word 94 low byte: mirrored (sprite_flags 40) with another hitbox (metal_wall_init); metal_wall_draw adds 10 to its part tables
scoped object metal_wall 95 ring_count u8  # spawn word 94 high byte: rings scatter_rings_random throws when it breaks (none at 0)
scoped object move_object_projected 88 projected_x_carry s32  # 16.16: + speed_x every frame; its whole pixels (8A) are added to projected_x and cleared, the fraction carries to the next frame
scoped object move_object_projected 8A projected_x_carry_int s16  # (whole field) whole part of projected_x_carry: added to projected_x, then cleared
scoped object opening_machine 82 zoom s32  # scene steps 0/1/4: 16.16 zoom (C0 at the start), its whole part (84) is the sprite_scale, growing by zoom_speed. Steps 2/3 reuse it as the distance flown away: + 2 a frame, sprite_scale = 4000 / (it + 10) (the majority use is the zoom)
scoped object opening_machine 84 zoom_int s16  # (whole field) whole part of zoom: copied to sprite_scale; the lunge moves up from 1B3 and ends at 32B, the engulf ends at 5B0 (compared signed)
scoped object opening_machine 86 zoom_speed s32  # 16.16 zoom growth a frame: 8000 (0.5) at the start, + 1000 a frame while lunging, + 10000 while engulfing
scoped object opening_machine 8A phase s8  # opening_machine_update's jump table in scene steps 0/1/4: 0/6 leap in, 1 approach, 2 lunge, 3 show mouth, 4 engulf, 5 hold; below 3 the part list animates (compared signed)
scoped object river_raft_spawner 8C last_raft_y_int s16  # whole part of last_raft_y (nested, as rising_saw 96): river_raft_spawner_update launches the next list once its own y_int <= it (cmp.h 8C, signed) and the waterfall is downstream
scoped object river_water 6E state u8  # river_water_update's jump table (movz.bw / inc.b 6E): 0/4 project, then 1 (2 at once when the waterfall's y_int, its parent's, is already <= its own), 1 waits for that, 2 two clip entries, 3 cut at the waterfall's line until the waterfall is 5C past
scoped object rope_bridge_plank 6E state u8  # rope_bridge_plank_update's jump table: 0/3 set up, 1 sags with the bridge, 2 falls (the leader passed or rope_bridge_collapsing); draw_rope_bridge_ropes tests 2 (slack rope)
scoped object rope_bridge_plank 6F plank_index u8  # group_member_number (spawn_object_groups_2): index into bridge_0_sag / bridge_1_sag (as collapsing_bridge_plank 6F); planks with it & 3 = 0 get the two rope parts (test of speed_fraction & 300)
scoped object rope_bridge_plank 92 takes_slot s8  # group_takes_slot of its record (spawn_object_groups_2): negative: a kind F rider marks its half in the plank's group_slots byte and the plank turns into record 0's plank (signed test), then 1; the stage 10 records never use it
scoped object sand_block 6E state u8  # sand_block_update's jump table: 0/4 project, then 1 (2 at once when the leader's y_int is already <= its y + box - 10), 1 platform until that, 2 bursts out (sand_face_update and sand_streams_update objects, two clip entries, sound 11), 3 cut at the leader's line, removed with its face once the leader passes
scoped object spiked_roller 72 event_offset u16 spiked_roller_event@spiked_roller_events  # offset of its next spiked_roller_events record (spiked_roller_start: spiked_roller_events[kind]): scroll position, then a coordinate (read again as [-1].coordinate once the event fired) and a state; spiked_roller_chase steps it by 8 with add.w #8, 72 (4 of its 8 uses). The rollers never join a list linked through 72
scoped object tornado 72 sound_timer u8  # frames to the next sound 12 while on screen (reloaded with 28): tornado_move, not in game mode 7
scoped object tornado 73 off_screen u8  # FF while off screen (tornado_start sets it; tornado_move: screen x > 180 or < -40 by direction): coming back clears it and sound_timer (as machine_fire 72/73)
scoped object propped_board 34 shake_strength s16  # 3C whenever it is touched (flags bit 2; mov.b #3C, 34: a byte store, the high byte stays 0), - 2 a frame, 0 once it was <= 2 (signed); its >> 4 is the shake in pixels
scoped object propped_board 36 shake_step u8  # inc.b every frame of a shake; bit 0 picks the side (test1 #0, 36)
scoped object propped_board 80 shake_dx s16  # this frame's screen x shift (+- shake_strength >> 4, mov.h; 0 from propped_board_start): board_prop_update and stuck_effect_update add it to their screen_x; player_stuck_wait_release waits for its holder's 80 to be 0 (probably this)
scoped object push_crate 92 push_quadrant u8  # stage record byte, 0-3: it moves only when the toucher's direction gives the same value ((24 + 40) / 40 - 1), at push_crate_push_angles[it]; rings drop behind it ([(it + 2) & 3], push_crate_release_rings)
scoped object push_crate 93 post_distance u8  # stage record byte (push_crate_init, through object_cursor): the stop post is set back by (22, 11) x it + (11, 1D) in projected x / y
scoped object quicksand 80 palette_list u32 rom  # quicksand_palettes (movea in quicksand_start; the C showed it as bytes 80, 81 and the halfword 82)
scoped object quicksand 84 palette_index u8  # as animate_sign 84 (0 at the start)
scoped object quicksand 85 palette_delay u8  # as animate_sign 85: 4
scoped object quicksand 86 palette_timer u8  # as animate_sign 86 (0 at the start)
scoped object railing_spawner 80 last_spawn_position u16  # scroll_position when it last laid its 10 railing segments (0 from railing_spawner_init, mov.h); the next batch comes 800 scroll units later
scoped object released_ring 7E pickup_delay s16  # 1E (scatter_rings, mov.h): counted down with flags bit 9 set (players ignore it); at 0 bit 9 is cleared and released_ring_bounce runs
scoped object released_ring 80 lifetime s16  # 12C (scatter_rings, mov.h): counted down while it bounces; at 0 it shrinks away (scattered_ring_vanish)
scoped object released_ring 92 bounce_speed s32  # speed_z for the next bounce: drop_rings stores the jump speed; each landing keeps 3/4 of it + 1.0 (released_ring_move)
scoped object rimmed_sinkhole 80 zoom_step u16  # sinkhole_growth_records word 0 (rimmed_sinkhole_start, on the new hole): added to sprite_scale every frame while it opens (up to 100)
scoped object ring_tally_percent 30 started u8  # 0 when start_ring_tally makes it; 1 after the first frame's set-up
scoped object ring_tally_percent 32 roll_delay u16  # 1: counted down (object_cursor), at 0 back to 1 and new random digits: they roll every frame until ring_tally_counted
scoped object ring_tally_percent 44 tens_digit u32 object  # the tens digit's second-pool block (screen x 8E); the ones digit is at 40 (x 9E), the hundreds at 48 (x 7E, drawn only when it shows 1)
scoped object ring_tally_percent 5C results_timer u16  # 12C: update_ring_tally_percent counts it down once counting ends; stage_results_done at 0
scoped object ring_tally_percent 60 blink_index u8  # 0-3: ring_tally_blink_palettes entry for the part at 38 (sprite 9DE0) once the bonus percentage is reached
scoped object ring_tally_percent 62 blink_delay u16  # 2: frames per blink step
scoped object update_ring_tally_percent 5C results_timer u16  # as ring_tally_percent 5C: - 1 a frame, stage_results_done when it was 1
scoped object update_ring_tally_percent 60 blink_index u8  # as ring_tally_percent 60: + 1 and & 3 (and.w #FFFFFF03, 60 in the original)
scoped object update_ring_tally_percent 62 blink_delay u16  # as ring_tally_percent 62: counted down, back to 2 at 0
scoped object ring_tally_player 30 started u8  # 0 from start_ring_tally; set after build_ring_tally_panel on the first frame, then update_ring_tally_panel runs
scoped object ring_tally_player 32 step_delay u16  # 4 at the start, then 2: frames between count steps (update_ring_tally_panel)
scoped object ring_tally_player 44 part_3 u32 object  # the third second-pool part (sprite 9E90 at 40,70, drawn in every step with the digits); players 2 and 3 move its screen x
scoped object ring_tally_player 60 rings_counted s8  # 0 at the start: the count shown, + 1 a step up to ring_total
scoped object ring_tally_player 61 step u8  # 0 at the start: update_ring_tally_panel's jump table
scoped object ring_tally_player 62 palette_delay u16  # 2 (object_cursor): cycle_ring_tally_backdrop_palette's frames per palette
scoped object ring_tally_player 6A character u8  # start_ring_tally: the player object's kind (47): ring_tally_portraits[it] (build_ring_tally_panel), ring_tally_character_sounds[it]
scoped object update_ring_tally_panel 32 step_delay u16  # as ring_tally_player 32: counted down, the next count step at 0 (then 2)
scoped object update_ring_tally_panel 44 part_3 u32 object  # as ring_tally_player 44: queued in every step
scoped object update_ring_tally_panel 60 rings_counted s8  # as ring_tally_player 60: + 1 a step; counting ends when ring_total <= it (signed), shown as BCD digits
scoped object update_ring_tally_panel 61 step u8  # the jump table: 0 counting, 1 gives the bonus (give_result_bonus, character sound, 88), 2 bonus shown (label palette cycling), 3 no bonus (sound 89), 4 no bonus shown, 5 as 1; counting jumps to 3 instead of 1 when gets_bonus is 0
scoped object update_ring_tally_panel 66 ring_total s8  # start_ring_tally: the player's stage_rings (low byte, written as a halfword): counting stops there (signed compare)
scoped object update_ring_tally_panel 67 gets_bonus u8  # start_ring_tally: 1 when the player gets the ring bonus (pick_ring_bonus_players); 0 skips steps 1-2 (as build_ring_tally_panel 67)
scoped object update_ring_tally_panel 6A character u8  # as ring_tally_player 6A: ring_tally_character_sounds[it] in step 1
scoped object cycle_ring_tally_backdrop_palette 60 palette_index s8  # step 2 reuses the count byte: 0-6, the ring_tally_backdrop_palettes entry of the part at 40 (signed test > 6)
scoped object cycle_ring_tally_backdrop_palette 62 palette_delay u16  # as ring_tally_player 62: counted down, back to 2 at 0
scoped object ring_tally_ticker 30 started u8  # 0 from start_ring_tally; set on the first frame (timer cleared)
scoped object ring_tally_ticker 32 timer s16  # + 1 a frame; at 6 (signed) back to 0 and sound 83, until ring_tally_counted
scoped object roller_back_zone 80 limit s16  # object_steps word (spawn_roller_back_zone): removed once the leader's x or y passes it along its direction (compared signed)
scoped object run_timed_spawns 32 timer s16  # the caller's frame count: an entry spawns once it reaches the entry's frame (signed), as ending 32
scoped object run_timed_spawns 61 list_index u8  # next entry of timed_spawns_2 / timed_spawns_3, as ending 61
scoped object sand_arch 92 row_direction u8  # stage record byte: direction (24) = it x 40; the balls take that direction
scoped object sand_arch 93 ball_count s8  # stage record byte: balls spawned (sand_arch_init, through object_cursor); sand_arch_update removes the row once 80 (balls gone) reaches it (signed)
scoped object sand_rock 92 ring_count u8  # stage record byte: rings_to_release when touched (none at 0); the angle is the word 94
scoped object saw_blade 80 next_shaft_z s16  # z level of the next shaft piece: the saw's z_int + 1D at the start; whenever 80 - z_int >= 1D (signed) a saw_shaft_update piece is left and 1D comes off (a piece every 1D of rise)
scoped object saw_blade 83 state u8  # saw_blade_update's jump table: 0/4 two clip entries and sound A, 1 slides out to the saw, 2 frees the clips, 3 follows the saw
scoped object saw_shaft_update 80 next_step_z s16  # its z_int when saw_blade_update leaves it: each time its blade (parent) has risen 5 more, 5 comes off and saw_shaft_animation steps
scoped object saw_shaft_update 82 state u8  # 0/2 growing with the blade (+ 1 when the animation ends), 1 done: drawn until off screen
scoped object scattered_ring 7E pickup_delay s16  # as released_ring 7E: 1E frames with flags bit 9 set, then scattered_ring_live
scoped object scattered_ring 80 lifetime s16  # as released_ring 80: 12C frames, then it shrinks away
scoped object scene_anchor 80 end_position u16  # scene_anchor_positions word 3 (1B00): removed once scroll_position reaches it (as scene_object 80)
scoped object scene_carrier 80 end_position u16  # scene_carrier_records' last word: removed once scroll_position reaches it
scoped object scenery_sprite 80 palette_list u32 rom  # scenery_sprite_palettes (movea in scenery_sprite_cycle_palette_start; the C showed bytes 80, 81 and the halfword 82)
scoped object scenery_sprite 84 palette_index u8  # as animate_sign 84 (0 at the start)
scoped object scenery_sprite 85 palette_delay u8  # as animate_sign 85: 6
scoped object scenery_sprite 86 palette_timer u8  # as animate_sign 86 (0 at the start)
scoped object scroll_credits 31 scrolled_out u8  # set once the block's last part is above screen y -14; the next frame frees the parts and removes the block (ending 31)
scoped object scroll_credits 32 timer s16  # + 1 a frame (object_cursor); at 2 the parts move up a pixel and wrap_credits_scroll_timer sets it back to 0 (as ending 32)
scoped object set_player_entry_by_scene 5E floor_block_high u16  # high half of floor_block (as player 5E): not FFFF (a floor block under the player) in game mode 1 gives state 1E
scoped object sinkhole 80 zoom_step u16  # sinkhole_growth_records word 0: added to sprite_scale every frame while it opens (up to 180)
scoped object sinkhole 8A unscaled_offset_x s8  # F8 (8 when flipped, neg.b): sprite_offset_x = it x sprite_scale >> 8
scoped object sinkhole 8B unscaled_offset_y s8  # D0: sprite_offset_y = it x sprite_scale >> 8
scoped object snow_mound 92 ring_count u8  # stage record byte: rings_to_release when touched (none at 0); the angle is the word 94
scoped object snow_mound 93 random_scatter u8  # stage record byte: released_rings_random (drop_rings randomizes each ring's speed and angle)
scoped object spawn_lava_column 92 takes_slot u8  # as lava_rock 92: 1 (a slot-triggered eruption) throws the column (lava_column_toss_start, speed_z -E.0)
scoped object spawn_lava_column 96 depth_offset s16  # (whole field) as lava_rock 96: passed to the thrown column
scoped object spawn_listed_geyser 68 next_listed_geyser u32 listed_geyser  # as geyser_front 68: the record (position, x, y, z, start position) spawned once scroll_position reaches it; then + A; read in place (08CA5D "cmp.h [68[R20]], 6435[R25]") and from + 2 (08CA66-08CA82)
scoped object spear_trap 84 spear_z s32  # the spear's own height: spear_trap_init copies z; spear_trap_draw (and _retract) put it in z to move (and fall), keep the result here and leave z = floor_z
scoped object spike_panel 93 start_step u8  # stage record byte: the step it starts at (81 = it) and holds while no player is within 40 in x; 1E frames after one comes it is cleared and the steps run (spike_panel_update only advances at 0)
scoped object spiked_disc 81 arm_frame u8  # its arm's 80 (spiked_disc_hold, a byte store through object_cursor): spiked_disc_hold_offsets index. spiked_disc_draw_held compares it as a halfword (cmp.h #8, 81): byte 82, the spin toggle, is 1 after the first frame, so the shadow is always drawn (original code)
scoped object stage_clear_banner 80 sparkle_offset u16  # 10 from game mode 17: a spawn_banner_sparkle at screen_x + it on each animation step, then + 10
scoped object start_ending_blast 30 started u8  # set to 1 (as ending 30)
scoped object start_ending_blast 32 timer s16  # cleared (as ending 32 and draw_ending_blast_frame 32)
scoped object start_ending_blast 60 frame_index u8  # cleared (as ending 60): the ending_*_blast_frames entry
scoped object start_map_trail 4A trail_done s8  # as map_screen 4A: 0, FF when the stage has no trail (stage_map_trails entry <= 0)
scoped object start_map_trail 4B segment_frames u16  # as next_map_trail_segment 4B: the trail's first word
scoped object start_map_trail 55 segment_timer u16  # the same first word: draw_map_trail_dot counts it down (testing its low byte) and starts the next segment at 0, reloading it from segment_frames
scoped object stuck_effect 80 draw_depth u16  # screen_y - 20 while the propped board's speed_z is 0, kept once it falls: its queue_sprite_at_depth depth (as board_prop 80)
scoped object timed_spawner 30 started u8  # 0 when spawned (run_title_screen); 1 on the first frame (61 and 32 cleared)
scoped object timed_spawner 32 timer s16  # + 1 a frame; each timed_spawns entry runs once it passes the entry's frame (signed)
scoped object timed_spawner 61 list_index u8  # next timed_spawns entry (frame, handler offset from 0973B4); removed at FFFF or title_done
scoped object title 30 started u8  # 0 when spawned; set to 1 by the first frame's set-up (title_land_slide writes it as a halfword, 31 = 0)
scoped object title 32 timer s16  # + 1 a frame: title_flying_craft_move spawns a smoke puff after A, title_smoke_puff_animate steps every 3 (it increments it with inc.b 32, compares with cmp.h)
scoped object title 44 emblem_bottom_left u32 object  # title_emblem_animate: the emblem's bottom-left quarter (52F0 at 63,98); its other second-pool parts sit in 38, 40, 48-54
scoped object title 61 frame_index u8  # title_smoke_puff_animate: entry of its frame list (097538), removed at FFFF
scoped object title_clouds 30 started u8  # 0 when spawned; set by the first frame's set-up
scoped object title_clouds 32 timer s16  # + 1 a frame (object_cursor); after 3 (signed) back to 0 and every cloud drifts
scoped object title_clouds 44 cloud_4 u32 object  # its fourth cloud's second-pool block (the object is the first, then 3C, 40, 44, 48...)
scoped object treadmill_gate_update 8C sound_c_timer u8  # frames to the next sound C (3C) while speed_z is at or below the tracked speed (6E)
scoped object treadmill_gate_update 8D sound_1e_timer u8  # frames to the next sound 1E (14) while speed_z is above it
scoped object treadmill_gate_update 8E reached_top u32  # 1 (with sound 1E) the first time it reaches the top (28 above its floor) still moving; never cleared (test.w / mov.w)
scoped object vine_init 14 base_x s32  # x at the start: vine_swing puts x back to it before walking the segments
scoped object vine_init 93 segment_count u8  # stage record byte: vine segments (also kept in 34 and 36; depth offset 82 = it x 8)
scoped object vine_init 94 phase_step u8  # stage record byte: speed (6E) = it << 16, added to the swing phase (3C) every frame
scoped object vine_swing 14 base_x s32  # as vine_init 14: x = it before the segment walk
scoped object vine_swing 16 base_x_int s16  # whole part of base_x (nested): the segments' draw depth uses x_int - it
scoped object vine_swing 48 segment_angle u32  # 16.16 angle (400 a turn): 0, then + the curl (38) at each segment (& 3FFFFFF); the segments step 18 x sine / cosine of it in x and z
scoped object vine_swing 4A segment_angle_int s16  # whole part of segment_angle: the sine/cosine index; the tip's angle left from the last frame, 100-2FF (signed), puts the segments in bucket 0
scoped object vine_swing 95 curl_scale u8  # stage record byte: curl (38) = sine_table[phase] x it
scoped object wall_update 37 depth_from_next u8  # wall_spawns record byte (spawn_walls): set: each part is queued at the next part's screen y - 10, else at its own + 10
scoped object animate_sinking_mine 80 sink_step u8  # entry of mine_sink_steps (0 1 2 1 0 3 4 3 -> mine_frames): + 1 a frame, & 7 (% 8); mine_sink clears it for mine_explode
scoped object breakable_panel 80 frame_index s8  # entry of breakable_panel_break_frames: + 1 every 3 frames once broken; past 10 (signed) the panel stays open (handler draw_breakable_panel)
scoped object breakable_panel 82 frame_timer u16  # counted down; when it was 0 the next break frame and 2 again (0 at the start: the first frame at once)
scoped object draw_breakable_panel 80 frame_index s8  # as breakable_panel 80: not 0 once the panel started breaking: the glowing arch outline (part 2) is no longer drawn
scoped object bubble_column 80 frame_index s8  # entry of mine_bubble_frames (+ 92 x 13): + 1 every 3 frames, 0 after 13 (signed)
scoped object bubble_column 82 frame_timer u16  # counted down; when it was 0 the next frame and 2 again
scoped object bubble_start 86 variant u16  # the spawn record's kind (in field 04 at the start, which also picks bubble_heights[kind] for z): bubble_pop moves every kind but 6 to a new random spot after each pop
scoped object bubble_update 84 draw_depth u16  # screen_y every frame: bubble_pop's queue_sprite_at_depth depth
scoped object bubble_near_parent_update 84 draw_depth u16  # FFF0 every frame (the lava bubbles of lava_column_land): bubble_pop's queue_sprite_at_depth depth
scoped object bubble_pop 84 draw_depth u16  # its queue_sprite_at_depth depth: screen_y (bubble_update) or FFF0 (bubble_near_parent_update)
scoped object bubble_pop 86 variant u16  # as bubble_start 86: kind 6 stays where it is; the others go to base_x/base_y + random 0-F after each pop
scoped object build_wall_parts 80 invisible u8  # FF when the wall kind has no parts (first part sprite negative: a collision box only), else 0: wall_update draws nothing while it is set
scoped object build_wall_parts 81 no_second_sprite u8  # FF when the kind has no second sprite (its wall_kind_parts second_sprite negative), else 0: then every step has a second part (palette 9FFE) and wall_update places it from the first
scoped object burning_boulder 82 path_high u16  # high word of path (nested): FFFF for the follower (path FFFFFFFF): on landing it goes to state 2 (copies lead_burning_boulder) instead of 1, and its draw depth is 20 less while it waits (states 0/6)
scoped object burning_boulder_flame 84 pause_timer s16  # frames before the next flame: 0 at the start; after each run of the animation a random 0-3F (not drawn meanwhile); counted down, the flame plays when it was <= 1 (signed)
scoped object button_box 80 step u8  # entry of button_box_steps / button_box_durations: steps 0-12 rise out of the floor and open the lid (stops at 13 until Eggman's 81 is 2), 13-22 press the button and sink back (draw_button_box at 23)
scoped object button_box 82 step_timer u16  # frames left of the current step (button_box_durations[step]); B4 from button_box_init before the first rise step
scoped object cactus 81 lifted u8  # 1 once the tornado (tornado_object) came within 60: it rises spiralling from then on
scoped object cactus 82 angle u16  # direction of its spiral: - 10 a frame (& 3FF), given to direction_to_speed with spiral_speed
scoped object cactus 84 spiral_speed u16  # + 18 a frame while it is lifted: the speed of its spiral (speed_x/speed_y from direction_to_speed), so the circle widens
scoped object carried_ring 80 glint_timer u8  # probably a glint timer: 78 at each glint (animation frame 15, as ring_spin); spawn_ring_rows writes the halfword 80 as 0, 20, 40... along a row; no reader found
scoped object carried_ring 81 glint_timer_high u8  # high byte of the halfword spawn_ring_rows writes at 80: cleared with each glint (two byte writes, mov.b); no reader found
scoped object carried_ring 82 hover_height u16  # stage_object_templates word 8 (spawn_ring_rows): z = its platform's z - it every frame (the ring floats this high)
scoped object centipede 81 path_write_index s8  # centipede_path entry the head writes its projected x/y to: + 1 a frame, 0 after 32 (signed); with no active players it rewrites the entry before it (the body holds)
scoped object centipede 82 wobble_angle u16  # the head's second push: - 10 a frame (& 3FF), half its speed along this angle on top of its heading, so it weaves
scoped object centipede 86 hop_timer u16  # frames of the hop's rise left: 5A when a piece lands (a sand puff that frame), - 1 a frame with speed_z - 600; at 0 it falls back (fall_to_floor)
scoped object centipede_segment 82 path_index s16  # first the start delay (centipede_init: 5, A, F... frames, counted down to 0, signed), then the centipede_path entry it reads next (+ 1 a frame, 0 after 32): each segment lags 5 entries more
scoped object centipede_segment 86 hop_timer u16  # as centipede 86
scoped object centipede_follow_path 82 path_index s16  # as centipede_segment 82: the entry copied to projected_x/projected_y, + 1, 0 after 32 (signed)
scoped object centipede_tail 82 path_index s16  # as centipede_segment 82: 32 frames of delay (counted down, signed), then the path entry it reads
scoped object centipede_tail 86 hop_timer u16  # as centipede 86
scoped object character_select_countdown 80 shown_seconds u8  # character_select_timer / 3C as last drawn: when the seconds change the digit sprite (ranking_letter_sprites 20 + it) is set and its zoom restarts at 10
scoped object collapsing_bridge_plank 96 depth_offset s16  # stage_object_records_2 record word (spawn_object_groups_2): draw_depth = the floor's screen y + it while it is a sagging plank (as rope_bridge_plank 96, not doubled)
scoped object crawler 80 walk_step s8  # entry of the variant's six crawler_walk_steps: + 1 every 4 frames, 0 after 5 (signed); crawler_init staggers the segments 1, 2... 5, 0; FF on the tail, which is not animated (crawler_segment_update tells it by FF)
scoped object crawler 81 moving u8  # flips every 5 frames once the head is triggered (stop and go): while set the piece moves at speed 200 along direction 94
scoped object crawler 82 frame_timer s16  # + 1 a frame; past 3 (signed) back to 0 and the next walking frame
scoped object crawler 86 step_timer u16  # frames to the next flip of moving: crawler_init gives the segments 4, 8, C... (a wave down the body), then 4
scoped object crawler_head 80 over_pit u8  # fall_to_floor's R1 every frame: 1 with no floor under the head; the segments read their parent's (the head's): only while it is set do they fall into a pit and remove themselves off screen
scoped object crawler_head 8C triggered u16  # 1 once a player came within 28 (35 for variant 1): every piece's crawler_step waits for its parent's (the head's)
scoped object crawler_head 92 variant u8  # as crawler 92
scoped object crawler_head 93 on_platform u8  # as crawler 93
scoped object debris 80 frame_index s8  # entry of debris_frames: + 1 every 4 frames in the air; past last_frame 9 comes off (signed); spawn_debris starts half the chunks at A
scoped object debris 81 last_frame s8  # 9, or 13 for the chunks that start at A (spawn_debris): the frames loop below it
scoped object debris 82 frame_timer u16  # + 1 & 3 in the air: the next frame when it wraps to 0 (spawn_debris: 3)
scoped object debris 84 lifetime u16  # frames it stays once landed (F from spawn_debris): counted down on the floor, drawn on the even ones (it blinks), removed at 0
scoped object door_roller_roll_out 80 state u8  # as door_roller 80
scoped object door_roller_roll_out 82 roll_out_timer s16  # 8 (spawn_door_rollers, 16-bit): y + 1 a frame while it rolls out; when it was <= 1 (signed) flags bit 22 (the ledge falls) and the next state
scoped object draw_character_chooser_labels 12 name_changed u8  # as character_chooser 12
scoped object draw_character_chooser_labels 81 character u8  # as character_chooser 81: the name label shows character_name_sprites[it]
scoped object draw_rope_bridge_ropes 6E state u8  # as rope_bridge_plank 6E: 2 (falling) shows the slack rope 9D20
scoped object draw_rope_bridge_ropes 8A group_slot u16  # as rope_bridge_plank 8A: with takes_slot set, its group_slots byte hides the first rope part when FF, the second when 80
scoped object draw_rope_bridge_ropes 92 takes_slot s8  # as rope_bridge_plank 92
scoped object eel_init 81 stretch_step s8  # as eel_anchor 81: 10 at the start
scoped object eel_anchor 80 pausing u8  # 1 during the 3C-frame pause at either end of the stretch; 0 when pause_timer runs out
scoped object eel_anchor 81 stretch_step s8  # added to speed (6E, the stretch) every frame (signed): + 10 at first, - 18 back in after passing 1111, + 28 out after going under 480; copied to every piece
scoped object eel_anchor 82 pause_timer u16  # 3C at each end of the stretch, counted down while pausing
scoped object eel_head 80 frame_index s8  # entry of eel_head_frames (facing 0, 0-2) or eel_head_retract_frames (0-3) / eel_head_extend_frames (0-2) by the stretch direction: + 1 a step, 0 past the last (signed)
scoped object eel_head 81 stretch_step s8  # as eel_anchor 81 (eel_anchor_update copies it to every piece): negative while the eel pulls back in (eel_head_retract_frames)
scoped object eel_head 82 explode_delay u16  # set when its den collapses: its speed (6E, the anchor's x its place in the chain) / the anchor's x B frames, so the pieces go one after another; counted down by eel_head_explode_delay, then eel_piece_start_explosion
scoped object eel_head 84 frame_timer u16  # frames to the next head frame (3), 3C after each cycle (a pause)
scoped object eel_segment 82 explode_delay u16  # as eel_head 82 (eel_segment_move / eel_segment_explode_delay); eel_init stores 30, 60, 90... here first: the start of the wave angle place_eel_piece steps
scoped object eel_segment 8C height_offset u32  # 16.16, eel_init: 4000 x the piece's wave amplitude (92): place_eel_piece takes it off the anchor's z before adding the wave
scoped object eel_piece 80 frame_index s8  # entry of underwater_explosion_frames: 2 at the start, + 1 every 3 frames; past 15 (signed) the anchor's piece_count - 1, sound 24, eel_piece_gone
scoped object eel_piece 82 frame_timer u16  # 0 at the start; counted down, when it was 0 the next frame and 2 again
scoped object eel_den 80 frame_index s8  # entry of eel_den_collapse_frames: + 1 every 3 frames once it collapses; past 2C (signed) eel_den_draw
scoped object eel_den 82 frame_timer u16  # counted down; when it was 0 the next frame and 2 again
scoped object eggman_booth 80 step u8  # entry of eggman_booth_steps (frame indexes): 0 when it has slid in, + 1 when step_timer runs out; at 1D it slides back up and is removed
scoped object eggman_booth 82 step_timer s16  # 2F from spawn_eggman_booth (the first frame); - 1 a frame, the next step when it goes negative (signed): eggman_booth_frame_durations[the step's frame]
scoped object eggman_booth 84 slide_y s16  # screen y of both parts: + 4 a frame to 2F (slide in, signed compare), - 4 a frame after the last step until under 4 (removed)
scoped object draw_eggman_booth 80 step u8  # as eggman_booth 80: sprite eggman_booth_frames[eggman_booth_steps[it]] (sound 41 at frame B outside stages 0 and 5)
scoped object draw_eggman_booth 84 slide_y s16  # as eggman_booth 84: both parts' screen_y
scoped object eggman_teleport 80 step u8  # entry of eggman_teleport_steps: + 1 each time step_timer runs out
scoped object eggman_teleport 81 frame_index u8  # eggman_teleport_steps[step]: index into eggman_teleport_frames and eggman_teleport_durations; FF ends (delete_object_and_children)
scoped object eggman_teleport 82 step_timer u16  # counted down; when it was 0 the next step: eggman_teleport_durations[frame_index]
scoped object eggman_teleport 84 flicker_phase u16  # + 1 & 3 a frame: Eggman drawn while it is not 0 (3 frames of 4)
scoped object eggman_teleport 86 beam_phase u16  # + 1 & 1 a frame: the beam (part 2) drawn on the odd ones
scoped object eggman_scene 80 step s8  # entry of the current animation: eggman_talk_steps (% A, + 1 every 7 frames), eggman_press_frames / durations (0-6, the one signed compare), eggman_run_frames (% 8); button_box_wait starts the box when its parent's is 1
scoped object eggman_scene 81 phase u8  # 1 once the opening pause ends (he talks), 2 from the fifth press frame (escape_started): button_box_rise waits for its parent's 2 to press the button
scoped object eggman_scene 82 step_timer u16  # counted down: 3C (eggman_scene_init) before he talks, each message's eggman_scene_text_durations, eggman_press_durations, 3C before running off, then 2 a run frame
scoped object eggman_scene 84 talk_timer u16  # frames to the next talking frame: 6 (one every 7 frames)
scoped object saw_shaft_base_draw 82 state u8  # build_saw_shaft_base sets 1, as saw_shaft_update 82 (done: drawn until off screen); saw_shaft_base_draw never reads it
scoped object electric_pole 80 frame_index s8  # electric_pole_rise_frames index (step -> sprite): + 1 every other frame while it rises, open at A (cmp.b + blt); electric_pole_update draws the side spark from 2 (electric_pole_side_spark_sprites[it - 2], signed) and the open sparks from A, there read as a halfword (cmp.h #A, 80[R23]: 81 is 0 then); 0 when it blows up (explosion 80)
scoped object electric_pole 81 last_frame s8  # F when it blows up: the last explosion_frames index for explosion_animate (explosion 81); the pole itself never reads it
scoped object electric_pole 82 frame_toggle u16  # + 1 & 1 every frame while it rises (inc.h / and.h): the next step when it comes back to 0; cleared when it blows up (explosion 82)
scoped object electric_pole 84 draw_offset_y u16  # B once it has risen (step A moves projected_y B up): added to screen_y before it is queued, so it is drawn B lower to make up for it
scoped object explosion 80 frame_index s8  # explosion_frames index: + 1 every other frame (82); drawn while <= last_frame (cmp.b 80, 81 + bge, signed), then delete_object_and_children
scoped object explosion 81 last_frame s8  # the last explosion_frames index: F for the small explosion (electric_pole_update, 08E43B: frames 0-F, palette 9000), 2B for explode_object (frames 1F-2B, palette 86D0)
scoped object explosion 82 frame_toggle u16  # + 1 & 1 every frame (inc.h / and.h): the frame advances when it comes back to 0; the code that starts the explosion clears it
scoped object explode_object 80 frame_index s8  # 1F: the first explosion_frames index of this explosion (as explosion 80)
scoped object explode_object 81 last_frame s8  # 2B: its last frame (as explosion 81)
scoped object explode_object 82 frame_toggle u16  # cleared (mov.h #0, as explosion 82); then handler explosion_animate and sound 7
scoped object ember_emitter 81 travel_direction u8  # byte +15 of its floor block (the floor's travel direction, 0-4 as in get_pacing_axis): 0/4 y-, 1 x+, 2 y+, 3 x- at the camera's slope speed to stay near screen x 40 (jump tables on it); copied to each falling ember's 81
scoped object ember_emitter 82 spawn_timer s16  # frames to the next falling ember (dec.h + bgt): 0 at the start (ember_emitter_start), then next_random & 3F after each one
scoped object ember_emitter 84 end_position u16  # ember_emitter_end_positions[kind (04)]: removed once scroll_position reaches it (cmp.h + bl, unsigned)
scoped object falling_ember 80 state u8  # falling_ember_update's jump table (movz.bw / inc.b): 0 (3 the same) raises it (z - 70 at a time) until it is above the screen top, 1 falls (y + speed_y, z + speed_z) to its floor (530 where there is none), 2 bursts (falling_ember_burst_animation or the slope one) and is removed; ember_emitter_move clears it
scoped object falling_ember 81 travel_direction u8  # ember_emitter_move copies its own 81 here (the floor's travel direction); falling_ember_update never reads it
scoped object fireball_fly 7F state u8  # fireball_fly's jump table (movz.bw / inc.b): 0 flies (2 runs the same code), 1 plays the rest of its animation on the floor, then takes next_handler (9C) 28 higher and clears it; init_fireball_flight clears it
scoped object fireball_fly 82 flight_timer s16  # frames of flight left (dec.h + bgt): init_fireball_flight sets 36 (24 in stage C), the count it divides the distance to target_x/target_y by; at 0 it lands (state + 1, z = floor_z)
scoped object flame_machine_init 86 time_left u16  # E10 frames: flame_machine_update counts it down (as flame_machine_update 86)
scoped object flame_machine_update 82 flash_frame_count u16  # + 1 every frame of a hit flash (flame_machine_flags bit C; inc.h + be), never cleared: the flash steps unless it wrapped to 0, so in practice every frame (no and.h as in the every-other-frame toggles)
scoped object flame_machine_update 86 time_left u16  # E10 (flame_machine_init): - 1 every frame some player is active (dec.h 86[R23]); when it was 0 (bl) the machine is destroyed as when its health runs out (flame_machine_destroyed)
scoped object flame_machine_update 8C destroyed_frames u16  # + 1 a frame once destroyed (flame_machine_destroyed): an explosion object (flame_machine_explosion_start) when it & 7 is 0; at 130 flame_machine_remove and the wreck (spawn_flame_machine_wreck)
scoped object flame_machine_back_wall 80 frame u8  # flame_machine_back_wall_frames[frame_index] (0-5): the wall's offset from the box (flame_machine_back_wall_offsets) and the rods' sprite (flame_machine_rod_sprites); at 5 it starts the arms (flags 4/5) and the rods are not placed or queued (cmp.b #5)
scoped object flame_machine_back_wall 81 frame_index s8  # index into flame_machine_back_wall_frames (0 1 2 3 4 5 4 3 2 1): + 1 a step; past 9 (cmp.b on its value + ble, signed) back to 0 with a 1E-frame pause (84), flags 2/3 (cannons fire) and A/B (arms take a disc), flag 9 cleared
scoped object flame_machine_back_wall 82 frame_phase u16  # + 1 & 3 every frame it moves: a step when it comes to 0, then it is set to 1 (the first step after 4 frames, the next ones every 3)
scoped object flame_machine_back_wall 84 pause_timer s16  # 1E at the end of each cycle: counted down while > 0 (cmp.h + bgt) before the wall moves again
scoped object flame_machine_flame 80 frame_index u8  # flame_machine_flame_frames index, + 1 every other frame (82); the frame read (kept in 94) ends the flame at B; explode_object takes it over (1F) when the machine is destroyed
scoped object flame_machine_flame 82 frame_toggle u16  # + 1 & 1 every frame (inc.h / and.h): the next frame when it is 1
scoped object flame_machine_arm 80 frame_index u8  # flame_machine_arm_frames index, + 1 every other frame (82) while the arm is active (flag 4+n): frame C starts the sway (flag 6+n), frame D ends it (back to 0, flag 4+n cleared); spiked_disc_hold reads it on its arm (spiked_disc 81)
scoped object flame_machine_arm 81 sway_step u8  # + 1 mod 29 per sway step (inc.b / remu.b): index into flame_machine_arm_sway_offsets (flame_machine_arm_draw)
scoped object flame_machine_arm 82 frame_toggle u16  # + 1 & 1 every frame while the arm animates: the next frame when it comes back to 0; flame_machine_arm_rest sets 1, so the first step comes on the next frame
scoped object flame_jet 80 frame_index s8  # flame_jet_flame_sprites index: + 1 every frame, back to 0 after 4 (cmp.b #4 + ble, signed): the 5-frame flicker
scoped object flame_jet 82 frame_phase u16  # + 1 & 3 every frame (inc.h / and.h); nothing reads it
scoped object flame_jet 84 angle u16  # direction of the flame front from the nozzle (direction_to_speed's angle, movz.hw): flame_jet_init sets 158, 350 for the other nozzle kind (its 92 set)
scoped object flame_jet 86 growth_rate s16  # added to the front's distance (6E, speed) every frame (movs.hw): flame_jet_init 200 (a mov.w that also clears 88), then the pilot flame flickers between 0 and 666 (FF80 down, 80 up; cmp.h #0 + bn); 380 after the trigger (flame_jet_pilot) until 6E passes F000, then 0 (flame_jet_ignite)
scoped object flash_mine_palette 81 flash_step u8  # mine_flash_steps index (palette 0-5-0): + 1 mod C (inc.b / remu.b) at every palette change
scoped object flash_mine_palette 84 flash_timer u16  # dec.h + bnl: the palette changes when it was 0, then 4 again (every 5 frames)
scoped object geyser_wait 82 draw_depth u16  # spawn_geyser sets EF; - 1 every frame its front (parent) spawns a row (the front's flags bit 0), so each row ends up 1 below the depth of the row after it; geyser_erupt queues the row geysers' jets at it
scoped object geyser_erupt 82 draw_depth u16  # as geyser_wait 82: - 1 when the front spawns a row; a row geyser (flags bit 31 clear) whose jet is up (geyser_jet_animation or frame >= 4) is queued at this depth (mov.h 82 -> queue_sprite_at_depth)
scoped object grate_steps 80 rows_left s16  # as build_grate_steps 80: F rows (mov.h #F; the C split the store into bytes 80 = F and 81 = 0)
scoped object grate_step_update 84 cut_z s16  # build_grate_steps: the step's z_int + 1E (a mov.w of 42-45, then add.h #1E); when its column's rising saw (rising_saws[kind]) has z - 20 <= it (signed), the next grate_step_cut_animation step and it - 7; debris (spawn_grate_step_debris, at this z) and sound 28 while the saw is within 20 of it
scoped object handle_rod_handle 82 start_angle u16  # the rod's angle when handle_rod_init made the handle (322: the rod gets 300 after the loop); handle_rod_handle_update follows the rod's angle when this is 300 or the rod's angle (its 82) is not 0, else it hangs straight down (index x 12 below)
scoped object how_to_play_trackball_hand 81 step u8  # the jump table (movz.bw / inc.b), from game_mode_timer 32: 0 (3 the same) the hand moves A down-left a frame until 20 left of where it started (projected_x), 1 pauses (pause_timer 8), 2 moves 4 up-right a frame back to the start, then 0; _start clears it
scoped object ice_launcher 80 step s8  # ice_launcher_frames index (step -> frame): + 1 when step_timer runs out; at C it launches an ice cube; past last_step (cmp.b 80, 81 + bge, signed) back to 0, sprite 7420
scoped object ice_launcher 81 last_step s8  # F (ice_launcher_init): the last step before it starts over
scoped object ice_launcher 82 step_timer u16  # dec.h + bnl: the next step when it was 0; reloaded with ice_launcher_frame_durations[step_duration_index], + 3 on the launch step (through object_cursor), 0 when it starts over
scoped object ice_platform_leader 8C travel s16  # spawn_ice_platform_leader: x - the record's reference word (where it starts along the swing); + speed_x_int every frame; outside -87..87 (it + 87 compared unsigned with 10F) cleared and speed_x turns (sound F): it swings 88 units each way
scoped object ice_platform_piece 88 draw_depth u16  # state 1: screen_y + depth_offset - sprite_offset_y, its queue_sprite_at_depth depth; kept while it falls (state 2); icicle_update (state 6) and the players on it read their platform's 88
scoped object ice_platform_piece 96 depth_offset s16  # spawn_object_groups: the record's word +14, + group_depth_step x the members still to spawn (as lava_rock 96)
scoped object init_fireball_flight 7F state u8  # cleared here: fireball_fly's jump table (0 flies, 1 plays the rest of its animation on the floor; as fireball_fly 7F)
scoped object init_fireball_flight 82 flight_timer s16  # 36 (24 in stage C): speed_x/speed_y = (target - position) / it (movz.hw 82); fireball_fly counts it down (dec.h + bgt, as fireball_fly 82)
scoped object lava_flow 96 unused_word u16  # lava_flow_start clears it (mov.h #0, 96, between the flame countdown at 94 = 2 and sound_timer = FF); nothing reads it
scoped object metal_wall 80 frame_index s8  # metal_wall_sprites index (0-4 rising, 5/6 hit, 7 broken) and the part tables' (metal_wall_draw, also through object_cursor): + 1 while it rises (compared signed with 81), then metal_wall_damage_frames[health] (metal_wall_update)
scoped object metal_wall 81 rise_limit s8  # 3 (metal_wall_init): metal_wall_rise steps frame_index while it is <= this (cmp.b, signed), so the rise ends on frame 4; then set to 7, which nothing reads
scoped object metal_wall 82 frame_count s16  # metal_wall_rise: + 1 a frame (mov.h / inc.h); when it was above 2 (signed) the next rising frame and 0 again: a frame every 4 frames
scoped object metal_wall 84 dust_timer u16  # 14 when a player comes within 80 (metal_wall_wait): metal_wall_draw counts it down (test.h / dec.h 84[R23]) and draws the dust pieces 4-7 on its odd values
scoped object opening_machine 80 scene_step_seen u8  # FF (opening_machine_start), then scene_step whenever it changes: each scene step's setup runs once (steps 0/1/4 the head, 2 the fly-away with its shadow part); as player B8
scoped object opening_machine 8C draw_depth u16  # its queue_sprite_at_depth depth in scene steps 0/1/4: screen_y at the start of each frame (mov.h C, 8C), 10 while it leaps in going up (opening_machine_leap_in)
scoped object player 7F health_pad u8  # the byte after health, read only by player_hud_init's mov.h 7E[R20], 24[R11] (health and it into hud_gauge_health / hud_gauge_frame, which it clears right after); nothing else reads or writes it
scoped object propped_board 82 draw_depth u16  # screen_y - 10 while its speed_z is 0, kept once it falls: its queue_sprite_at_depth depth (as board_prop 80, stuck_effect 80)
scoped object push_crate 80 break_frame u8  # push_crate_break_sprites index: + 1 every other frame while it breaks (movz.bw); at 2 it is broken (push_crate_move then only queues it in bucket FF)
scoped object push_crate 81 stopped u8  # 1 once the crate is within 33 in y of its stop post (push_crate_stop_post_update, on its parent, also zeroes its speed): push_crate_update no longer lets it be pushed
scoped object push_crate 82 frame_count u16  # 12C (push_crate_init; only bit 0 matters), + 1 & 1 a frame while it breaks (inc.h / and.h): the next break frame when it wraps to 0
scoped object push_crate 84 ring_timer s16  # counted down while it moves (speed_x not 0); below 0 (dec.h + bp) with rings left: one ring behind it (push_crate_release_rings) and 10 again
scoped object push_crate 94 ring_count s16  # spawn word 94: rings it holds; one less per ring dropped while pushed (dec.h), the rest when it breaks (push_crate_release_all_rings: rings_to_release = it); cmp.h #0 + ble (signed)
scoped object released_ring 84 release_delay u16  # 0, 10, 20... (drop_rings: + 10 a ring): released_ring_wait counts it down (dec.h + bnl); when it was 0 sound 13, it is cleared and the ring starts (released_ring_start)
scoped object released_ring 8A projected_x_carry_int s16  # as move_object_projected 8A (whole part of the projected-x carry): released_ring_start clears it every frame before released_ring_move
scoped object rimmed_sinkhole 84 open_position u16  # sinkhole_growth_records word 1 + scroll_position (rimmed_sinkhole_start, on the new hole): rimmed_sinkhole_open does nothing until scroll_position reaches it (cmp.h + bl, unsigned); as sinkhole 84
scoped object rimmed_sinkhole 86 edge_zone u16 object  # the object it was spawned from, which stays as its edge zone (mov.h R23, 86[R20]; its parent is the hole): rimmed_sinkhole_open sets its flags bit A when the hole goes off screen, which removes it
scoped object rimmed_sinkhole 88 draw_depth u16  # the hole's screen_y - 30 (at least FFF1): its queue depth, the rim's is it - 8; the edge zone copies it every frame; a player falling over the edge is drawn at the touched object's 88 - 1 (as sinkhole 88)
scoped object rimmed_sinkhole 8C unscaled_hitbox_size_x u16  # 4C (rimmed_sinkhole_start): hitbox_size_x = it x sprite_scale >> 8 while it opens (as sinkhole 8C)
scoped object sand_arch 80 balls_gone s8  # the row's: + 1 for each ball that falls out of the stage (sand_arch_ball_move, on its parent); sand_arch_update removes the row once it reaches ball_count (cmp.b 93, 80: signed)
scoped object sand_arch 81 row_position u8  # the balls': 6, C, 12... (sand_arch_init): sand_arch_ball_init moves the ball once by 200 x it along its direction (movz.bw 81), each ball C pixels further. The row adds is_on_screen to its own 81 and keeps bit 0; nothing reads that
scoped object sand_arch 82 bob_phase s16  # the balls': -n x (94 / (ball count + 1)) & 3FF (sand_arch_init); sand_arch_ball_delay adds 2 until it passes 3FF (cmp.h, signed: the balls start one after another), then & 3FF; sand_arch_ball_bob: + C & 3FF, speed_z = -sine_table[it] x C
scoped object sand_rock 80 crumble_frame s8  # sand_rock_crumble_frames index (part 2 one ahead): + 1 every 2 frames while crumbling, removed past D (cmp.b, signed); touchable only while 0 (as snow_mound 80)
scoped object sand_rock 81 crumbling u8  # 1 from the first touch (flags bit 2; drop_rings first when 92 has rings): the crumble runs every frame from then on (as snow_mound 81)
scoped object sand_rock 82 frame_count s16  # + 1 a frame while crumbling; past 1 (signed) the next frame and 0 again (as snow_mound 82)
scoped object saw_blade 84 sound_timer s16  # state 3: counted down (dec.h, signed); at 1 or less sound 8 and 3C again; rising_saw_init clears it (the first sound at once)
scoped object saw_blade 88 draw_depth u16  # screen_y (states 1 and 3, while above screen y 140): its queue depth and its clip entries'; its sparks (saw_sparks_update) draw at it + 1
scoped object scenery_sprite 88 unused_stage u16  # scenery_sprite_start writes stage (mov.h 706E[R25], 88[R20]); no scenery handler reads it
scoped object scenery_sprite 8A depth_offset s16  # scenery_sprite_kinds word +6 (scenery_sprite_start): every scenery handler queues it at screen_y + it (add.h)
scoped object fireball_start 7F state u8  # as init_fireball_flight 7F: machine_fire_update clears it in each fireball
scoped object fireball_start 96 target_x s16  # as init_fireball_flight 96: machine_fire_update writes the spawns_with_next_handler record's word +4, or (moving +x) the fireball's x_int + its lane's random 0-1F
scoped object fireball_start 9A target_y s16  # as init_fireball_flight 9A: the record's word +6, or the fireball's y_int (a halfword write over the player byte and the next)
scoped object join_character_chooser 80 portrait_changed u8  # 1 when the shown character changes (a trackball turn, or skipping a taken one): the portrait and palette are reloaded (join_chooser_portraits / _palettes), then 0 again
scoped object join_character_chooser 81 character u8  # character shown (0 Sonic, 1 Ray, 2 Mighty; the first free one from the player's kind at the start): + the turn (byte 13), 2 below 0 (add.b + bp), mod 3, free ones only; taken into the player object's kind at the end (as character_chooser 81)
scoped object join_character_chooser 82 countdown u16  # 21C (start_join_character_chooser: 9 seconds): dec.h a frame, the digit drawn is it / 3C; when it was 0 (bl) the shown character is taken
scoped object lid_trap 81 sprung u8  # 1 once touched (flags bit 2: the pad turns solid, kind 5, frame 8848, sound 11): while set the lid (its child) snaps shut, a lid_trap_close_steps frame every 4 frames
scoped object mine 80 frame_index s8  # mine_explode: underwater_explosion_frames index, + 1 a step (remu.b 17: removed when it wraps to 0; cmp.b #F, signed); mine_sink clears it (animate_sinking_mine steps it through mine_sink_steps while it sinks)
scoped object mine 82 timer u16  # mine_float: the bob phase, + 10 & 3FF a frame (speed_z = sine_table[it]); cleared when it reaches the water (mine_fall) and on the bottom (mine_sink); mine_explode: frames left of the step (dec.h + bnl, from underwater_explosion_durations)
scoped object mine_splash 80 frame_index s8  # mine_splash_frames index: + 1 every 3 frames; removed past 19 (cmp.b, signed)
scoped object mine_splash 82 frame_timer u16  # counted down (dec.h + bnl); when it was 0 the next frame and 2 again
scoped object place_eel_piece 82 wave_angle u16  # + 8 & 3FF a frame (eel_init starts the pieces at 30, 60, 90...): z = the anchor's z - height_offset + sine_table[it] x wave_amplitude, speed_y gets twice the wave
scoped object place_eel_piece 8C height_offset u32  # 16.16, 4000 x wave_amplitude (eel_init): taken off the anchor's z (sub.w 8C[R23]) so the wave stays above the anchor (as eel_segment 8C in vars_1.txt)
scoped object place_eel_piece 92 wave_amplitude u16  # the piece's spawn word 92 (eel_init: the record's wave_step, + wave_step a piece: 8, 10, 18...): multiplies sine_table[wave_angle] (movz.hw 92)
scoped object ring_spin 80 unused_glint_timer u16  # ring_spin writes 78 here (mov.h) at each glint (animation step 15), spawn_ring_rows staggers it 0, 20, 40... along a row; no reader found (probably meant as a glint timer; vars_1.txt names the same halfword carried_ring 80/81)
scoped object rising_flame 7F state u8  # rising_flame_update's jump table: 0/4 init, 1 rise 28, 2 spawn the next flame at the fire's top, 3 wait off screen; the spawners start it at 0
scoped object rising_flame 81 off_screen u8  # FF once below screen y 110 or left of screen x -40 (rising_flame_wait_offscreen): rising_flame_update removes it; rising_flame_init clears it
scoped object rock_wall 80 break_frame s8  # rock_wall_break_frames index: + 1 every 3 frames once it is breaking, up to D (cmp.b, signed); at 5 it drops its rings (92, angle 94); below 1 (still whole) the twinkle and glint parts run
scoped object rock_wall 81 breaking u8  # 1 once touched by an attacking object (flags bit 2 and the toucher's flag 40): the break runs from then on
scoped object rock_wall 82 frame_count s16  # 2 (rock_wall_init: the first break frame at once), + 1 a frame while breaking; past 2 (signed) the next frame and 0 again
scoped object rock_wall 84 twinkle_delay u16  # 1E when the glint (part 3) has run its 20 steps (rock_wall_update, through object_cursor); counted down (dec.h, unsigned); at 0 the sparkle (part 2) and the glint animate and are drawn
scoped object sand_face 96 depth_offset s16  # taken off its screen_y for its queue depth (sub.h): 70 from sand_block_update, 38 once the block is gone and it falls; sand_streams_update queues the streams at its parent face's screen_y - 96 too
scoped object sand_puff 84 frame_index s16  # sand_puff_frames index: + 1 a frame, 0 past 2 (cmp.h, signed)
scoped object shadow 80 flicker_timer s8  # on floor types 9 and 4 (and stage 10 floor blocks whose type byte, +12, is 20): counted down, when it was 1 or less (signed) 2 again and the shadow is hidden that frame (flags bit 19): it shows every other frame; shadow_start clears it
scoped object lava_column 8C side u16  # 0, or 1 when the column's x_int is C97 or more (spawn_lava_column): the lava_columns entry it takes once standing (lava_column_rise; movz.hw 8C index) and the one that removes it when a newer column takes it
scoped object lava_column 96 depth_offset s16  # the rock's depth_offset for a thrown column (spawn_lava_column, when the rock's takes_slot is 1): lava_column_toss sorts it at its floor's screen y + it (add.h 96)
scoped object sinkhole 84 open_position u16  # sinkhole_growth_records word 1 + scroll_position (sinkhole_start): sinkhole_open does nothing until scroll_position reaches it (unsigned cmp.h)
scoped object sinkhole 88 draw_depth u16  # screen_y, copied every frame once it opens (sinkhole_open): a player falling over its edge (touch kind 1B, player flag 20000) is drawn at the touched object's 88 - 1
scoped object sinkhole 8C unscaled_hitbox_size_x u16  # 4C (sinkhole_start): hitbox_size_x = it x sprite_scale >> 8 while it opens (as unscaled_offset_x 8A)
scoped object snow_mound 80 burst_frame s8  # snow_mound_burst_frames index of the mound (part 2 shows it + 2): + 1 every second frame while bursting, deleted past E (cmp.b, signed); not 0 = parts 2 and 3 are drawn (the touch and rider lists only while it is 0)
scoped object snow_mound 81 bursting u8  # 1 from the first touch (flags bit 2; drop_rings first if 92 has rings): every frame then runs the burst
scoped object snow_mound 82 frame_count s16  # + 1 a frame while bursting; past 1 (signed) the burst steps on and it goes back to 0
scoped object snow_mound 84 part_3_count s16  # + 1 a frame while bursting; on a burst step past 2 (signed) part 3 (x-flipped, 16 higher) takes its next frame and it goes back to 0: every other step
scoped object snow_mound 86 part_3_frame u16  # snow_mound_burst_frames index of part 3: 4 (snow_mound_init), + 1 each part_3_count step
scoped object spear_trap 82 phase_timer u16  # counted down (dec.h, moves on when it was 0): spawn word 94 before the first rise (spear_trap_init), then C frames rising, 3C up, and spawn word 92 between rounds (spear_trap_retract)
scoped object spike_panel 80 frame_index s8  # spike_panel_frame_sequence[step] (0 flat .. 5 up): indexes the head, part 1 and part 2 offset/sprite tables; 5 widens the boxes; spike_panel_hitbox_update (reading it on its parent) puts the hitbox part in the touch list only from 4 (signed compares)
scoped object spike_panel 81 step s8  # index into spike_panel_frame_sequence and spike_panel_step_durations: start_step (93) while held, + 1 when step_timer runs out, 0 again past 8 (signed)
scoped object spike_panel 82 step_timer s16  # frames left in the step (dec.h, steps on below 0, signed, only while start_step is 0): spike_panel_step_durations[step], spawn word 94 (the rest time) for step 0
scoped object spike_panel 86 trigger_delay u16  # 1E (spike_panel_init): counted down by spike_panel_hold while a player is within 40 in x (or start_step is 0); when it was 0 start_step is cleared and spike_panel_update takes over
scoped object spiked_disc 80 spin_frame u8  # spiked_disc_sprites index: + 1 & 3 (inc.b / and.b) on each spin step
scoped object spiked_disc 82 spin_toggle u16  # inc.h + and.h #1 a frame, a spin step at 0 that sets it back to 1: after the first step it is always 1 and the disc spins every frame (original code; spiked_disc_draw_held's halfword compare at 81 takes this field's low byte as its high byte)
scoped object tornado 84 screen_threshold s16  # as fire_front 84 (the same chaser code): tornado_records word; FFB0 / 190 (by direction) while holding back
scoped object tornado 86 catch_up_factor u16  # as fire_front 86 (the same chaser code): camera_slope_speed_int x it >> 8
scoped object tornado 88 base_speed s16  # as fire_front 88 (the same chaser code): the record's speed, 0 while holding back
scoped object tornado 8A saved_threshold s16  # as fire_front 8A (the same chaser code)
scoped object tornado 8C saved_base_speed s16  # as fire_front 8C (the same chaser code)
scoped object tornado 8E holding_back u8  # as fire_front 8E (the same chaser code); tornado_start clears it
scoped object tornado 8F hold_timer u8  # as fire_front 8F (the same chaser code)
scoped object tornado 90 wait_timer s16  # as fire_front 90 (the same chaser code): the record's wait, counted down outside game mode 11; tornado_start clears it; 0 on a direction 3 record unfreezes the players
scoped object tornado 92 hold_start_position u16  # (whole field, the C's spawn_arg_1) as fire_front 92 (the same chaser code): scroll_position when it started holding back, gives up after 300 units
scoped object vine_init 82 depth_offset s16  # segment_count x 8 (vine_init): vine_swing sorts each segment at its screen y - (x_int - base_x_int) + it
scoped object vine_swing 80 holding u8  # 1 every frame, 0 while the swing phase (3C) is 9F-110 and vines_hold_players is 0: the bar copies it (vine_bar 80) and its held players (state 15) let go at 0
scoped object vine_swing 82 depth_offset s16  # as vine_init 82: each segment's draw depth = its screen y - (x_int - base_x_int) + it (add.h 82[R23])
scoped object wall_update 80 invisible u8  # (as build_wall_parts 80 in vars_1.txt) FF when its wall_kinds record has no parts (first part sprite negative, build_wall_parts), else 0: wall_update only puts it in the wall list, nothing is drawn
scoped object wall_update 81 no_second_sprite u8  # (as build_wall_parts 81 in vars_1.txt) FF when its kind has no second-part sprite (wall_kinds +12 negative, build_wall_parts); at 0 wall_update places each part's second part at the part + its step and queues it in bucket FF
scoped object wall_update 82 end_position u16  # wall_spawns record word 2 (spawn_walls): 0 = removed once off the left of the screen (check_wall_left_screen), else once scroll_position reaches it (with its parts)
scoped object player_effect 96 depth_offset s16  # 28 (spawn_player_effect; player states 30 and 33 put 70): player_effect_update sorts it at screen_y + it (add.h 96)
scoped object sand_puff 82 lifetime u16  # 2D (spawn_centipede_sand_puff): sand_puff_animate counts it down (dec.h) and removes the puff when it was 0
scoped object shaking_block 80 frame_index s8  # entry of its kind's 3 in shaking_block_frames: + 1 every 3 frames, 0 after 2 (cmp.b, signed)
scoped object shaking_block 82 frame_timer u16  # counted down (dec.h); when it was 0 the next frame and 2 again (as checker_pad 82)
scoped object sink_swirl 96 depth_offset s16  # sink_swirl_effects word (8 or FFF0, spawn_sink_swirl): the streak and its puff are queued at screen_y + it
scoped object sinking_effect 96 depth_offset s16  # as player_effect 96: 70 (player state 33, which gives the effect this handler); sorted at screen_y + it
scoped object sliding_door 82 open_delay u16  # A (sliding_door_init): counted down (dec.h) by sliding_door_wait only while every playing player has stage_b_path_done; when it was 0 sliding_door_open
scoped object spawn_grate_step_debris 84 cut_z s16  # the step's z_int + 1E (build_grate_steps; mov.w 42 then add.h #1E): grate_step_update compares its saw's z - 20 with it (signed) and moves it up 7 a frame while cutting; the debris start at this z
scoped object spike_gate_beam 81 bounce_count s8  # + 1 at each landing (inc.b; a 6-pixel hop up each time); past 2 (signed) 0 again, sound 7 and spike_gate_beam_wait
scoped object spike_gate_beam 82 pause_timer u16  # counted down (dec.h; moves on when it was 0): 8 frames at the top (spike_gate_beam_rise sets it), A on the ground (spike_gate_beam_drop)
scoped object static_triangle 80 frame_index s8  # static_triangle_frames index: + 1 every 3 frames, 0 after 2 (cmp.b, signed); the reset is mov.h #0, 80 (a halfword: 1 use against 3 of the byte), which the C shows as two byte stores (80 and 81)
scoped object static_triangle 82 frame_timer u16  # counted down (dec.h); when it was 0 the next frame and 2 again (as checker_pad 82)
scoped object stone_block 96 depth_offset s16  # stone_block_positions word 3 (8 or 30): stone_block_draw sorts it at screen_y + it
scoped object treadmill_update 80 belt_phase u8  # + 9 x the players' speed level above spawn word 92, & 7F, a frame (add.b / and.b): treadmill_belt_sprites[it >> 4]; the roller (treadmill_roller_update) shows treadmill_roller_sprites[its parent's 80 >> 5]
scoped object turtle 80 stroke_step u8  # turtle_swim_frames / turtle_swim_speeds index: 1 when touched (turtle_wait), + 1 & F every 3 frames in turtle_swim; turtle_wait's head code waits for 4, which never comes
scoped object turtle 82 stroke_timer u16  # 0 when touched; counted down by turtle_swim (dec.h): when it was 0 the next stroke step and 2 again, 3C after the last one (gliding)
scoped object vine_bar 80 holding u8  # copied from its anchor's 80 (vine_swing) every frame: players held by it (state 15, PLAYER holder) stay while it is not 0; "CATCH!!" shows while the anchor's is 1
scoped object vine_bar 82 anchor_z s16  # the anchor's z_int when vine_init made it (mov.h 42[R23], 82[R3]); nothing reads it
scoped object wall_pad 80 frame_step u8  # wall_pad_frames index: + 1 (mod B, remu.b) each time frame_timer runs out; it runs while touched or while not 0, so one round per touch
scoped object wall_pad 82 frame_timer u16  # counted down (dec.h); when it was 0 the next step and wall_pad_frame_durations[step]
scoped object whirlwind 80 frame_index s8  # whirlwind_sprites index + 1 & 3 a frame while it moves; 0 when it vanishes, then whirlwind_vanish_sprites index + 1 every other frame, removed past 7 (cmp.b, signed)
scoped object whirlwind 82 spin_angle u16  # + spin_step & 3FF a frame: a 100-speed circle (direction_to_speed) added to its heading (24, speed 6E)
scoped object whirlwind 84 timer s16  # F0 (whirlwind_aim), counted down by whirlwind_move: at 0 (signed) or when touched it vanishes; whirlwind_vanish reuses it as an every-other-frame toggle (inc.h + and.h #1)
scoped object whirlwind 86 spin_step u16  # whirlwind_spin_step when spawned (spawn_whirlwind: + B & 3F for each new one)
scoped object white_puff 80 frame_index u8  # white_puff_frames index: + 1 (mod A, remu.b) each time frame_timer runs out; back at 0 the last, faintest frame is held
scoped object white_puff 82 frame_timer u16  # counted down (dec.h); when it was 0 the next frame and 3 again (4 frames each), 5A after the last frame

# ======== labels the gotos jump to (code lines, from the helpers) ========
00015D  code  run_frame_check_p3_start                      # run_frame, shared credits (cabinet_config 0/1) after starts 1 and 2: on a 3-player cabinet (bit 0) start 3 (bit 7, 12 without start buttons) starts the game, else the system mode runs
00019F  func  run_frame_check_p2_start                      # run_frame, individual coin chutes (cabinet_config 2; 3 after player 3): p2_credits >= credits_per_start and start 2 (bit 5, or 18) start the game
0001BD  code  run_frame_check_p1_start                      # run_frame: then p1_credits and start 1 (bit 4, or 10); none pressed: the system mode runs
0001DB  func  run_frame_start_game                          # run_frame, a start pressed with enough credits: system_mode 6 (the game), game_mode 0, system_mode_step 0, game_in_progress FF; then run_game_mode, run_objects, build_sprite_list, draw_queued_strings
000BBA  code  game_mode_continue_draw_count                 # game mode 0x0E: draws continue_count at 31F730, then a start with credits (by cabinet_config) resumes saved_game_mode; every frame but the one where a second ends with continue_count reaching 0
000FC8  code  game_over_check_p2_start                      # game mode 7, individual coin chutes (cabinet_config 2; 3 when player 3 cannot start): p2_credits and start 2 (bit 5), then p1_credits and start 1 (bit 4) start a new game
0018C5  code  check_zones_next_hitter                       # check_zones: the next object of the hitter list (link 68, from 20F016), tested against the whole zone list; a hit or the zone list's end comes back here
001926  code  check_zones_link_zone                         # check_zones: the hitter is inside the zone (x/y box, z ranges overlap): flags bit 27 on both, the hitter's 14 = the zone, the zone's 14 (anchor_x) = the hitter; then the next hitter
001942  code  check_zones_next_zone                         # check_zones: the next zone (link 68); every failed box test comes here; after the last zone the next hitter
001E09  code  check_hits_against_touchables                 # check_hits: check_hit of the hitter against the touchable list (R11 = 20F018); the last hitter comes straight here, the others when their pair checks left flag 2 (touched) clear
001ECD  code  check_hit_within_box_left                     # check_hit, box/round (R17 a box, the other round): the round one is not left of the box (the box's left edge R2, x + 7C, at most its centre, or its centre + radius 10); then the box's right edge (R2 + 10)
001EE5  code  check_hit_within_box_x                        # check_hit, box/round: the round one overlaps the box in x; then the same in y, then the heights
00203B  code  check_hit_add_slope_to_dx                     # check_hit, hitter on a sloped floor (76): dx (R2) += the slope correction R1, dy (R3) -= it; the signs of dx/dy (and which is larger) pick this or 002048
002048  code  check_hit_sub_slope_from_dx                   # check_hit: dx (R2) -= the slope correction R1, dy (R3) += it
00207B  code  check_hit_next_pair                           # check_hit: the next object of the list R10 walks (the hitters after R17, or the touchable list); every failed test of the pair goes here; the list's end returns
00213D  code  check_platforms_next_rider                    # check_platforms: the next object of the rider list (link 72, from 20F01E), tested against the platform list
002173  code  check_platforms_link_rider                    # check_platforms: rider within the platform's x/y box: rider flag 3 and 74 = platform, platform 74 = rider; rider z not above the platform's 56: platform flag 3 and (once) its 6F byte joins the bridge load list (1400, 1500 when 95); next rider
0021AD  code  check_platforms_next_platform                 # check_platforms: the next platform (link 72); a failed box test comes here; after the last one the next rider
0022C8  code  check_walls_next_rider                        # check_walls: the next object of the rider list (link 72, from 20F01E), tested against the wall list
00231A  code  check_walls_link_wall                         # check_walls: rider within the wall's box and height: flags bit 5 on both, each one's 78 (wall) = the other; then the next rider
00232E  code  check_walls_next_wall                         # check_walls: the next wall (link 72); a failed test comes here; after the last one the next rider
002C2E  code  player_place_drop_in                          # player_place: entry_status 81 (dropping in) and placed again next frame: no floor at the restart point (floor z 7FF or more), or touching an object (flag 2) or a zone (flag 27)
002C32  code  player_place_retry                            # player_place: handler stays player_place (placed again next frame) with entry_status R1 (81, or FF when previous_z's integer part is 800)
002D33  func  player_dispatch_state                         # player_update: jump on the state (table 002D3E); the prologue falls in here and the state code comes back here whenever it changes the state
002E6F  code  player_move_steer                             # player_move: steering (apply_floor_push + player_steer, or 002E93), speed, movement, find_floor, quicksand/sinking floors; check_player_step is skipped (straight here) with per_player_view_mode, states 4/17, floor type A, flag 31
002E93  code  player_move_steer_in_air                      # player_move: player_steer_in_air: no floor block under the player, or state 2 (falling) when it may steer
002F64  func  player_move_check_edge                        # player_move, floor type 0 or one without a forced state (113CD9): set_floor_sprite_priority, check_player_floor_edge (states 5-9 re-dispatch), apply_moving_floor, project, keep_player_on_screen
002FCD  code  player_move_finish                            # player_move's end unless respawning: check_player_jump, update_player_blink, remember_platform, add_player_to_lists, then player_animate (002FF1)
003322  func  player_animate_run_cycle_alternate            # player_animate_run_cycle: the 111ACC animations (11204C/111D8C per character) with flag 31, on floor type 1 attributes 2/3 facing within 5 of floor_1_facing_bases, or attributes 1/4 while alternate_run_toggle is 0-7F
003343  func  player_animate_run_from_table      # the run cycles' common part: animation = R10's table by facing, walk_cycle += the animation speed, a new frame past 4F0 (state 0x14) or 600, then drawn
00376B  func  player_animate_jumping_land                   # 002FF1 state 4: the jump ends after 50 frames, or when state_timer is out and z is at the floor: state 1, z = floor, jump_delay 0, flags 13/6/20/21 cleared, palette back
0037A4  func  player_draw                    # the player's drawing, unless object flags bit 19 hides it: at its floor (or the previous one) or, with player flags bit 0, its parts at draw_depth
003942  code  player_teetering_ledge_direction_2            # state 5 setup on a side-4 ledge, slope side 0 (or 2 on floor attribute 4): ledge_direction 2, teeter animation 1 (table 1128AE), mirrored (sprite flag 6)
003A81  code  player_teetering_fall_off                     # state 5 ends: state 6 (falling off), player_status FF (1 and flag 10 at health 8 or less) unless floor type D, mishap_flag; state_timer 0 at the start, or out at the animation's frame 7
003BD8  code  player_falling_off_edge_block_1f_depth        # state 6 setup, previous floor block type 1F or 11 (ledge side 0/1, or ledge_direction 0/1 on side 4): fall_draw_depth = the platform's (74) field 88 - 1, then -4
003C46  code  player_falling_off_edge_floor_1_depth         # state 6 setup, floor type 1 (ledge side 0/1, or ledge_direction 0/1 on side 4): fall_draw_depth = the platform's (74) field 88 - 1, then 003C59
003C59  code  player_falling_off_edge_depth_minus_10        # state 6 setup: fall_draw_depth -= 10 (after -70 on the far half of floor block type 1D, -20 on stage A), then the fall animation (+1C), sprite priority, speed_z 0, voice at health 8 or less
003D29  code  player_falling_off_edge_check_sinking         # state 6: floor block type 22 (R1 = 1DC0000) or 26 (240000), compared with cmp.h, which reads R1's low half, 0 for both: at z_int 0 or more, while y is less than group_leaders[0]'s, state 0x33 (sinking)
003E74  code  player_over_edge_depth_minus_10               # state 7 setup: fall_draw_depth -= 10 (floor type 1, after the platform's depth on ledge sides 0/1; other floors on ledge sides 0/1, -20 more on stage A)
003EBC  code  player_over_edge_slide                        # state 7 substate 1: slides over the edge (x/y += speeds, project), slip_distance += speed; then 003F04
003F04  code  player_over_edge_stop_sliding                 # state 7: the slide ends (speed 0, player flag 7, or slip_distance 20000000, 8000000 on floor type 9 or with flag 0x20000): substate 2 (hang)
004035  code  player_over_edge_hang                         # state 7 substate 3: update_ledge_pullback, speed 0; on a moving edge (flag 8) the drop (0040C9) once edge_object (12A) has passed the player along its direction (24)
004089  func  player_over_edge_check_pull_up                # state 7: ledge_pullback >= 14: substate 4 (climb back); state_timer out: the drop (on a kind 29 object or with flag 0x20000 only once the touched object's sprite_scale is 100+)
0040C9  code  player_over_edge_drop                         # state 7: state 8 (dropping), player_status FF (1 and flag 10 at health 8 or less), mishap_flag
0040F7  func  player_over_edge_hang_finish                  # state 7, the hang's frame end (substates 2-3, the drop): animate, blink, project, contacts unless flag 0x20000, lists; drawn at the previous floor (ledge side 2+), touched_by's 88 + 1 or fall_draw_depth
0041CC  code  player_over_edge_climb_up                     # state 7 substate 5 (climbing back): find_floor; on a moving edge back on the floor (004243) once past edge_object
00421B  code  player_over_edge_check_on_floor               # state 7: with flag 0x20000 back on the floor when no longer touched (flag 2); else once on a floor block (not floor type 2) that is the previous one
004243  code  player_over_edge_back_on_floor                # state 7: state 0, flag 0x20000 cleared, heading and target_heading = ledge_recover_headings[ledge_side]
004473  code  player_dropping_fall                          # state 8 substate 3: falls down the screen (speed_y + 6000 on projected_y); at screen y 110 respawn (stage 5, stage 8 below scroll 4000) or -8 health (respawn, or 0x16 at 0)
0045E2  code  player_falling_into_pit_depth_at_platform     # state 9 setup (ledge side 0/1, or ledge_direction 0/1 on side 4): fall_draw_depth = the platform's (74) field 88 - 1, then 0045F5
0045F5  code  player_falling_into_pit_depth_minus_10        # state 9 setup: fall_draw_depth -= 10 (after -70 on the far half of floor block type 1D), mishap_flag, substate + 1
004647  code  player_falling_into_pit_check_sinking         # state 9: floor block type 22 (R1 = 1DC0000) or 26 (240000), compared with cmp.h, which reads R1's low half, 0 for both: at z_int 0 or more, while y is less than group_leaders[0]'s, state 0x33 (sinking)
00478F  func  player_climbing_start                         # state 0xA substate 0 (not from the top after a jump): flags 6/13 cleared, palette back, speed 0, control_lock 0, substate 1
0047AF  func  player_climbing_grab_wall                     # state 0xA substate 1: y (x when record byte 0 is set) kept in the record's A..A+C; from below climb_remaining = record +6, climb animation, snapped to the block edge, climb_base_z; from the top substate 2
0047CF  code  player_climbing_clamp_to_wall                 # state 0xA: y (or x) = the bound it crossed (record A or A+C), fraction 0
004867  func  player_climbing_descend_over_edge             # state 0xA substate 2 (climbing down from the top): animate_around_anchor until it ends, then the climb animation at frame 6 at the anchor's screen position, 004965
004965  func  player_climbing_begin_climb                   # state 0xA: step_accumulator 0, substate 3
00496F  func  player_climbing_move                          # state 0xA substate 3: a step's animation running (animation_speed >= 0) goes to 004B21; else the trackball feeds step_accumulator (vertical) or side_step_accumulator
0049ED  code  player_climbing_reset_side_input              # state 0xA: side_step_accumulator 0 when speed_x's sign is opposite to it
004A24  func  player_climbing_side_step                     # state 0xA: side_step_accumulator reached 2 (8 with fast_climb): accumulators 0, sideways animation (112BAE per character); screen_x moves 2 if it stays in the record's range and on screen
004B16  code  player_climbing_reset_vertical_input          # state 0xA: step_accumulator 0 when speed_y's sign is opposite to it
004B21  func  player_climbing_check_vertical_step           # state 0xA: step_accumulator >= 4 steps down (flag 7), <= -4 up; then the step animation: z moves 2 a frame with climb_remaining, screen position by record +4
004B3E  func  player_climbing_start_vertical_step           # state 0xA: a new step: accumulators 0, the climb animation (112B6A per character) at speed 4; refused (004BC6) below climb_base_z or at the screen's edge (y D0 down, 10 up)
004BC6  code  player_climbing_block_step                    # state 0xA: no step: animation_speed FF, step_accumulator 0
004BD1  func  player_climbing_animate_vertical_step         # state 0xA, a vertical step's animation (none at FF): animation_speed 4 - min(|speed_y|, 3), x4 fast_climb; each new frame z and climb_remaining +-2, screen x/y by record +4; ends at frame 6/0; then 004C7C
004C7C  func  player_climbing_update_position               # state 0xA: climb_remaining > 0: standing again at record +6 (the foot), screen position unprojected to x/y, lists, splash zone, draw; at 0 the pull-up (112B84) and substate 4; also straight from state 0xA when touched by kind 5
004D95  func  player_climbing_pull_up                       # state 0xA substate 4: animate_around_anchor; at its end z = record +8 (the top), state 0, placed A past the edge, turned to 080 (380 when record byte 0 is set)
004F83  func  player_animate_skidding_slow_down             # 002FF1 state 0xC substate 1: a dust puff every 4 frames (not on floor type 7 or clipped), skid animation to frame 2, ground_speed/speed minus skid_slowdown/skid_speed_slowdown; at 0 substate 2
0055B1  code  player_hurt_dispatch_substate                 # state 0xD after hurt_contact_delay (its contact check sends only state 0x28 back to 002D33): push_player_out_of_wall, state = 0xD, jump on substate (table 0055C8)
005624  func  player_hurt_knockback                         # state 0xD substate 0 (after -4 health) or 1 (flag 24 set, no loss): flags 2/6/13 cleared, hurt_contact_delay A, substate 2, speed_z -80000, pushed along the toucher's direction (not kind 24), sound 21 and voice
0056F6  func  player_hurt_set_animation                     # state 0xD: the knockback animation (113260/113500/1133B0 per character, by facing) and the hurt flash (period 3, FF with flag 24); also after 005B9F
005766  func  player_hurt_fly                               # state 0xD substate 2: gravity 6000 (/4 on floor type 4 unless the toucher is kind 27), move, find_floor, check_fall_into_pit (state 9 re-dispatches); then 0057CA
0057CA  code  player_hurt_check_landing                     # state 0xD: a floor 8 or more above sends the player back to the previous x/y/floor; landing: substate 3, state_timer 2, sound 20, next frame; else the second frame once falling
00585C  code  player_hurt_landed                            # state 0xD substate 3: state_timer frames on the floor, then substate 4: a hop (speed_z -40000, half on floor type 4), speeds 0
0058FA  func  player_hurt_land_after_hop                    # state 0xD substate 4 lands: substate 5, z = floor, animation_timer 0
00596A  func  player_hurt_get_up                            # state 0xD substate 5: the get-up animation; at its end substate 6, mishap_flag 0, palette back, state_timer 1E, invulnerable 78 with blink flag 29 (unless flag 24), state 0x16 at health 0
0059FA  code  player_hurt_recover                           # state 0xD substate 6: state_timer frames, then walking (state 1); the jump button (state 4) or trackball input ends it early
005B9F  code  player_hurt_shocked                           # state 0xD substate 8 (7: touched kind E or 15, electric fence posts and poles): palette per animation frame (113F4F + character*10); animation done and state_timer (4) out: substate 2, normal palette, 0056F6
005DB4  code  player_bouncing_end                           # state 0xE substate 3 ends (state_timer out: 0; jump button: 4; trackball: 1): flag 2 cleared, substate 0, speed 0, animation_speed 0, headings turned around (target_heading + 200), set_facing
0061D2  func  player_in_zone_steer                          # state 0x22 substate 1 after the contact check: trackball speed (squared, max 5000000) and state 1/13/14 by it (0 when still), player_animate; back to 0x22 while flag 27 (in a zone) is set
006373  code  player_entering_stage_wait_path               # state 0x2A entry_step 1: once the entry path ends (path 0) flag 26 cleared, enter_on_respawn 0, state/substate 0 (the state is then overwritten with previous_state); player_move
00652D  func  player_tumbling_start                         # state 0x20 substate 0 (after -4 health) or 1 (flag 24): hurt_contact_delay 78, substate 2, speed_z -80000, pushed along the toucher's direction (100 on floor type D; none from kind 17), tumble animation, voice, spawn_tumble_effect
00669A  code  player_tumbling_fly                           # state 0x20 substate 2: gravity, move, find_floor, back from a floor 8+ above; tumble frames while hurt_contact_delay > 50 or in the air; landing: substate 3, frame 7, sound 20
00703A  code  player_stage_scene_geyser_start               # state 0x18, scene_step 5: placed at x 17D, y 154D (+0/+20/-20 per player), hop animation (113AF0 per character), speed_z -30000, the voice
0070E9  code  player_stage_scene_ride_geyser                # state 0x18, scene_step 6: scene_timer above 90: screen x drifts (0/+1/-1 per character), z rises D a frame; else on stage_00_geyser's z - 20, hopping (scene_hop_z)
00717F  code  player_stage_scene_shrink_away                # state 0x18, scene_step 7: pose animation (113B14 per character) at a per-character screen point (113FAB); after kind*1E+3C frames sounds 16 and voice, then sprite_scale shrinks (scene_zoom)
0072C3  code  player_stunned_dispatch_substate              # state 0x2F after its contact check (unless that hurt the player): jump on substate (0/3 setup, 1 wait, 2 recovery)
007357  code  player_stunned_wait                           # state 0x2F substate 1: state_timer (3C) counts down; then substate 2 with the recovery animation (114244 per character), or state 0x16 at health 0
00749A  code  player_stage_exit_stage_4                     # state 0x21 on stage 4 (by goal_step): waits 168 frames facing 1225,1439, runs there, hops within A0 of it (sound 16), falls until screen y B8 (players_finished)
007AE7  code  player_stage_b_sequence_start                 # state 0x38 ending_step 0/6: ending_step + 1, path 4D, control_lock 0, z = floor; player_move
007B68  code  player_stage_b_sequence_follow_path           # state 0x38 while its path runs (ending_step 1/3): state = previous_state, player_move
007E1F  code  player_carried_off_follow_path                # state 0x31 sequence_step 1: runs path 2F as state 0x13 (player_move) while a floor block is under the player; none: 007E24
007E24  code  player_carried_off_grabbed                    # state 0x31: sequence_step 2, the caught animation (1128D8 per character), voice 36/37/77
007E88  code  player_carried_off_follow_carrier             # state 0x31 sequence_step 2: animate_and_move; eases toward scene_object below z 9C0 (none: +4 a frame), +4 a frame from 9C0; at z 9D0 step 3
007FCC  code  player_carried_off_on_seesaw                  # state 0x31 sequence_step 4: hurt while previous_state is 0xD; else burning_boulder_released FF, on the floor, check_player_stalled; then 008006
008006  code  player_carried_off_wait_seesaw                # state 0x31: standing until seesaw_launched; then step 5: launched animation (1149D6 per character), voice, z - C a frame, off the top of the screen: step 6
008112  code  player_trapped_dispatch_substate              # state 0x32 after its contact check (unless state 0x20): jump on substate (trap start, flash, shake to escape, jump out)
008CE9  code  player_pulled_start                           # state 0x26 substate 0/6: substate 1, sprite priority, flags cleared, pull_angle 300, pull_start_distance, voice; eases toward scene_object, thrown (substate 2) once pull_angle is 1D0 or less
008E5E  code  player_pulled_carried                         # state 0x26 substate 4: control_lock 7F, player_move_with_scroll, carried animation (9C4D per character); x CD0 sets drift_turned_back; back at x 630 or less: substate 5, drift_done FF
008ECC  code  player_pulled_skid                            # state 0x26 substate 5: heading 2A0, speed 5000000, state 0xC (skidding, previous_state kept), sound 48/6D/49, then player_animate_skidding
008F8F  code  player_floor_8_sequence_drop_in               # state 0x25 sequence_step 2: step 3, scroll stopped, flag 24, z raised by A0 + character*20, speeds 0, direction 200, hurt animation, voice 57/65/67; falls, landing: step 4
00918E  code  player_floor_8_sequence_drift                 # state 0x25 sequence_step 7: control_lock 10, player_move_with_scroll; step 8 (speed_z 0) once y reaches 4F2
0091BD  code  player_floor_8_sequence_drift_fall            # state 0x25 sequence_step 8: gravity, moved by the scroll drift (compute_scroll_drift), find_floor (+20); landing: step 9
0092B4  code  player_floor_8_sequence_ride_scroll           # state 0x25 sequence_step 10: player_move_with_scroll and animate; then 0092C5 unless dying
0092C5  code  player_floor_8_sequence_move_down             # state 0x25: y + 4 a frame without scroll drift; from y 800 control_lock 7F, speed 0; at y 9B0 step 11, state_timer 258, hop (speed_z -20000, speed_y 40000), 113AA8 animation, spawn_player_effect
00935D  code  player_floor_8_sequence_hop                   # state 0x25 sequence_step 11: low gravity (2000) until y A1C; then step 12: escape_progress 0, spawn_struggle_marker, spawn_sign_2 kind C above, voice 38/3C/44
0093FF  func  player_floor_8_sequence_struggle              # state 0x25 sequence_step 12: trackball speed >> 4 into escape_progress, frames by progress (009C13 per character); state_timer out: step 14; progress 400: step 13, jump out (speed_z -80000, speed_y 20000)
009475  code  player_floor_8_sequence_reset_escape          # state 0x25 step 12: escape_progress 0 when the trackball is still or pushes toward headings 280-380
009537  code  player_floor_8_sequence_jump_out              # state 0x25 sequence_step 13: gravity, move, falling animation; landing: bounce (speed_z reversed and halved, speeds halved), sequence_bounced FF, step 16
009649  code  player_floor_8_sequence_bounce                # state 0x25 sequence_step 16: gravity, move, animate; landing: step 15 (speeds 0, 1140EC animation per character, state_timer 3C)
00A0A2  code  player_caught_carried                         # state 0x30 substate 1: animate_and_move (voice at its end); in game mode 0x19 eases toward scene_object, else z + 84000 a frame; at z 4C8 substate 2 (dropped, hurt)
00A44E  func  player_knocked_off_start                      # state 0x36 substate 1 (with health): invulnerable 258, hurt_contact_delay A, speed_z -80000, pushed along the toucher's direction, sounds 1D and voice, hurt animation and flash
00A566  func  player_knocked_off_fly                        # state 0x36: gravity 6000 (/4 on floor type 4), move, find_floor, second frame once falling; at screen y 120: -8 health (respawn, or 0x16 at 0)
00B6F8  code  push_player_out_of_wall_push_back             # push_player_out_of_wall: back to the previous x/y/floor (flag 12 except stage 8 below scroll 4000 and states 25/26), control_lock 0: a new wall, or the same one for 1E frames (wall_timer)
00B7EE  func  push_player_out_of_wall_deflect               # push_player_out_of_wall: direction deflected off the wall (by the wall direction's sonic_bounce_records angle, +-80), control_lock 4; straight back on stage 8 below scroll 4000; floor type 4 or speed 200+ (and states 25/26)
00B97B  func  player_steer_speed_from_trackball             # player_steer: target speed = |trackball + push| squared * 1.5 (max 5000000; /4 with flag 31, 0 when sinking), slowed by sink_depth on floor type A and to 60/100 on floor type 4; then 00BA2C
00BA2C  func  player_steer_approach_speed                   # player_steer: speed toward the target (apply_speed_scale; up at once, down 1/32; 1/32 and 1/64 on floor type 7), animation_speed, state 0/1/13/14 by it (not in 4/17/25/26/2D); paths come straight here
00BB67  func  player_steer_turn_and_ground_speed            # player_steer's end: turn_player; ground_speed = speed >> 8 x heading_speed_factors[(heading + 20) >> 6] (= speed with flag 31); a slope (76) scales it and sets direction (slope_speed_direction_pairs)
00C0B4  code  shadow_update_flicker                         # shadow_update: palette 87A0, hidden every other frame (var_80 from 2, flags bit 19): floor type 9, game mode 1A from sequence_step 2, or the player's flag 4
00C78F  code  find_floor_next_block                         # find_floor: the next of the nearby floor blocks; a position outside the block's x/y box comes here; none left: 00C793
00C793  func  find_floor_no_block                           # find_floor: no nearby block holds the position: floor_block -1 (a real one kept in previous_floor_block), height and slope from no_floor_block
00C7F7  func  find_floor_set_floor_z                        # find_floor, the block found or no_floor_block (R10): floor_z = its height (+10), floor_slope = +13; on a slope floor_z += (y - y min, x - x min with floor_slopes bit 1) >> 12 (rounded) x factor >> 4
00C967  func  check_player_step_against_floor               # check_player_step: on a path z = floor; else z vs floor: within 8 z = floor, lower floor state 2, higher (a wall) back to the previous place, bounce at speed 500+ or stop; on a platform too (its z when flat, unless over a type 24 block)
00D580  code  pay_and_join_own_credits                      # pay_and_join, individual coin chutes (cabinet_config 2; 3 for players 1/2): player 2 pays from p2_credits with start 2 (bit 5, or 18), player 1 from p1_credits with start 1 (bit 4, or 10)
00DCFF  func  follow_player_path_start                      # follow_player_path: a new path (path_request or all_players_path_request): path_pointer = player_path_offsets[path + player], speeds 0, z = floor, flags cleared, state 1, flag 0; then 00DDBD
00DD53  func  follow_player_path_run                        # follow_player_path: a running path: a speed-0 record waits path_wait frames; else heads for the record's x,y until within 8 (records with bit 1 are passed at once), then 00DDA7
00DDA7  func  follow_player_path_next_record                # follow_player_path: the next 8-byte record, or the path's end (path 0) when its speed word is negative
00DDBD  func  follow_player_path_load_record                # follow_player_path: path_speed from the record; bit 0: a jump (state 4); speed 0: path_wait from +2, else target_heading toward its x,y
00DDF2  func  follow_player_path_aim_at_point               # follow_player_path: target_heading and facing_angle = angle to the record's point (+2, +4) - 80; heading too if object flags bit 0 was set (follow_player_path_start; cleared here); then 00DE2B
00DE2B  func  follow_player_path_target_speed               # follow_player_path's result: path_speed << 16; 0, with speed stopped, when that is 200000 or less and the speed's whole part is under 20
00E58D  code  check_player_contacts_ring_health             # check_player_contacts, a ring (kinds 3, 2A) at health settings 1-3: rings_to_health + 1, a health point every R1 rings (4, 8 at setting 3; half with 2+ players in play)
00E5C6  func  check_player_contacts_clear_state             # check_player_contacts, nothing holds the player (no or ignored touch, a path, a ring, invulnerable): pushed_out 0, last_hazard and last_damaged FFFF, object flags 18/23/31 and player flags bit 1 off
00E62B  func  check_player_contacts_hurt_unless_invulnerable  # check_player_contacts, kinds 4, F, 10, 16 (27 with flag 12): ignored while invulnerable_timer >= 0, else 00E631
00E631  func  check_player_contacts_hurt                    # check_player_contacts: R3 = 0 (hurt substate 0, -4 health); kinds 9, D, 18, 28 come straight here (29 with flag 12)
00E634  func  check_player_contacts_attack_or_hurt          # check_player_contacts: jumping (state 4): the attack (00E70C); else state 0xD with substate R3 (1 for kind B) set once per object (last_hazard), hurt_contact_delay 2
00E65A  func  check_player_contacts_solid                   # check_player_contacts, solid kinds 5, C, 20, 21 (11 when slow): jumping attacks; climbing undoes the step; else back to the previous place: bounce (state 0xE) at speed 500+, or stop; pushed out after 1E frames
00E70C  func  check_player_contacts_attack                  # check_player_contacts: the jumping player hits the object once (last_damaged): its flag 6, health - attack_power; a sturdy one (health left; kinds 9, B-E, 11, 18, 19) throws the player back, else hit_stop 3
00E9A2  code  check_player_contacts_move_with_object        # check_player_contacts, kind 19 touched from side 0 (or 8, keeping the new position): the player moves with the object (speed_x, speed_y * 2 unprojected; x gets it twice), then stops (state 0)
00F62E  func  electric_pole_draw                            # electric_pole_update: unproject, delete_if_off_screen, the pole (screen_y + 84) and its sparks, touchable list; untouched, or touched with health 4+ after kind = 15
00FA4E  code  metal_wall_draw_part_in_bucket                # metal_wall_draw: queue_sprite_in_bucket(R0) for a part: part 0 in FF, part 1 in FE, part 2 in FE once the wall's health is 0 (else at depth screen_y - 20)
00FE52  func  flame_machine_draw                            # flame_machine_update: no players active, or time (86) left: unproject, queue, touchable list, hit flash on a health change, the parts; health 0 falls into flame_machine_destroyed
012EF6  func  sand_rock_crumble                             # sand_rock_update, crumbling (81 set; touched with no rings to drop): every second frame (82) the next sand_rock_crumble_frames frame (80, 0-D) on the rock and its piece; past D the rock is deleted
012F5E  func  sand_rock_draw                                # sand_rock_update and sand_rock_crumble every frame: queue_sprite_at_depth screen_y + 14; add_to_touchable_list while crumble_frame (80) is 0
0130A8  func  snow_mound_burst                              # snow_mound_update, bursting (81 set; touched, after drop_rings if it has rings): every second frame (82) snow_mound_burst_frames[80] (piece 80+2), every other step (84) the third piece's frame (86); past E deleted
013147  func  snow_mound_draw                               # snow_mound_update: queue the mound (depth screen_y + 12), its pieces while bursting, else the touchable and rider lists; the last piece in bucket FF
094069  code  event_19_next_player                          # game mode 0x19: the next player block (R11 + 200) of the loop that looks for a playing player whose object's x passes 17C (barred_gate_closing)
09407D  func  event_19_check_rope_bridge                    # game mode 0x19 after that loop: rope_bridge_collapsing: scroll mode 0 at speed 1E; else from scroll DA0 the scroll stops and scene_timer counts to 78 (rope_bridge_collapsing); then the stage frame
0942DD  code  event_14_count_down                           # game mode 0x14 scene_step 2: scene_timer (12C) counts down; at 0 scroll mode 7 and game mode 3; else 0942F5
0942F5  code  event_14_stage_frame                          # game mode 0x14: spawn_sign(FDEF, AF0, E, 28) at scene_timer 3C, update_player_summary, scroll stopped (mode 1), game mode 7 with no player in play, the stage frame
0943B7  code  event_13_hold_scroll                          # game mode 0x13 scene_step 1: scroll mode 1 while scene_timer (78) runs, then step 2 and scroll mode 0; update_screen_wipe; then 094465
094455  code  event_12_stop_scroll_at_80b1                  # game mode 0x12: scroll mode 1 once scroll_position reaches 80B1 (players not all out or done with the drift), then 094465
094465  func  event_12_stage_frame                          # game modes 0x12 and 0x13: next_stage_slot_request 0, update_player_summary, game mode 7 with no player in play, event_12_fast_scroll (mode 7 -> mode 0 at 20), update_scroll and the stage frame
0946DA  code  event_0d_start_scroll                         # game mode 0x0D: scroll mode 0 at speed 1C: every frame of scene_step 2, and the frame step 1's B4-frame wait ends
0952D9  code  event_1f_play_alarm_sound                     # game mode 0x1F: sound 7C at escape_alarm_timer 1 (after start_escape_alarm_flash, camera shake, sound F) and again at 30C
0132EF  code  rock_wall_keep_touchable                  # rock_wall_update: add_to_touchable_list, then drawn; an intact wall not touched by an attacking player (flags 4 / toucher flag 40) skips the break steps to here
013801  code  sand_arch_ball_draw                       # sand_arch_ball_move: queue_sprite_at_floor, then the spray part within 10 of the surface, else the shadow at the floor; the ball over a pit (floor_z >= 800) falls and comes here
01446D  code  sliding_door_wait_count_ready             # sliding_door_wait: R3 + 1 for a player that is not playing (status not negative) or, after R2 + 1, a playing one with stage_b_path_done (player 140); the door opens when R3 is 3 and R2 not 0
014BC0  func  eel_anchor_stretch                        # eel_anchor_update: field 6E += 81 (signed), turning back with a 3C-frame pause past 1111 / under 480, and every piece gets 24, 81 and 6E x n; the pause (80 set) comes here on its last frame
014C53  func  eel_anchor_draw_shadow                    # eel_anchor_update while pausing, and eel_anchor_stretch's end: the anchor's (R23) third child, the shadow (A898 palette 7A0), put at its floor (place_sprite_at_cursor_floor) and queued in bucket FF
0166E3  code  set_screen_push_edges_past_quadrant_0     # set_screen_push_edges: sine_entry (R11) one more quarter (200) into sine_table and quadrant_bits (R0) bit 1; every section direction but x >= 0, y >= 0 comes here (R0 from 0 ends 1, 3, 7 or F)
016BC8  func  update_scroll_at_end                      # update_scroll: scroll_ended = FF; only stage 3 goes on (its end sequence: vram scripts, speed up and slow down, spawn_stage_03_end_pillar); also when scroll_position reaches scroll_end
01DCFD  code  ending_eggman_fly_away_empty_line         # ending_eggman_fly_away (circle wipe): the line is farther than the radius from line 70: window left 1, right 0 (nothing)
01DD0C  code  ending_eggman_fly_away_whole_line         # ending_eggman_fly_away (circle wipe): radius^2 - dy^2 negative in 16 bits yet the line within the radius of line 70 (the square overflows once the circle is wider than the screen): window 0..13F
01E54F  func  ring_spin_keep_spinning                   # ring_spin: not collected: animate (spawn_ring_glint at frame 15), removed off the left of the screen, touchable list, drawn; a touch by a player in state D/20 with hurt_contact_delay comes here too
01E678  func  carried_ring_keep_spinning                # carried_ring_spin: not collected: flags bit 4 = bit 3, z = its platform's z - 82, the platform's floor block, then as ring_spin_keep_spinning; a player in state D/20 with hurt_contact_delay comes here too
080464  func  spiked_roller_chase_read_script           # spiked_roller_chase: with no wait left and scroll_position at the record's: threshold 88, factor 8E, base 90, wait 82 (no wait: kind 18, players_frozen 0), or the end (state 5 once off screen); not holding back and no mishap comes here
0804C8  func  spiked_roller_chase_move_step             # spiked_roller_chase: on its floor unless roller_kind is 2 or 5 (over 8 above it: state 1, chaser_object 0), hold-back limits, speed by the threshold 88, moves along 94 after the wait 82 unless grey_screen_active, then the rumble
080564  code  spiked_roller_chase_catch_up              # spiked_roller_chase: past the screen threshold (88): wait 82 = 0, stop holding back, speed = max(camera ground speed x 8E >> 8, base 90)
080570  code  spiked_roller_chase_stop_holding_back     # spiked_roller_chase: 81 = 0, threshold 88 and base 90 back from 92/84; after 300 scroll units, the hold timer (9E) or every player finished
0805A4  code  spiked_roller_chase_use_base_speed        # spiked_roller_chase: speed = base 90 (on screen before the threshold, or when the catch-up speed is not above it)
0806A6  func  spiked_roller_chase_rumble                # spiked_roller_chase: on screen: back from off screen (97) the rumble timer 96 restarts at 0; while it moves sound B every spiked_roller_rumble_delays[speed] frames
0806F0  code  spiked_roller_chase_no_rumble             # spiked_roller_chase: off screen (by path direction) or not moving: 97 = FF, so the rumble starts at once when it is back
0806F7  func  spiked_roller_chase_check_event           # spiked_roller_chase, after the rumble: at the scroll position of its next spiked_roller_events record (72), with substate 98 at 0, a passed coordinate test sets state 80 from it and 72 + 8; roller_ram_hit = 0
080B05  code  spiked_roller_roll_into_machine           # spiked_roller_crash_into_machine substate 1 (substate 0 falls in): speed 50 from scroll 8600, y += speed_y, animate, rumble sound B, substate 2 at y <= AE0
083728  func  machine_fire_read_script                  # machine_fire_update state 2: until z - height reaches the record's z the climb goes on; then the record (84, 86, 88, 90), or the end (state 3); not holding back and no mishap comes here
083762  func  machine_fire_climb_step                   # machine_fire_update state 2: hold-back limits, speed from the screen-y threshold 84, z climbs after the wait 90, projected x/y follow the slope, sound 9 every 3C frames on screen, x/y, touchable list
0837A1  code  machine_fire_stop_holding_back            # machine_fire_update: 8E = 0, threshold 84 and base 88 back from 8A/8C (300 scroll units or the hold timer 8F)
0837D5  code  machine_fire_use_base_speed               # machine_fire_update: speed = base 88
0838D4  code  machine_fire_move_spawning_fireballs      # machine_fire_update state 4 (state 3 sets up and falls in): x + 1 pixel a frame for 80 frames, on its floor; per lane (3, timers at 72-74) a random 0-1F delay, then a fireball_start (next handler flame_start)
083972  code  machine_fire_next_lane                    # machine_fire_update state 4: the next lane timer and lane count (70EC + 1, 70EA - 1); a spawned fireball comes back here
085A61  func  fire_vent_blast                           # fire_vent_update: blast_delay FFFF, burst_state 1, grille 6A50 palette 9120, no more glow; a fire_vent_burst_animate child at +3A/-31 and sound A; the delay reaching 0 on screen comes here
085B00  func  fire_vent_draw                            # fire_vent_update and fire_vent_blast: update_screen_position; touchable once blasting (88 not 0; FF, the burst's end, becomes 1 with touch kind C); queue_sprite_at_depth at screen_y - 48
0864FE  code  burning_boulder_load_record               # burning_boulder_update state 1: the record's threshold, factor, base, wait, next record; scroll_position at the record's comes here past its y word, else the y test falls in
08654C  code  burning_boulder_stop_holding_back         # burning_boulder_update: threshold and base back from the saved copies, slow_timer 78 (speed 10), sound timer 0
0865C4  code  burning_boulder_use_base_speed            # burning_boulder_update: speed = base, or 10 while slow_timer runs
086974  code  ember_emitter_keep_up                     # ember_emitter_move: behind screen x 40 (x >= 40 for floor directions 0/3, <= 40 for 1/2/4) it moves along its floor direction by camera_slope_speed >> 7
087366  func  bridge_fire_read_script                   # bridge_fire_rise: when z - height reaches the record's z or scroll_position its position: removed at the end record, state 7 when the threshold is negative, else threshold, factor, base, wait
0873BF  func  bridge_fire_climb_step                    # bridge_fire_rise: as machine_fire_climb_step (hold-back limits, speed by the threshold 84, z climbs after the wait 90, sound 9, touch list), and at z - 66 <= 534 stage_trigger_bits = FFFFFFFF once (9A)
0873FE  code  bridge_fire_stop_holding_back             # bridge_fire_rise: threshold and base back from the saved copies (300 scroll units or the hold timer)
087432  code  bridge_fire_use_base_speed                # bridge_fire_rise: speed = base
08763C  func  lava_flow_read_script                     # lava_flow_advance: when scroll_position or x reaches the record's: removed with its parts at the end, else threshold, factor, base, wait
087689  func  lava_flow_move_step                       # lava_flow_advance: hold-back limits, speed by the threshold 84, moves +x after the wait 90, animate_all_parts, sound 10 every frame on screen (98, the sound timer, is also the off-screen mark and is cleared every frame before its countdown; none in game mode 7), spawn_lava_flame, extend_lava_flow, touch list, drawn
0876C8  code  lava_flow_stop_holding_back               # lava_flow_advance: threshold and base back from the saved copies (300 scroll units or the hold timer)
0876FC  code  lava_flow_use_base_speed                  # lava_flow_advance: speed = base
0883F4  func  icicle_dropper_coordinate_reached         # icicle_dropper_move: its x or y (by direction 24) reached the record's coordinate: R10 past the record's scroll position, then icicle_dropper_load_record
0883F9  func  icicle_dropper_check_scroll_position      # icicle_dropper_move: scroll_position below the record's: icicle_dropper_move_step with the current record, else icicle_dropper_load_record
088401  func  icicle_dropper_load_record                # icicle_dropper_move: threshold, factor, base, wait, next record; at the end icicle_dropper_ready = FF and removed (the last dropper: icicle_dropper_finish)
08843A  func  icicle_dropper_move_step                  # icicle_dropper_move: on its floor (or with the ice platform: moving_floor_object x - 98 + 2D), hold-back limits, speed by the threshold, moves along 24, a drop_icicle_row every 400 of travel (300 on the platform)
0884DC  code  icicle_dropper_catch_up                   # icicle_dropper_move: past the threshold: wait 0, stop holding back, speed = max(camera slope speed x factor >> 8, base)
0884E8  code  icicle_dropper_stop_holding_back          # icicle_dropper_move: threshold and base back from the saved copies (300 scroll units, the last dropper with every player finished, or the hold timer)
0884FE  func  icicle_dropper_catch_up_speed             # icicle_dropper_move: the speed when catching up: camera_slope_speed (by the floor slope's factor) x factor 86 >> 8, at least base 88; then icicle_dropper_move_at_speed
088537  code  icicle_dropper_use_base_speed             # icicle_dropper_move: speed = base
08853C  func  icicle_dropper_move_at_speed              # icicle_dropper_move: chaser_speed = R1; after the wait 90 (last dropper: 400 once every player finished) along 24: travel 94, platform_offset 98, x/y unless riding; unless flag 1 drop_icicle_row every 400 (300 riding)
08879A  code  drop_icicle_row_line_up                   # drop_icicle_row: an icicle within 40 of the script's end coordinate (placed 20 from it by direction): flag 1 on it and on the dropper (no more rows), its 84 = that coordinate
089330  func  tornado_load_record                       # tornado_move: threshold, factor, base, wait, direction (box F0 x 80 turned with it; players_frozen 0 on an x- record with no wait), group_leader_0_held 0 at record 08A72E; at the end removed with its columns (goal_unlocked FF if the record says)
089341  func  tornado_remove_with_columns               # tornado_move: flag 400 on its five columns (96-9F: tornado_column_circle deletes itself), then delete_current_object; while map_screen_active, and at the script's end (tornado_load_record)
089429  func  tornado_move_step                         # tornado_move: on its floor + 8, rotation_angle - 4, zone list, tornado_throw_whirlwinds, listed hazards, hold-back limits, speed (x4 once every player finished), moves along 24, sound 12 every 28 frames on screen
08949E  code  tornado_hazard_x_at_block_edge            # tornado_move: moving along y: the hazard's x = its floor block's x min - 8 (y = its y, or y - 80 for direction 2)
0894D5  code  tornado_hazard_y_at_block_edge            # tornado_move: moving along x: the hazard's y = its floor block's y min - 8 (x = its x - 80 for direction 1, its x for 3)
0894EC  code  tornado_place_hazard                      # tornado_move: spawn_tornado_hazard at x (R7), y (R8) and its z, then the next hazard list entry (48 + 4)
089561  code  tornado_catch_up                          # tornado_move: above the screen or past the threshold 84: stop holding back, speed = max(camera slope speed x 86 >> 8, base 88)
089567  code  tornado_stop_holding_back                 # tornado_move: 8E = 0, threshold 84 and base 88 back from 8A/8C (300 scroll units or the hold timer 8F)
08957D  func  tornado_catch_up_speed                    # tornado_move: the speed when catching up: camera_slope_speed (by the floor slope's factor) x factor 86 >> 8, at least base 88; then tornado_move_at_speed
0895B6  code  tornado_use_base_speed                    # tornado_move: speed = base 88
0895BB  func  tornado_move_at_speed                     # tornado_move: chaser_speed = R1 (x4 once every player finished); after the wait 90 moves along 24; off screen (x > 180 or < -40 by direction) 73 = FF, else sound 12 every 28 frames (not in game mode 7)
0896D9  code  tornado_column_bounce_radius              # tornado_column_circle: the radius (3C) left 1000-6800: a new random rate (12; negative from the top) and the radius set to that limit
089FBD  func  sand_block_leader_read_script             # sand_block_leader_move: the wait counts down, then at the record's scroll position: removed at the end, else threshold, factor, base, wait
089FFE  func  sand_block_leader_move_step               # sand_block_leader_move: find_floor, projected, rider list, hold-back limits, speed by the threshold; after the wait moves along 24, centred on its floor block; y <= A2B: sand_block_leader_active 0
08A06B  code  sand_block_leader_catch_up                # sand_block_leader_move: past the threshold (or screen y > E0 along x): wait 0, stop holding back, speed = max(camera ground speed x factor >> 8, base)
08A077  code  sand_block_leader_stop_holding_back       # sand_block_leader_move: threshold and base back from the saved copies (300 scroll units or the hold timer)
08A0AB  code  sand_block_leader_use_base_speed          # sand_block_leader_move: speed = base
08A346  code  sand_block_shrink                         # sand_block_update state 3 (state 2 builds the cut and falls in): hitbox_size_y = leader y - y, clip line at the leader's projected position; at 0 the face falls, clips freed, removed
08AB91  func  biting_head_read_script                   # biting_head_chase: the wait counts down, then at the record's scroll position or coordinate (by direction) biting_head_load_record
08ABED  func  biting_head_load_record                   # biting_head_chase: threshold, factor, base, wait, direction, next record; at the end its parts are freed and it is removed
08AC2E  func  biting_head_chase_step                    # biting_head_chase: projected; its child box at x + 40 (same y, z); both in the zone list; hold-back limits (hold timer FF outside game_mode 3); then biting_head_choose_speed
08ACC6  code  biting_head_catch_up                      # biting_head_chase: above the screen or past the threshold: wait 0, stop holding back, speed = max(camera slope speed x factor >> 8, base)
08ACD2  code  biting_head_stop_holding_back             # biting_head_chase: threshold and base back from the saved copies (300 scroll units or the hold timer)
08ACE8  func  biting_head_catch_up_speed                # biting_head_chase: speed = camera_slope_speed (R8 = floor_slope_factors[floor_slope] on a slope) x catch_up_factor >> 8, base_speed when not above it; then biting_head_move_at_speed
08AD21  code  biting_head_use_base_speed                # biting_head_chase: speed = base
08AD26  func  biting_head_move_at_speed                 # biting_head_chase: chaser_speed = R1, moves along 24 >> 8 after the wait; parts 0/4 animate and it bobs while moving; bite steps, bounce; x <= -355: box heights 50, x <= -431: biting_head_done; drawn
08AE25  code  biting_head_apply_bounce                  # biting_head_chase: screen_y += the biting_head_bounce step (the head shakes after a bite)
08B026  func  fire_front_read_script                    # fire_front_chase: the wait counts down, then at the record's scroll position or coordinate (by direction) fire_front_load_record
08B091  func  fire_front_load_record                    # fire_front_chase: threshold, factor, base, wait (a wait stops it), next record; at the end chaser_object = 0 and removed
08B0CF  func  fire_front_chase_step                     # fire_front_chase: zone list, hold-back limits (hold timer FF outside game_mode 3), speed by the threshold; after the wait moves along 24 >> 8, sets speed_x/y; factor 0 record: width F2, chaser_stopped FF
08B133  code  fire_front_catch_up                       # fire_front_chase: past the threshold: wait 0, stop holding back, speed = max(camera ground speed x factor >> 8, base)
08B13F  code  fire_front_stop_holding_back              # fire_front_chase: threshold and base back from the saved copies (300 scroll units or the hold timer)
08B173  code  fire_front_use_base_speed                 # fire_front_chase: speed = base
08B23D  code  falling_grate_wait_for_leader             # falling_grate_update state 1 (state 0 falls in): when the leader passes: sound D and falls (state 2); within 60 it rattles (z +-1); platform, drawn
08B5BA  code  rising_saw_read_script                    # rising_saw_rise: with no wait left, at the record's scroll position or z: removed at the end, else threshold, factor, base, wait
08B62A  code  rising_saw_stop_holding_back              # rising_saw_rise: threshold and base back from the saved copies (300 scroll units or the hold timer)
08B673  code  rising_saw_use_base_speed                 # rising_saw_rise: speed = base
08B821  code  saw_blade_follow_saw                      # saw_blade_update state 3 (state 2 frees the clips and falls in): on the saw, a shaft piece every 1D of rise, spins, sound 8 every 3C frames, drawn above screen y 140
08BD21  code  build_saw_shaft_base_end_piece            # build_saw_shaft_base: pieces 0 and 2 use frame 7FE8 (piece 2 also flipped, flags 80); piece 1 is 7FB8
08C82F  func  geyser_front_read_script                  # geyser_front_rise: with no wait left, at the record's scroll position or z: removed with its hazard box at the end, else threshold, factor, base, wait
08C87F  func  geyser_front_rise_step                    # geyser_front_rise: hold-back limits, speed by the screen-y threshold; after the wait z -= it, x/y along the slope; hazard box A0 below; spawn_listed_geyser; rows every 30 of rise, spawn_geyser_line at the top
08C8C4  code  geyser_front_stop_holding_back            # geyser_front_rise: threshold and base back from the saved copies (300 scroll units or the hold timer)
08C8F8  code  geyser_front_use_base_speed               # geyser_front_rise: speed = base
08C9CD  func  geyser_front_spawn_geyser_row             # geyser_front_rise: flag 1, previous_floor_z_int = z, a row of 5 geysers 2E apart with delays from geyser_row_delays; every 30 of rise, and at the top height after spawn_geyser_line
08CA3D  func  geyser_front_rise_sound                   # geyser_front_rise: while screen_y <= 130 sound_timer (7E) counts down, at 1 reloaded with 32 and sound 12; after a geyser row, or when none is due
08CD74  func  waterfall_read_script                     # waterfall_move: the wait counts down, then at the record's scroll position: removed with its parts at the end, else threshold, factor, base, wait
08CDBF  func  waterfall_move_step                       # waterfall_move: projected, hold-back limits, speed by the threshold (or screen y E0); after the wait y -= it; y <= 1480: geyser_front_start; sound 10 every 3C frames unless map_screen_active; animated, drawn
08CE13  code  waterfall_stop_holding_back               # waterfall_move: threshold and base back from the saved copies (300 scroll units or the hold timer)
08CF90  code  river_water_cut_at_waterfall              # river_water_update state 3 (state 2 builds two clip entries and falls in): frame from the waterfall's, clipped at its line; removed when the waterfall is 5C past
08D196  code  geyser_line_load_record                   # geyser_line_move: speed, wait, next record; at the end spawn_geyser_burst and removed; scroll position reached comes here past the y word, else the y test falls in
08D43C  func  propeller_machine_read_script             # propeller_machine_chase: the wait counts down, then at the record's scroll position or y propeller_machine_load_record
08D463  code  propeller_machine_load_record             # propeller_machine_chase: threshold, factor, base, wait, next record; at the end its part block is freed and it is removed
08D495  func  propeller_machine_chase_step              # propeller_machine_chase: hold-back limits; after the wait speed by the threshold, y += it; propellers animate; y >= 1B00: height 68, 1BB0: geyser_front_start; sound B every 3C frames on screen; zone list, drawn
08D4BF  code  propeller_machine_stop_holding_back       # propeller_machine_chase: threshold and base back from the saved copies (300 scroll units or the hold timer)
08D505  code  propeller_machine_use_base_speed          # propeller_machine_chase: speed = base
08E10B  code  delete_object_and_children_next_child     # delete_object_and_children: deleted_child = its child (28); at FFFF the object itself is deleted (R0 = 1), else that child is freed next
08ECCA  func  enter_ranking_initials_count_down         # enter_ranking_initials: 84 - 1; at 0 enter_ranking_initials_save_record, else the timer and rank, the trackball letter and the letters drawn; a button press (next letter, 93 up to 3) comes here
08EE21  code  enter_ranking_initials_draw_letter        # enter_ranking_initials: queue_sprite_on_top for a letter part; the letter being chosen skips it one frame in 4 (blink)
08EE65  func  enter_ranking_initials_save_record        # enter_ranking_initials: the player's 135 = 1, the record sorted into ranking (initials 'sex' and co. become 'tak'), copied to saved_ranking with ranking_checksum; time out or the button on letter 3
08F695  code  drift_ranking_background_swing_down       # drift_ranking_background: direction (3C) - 2 while 3E is set, under 180 3E = 0; the upward swing comes here in the frame it passes 280
08F7B6  code  ranking_row_set_portrait_frame            # ranking_row_update: frame = ranking_portrait_frames[character][step (+14 on the top row)], the step + 1 while animating (up to 13); every other frame while animate is set, every frame when not
08FCEF  code  character_chooser_start_turn              # character_chooser_wait / _turn: step_timer 0, turn_timer 18, handler character_chooser_turn and run it; a turn that ends on a taken character goes on turning from here
08FD07  func  character_chooser_idle                    # character_chooser_wait: no turn this frame: a taken character turns on, else the idle portrait steps (chooser_idle_steps every 3 frames), the side portraits (taken ones dimmed) and the labels
0901DB  func  join_character_chooser_take_character     # join_character_chooser_update: the first free character from the shown one (81) becomes the player's kind, its bit in characters_taken, the player's bit cleared; countdown end, joining_closed or the button
090E6E  code  wall_update_remove                        # wall_update: frees its part chain and deletes it; map screen, left the screen (82 = 0) or scroll_position reached 82
090E89  func  wall_update_list_and_draw                 # wall_update: add_to_wall_list; unless 80, its parts drawn stepping from its screen position (shadow parts when 81 is 0)
0912C7  func  spawn_object_groups_mark_silent           # spawn_object_groups: type 6 records: group_silent = FF (the members' 91: no sound D when they fall), then as type 0
0912D9  func  spawn_object_groups_no_depth_step         # spawn_object_groups: group_depth_step = 0 (types 0 and 6; type 2 sets F8)
0912DF  func  spawn_object_groups_build_group           # spawn_object_groups: within 100 of the record's scroll position: leader, slot flag, floor block, count (waits for that many free objects), steps, direction, start, member record; a group slot if flagged; the members
0914F1  code  crumbling_floor_wait_for_leader           # crumbling_floor_update state 1 (state 0 falls in): grey palette while grey_screen_active; flag 400000 from its rider; falls when the leader passes, cracks within 60 of it; platform, drawn
0915C6  func  crumbling_floor_crack                     # crumbling_floor_update: within 60 of the leader: the cracking steps (crumbling_floor_play_crack_steps), then holds the last step's offset (apply_step_screen_offset)
0915CB  func  crumbling_floor_play_crack_steps          # crumbling_floor_update: animate_with_screen_offset; at frame 4 the timer stops (FF), and with flag 400000 it falls at once
0915F3  code  crumbling_floor_start_falling             # crumbling_floor_update: state 2, speed_z 0, falling steps from frame 4, sound D when on screen unless the row is silent (91)
091655  func  crumbling_floor_draw                      # crumbling_floor_update state 1: add_to_platform_list; drawn while screen_x is within -A0..1E0, at draw_depth (88) = screen_y + depth_offset (96) - sprite_offset_y
091810  func  group_leader_read_script                  # group_leader_move: the wait counts down, then at the record's scroll position: removed at the end, else threshold (34), factor (36), base (38), wait
09184C  func  group_leader_move_step                    # group_leader_move: project_object, hold-back limits (300 scroll units, hold_timer 84; the catch-up branch, behind the threshold, also ends it), speed by the threshold (base or catch-up) into chaser_speed, moves along 24 unless waiting (80) or grey_screen_active
0918A1  code  group_leader_catch_up                     # group_leader_move: past the threshold: wait 0, stop holding back, speed = max(camera ground speed x factor >> 8, base)
0918AC  code  group_leader_stop_holding_back            # group_leader_move: 3E = 0, threshold and base back from 3A/3C (300 scroll units or the hold timer)
0919BE  code  lava_rock_wait_for_leader                 # lava_rock_update state 1 (state 0 falls in): within 5A of the leader it starts bobbing (state 2, rise 10); a triggered group slot does it with its delay; glows, platform, drawn
091B38  func  lava_rock_erupt                           # lava_rock_update: state 3, break animation, spawn_lava_column (two for the wide rock), sound 7 on screen; the leader passing or its slot delay ending
091B9B  func  lava_rock_draw_at_previous_floor          # lava_rock_update state 2 and lava_rock_erupt: on screen (screen_x -A0..1E0) add_to_platform_list, draw_depth (88) = the screen y of previous_floor_z + depth_offset (96), queued at it
0938DB  func  spawn_object_groups_2_pick_bridge         # spawn_object_groups_2 type 0: bridge 0 if its plank count is 0, else bridge 1 if 0 (else waits); that count + 1. It first compares free_object_stack with the record's word at +5, the high half of the floor block (0), not the member count at +7: that wait never happens
09390B  func  spawn_object_groups_2_build_group         # spawn_object_groups_2: within 100 of the record's scroll position: the planks of the chosen bridge (count added to its plank count), last one in bridge_N_last_plank
093B60  code  rope_bridge_plank_sag                     # rope_bridge_plank_update state 1 (state 0 falls in): z eases to its floor + bridge_N_sag[plank]; falls when the leader passes or rope_bridge_collapsing; platform, ropes, drawn
093BF7  func  rope_bridge_plank_start_falling           # rope_bridge_plank_update: state 2, the bridge's last plank clears its plank count, tumbling animation, sound 28 on screen
093C44  func  rope_bridge_plank_check_rider_and_draw    # rope_bridge_plank_update after the sag or the fall's start: takes_slot (92) < 0: a kind F rider's half (FF/80 in group_slots) makes it record 0's plank; on screen: platform list, ropes, drawn (88)
093E95  code  draw_rope_bridge_ropes_show_rope          # draw_rope_bridge_ropes: a rope part (9D20 slack while the plank falls) at the plank's screen position + its offsets, queued; a kind F rider's slot (FF / 80) hides rope 1 / 2
096405  code  map_eggman_hold_and_loop                  # map_eggman_update state 3: while 48 counts down frame 3 holds (stage_map_sounds_2 once), then frames 4-5 loop (stage_map_sounds_3 once; stage 9 counts it)
0966CE  code  draw_map_stage_markers_show_marker        # draw_map_stage_markers: queue_sprite_in_layer and the next block (+30); while blinking, the current stage's badge is skipped (its block reused) when bit 3 of the counter at 57 is clear
0987EB  func  ring_tally_percent_set_digits             # ring_tally_percent_update: the three digits of R3's low byte (BCD, frames from 098908); the percentage once counted, a random number while rolling
098853  func  ring_tally_percent_draw_digits            # ring_tally_percent_update: queue_sprite_in_layer of the ones (40) and tens (44) digits, the hundreds (48) only when it shows 1 (frame 9DF0); after ring_tally_percent_set_digits or roll_delay's wait
098C7E  func  update_ring_tally_panel_count_up          # update_ring_tally_panel state 0 (state 1, the bonus, falls in): drawn; every 2 frames the shown rings + 1 up to the player's, then the next state
09A2B0  code  build_sprite_list_write_entry             # build_sprite_list: writes the 16-byte sprite entry (flags, the frame's size words, screen y/x, address, palette); the unscaled frame (flags 1xxx) comes here, the scaled one falls in
09A3B6  code  build_sprite_list_write_parts             # build_sprite_list: multi-part sprites with zoom R1 (D00x: 100, C000: sprite_scale): scale_x/scale_y (R8/R9) = +-R1 by the flip bits 6/7, then one entry per part of the part list (up to 10)
09A3E2  code  build_sprite_list_y_not_flipped           # build_sprite_list: scale_y (R9) = R1 (no y flip); the x-flipped case comes here after scale_x = -R1
09A9CC  code  draw_vertical_span_8bpp_store_pair        # draw_vertical_span_8bpp: the two-pixel loop (store, next row, store, next row); an odd count enters at the second store
09AA4A  func  draw_line_shallow_even_pixel              # draw_line (4 bpp, more columns than rows): the even x pixel (low nibble) into R0; same row: on to the odd pixel of that byte, else written and one row on
09AA64  func  draw_line_shallow_odd_pixel               # draw_line (4 bpp, shallow): an odd x pixel after a row step: reads the byte first
09AA67  func  draw_line_shallow_set_high_nibble         # draw_line (4 bpp, shallow): colour into R0's high nibble, the byte written (with a pending even pixel), next byte; a carry moves one row
09AAA8  code  draw_line_steep_odd_pixel                 # draw_line (4 bpp, more rows than columns): odd x pixels (high nibble) down the rows; a carry moves to the next byte's even pixel
09AADD  code  draw_line_diagonal_odd_pixel              # draw_line (4 bpp, 45 degrees): an odd x pixel (high nibble), then the next byte one row down
09ABCE  code  draw_vertical_span_4bpp_store_pair        # draw_vertical_span_4bpp: the two-pixel loop (nibble mask by x parity, next row twice); an odd count enters at the second store
09CE1B  func  test_work_ram_verify                      # test_work_ram: compares 200000-20FFFF with the patterns (9ED5C) it was filled with (bits 5/6 of memory_test_video_word), then restores it; the fill leaves for here at 210000
09CED1  func  test_video_ram_verify                     # test_video_ram: compares 300000-31FFFF with the patterns (error bits D-10); the fill leaves for here at 320000
09CFC5  func  test_sprite_ram_verify                    # test_sprite_ram: compares 400000-41FFFF with the patterns (error bits 9-C); the fill leaves for here at 420000
09D0B1  func  test_palette_ram_verify_first_half        # test_palette_ram: compares 600000-603E3F, all the fill wrote, with the patterns (error bits 7, 8); reached when the fill's 8 passes end: neither the fill's exit at 604000 nor its own is ever taken
09D129  func  test_palette_ram_fill_second_half         # test_palette_ram: fills 604000-607E3F with the patterns; reached when the first compare's passes end (its exit at 604000 is never taken)
09D162  func  test_palette_ram_verify_second_half       # test_palette_ram: compares 604000-607E3F (error bits 11, 12); reached when the fill's 8 passes end: neither the fill's exit at 608000 nor its own is ever taken
09D231  func  test_sound_ram_verify                     # test_sound_ram: compares 700000-701FFF with the patterns (error bit 4), then restores it; the fill leaves for here at 702000
09DF0A  func  test_coin_assignments_draw_main_page      # test_coin_assignments: clears the text layer, coin_manual_page 0, clamps the settings, prints chute type, coin/credit setting and credits to start (or MANUAL SETTING); first frame and the manual page's exit
09DFE8  code  test_coin_assignments_draw_setting_number # test_coin_assignments: the setting number in BCD at text cell (31, 9); individual chutes come here with individual_coin_setting_numbers[n]
09E346  func  test_coin_assignments_manual_page         # test_coin_assignments: the MANUAL SETTING page (printed on its first frame), its cursor and settings; the main page's item 3 comes here
09F7DD  code  update_coins_service_p2                   # update_coins (L_0FF7DD in the C, the 0FFxxx copy): a service press gives block 2 a credit, then block 1; individual chutes, 2 players, and after block 3 with 3 players
09F886  code  update_coins_individual_chute_2           # update_coins (L_0FF886 in the C): individual chutes: chute 2 (released bit 3) credits the current block, then the next block for chute 1; 3 players come here after chute 3 credited block 1

# ======== ROM data the code points at (table and data lines, from the helpers) ========
01B154  data   nbg0_picture_palette         # show_nbg0_picture: queue_vblank_copy of these 16 colours (10 halfwords) to palette RAM 608BE0 with the NBG0 picture (vram script 17280A)
01F5AC  data   ring_glint_animation         # spawn_ring_glint: animation = it; last step 0F, frames 7D10-7D88
01F5CE  data   ring_sparkle_animation       # spawn_ring_sparkle, spawn_banner_sparkle, player_hud_init (hud_ring_effect_anim): last step 0F, frames 7C90-7D08
0851AF  data   opening_machine_landed_animation    # opening_machine_leap_in on landing (y 63): part-list animation, last step 1: two 4-part lists (5720/5728, 5750, 5740, 5740)
0851F9  data   opening_machine_fly_away_animation  # opening_machine_update scene_step 2 (flies into the distance): last step 1, 4-part lists (5730/5738, 5758, 5740, 5740)
085243  data   opening_machine_leap_animation      # opening_machine_update scene_steps 0/1/4 (rising from below): last step 1, 2-part lists (5720/5728 and 5750)
08526D  data   how_to_play_button_hand_animation   # how_to_play_button_hand_start: part-list animation, last step 3, offsets to the lists at 085277 085289 08529B 085289 (stepped by how_to_play_button_presses)
085277  data   how_to_play_button_hand_parts       # how_to_play_button_hand_start: first part_list (step 0): 2 parts, 71E0 and 71D0 (palettes 8540/8530)
08A72E  data   tornado_kind_0_end_record    # tornado_records: the record after kind 0's 7th (last) moving record: scroll 54C0, 86 = FFFF (end), 88 = 0 (no goal_unlocked); tornado_move clears group_leader_0_held when tornado.path reaches it
08BE61  table  biting_head_move_frames u16  # biting_head_frames + 2: [animation_frame 0-3] 7A78 7A80 7A88 7A90, written into parts 0 and 4 while it moves (biting_head_chase)
08BE6B  data   biting_head_bite_step_records # biting_head_bite_steps + 2: 6 records of 4 bytes, jaw frame (u16) and frames to hold (u8), read as r10 + step x 4 (biting_head_chase, part 3)
08BF03  data   brick_wall_brick_records     # brick_wall_wait: after the count brick_wall_bricks (0E), 6 words a brick: sprite flags, screen dx, dy, speed x, speed y, offset into brick_animations
08BFAD  data   brick_wall_dust_records      # brick_wall_wait: after the count brick_wall_dust (5), 4 words a puff: sprite flags, screen dx, dy, delay (field 80)
08F0DB  data   out_of_rank_text             # enter_ranking_initials: queue_string_at(x, C8, palette 8F70, scale 100, it): "OUT OF RANK!"
093F6B  table  rope_plank_hitbox_size u16   # stage_object_records_2 record 0 +12: [0] hitbox_size_x 18, [1] hitbox_size_y 5C (rope_bridge_plank_update's kind F branch)
094009  table  wide_rope_bridge_rope_offsets s16  # rope_bridge_rope_offsets + 8, used instead for kind 30 (the wide plank): (-1C, -10), (62, 30) (rope_bridge_plank_update)
095944  data   event_1a_scroll_script_step_1  # event_1a_scroll_script record 1 (3400, 0, 20): run_game_mode (game mode 0x1A) leaves R11 here after reading event_1a_first_scroll_mode/speed
096D78  table  map_eggman_gesture_frames s8 # map_eggman_update step 2: pointer kept in the object, one byte each 4 frames: sprite 5760 + (n - 1) x 8, sound 21 on 7; FF ends (stays on the last): 1-7 6 3-7 6
097F69  data   title_sky_cycle_palettes     # title_sky_wave: cycle_palette source (palette_cycle_0, delay 3): 10h (16) frames of 16 colours, each copied to the 5 rows at 608F60
09C58E  data   default_settings             # eeprom_defaults + 0E: load_settings copies 19 halfwords over setting_advertise_sound (20F60E) when the EEPROM is from another version or both copies fail
09FEE6  table  start_prompt_texts u16       # blink_start_prompt's prompt_texts (draw_player_start_prompt, draw_attract_credits; via 0FFEE6): offsets from here to print_text_lines texts: 0 "Push start/button", 1 "Insert more/coin", 2 "Insert/coin", 3 "2 credits/start"
09FF84  data   two_credits_one_play_text    # draw_attract_credits (common chutes, 2 credits a start, not free play; via 0FFF84): print_text record, cell F798, attribute 0, "2 credits 1 play"
09FFAE  data   credits_label_text           # draw_credit_count_now (via 0FFFAE): print_text_here record, attribute 0, "credit  " (an 's' goes over the first space for 2 or more)
09FFC0  data   free_play_text               # draw_credit_count_now with free_play (via 0FFFC0): print_text_here record, attribute 0, "Free play"
102136  data   text_font                    # load_text_font: 9A0 halfwords of 4 bpp characters copied to 31C000, then (from 103476) 1-bit rows of 5B more for expand_font
104BD2  data   warning_screen_text          # attract_warning: print_text_list records (cell, attribute, characters): "W A R N I N G", "This game is to be used only in JAPAN." ... "SEGA ENTERPRISES,LTD."
104D60  data   default_palette_list         # load_default_palettes: load_palette_list list, 5 entries (source, palette RAM offset, kind)
1076AE  data   tile_bank_1_palette_list     # load_palette_list list (1 entry: counts 1076CA, colours 107750 to 608000, kind 4) loaded with 31FF00 bit 10 set: stage_palette_sets entries whose stage_tile_bank_bits is 1, init_nbg0_picture_screen, load_credits_palettes, run_title_screen
1076BC  data   tile_bank_0_palette_list     # the same with bit 10 clear (counts 104F72, colours 104FCE to 608000, kind 4): the other stage_palette_sets entries, map_intro_update
107FB0  data   stage_04_cycle_palettes      # animate_palette_stage_04: cycle_palette source (palette_cycle_0, delay 4): 8 frames of 16 colours, each copied to 2 rows at 608500
1080B0  data   stage_09_cycle_palettes      # animate_palette_stage_09: palette_cycle_0, delay 3, 7 frames, 2 rows at 6088A0
108290  data   stage_06_cycle_palettes_0    # update_stage_06_background step 5: cycle_palette_set 0 (209090): delay 2, 6 frames, 3 rows at 6083A0
108350  data   stage_06_cycle_palettes_1    # update_stage_06_background step 5: cycle_palette_set 1 (2090A0): delay 7, 6 frames, 2 rows at 608400
108410  data   stage_0a_cycle_palettes_1    # animate_palette_stage_0a: palette_cycle_1, delay 5, 0A frames, 9 rows at 608A60
108550  data   stage_0a_cycle_palettes_0    # animate_palette_stage_0a: palette_cycle_0, delay 5, 6 frames, 1 row at 608A40
108610  data   stage_0c_cycle_palettes      # animate_palette_stage_0c: palette_cycle_0, delay 6, 8 frames, 1 row at 608800
108710  data   stage_0d_0e_cycle_palettes   # animate_palette_stage_0d_0e: palette_cycle_0, delay 6, 8 frames, 1 row at 6088C0
108810  data   stage_0f_cycle_palettes_0    # animate_palette_stage_0f: palette_cycle_0, delay 0C, 6 frames, 3 rows at 608920
1088D0  data   stage_0f_cycle_palettes_1    # animate_palette_stage_0f: palette_cycle_1, delay 6, 6 frames, 1 row at 608900
108990  data   stage_10_cycle_palettes_0    # animate_palette_stage_10: palette_cycle_0, delay 6, 8 frames, 6 rows at 608B40
108A90  data   stage_10_cycle_palettes_1    # animate_palette_stage_10: palette_cycle_1, delay 4, 8 frames, 1 row at 608940
108B90  data   ending_eggman_cycle_palettes # ending_eggman_fly_away: palette_cycle_0, delay 6, 9 frames, 1 row at 608BC0
10DFCC  data   slope_speed_direction_pairs  # floor_slope_direction_tables offsets are from here: per slope 16 pairs (by direction >> 6) of speed factor (1000 = 1.0) and direction (player_steer, compute_ground_speed)
10F6CC  data   sonic_standing_animations    # per facing: 8 steps of (sprite flags, frame), frames 0CF0-0E50 (player_animate_standing; states 18 and 38)
10F90C  data   mighty_standing_animations   # the same for Mighty (kind 2): frames 2918-2A78
10FB4C  data   ray_standing_animations      # the same for Ray (kind 1): frames 4478-45D8
10FD8C  data   sonic_walking_animations     # state 1 (player_animate_walking), player_reset: per facing, 16 steps (flags, frame), frames 0000-0478; the standing sets lie below it, the walk and run sets from it to 11230C
1101CC  data   mighty_walking_animations    # state 1, Mighty: frames 1C28-20A0
11060C  data   ray_walking_animations       # state 1, Ray: frames 3788-3C00
110A4C  data   sonic_running_animations     # state 13 (player_animate_running), also states 2, 17, 32 and 25: per facing, 10 steps (flags, frame), frames 0480-0748
110D0C  data   mighty_running_animations    # the same for Mighty: frames 20A8-2370
110FCC  data   ray_running_animations       # the same for Ray: frames 3C08-3ED0
11128C  data   sonic_running_fast_animations   # state 14 (player_animate_run_cycle table): per facing, 10 steps, frames 0A20-0CE8
11154C  data   mighty_running_fast_animations  # state 14, Mighty: frames 2648-2910
11180C  data   ray_running_fast_animations     # state 14, Ray: frames 41A8-4470
111ACC  data   sonic_alternate_run_animations  # player_animate_run_cycle instead of the run table: object flags bit 31, or floor type 1 facing along floor_1_facing_bases: per facing, frames 0750-0A18
111D8C  data   mighty_alternate_run_animations # the same for Mighty: frames 2378-2640
11204C  data   ray_alternate_run_animations    # the same for Ray: frames 3ED8-41A0
11230E  data   sonic_attack_jump_animations # state 4 while attack_power >= 1 (always: it is 4), player_launch: per facing, sprite flags, first frame, then 5 steps (flags, frame); frames 10F8-11C0
1123FE  data   sonic_plain_jump_animations  # state 4 when attack_power is 0 (never set so): the same layout, frames 1008-10F0
1124EE  data   mighty_attack_jump_animations # the attack jump for Mighty: frames 2D20-2DE8 (and 1120)
1125DE  data   mighty_plain_jump_animations  # the plain jump for Mighty: frames 2C30-2D18
1126CE  data   ray_attack_jump_animations    # the attack jump for Ray: frames 4880-4968
1127BE  data   ray_plain_jump_animations     # the plain jump for Ray: frames 4790-4878
1128AE  table  teetering_animations teeter_animation_offsets  # state 5: [character x 2 + (0 far edges 0/1, 1 near edges 2/3)]: offset from here to a first frame and its 13-step teeter animation; state 6 adds 1C to it for the fall that follows
1128D8  data   sonic_near_edge_fall_animation  # Sonic's near-edge teeter animation + 1C (state 6 falling off edge 2/3); set directly when dropped (states 27, 30, 31): last step 4, (frame, dx, dy) 1570-1590 (animate_and_move)
11293C  data   mighty_near_edge_fall_animation # the same for Mighty: frames 3198-31B8
1129A0  data   ray_near_edge_fall_animation    # the same for Ray: frames 4D18-4D38
1129E6  data   sonic_far_edge_hang_animations   # states 7 and 2 -> 8, edge sides 0/1/4: +0 hanging on (state 7, 10 frames 17B0-17F8), +16 climbing back (5, 1800-1820), +22 the slip effect's 9 frames, +36/+38 single frames (state 8)
112A20  data   sonic_near_edge_hang_animations  # the same for sides 2/3: frames 1738-1780, 1788-17A8, effect 15C8-1600, 15B8, 15C0
112A5A  data   mighty_far_edge_hang_animations  # the far-edge set for Mighty: frames 33A0-33E8, 33F0-3410
112A96  data   mighty_near_edge_hang_animations # the near-edge set for Mighty: frames 3328-3370, 3378-3398
112AD2  data   ray_far_edge_hang_animations     # the far-edge set for Ray: frames 4F58-4FA0, 4FA8-4FC8
112B1E  data   ray_near_edge_hang_animations    # the near-edge set for Ray: frames 4EE0-4F28, 4F30-4F50
112B6A  data   sonic_climb_animation        # state 0A (start_climb, start_climb_at_spawn, each step): last step 0B, frames 18B8-1910
112B84  data   sonic_climb_over_animation   # state 0A at the top (forwards) or starting down from it (backwards): last step 9, (frame, dx, dy) 1918-1960 around the anchor
112BAE  data   sonic_climb_sideways_animation # state 0A side steps along the wall: last step 5, frames 1968-1990
112BBC  data   mighty_climb_animation       # the climb for Mighty: frames 34A8-3500
112BD6  data   mighty_climb_over_animation  # the climb over the top for Mighty: frames 3508-3550
112C00  data   mighty_climb_sideways_animation # the side steps for Mighty: frames 3558-3580
112C0E  data   ray_climb_animation          # the climb for Ray: frames 5060-50B8
112C28  data   ray_climb_over_animation     # the climb over the top for Ray: frames 50C0-5108
112C52  data   ray_climb_sideways_animation # the side steps for Ray: frames 5110-5138
112C60  data   sonic_skid_animations        # state 0C (player_animate_skidding), by facing + 8: per facing, 7 steps (flags, frame), frames 0E58-1000
112E60  data   mighty_skid_animations       # state 0C, Mighty: frames 2A80-2C28
113060  data   ray_skid_animations          # state 0C, Ray: frames 45E0-4788
113260  data   sonic_hurt_animations        # state 0D and the hurt steps of states 19, 25, 29, 30, 31, 36: per facing, 9 steps (flags, frame), frames 13A8-1508
1133B0  data   mighty_hurt_animations       # the same for Mighty: frames 2FD0-3130
113500  data   ray_hurt_animations          # the same for Ray: frames 4B50-4CB0
113650  data   sonic_shocked_animations     # state 0D step 7 (touched kind E or 15, electric fences and poles): per facing (4 groups), 8 steps 1510-1568, palette from shocked_palettes
1136F8  data   mighty_shocked_animations    # the same for Mighty: frames 3138-3190
1137A0  data   ray_shocked_animations       # the same for Ray: frames 4CB8-4D10
113848  data   attack_power_3_effect_animations # draw_attack_effect with attack_power 3: per facing, a word, then 5 (flags, frame) steps 5928-59E8 by the player's animation_frame
113918  data   attack_power_4_effect_animations # draw_attack_effect with attack_power above 3 (the jump sets 4): frames 59F0-5AB0
113A28  table  ray_bounce_records u16       # as sonic_bounce_records for character 1 (state 0E: [bounce_direction] direction, sprite_flags, animation offset); frames 4FD0-5058
113A68  table  mighty_bounce_records u16    # the same for character 2: frames 3418-34A0
113AA8  data   sonic_drift_animation        # player_move_with_scroll (state 25) drifting slowly or straight, player_floor_8_sequence_move_down: last step 3, frames 19D8 19E0 19E8 19E0
113AB2  data   drift_minus_x_start_animation # player_move_with_scroll, every character, direction 240-3C0 (toward -x): last step 2, frames 19D8 1A18 1A20; backwards to stop
113ABA  data   drift_minus_x_animation      # player_move_with_scroll after drift_minus_x_start_animation: last step 3, frames 1A28 1A30 1A38 1A30
113AC4  data   drift_plus_x_start_animation # player_move_with_scroll, direction 40-1C0 (toward +x): last step 2, frames 19D8 19F0 19F8; backwards to stop
113ACC  data   drift_plus_x_animation       # player_move_with_scroll after drift_plus_x_start_animation: frames 1A00 1A08 1A10 1A08
113AD6  data   ray_drift_animation          # the slow drift for Ray: last step 2, frames 8320-8330
113ADE  data   mighty_drift_animation       # the slow drift for Mighty: last step 2, frames 8308-8318
113AF0  data   sonic_geyser_hop_animation   # player_stage_scene_geyser_start (state 18, scene_step 5): last step 4, frames 1AB8-1AD8
113AFC  data   ray_geyser_hop_animation     # the same for Ray: frames 51D8-51F8
113B08  data   mighty_geyser_hop_animation  # the same for Mighty: frames 3620-3640
113B14  data   sonic_shrink_away_animation  # player_stage_scene_shrink_away (state 18, scene_step 7): last step 5, frames 1AE0-1B08
113B22  data   ray_shrink_away_animation    # the same for Ray: frames 5200-5228
113B30  data   mighty_shrink_away_animation # the same for Mighty: frames 3648-3670
113B3E  data   sonic_state_1b_animation     # state 1B (state_timer 41, then standing): last step 7, frames 1B18-1B48 at speed 5
113B50  data   ray_state_1b_animation       # the same for Ray: frames 5238-5268
113B62  data   mighty_state_1b_animation    # the same for Mighty: frames 3680-36B0
113B74  data   sonic_opening_run_animation  # state 1C (opening scene) scene_steps 1/4, while the runner's zoom grows: last step 0B, frames A158-A1B0
113B8E  data   sonic_opening_voice_animation # state 1C at scene_timer 140, with the character's voice (36): last step 0B, frames 1BC8-1C20
113BA8  data   ray_opening_run_animation    # the opening run for Ray: frames A0F8-A150
113BC2  data   ray_opening_voice_animation  # the scene_timer 140 animation for Ray (voice 46): frames B918-B970
113BDC  data   mighty_opening_run_animation # the opening run for Mighty: frames A060-A0B8
113BF6  data   mighty_opening_voice_animation # the scene_timer 140 animation for Mighty (voice 77): frames A000-A058
113C10  data   sonic_stuck_animation        # player_stuck_shake (states 1D, 2C) with no escape progress: last step 5, frames 1A48-1A60 and back
113C1E  data   ray_stuck_animation          # the same for Ray: frames 5188-51A0
113C2C  data   mighty_stuck_animation       # the same for Mighty: frames 35D0-35E8
113C3A  table  sonic_stuck_struggle_animations u32 rom  # player_stuck_shake: [escape_progress / 5555, at most 2] two-frame animations (1A68-1A80)
113C58  data   sonic_stuck_escape_animation # player_stuck_shake at escape_progress 10000 (sound 2B), then player_stuck_jump_out: last step 1, frames 1A88 1A90
113C5E  table  ray_stuck_struggle_animations u32 rom    # the same for Ray (51A8-51C0)
113C7C  data   ray_stuck_escape_animation   # the escape for Ray: frames 51C8 51D0
113C82  table  mighty_stuck_struggle_animations u32 rom # the same for Mighty (35F0-3608)
113CA0  data   mighty_stuck_escape_animation # the escape for Mighty: frames 3610 3618
113CA6  data   stuck_effect_animation       # player_stuck_begin: the object over the stuck player (stuck_effect_update, palette 8380): last step 1, frames 1A98 1AA0
113CD9  table  floor_type_player_states floor_type_player_state  # player_move: [floor_type] the state the floor forces (0 none): 2 -> 0A (climbing), 3 -> 10 (launched), 8 -> 25 (floor type 8 sequence)
113CE7  table  screen_edge_scroll_carry_flags s8 # keep_player_on_screen: [(camera_scroll_direction >> 8, + 4 when its low byte is set) x 4 + screen_edge]: FF = besides the push the scroll carries the player
113D13  data   skid_dust_animation          # spawn_skid_dust: last step 5, frames 58F8-5920 (palette 8260)
113D59  table  player_palette_lists u32 rom # player_reset: [stage x 3 + character] palette list (player 80 palette_list): 113E3D/113E45/113E4D, stage 8 113E55/113E5D/113E65
113EE0  table  screen_edge_push_directions screen_edge_push # push_player_from_screen_edge: [screen_edge] the direction compared with the player's (back to previous x/y unless within 100 after it), then the push direction (length 200)
113F10  table  hud_gauge_positions_3p screen_point   # player_hud_init: [player x 2] health gauge x, y (20,C7 88,C7 F0,C7)
113F1C  table  hud_gauge_positions_2p screen_point   # the same on 2-player cabinets (20,C7 F0,C7): player_hud_init 00A740 "movea.w 113F1C(R0), R10" then [R10+] x, y
113F24  table  hud_portrait_positions_3p screen_point # player_hud_init: [player] portrait x, y (08,BE 70,BE D8,BE): 00A790 "movea.w 113F24(R0), R10" then [R10+] x, y (00A7A8, 00A7AD)
113F30  table  hud_portrait_positions_2p screen_point # the same on 2-player cabinets (08,BE D8,BE): 00A7A0
113F38  table  hud_portrait_sprites hud_portrait_sprite  # player_hud_init, player_hud_start: [character] portrait sprite and palette: 89C8/8850, 89D8/88B0, 89D0/8880
113F4F  table  shocked_palettes shocked_palette_row  # player_hurt_shocked: [character x 8 + animation_frame]: the character's palette (8130/8140/8150) and 8370 in turn
113F7F  table  fall_sprite_priorities s8    # set_fall_sprite_priority: [floor block byte +14 x 4 + side] 0-2 into sprite_palette bits 13-14; negative keeps them
113FAB  table  shrink_away_positions screen_point    # player_stage_scene_shrink_away: [character] x, y minus A0, 70 give screen_x and projected_y: (A0,70) (-1E0,70) (1E0,0); player_state_stage_scene 0071D1 "movea.w 113FAB(R0), R10", then "mov.h [R10+], R0; sub.h #A0, R0"
113FDD  data   hud_wait_text                # player_hud_update while player_status is 82 (waiting for the stage): blink_player_hud_message text, attribute 0, "    Wait    "
113FF5  data   hud_message_blank_text       # erase_text_lines over the player's HUD message (not playing, continue, joining, hud start/update): attribute 0, four lines of 13 spaces
11402E  table  opening_scene_player_positions u16 # state 1C scene_steps 1/4: [(players playing - 1) x 9 + player x 3] screen x, projected y, zoom: A0,D0,100 alone; 60,C0,E0 / A0,D0,100 / E0,C0,E0 with more
114064  data   attack_text                  # player_animate jumping on stage 5 until cage_bar_hit: queue_string_at above the player, palette 8440: "ATTACK!"
11406C  data   sinking_shadow_animation     # shadow_update on floor types A/B or object flag 31 (quicksand): the shadow object's animation, last step 2, frames 75A8-75B8 (palette 1020, zoom 120)
1140D4  data   sonic_floor_8_ride_animation  # state 25 sequence_step 9 (landed after the drift fall), animated while the scroll carries the player (step 10): last step 2, frames 76E0-76F0
1140DC  data   ray_floor_8_ride_animation    # the same for Ray: frames 75C0-75D0
1140E4  data   mighty_floor_8_ride_animation # the same for Mighty: frames 7650-7660
1140EC  data   sonic_floor_8_landing_animation  # player_floor_8_sequence_bounce on landing (step 15, state_timer 3C): last step 7, frames 8A08-8A28
1140FE  data   ray_floor_8_landing_animation    # the same for Ray: frames 8A58-8A78
114110  data   mighty_floor_8_landing_animation # the same for Mighty: frames 8A30-8A50
11412C  data   hud_continue_text            # state 23: print_text_here at hud_continue_positions, attribute 0, "CONTINUE ?"
114162  data   hud_game_over_text           # state 24: print_text_here / erase_text_here at hud_game_over_positions, attribute 0, " GAME OVER"
114183  data   hud_ring_count_blank_text    # state 23: erase_text_here over the ring count (hud_ring_count_positions), attribute 0, 11 spaces
114244  data   sonic_stun_recover_animation # player_stunned_wait (state 2F) after 3C frames with health left: last step 0D, frames 8418/8420 then 8428-8448
114262  data   ray_stun_recover_animation   # the same for Ray: frames 8488/8490 then 8498-84B8
114280  data   mighty_stun_recover_animation # the same for Mighty: frames 83D8/83E0 then 83E8-8408
11429E  data   ray_stunned_animation        # state 2F set-up, Ray only (the others keep frame 8410/83D0): last step 6, frames 8450-8480
1142E4  data   blown_away_effect_animation  # player_state_blown_away (1F): the effect object (blown_away_effect_update, palette 8360): last step 7, frames 5AB8-5AF0
1142F6  data   tumble_effect_animation      # spawn_tumble_effect (state 20): last step 0A, frames 5AB8-5AC8 twice, then 5AD0-5AF0
11430E  data   sonic_tumble_animations      # player_tumbling_start (state 20), player_state_blown_away (facing 2's entry): per facing, 13 steps (flags, frame), frames 11C8-13A0, 13E8-1508
1144DE  data   ray_tumble_animations        # the same for Ray: frames 4970-4B48, 4B90-4CB0
1146AE  data   mighty_tumble_animations     # the same for Mighty: frames 2DF0-2FC8, 3010-3130
11487E  data   sonic_knocked_out_animations # player_tumbling_fly landing with no health: [facing >> 2] (sprite flags, offset from here) to 25-frame animations 9430-94B8 / 94C0-9548, palette A90; then state 16
1148F6  data   ray_knocked_out_animations   # the same for Ray: frames 9550-9668, palette AA0
11496E  data   mighty_knocked_out_animations # the same for Mighty: frames 9310-9428, palette A80
1149D6  data   sonic_seesaw_launch_animation # player_carried_off_wait_seesaw (state 31) once seesaw_launched, with the voice: last step 4, frames B290-B2B0 (Ray 1149E2, Mighty 1149EE)
1149E2  data   ray_seesaw_launch_animation       # player state 0x31 (carried off), once seesaw_launched: Ray's frames B260-B280 (speed 3) while z - C0000 a frame throws him off the top of the screen, voice 37
1149EE  data   mighty_seesaw_launch_animation    # the same for Mighty: frames B2C0-B2E0, voice 77 (Sonic's is 1149D6)
1149FA  data   sonic_trapped_animation           # player state 0x32 substep 0/6 (trapped): Sonic's 28 steps B470-B4C0 (speed 2) as trap_flash_start/trap_start appear (sound 21); substep 1 waits for its end
114A34  data   ray_trapped_animation             # the same for Ray: 24 steps B508-B558
114A66  data   mighty_trapped_animation          # the same for Mighty: 28 steps B3F0-B440
114AA0  table  sonic_trap_struggle_animations u32 rom   # state 0x32 substep 2: animation = [escape_progress >> 16] (0-3: two-frame animations B4C0-B4E0); the clamp lets index 4 through, which reads the next table's first entry
114AB0  table  ray_trap_struggle_animations u32 rom     # the same for Ray (B558-B578)
114AC0  table  mighty_trap_struggle_animations u32 rom  # the same for Mighty (B440-B460); its index 4 reads the first animation's bytes (01 00 C0 B4) as a pointer
114B18  data   sonic_pit_sinking_animation       # player state 0x33 (sinking after falling into a pit onto floor block type 22/26): Sonic's frames B640-B650, speed 4
114B20  data   ray_pit_sinking_animation         # the same for Ray: frames B658-B668
114B28  data   mighty_pit_sinking_animation      # the same for Mighty: frames B610-B620
114B30  data   pit_sinking_shadow_animation      # shadow_update while its player is in state 0x33: frames B628-B638, palette 2D50, 8 below the floor in place of the shadow (11406C is the quicksand one)
114B38  table  edge_voice_scroll_ranges edge_voice_scroll_range  # [stage]: scroll start, length (stage 3 2000/4000, 4, 8, 9, A, 10; others 0): keep_player_on_screen plays the character's voice (39/43/4C) once (player flags bit 5) when a screen edge pushes a player in that range with 2+ players in play
115BA3  table  test_menu_cursor_positions u8     # (column, row) of the 11 TEST MODE items, FF ends: test_menu, draw_test_menu (test_cursor_positions = it)
115BBB  table  game_assignments_cursor_positions u8   # 8 pairs, FF: difficulty (column 7, row 5), vitality (4, 9), advertise sound (9, 13) ... exit (9, 23) (test_game_assignments)
115BCC  table  coin_assignments_cursor_positions u8   # 5 pairs, FF: chute type (5, 3), credit to start, coin/credit setting, manual setting (1, 22), exit (1, 24)
115BD7  table  coin_manual_cursor_positions u8   # the MANUAL SETTING page: coin to credit, bonus adder, chute 1 and 2 multipliers, exit; FF
115BE2  table  bookkeeping_cursor_positions u8   # just FF: test_bookkeeping's (empty) cursor list
115BE3  table  backup_clear_cursor_positions u8  # YES (12, 10), NO (12, 12), FF (test_backup_clear)
115BE9  data   test_menu_text                    # "TEST MODE", MEMORY TEST ... BACKUP DATA CLEAR, EXIT, "SELECT BY SERVICE BUTTON" / "AND PUSH TEST BUTTON" (draw_test_menu)
115CBA  data   individual_mode_text              # "INDIVIDUAL" at row 3 col 15 (toggle_test_continue_mode)
115CCA  data   test_menu_output_test_text        # the "OUTPUT TEST" menu line; draw_test_menu prints it only with moving_seat_dip
115CDB  data   continue_mode_text                # "CONTINUE  " at row 3 col 15 (toggle_test_continue_mode; test_outputs after redrawing the menu)
115CEB  table  test_cursor_arrow u8              # ">": blink_test_cursor's on phase (draw_blinking_test_cursor writes 200 | this byte at the cursor)
115CED  table  test_cursor_space u8              # " ": the off phase, and erase_test_cursor
115CEF  data   sound_test_text                   # "SOUND TEST", "No.", the two button lines (test_sound)
115D3F  data   crt_test_color_bars               # 4 records (u16 x 30, u16 y 38/58/78/98, u8 first colour 10/30/50/70): test_crt fills 32 bars 8 x 20 each, colour + 1 per bar (C.R.T. TEST 1/2)
115D53  table  crt_test_border_lines u16         # 4 lines (x1, y1, x2, y2): top, bottom, left, right edge of the screen in colour 7E before the crosshatch (C.R.T. TEST 2/2)
115D73  data   crt_test_1_text                   # "C.R.T. TEST 1/2", RED / GREEN / BLUE / WHITE labels, "PUSH TEST BUTTON TO CONTINUE"
115DCE  data   crt_test_2_text                   # "C.R.T. TEST 2/2", "PUSH TEST BUTTON TO EXIT"
115DFF  data   game_assignments_text             # "GAME ASSIGNMENT", the item labels, EASY/HARD and MIN/MAX scales, button lines (test_game_assignments)
115F13  data   monitor_flip_normal_text          # "NORMAL  " (row 15 col 25)
115F21  data   monitor_flip_reverse_text         # "REVERSE"
115F46  data   moving_seat_erase_text            # blanks over the MOVING SEAT line when moving_seat_dip is 0
115F74  data   advertise_sound_off_text          # "OFF" (row 13 col 28)
115F7D  data   advertise_sound_on_text           # "ON "
115F86  data   start_button_use_text             # "USE      " (row 19 col 25)
115F95  data   start_button_no_use_text          # "NO USE  "
115FA3  data   moving_seat_use_text              # "USE      " (row 21 col 25)
115FB2  data   moving_seat_no_use_text           # "NO USE  "
115FC8  data   output_test_seat_text             # test_outputs with setting_moving_seat: "USE", 1P SEAT, 2P SEAT, "PUSH 1P/2P ATTACK TO MOVE A SEAT", OFF, OFF
11601B  data   output_test_no_use_text           # "NO USE  " on the MOVING SEAT line (setting_moving_seat 0)
116029  data   output_test_text                  # "OUTPUT TEST", "MOVING SEAT", "PUSH TEST BUTON TO EXIT" (sic)
116064  data   output_test_1p_on_text            # " ON" (row 10 col 26): 1P attack held, port D bit 2 set
11606D  data   output_test_1p_off_text           # "OFF"
116076  data   output_test_2p_on_text            # " ON" (row 12 col 26): 2P attack held, port D bit 3 set
11607F  data   output_test_2p_off_text           # "OFF"
116088  data   coin_assignments_common_text      # the "COIN CHUTE #2" line, then coin_assignments_text (common chutes)
116099  data   coin_assignments_text             # "COIN ASSIGNMENTS", CREDIT TO START, COIN/CREDIT SETTING #, COIN CHUTE #1, MANUAL SETTING, EXIT, button lines
116148  data   coin_chute_2_label_erase_text     # 13 blanks over "COIN CHUTE #2" (chute type changed to individual)
11615B  data   coin_chute_labels_text            # "COIN CHUTE #2" and "COIN CHUTE #1" (chute type changed to common)
11617F  data   coin_chute_type_individual_text   # "COIN CHUTE TYPE INDIVIDUAL"
11619F  data   coin_chute_type_common_text       # "COIN CHUTE TYPE     COMMON"
1161E5  data   coin_setting_number_manual_text   # "MANUAL" after "COIN/CREDIT SETTING #" (row 9 col 31) when setting_coin_number < 0
1161F1  data   coin_setting_number_erase_text    # 7 blanks there (apply_coin_setting)
1161FE  data   coin_manual_page_common_text      # COIN CHUTE #2 MULTIPLIER and its table lines, then coin_manual_page_text (common chutes)
116277  data   coin_manual_page_text             # the MANUAL SETTING page: heading, COIN TO CREDIT, BONUS ADDER, COIN CHUTE #1 MULTIPLIER and its table, EXIT, button lines
116386  data   chute_1_coin_text                 # "COIN " after chute 1's multiplier (row 12 col 33) when it is 1
116391  data   chute_1_coins_text                # "COINS"
11639C  data   chute_2_coin_text                 # "COIN " (row 18 col 33)
1163A7  data   chute_2_coins_text                # "COINS"
1163B2  data   coin_credit_rows_erase_text       # blanks over rows 14 and 20 (the credit rows of both tables) before draw_coin_credit_table
116632  data   coin_setting_erase_text           # blanks over the coin setting lines (rows 11-15 and 17-21 from col 19) and its number (row 9 col 31)
1167C4  data   coin_setting_text_1               # coin_setting_texts[0]: "1 COIN  1 CREDIT" for chute 2 (row 17), then coin_setting_text_1_individual; test_coin_assignments prints it when the chutes become common
1167D8  data   coin_setting_text_1_individual    # coin_setting_texts_individual[0]: the chute 1 line (row 11) only
116E9F  data   coin_setting_text_manual          # "MANUAL SETTING" at rows 17 and 11 in place of the setting lines (common chutes, setting_coin_number < 0)
116EB1  data   coin_setting_text_manual_individual   # the row 11 line only (individual chutes)
116EC5  table  coin_setting_values coin_setting_value  # [(setting number - 1) * 4]: coins per credit, bonus adder, chute 1 and chute 2 multipliers for the 26 COIN/CREDIT SETTINGs (apply_coin_setting)
struct coin_setting_value 4 rom       # a record of coin_setting_values (116EC5), one per COIN/CREDIT SETTING: apply_coin_setting "movea.w 116EC5(R0), R10" with R0 = setting - 1 (09E29B), then [R10+]; census ROM 116EC5-116F2C (26 records: 01 00 01 01 ... 01 01 01 01, the last free play)
field coin_setting_value 00 coins_per_credit u8    # "mov.b [R10+], 760F[R25]" (09E2A3): setting_coins_per_credit (1-4)
field coin_setting_value 01 bonus_coins u8         # "mov.b [R10+], 7612[R25]" (09E2A9): setting_bonus_adder (0 none, 1 free play, 2-5: that many coins give one more)
field coin_setting_value 02 chute_1_coin_value u8  # "mov.b [R10+], 7610[R25]" (09E2AF): setting_chute_1_multiplier (1-6)
field coin_setting_value 03 chute_2_coin_value u8  # "mov.b [R10], 7611[R25]" (09E2B5): setting_chute_2_multiplier (1-6)
116F2D  table  chute_1_credit_cell_positions u8  # 9 (column, row) pairs on row 14: where draw_coin_credit_cell draws the credits for 9 down to 1 coins
116F3F  table  chute_2_credit_cell_positions u8  # the same on row 20 (common chutes)
116F51  data   input_test_start_erase_text       # blanks over the three START lines (setting_no_start_buttons)
116F7D  data   input_test_text_3p                # "PLAYER 3", START, ATTACK, "3P ----", then input_test_text (3-player cabinets)
116FA7  data   input_test_text                   # "INPUT TEST", CONTROL BALL, 1P/2P ----, PLAYER 1/2 START/ATTACK, COIN CHUTE #1, SERVICE BUTTON, TEST BUTTON, exit line
117072  data   input_test_chute_3_text           # "#3" (row 20 col 12), then input_test_chute_2_text (individual chutes on 3-player cabinets)
117078  data   input_test_chute_2_text           # "#2" (row 19 col 12)
117080  table  input_test_bits_3p u8             # [line]: bit of input_ports_active test_inputs shows (start buttons, 3 players): 07 12 (start/attack 3), 04 10, 05 18 (1, 2), 00 0C (service), 01 0D (test)
117082  table  input_test_bits_2p u8             # the same from player 1's entries (8 lines)
11708A  table  input_test_bits_no_start_3p u8    # setting_no_start_buttons: 12 10 18 (attacks 3, 1, 2), 00 0C, 01 0D
11708B  table  input_test_bits_no_start_2p u8    # the same from attack 1 (6 lines)
117091  table  input_test_on_texts_3p u32 rom    # [line] the "ON " record test_inputs prints for a pressed input (start buttons, 3 players, 10 lines)
117099  table  input_test_on_texts_2p u32 rom    # from entry 2 (8 lines)
1170B9  table  input_test_on_texts_no_start_3p u32 rom   # no start buttons, 7 lines
1170BD  table  input_test_on_texts_no_start_2p u32 rom   # 6 lines
1170D5  table  input_test_off_texts_3p u32 rom   # [line] the "OFF" record for a released input (10 lines)
1170DD  table  input_test_off_texts_2p u32 rom   # 8 lines
1170FD  table  input_test_off_texts_no_start_3p u32 rom  # 7 lines
117101  table  input_test_off_texts_no_start_2p u32 rom  # 6 lines
1171A9  data   input_test_coin_1_on_text         # "ON " (row 18 col 16): input_ports_active bit 2
1171B2  data   input_test_coin_1_off_text        # "OFF"
1171BB  data   input_test_coin_2_on_text         # row 19: bit 3
1171C4  data   input_test_coin_2_off_text
1171CD  data   input_test_coin_3_on_text         # row 20: bit 6 (individual chutes on a 3-player cabinet)
1171D6  data   input_test_coin_3_off_text
1171DF  data   input_test_trackball_1_good_text  # "GOOD" (row 5 col 23) once trackball 1 went all four ways (move_input_test_cursors)
1171E9  data   input_test_trackball_2_good_text  # row 12
1171F3  data   input_test_trackball_3_good_text  # row 19
1171FD  table  input_test_cursor_top_limits s16  # [3 - cursor]: the smallest screen_y the three trackball cursors may have (38, 70, A8; [0] unused), compared signed (move_input_test_cursors)
117205  table  input_test_cursor_bottom_limits s16   # [3 - cursor]: the largest (5D, 95, CD)
11720D  data   backup_clear_text                 # "BACKUP DATA CLEAR", "YES(CLEAR)", "NO (CANCEL)", button lines
117275  data   backup_clear_done_text            # two blanks (row 16 col 28), then backup_clear_completed_text: test_backup_clear prints it after reset_bookkeeping and reset_ranking
11727B  data   backup_clear_completed_text       # "    COMPLETED    " (row 16), printed before the reset
117292  data   bookkeeping_text                  # "BOOKKEEPING", COIN CHUTE #1-#3, TOTAL COINS, the credit, game and time labels, exit line
1173E8  data   bookkeeping_chute_3_erase_text    # blanks over "COIN CHUTE #3" unless individual chutes on a 3-player cabinet
1173FB  data   memory_test_text                  # "MEMORY TEST", <ROM> IC8 IC9 IC17 IC18, <RAM> IC16 ... IC75, "NOW CHECKING"
1174D0  data   memory_test_done_text             # after the tests: blanks rows 24-25, "PUSH TEST BUTTON TO EXIT"
117526  data   memory_test_error_text            # 24 blanks on row 25 in attribute 1C, printed when memory_test_errors != 0
117544  data   memory_test_dashes_text           # "----" at the 19 result places (before the tests and under the results)
1175DE  data   memory_test_result_texts          # 10-byte one-record lists: GOOD x 19 then " BAD" x 19, one per result place (offsets in memory_test_good_offsets/bad_offsets)
11775A  table  memory_test_good_offsets u16      # [bit of memory_test_errors, 0-18]: offset from 1175DE of that chip's "GOOD"
117780  table  memory_test_bad_offsets u16       # [bit]: offset of its " BAD" (attribute 04)
1177A6  data   eeprom_caution_text               # "CAUTION", "YOU HAVE EXCEEDED THE EEPROM'S WRITING FREQUENCY LIMIT OF 10,000 TIMES" (exit_test_mode, eeprom_write_count >= 10000)
117876  data   attract_demo_1_inputs             # attract_demo_1: demo_input = it: 16-byte header (stage 9, stage_start_section 0B) then 7 bytes a frame (buttons, three trackballs' x/y)
118876  data   attract_demo_2_inputs             # attract_demo_2: the same layout, stage 8, stage_start_section 10
145A46  data   stage_03_end_script_1             # update_scroll (stage 3 past scroll_end; never reached in the scenarios) runs it with run_vram_script; it is 8 bytes before the tile set script at 145A4E (page 11, a brick wall) and starts on the tail of the script before it (8E: no such command)
146161  data   stage_03_end_script_3             # the third one (end step 1); 8 bytes before the script at 146169 (page 0, a wall with an opening)
146DD1  data   stage_03_end_script_2             # the second one (set-up); 8 bytes before the script at 146DD9 (page C, a platform on legs)
158E53  data   stage_0c_nbg3_script              # init_nbg3_layer, stage C: fills NBG3 page 5 (nbg3_pages 05050505)
15902B  data   stage_0d_nbg3_script              # stage D: page 5
1593D4  data   stage_0e_nbg3_script              # stage E: page 5
15979C  data   stage_0f_nbg3_script              # stage F: page 5
170602  data   blank_tile_set_lists              # get_tile_set_list adds the u16 offsets at stage_tile_sets[stage] (1705DA, stages 0-2, 11, 12) to it: script lists (FFFFFFFF ends) of the clear_tile_page scripts of pages 0-3 and A-D, or empty
17064A  data   clear_tile_page_00_script         # vram script: clears page 0 and fills its 200 cells with 0 (update_stage_06_background step 3, blank_tile_set_lists)
170692  data   clear_tile_page_03_script         # update_stage_00_exit_scene step 0
1706C2  data   clear_tile_page_0a_script         # init_nbg2_layer (NBG2 pages 0A-0D), update_stage_06_background step 3
1706DA  data   clear_tile_page_0b_script         # init_nbg2_layer
1706F2  data   clear_tile_page_0c_script         # init_nbg2_layer
17070A  data   clear_tile_page_0d_script         # init_nbg2_layer
170722  data   clear_tile_page_0f_script         # init_nbg1_layer (NBG1 pages 0F-12)
17073A  data   clear_tile_page_10_script         # init_nbg1_layer
170752  data   clear_tile_page_11_script         # init_nbg1_layer
17076A  data   clear_tile_page_12_script         # init_nbg1_layer
170782  data   clear_tile_page_05_script         # init_nbg3_layer, stages 9 and B (NBG3 pages 5-8)
17079A  data   clear_tile_page_06_script         # init_nbg3_layer, stages 9 and B
1707B2  data   clear_tile_page_07_script         # init_nbg3_layer, stages 9 and B
1707CA  data   clear_tile_page_08_script         # init_nbg3_layer, stages 9 and B
1707E2  data   stage_00_tower_top_page_1_script  # run_stage_05_switch step 2: stage 0's NBG0 picture, the top of the tower (stone platform, a pillar with a banner; render, tile bank 1), page 1 part
170819  data   stage_00_tower_top_page_3_script  # page 3 (and one cell of page 4)
17098A  data   stage_00_tower_top_page_0_script  # page 0
170BC1  data   stage_00_tower_top_page_2_script  # page 2
170D3B  data   stage_00_exit_tower_wall_script   # update_stage_00_exit_scene step 0 (NBG0 y 20-27): page 2, the tower's wall with windows (render) as the camera climbs
170E58  data   stage_00_exit_beam_script         # step 1 (NBG0 y F6-FD): page 0, a horizontal beam on diagonal struts (render)
170EE5  data   stage_00_exit_beam_end_script     # step 1: page 1, the beam's rounded end (render)
170F35  data   stage_00_exit_zoom_page_0_script  # step 8: page 0 (NBG0 pages 02020200), zoomed by steps 9-10 (render: a rocky island with buildings and a long pole on a dark dotted field)
171242  data   stage_00_exit_zoom_page_1_script  # step 8: page 1 (NBG1 pages 02020201), the rest of that picture
1712D4  data   stage_00_exit_zoom_page_2_script  # step 8: page 2, white dots on dark all over (render)
1715D3  data   cloudy_horizon_script             # page 0, a cloudy sky over the sea (render): NBG3 of the stage 6 scene (update_stage_06_background steps 1 and 5) and of the ending's tower scene (ending_tower_animate)
171663  data   stage_06_island_tower_script      # update_stage_06_background step 5: page 5 (NBG0), the tower on its island on the horizon (render)
1716C1  data   stage_06_upper_jaw_script         # step 3: page 1 (NBG0), the machine's upper jaw with eyes and teeth (render); step 4 slides it down
17176E  data   stage_06_lower_jaw_script         # step 3: page 6 (NBG3), the lower jaw (render); step 4 slides it up to close
171882  data   stage_06_forest_script            # step 1: page 5 (NBG0), a band of trees (render) that step 2 scrolls sideways
1718B6  data   stage_00_coast_script             # run_stage_05_switch step 2: page 5, the city's coast seen from high above (render, bank 1): one of stage 0's NBG3 pages
171BAB  data   stage_00_horizon_script           # run_stage_05_switch: page 6, a horizon band (render)
171BEB  data   blue_sky_script                   # page 7, blue sky with clouds (render, tile bank 1): stage 0's NBG3 (run_stage_05_switch, nbg3_pages 07070707) and the ending's Eggman scene (build_ending_scene)
171C77  data   aerial_city_script                # page 8 (and one cell of 9), a city seen from high above (render, bank 1): run_stage_05_switch and build_ending_scene
172005  data   default_nbg3_script               # init_nbg3_layer for stages 0-3, 5-7, 11, 12: page 5 (nbg3_pages 06060505), then set_nbg3_page_5_flags
172175  data   stage_04_nbg3_script              # init_nbg3_layer, stage 4: page 5 (nbg3_pages 06060505)
1721B9  data   stage_04_nbg3_fill_script         # then page 6: cleared and filled with tile 0CE9
1721D1  data   map_monitor_script                # map_intro_update: page 1 (NBG0 01010101), the island map on a monitor above a console (render)
172339  data   map_eggman_head_script            # map_intro_update: page 0 (NBG2), the back of Eggman's head at the bottom (render), with map_eggman_update's sprites
172386  data   stage_0a_nbg3_script              # init_nbg3_layer, stage A: page 7 (nbg3_pages 07070707)
17242A  data   stage_10_nbg3_script              # init_nbg3_layer, stage 10: page 5
1725B2  data   ending_eggman_machine_script      # build_ending_scene step 0: page 5 (NBG2 05050505), Eggman in his flying machine (render, bank 1)
172653  data   ending_stripes_script             # ending_eggman_scene_fade_in: page 2 (NBG1 02020202), diagonal stripes (render): the background of the last screen
17280A  data   sonic_head_pattern_script         # page 1, a repeating pattern of Sonic heads (render, bank 1): show_nbg0_picture (ranking, PLAYER SELECT) and build_ending_scene
1729B2  data   ending_logo_script                # build_ending_scene: page 3, the SEGA / SONIC THE HEDGEHOG logo (render): NBG0 of the last screen
172A84  data   ending_bars_script                # build_ending_scene: page F, horizontal bars across 4 tile rows (render): NBG3 of the last screen
172CED  data   stage_08_nbg3_script              # init_nbg3_layer, stages 8 and 13: page 5, rows of tiles 1E00-1E03
172D11  data   title_sky_script                  # run_title_screen: page 5 (NBG3 05050505), a light sky over a distant shore (render, bank 1)
172D41  data   title_waving_background_script    # run_title_screen: page 0 (NBG2), the layer title_sky_wave scrolls line by line

# ======== scratch work RAM as each routine uses it (scoped global lines, from the helpers) ========
scoped global stage_clear_banner_hold 20F0EA banner_letter_offset_x u16  # x of the next letter from the banner's screen_x: 8, + 10 a letter (add.h into the letter's 0E)
scoped global stage_clear_banner_hold 20F0EC banner_letters_left s16  # the first halfword at the banner's animation (48): letters left (dec.h + bgt) and each letter's number (its kind, 47); its bit 0 (a test1 of the 32-bit word, left as temp_1) sets sprite flags bit 7 on odd letters
scoped global spawn_banner_sparkle 20F0EA sparkle_offset_x u16  # R0 (offset_x) kept across allocate_object: added to the banner's screen_x for the sparkle
scoped global start_player_objects 20F0EA player_handler_list u32  # player_objects (002405) or player_objects_alt (00241F): the list reset_objects_from_list starts objects from (its object_handler_list)
scoped global reset_objects_from_list 20F0EA object_handler_list u32  # set by start_player_objects(_alt): a count and that many handlers, read once into handler_cursor
scoped global reset_objects_from_list 20F0EE player_block_cursor u32  # 208400 (the first player block), + 200 a handler: its is_joining (9) set gives player_join, else player_status picks player_resume; 208600 = player 1, past it player 2
scoped global player_animate 20F0EA saved_sprite_offset_y u8  # sprite_offset_y kept while project_object runs with 12 (three places, 6 accesses); the climb pull-up (004D9B) keeps animate_around_anchor's last-step flag in the same byte (2 accesses), and the climbing side step its new screen_x as a long (004A77, 2 accesses: not named)
scoped global player_animate 20F0EE side_step_projected_y u32  # climbing side step: projected_y +- 1.0 (half the 2.0 x step, sign from the record byte), checked against the record's range and the screen, then copied into projected_y
scoped global compute_camera_slope_speed 20F0EA slope_velocity_y u32  # (camera_velocity_y << B) / (10000 + |slope_factor|): squared into camera_slope_speed, then the dy of angle_of_vector for camera_slope_direction
scoped global compute_scroll_drift 20F0EA unused_drift_divisor u32  # 10000 + |floor_slope_factors[slope]|, the divisor of camera_velocity_y << B: stored, never read back (compute_camera_slope_speed keeps its quotient there and does read it)
scoped global compute_scroll_drift 20F0EE unused_drift_byte u8  # the R9 byte both callers pass (0): stored, never read
scoped global check_player_step 20F0EA step_floor_z s32  # the floor height at the new position: floor_z, or the platform's z (flat platforms); z takes it for steps of 8 or less; compared signed with z after a blocked step
scoped global check_player_step 20F0EC step_floor_z_int u16  # its whole part (the high half): z_int minus it is the step
scoped global check_player_step 20F0EE step_blocking_floor_block u32  # floor_block of a step too high, kept while the previous block is restored: get_floor_block_side of it gives the bounce direction (speed_int >= 500, state E)
scoped global spawn_slip_pieces 20F0EA slip_pieces_left s16  # 2, counted down (dec.h + bgt)
scoped global spawn_slip_pieces 20F0EC slip_piece_speed u32  # 8000 (0.5 a frame), negated for the second piece: its speed_x (ledge sides 0/2/4) or speed_y (1/3)
scoped global spawn_player_ride 20F0EA ride_start_x u32  # A60 or AE0 (16.16), by the side the player is past: the ridden object's x
scoped global check_player_contacts 20E500 contact_saved_x u32  # kind 19 (pushes players along): x before the player is put back at previous_x for side_of_box; restored when the side is 8 (before it in x)
scoped global check_player_contacts 20E504 contact_saved_y u32  # the same for y
scoped global check_player_contacts 20F0EA contact_box_side u16  # side_of_box's R3 bits (8 before it in x, 4 past it, 2 before it in y, 1 past it; 0 none): 0 and 8 move the player with the object
scoped global spawn_ring_rows 20E500 ring_row_rings_left s16  # the record's count: rings left in the row (dec.h + bgt), one template object and one pool 2 shadow each
scoped global spawn_ring_rows 20E502 ring_row_template u16  # the record's offset into stage_object_templates (every ring of the row)
scoped global spawn_ring_rows 20E504 ring_row_x u16  # x of the next ring: the record's x, + ring_row_dx a ring
scoped global spawn_ring_rows 20E506 ring_row_y u16  # y of the next ring: the record's y, + ring_row_dy a ring
scoped global spawn_ring_rows 20E508 ring_row_dx u16  # the record's s8 dx (movs.bh)
scoped global spawn_ring_rows 20E50A ring_row_dy u16  # the record's s8 dy (movs.bh)
scoped global spawn_ring_rows 20E50C ring_row_next_80 u16  # 0, + 20 a ring: each ring's halfword 80 (ring_spin writes 78 there at each glint; no reader found)
scoped global scatter_rings 20F0EA scatter_fixed u16  # FFFF from scatter_rings_fixed, 0 from scatter_rings_random: low byte set = no random speed, high byte (scatter_fixed_jump) set = no random jump; scatter_rings then keeps each odd ring's random direction here (the next ring mirrors it: 400 - it), which overwrites both bytes for the rings after it. The low byte test (1 access, AS8) is not named
scoped global scatter_rings 20F0EB scatter_fixed_jump u8  # high byte of scatter_fixed: 0 = upward speed_z from next_random (& 1FFFF + 80000), else speed_z stays 0
scoped global spiked_roller_start 20F0EA roller_parts_left s8  # the kind record's part count (5), counted down (dec.b + bgt)
scoped global spiked_roller_start 20F0EC roller_part_record u32 spiked_roller_part  # spiked_roller_layouts + the kind's offset + 2: the part record every part copies (sprite flags, hitbox sizes, 94, 30, 7C/7D)
scoped global spiked_roller_start 20F0F2 roller_part_frame_cursor u32 spiked_roller_start_frames  # spiked_roller_part_frames (0821DC, movea): the next part's starting frame (46), + 1 a part (the C split the constant store into temp_3 = 21DC, temp_4 = 8 before this name)
scoped global spiked_roller_start 20F0F6 roller_first_state u16  # the kind record's first state: written as a halfword into the leader's 80/81 once the parts are made
scoped global spiked_roller_start 20E500 roller_part_x u32  # x of the next part: the leader's x, + roller_step_x a part
scoped global spiked_roller_start 20E504 roller_step_x u32  # the layout's s8 dx << 16
scoped global spiked_roller_start 20E508 roller_part_y u32  # y of the next part: the leader's y, + roller_step_y a part
scoped global spiked_roller_start 20E50C roller_step_y u32  # the layout's s8 dy << 16 (a long: 20E50C-20E50F)
scoped global catapult_wait_for_platform 20F0EA catapult_pair_template u32 object_pair_template  # object_pair_templates + the record offset catapult_x_start/y_start leave in 34 (0 / 1A): the second part's words are read from +C (hitbox sizes, offsets, handler, animation)
scoped global propped_board_start 20F0EA board_props_left s16  # 2 props, counted down (dec.h + bgt): the first (2) at y - 70, the second at y + 70
scoped global release_board_rings 20F0EC board_rings_left s16  # 8 scatter_rings_fixed calls, y + 20 each, counted down (dec.h + bgt)
scoped global spawn_door_rollers 20F0EA door_rollers_left s16  # 4 to 1, counted down (dec.h + bgt): also the door_roller_layout record (x 6) of the roller being made
scoped global spawn_chaser_back_zone 20F0EA chaser_zone_index u32  # R1 (zone_index) kept across allocate_object: the object_steps record (x 6): limit (80) and handler
scoped global spawn_roller_back_zone 20F0EA roller_zone_index u32  # R1 (zone_index) kept across allocate_object: the object_steps record (x 6) whose limit goes into 80
scoped global machine_fire_update 20F0EA fireball_lane s8  # states 3/4: the lane 3, 2, 1 (dec.b + bgt): its fireball starts at y + lane x 38; state 1 counts the spawns_with_next_handler records in the halfword (3 accesses as u16: not named)
scoped global machine_fire_update 20F0EC fireball_lane_timer_cursor u32  # states 3/4: the lane's timer byte in the object (72, 73, 74; + 1 a lane): counted down; at 0 a new random 0-1F (also added to the fireball's target x) and a fireball
scoped global cage_start 20F0EA cage_bars_left s16  # A bars (first the free_object_stack check), counted down (dec.h + bgt)
scoped global cage_start 20F0EC cage_bar_x u16  # x of the next bar: the cage's x_int, + 18 a bar
scoped global cage_start 20F0EE cage_bar_y u16  # the cage's y_int: every bar's y
scoped global spreading_flame_burn 20F0EA flame_chaser_gap s16  # y_int minus the chaser's y_int (+ 40 in stage C while chaser_speed > 8): the fireball's target y is y - max(random 0-3F, it) (signed compare)
scoped global scenery_sprite_start 20F0EA scenery_sprite_kind u16  # the object's 04 (the scenery_sprite_kinds index) kept before 04 becomes the sprite: kind D goes to z 8D0 instead of its floor
scoped global burning_boulder_start 20F0EA boulder_flames_left s8  # 4 flame parts, counted down (dec.b + bgt)
scoped global burning_boulder_start 20F0EB boulder_flame_number u8  # 0-3: each flame part's kind (47), its burning_boulder_flame_layout entry
scoped global spawn_fire_trench_flames 20F0EA trench_flame_y u16  # y of the next flame column: E47, + 50 a column
scoped global spawn_fire_trench_flames 20F0EC trench_flames_left s16  # 3 columns, counted down (dec.h + bgt)
scoped global bridge_fire_start 20F0EA bridge_flames_left s16  # 3 flame parts (first the free_object_stack check), counted down (dec.h + bgt)
scoped global bridge_fire_start 20F0EC bridge_flame_offset_y u32  # 0, + 20.0 before each part: added to the fire's y (20, 40, 60)
scoped global spawn_lava_flame 20F0EA lava_flame_random u32  # next_random's value kept across allocate_object: bit 0 picks the animation, bytes 0 and 2 the spot (anchor y, anchor x)
scoped global ice_floor_start 20F0EA ice_row_start_x u32  # the spawner's x, put back after each row of 7 cubes (x moves + 2E a cube)
scoped global ice_floor_start 20F0EE ice_cube_floor_block u32  # ice_floor_floors[04]: every cube's floor_block
scoped global drop_icicle_row 20F0EA icicle_row_offset u16  # 20, + 48 an icicle: added to the icicle's x (directions 0/2/4/5) or y (1/3)
scoped global icicle_buried_part_draw 20F0EA buried_part_depth u16  # the parent's 88 + 1: depth of the two clip entries and the buried copy
scoped global tornado_start 20F0EA tornado_kind u16  # the object's 04 (tornado_records index): 0 freezes the players and gets spawn_chaser_back_zone 1, else spawn_tornado_rear_zone and a second-pool part on each column (that test reads the low byte, 1 access: not named)
scoped global biting_head_move_at_speed 20F0EA biting_head_depth u16  # screen_y - 50: the depth of the two clip entries (part_list + 54, + 40) and the head
scoped global rising_saw_init 20F0EA saw_blades_left s8  # 1 (one blade with its sparks), counted down (dec.b + bgt)
scoped global rising_saw_init 20F0EC saw_blade_offset_x u8  # 8, + 10 a blade: the blade's sprite_offset_x
scoped global build_grate_steps 20F0EA grate_step_number s8  # 0-2 along the row (signed compare with 3): each step's kind (47), the rising_saws entry grate_step_update watches
scoped global build_grate_steps 20F0EB grate_rows_to_build s8  # 1: rows this call builds, counted down (dec.b + bgt); the object's rows_left counts the calls
scoped global spawn_grate_step_debris 20F0EA grate_debris_record u32  # the next 10-byte grate_step_debris record (x/y offsets, speeds, animation offset)
scoped global spawn_grate_step_debris 20F0EE grate_debris_left s16  # the list's count word, counted down (dec.h + bgt)
scoped global build_saw_shaft_base 20F0EA saw_shaft_part s16  # 0, 1, 2 (signed compare with 3): parts 0 and 2 get sprite 7FE8 (2 flipped), part 1 7FB8
scoped global geyser_front_spawn_geyser_row 20F0EA geysers_left s16  # 5 geysers in the row (first the free_object_stack check), counted down (dec.h + bgt)
scoped global geyser_front_spawn_geyser_row 20F0EC geyser_y u32  # y of the next geyser: the front's y, + 10 then + 2E a geyser (on geyser_y_int)
scoped global geyser_front_spawn_geyser_row 20F0EE geyser_y_int u16  # the whole part of geyser_y
scoped global geyser_front_spawn_geyser_row 20F0F0 geyser_delay_cursor u32  # geyser_row_delays + A x the row pattern byte: the next geyser's wait word, + 2 a geyser
scoped global railing_spawner_update 20F0EA railing_segments_left s16  # A segments, counted down (dec.h + bgt); the spawner first moves + A x B8 in y
scoped global railing_spawner_update 20F0EC railing_segment_y u32  # y of the next segment: the spawner's y, - B8 (on railing_segment_y_int) before each segment
scoped global railing_spawner_update 20F0EE railing_segment_y_int u16  # the whole part of railing_segment_y
scoped global spawn_walls 20F0EA wall_corner_x u32  # x + hitbox_size_x << 16: the box's far corner, projected with wall_corner_y for the screen width (speed_z_fraction)
scoped global spawn_walls 20F0EE wall_corner_y u32  # y + hitbox_size_y << 16: gives the screen height (speed_z_int)
scoped global wall_update 20F0EA wall_sprite_offset_x u16  # sprite_offset_x (zero-extended byte): taken from the parts' screen x for the on-screen test (-20 .. 160)
scoped global spawn_object_groups 20F0EC group_floor_block u32  # the record's long: every member's floor_block (5C)
scoped global spawn_object_groups 20F0F2 group_direction u16  # the record's direction word: every member's 24
scoped global spawn_object_groups 20F0F4 group_leader u16  # group_leaders[the record's byte]: every member's parent (26)
scoped global spawn_object_groups 20F0F0 group_bridge_number u8  # spawn_object_groups_2: 0 or 1 (type 2 bridge 0, type 3 bridge 1, type 0 the first bridge without planks): every member's 95; the last member goes to bridge_N_last_plank
scoped global update_escape_countdown 20F0EA escape_seconds_bcd u16  # escape_seconds in BCD (convert_to_bcd): tens digit (bits 4-7, drawn when not 0) and units digit
scoped global update_escape_countdown 20F0EC escape_digit_scale u16  # escape_countdown_frames x 9: both digits' sprite_scale
scoped global play_demo_buttons 20F0EA demo_patched_inputs u32  # the input word (R0) with bits 19-1B replaced by the demo record's three button bits (movbsd into it), then handed back

# ======== local variables (local lines, from the helpers): by the instructions that write them ========
local run_frame 000166,000173,000188,000195,0001A6,0001B3,0001C4,0001D1 r0 start_bit  # bit of inputs_pressed tested for a player's start: 4/5/7 (start buttons 1-3), or 10/18/12 (the players' buttons) when setting_no_start_buttons
local attract_title 000291 r10 text  # 000644: the text record print_text prints and advances past; then only handed to run_title_screen and back
local attract_title 00022B t1 cnt_bits  # io_cnt read before clearing bit 1 (display off while the title is rebuilt)
local advance_attract_screen 0003C9 r0 next_step  # (attract_step + 1) & 7: the index into attract_sequence, stored back in attract_step
local clear_work_ram 0004EF r0 fill_word  # the value written to every longword
local clear_work_ram 0004E8,000552 r8 blocks_left  # FC rounds of 32 longwords (0x80 bytes)
local clear_work_ram 0004E1,0004F2,0004F5,0004F8,0004FB,0004FE,000501,000504,000507,00050A,00050D,000510,000513,000516,000519,00051C,00051F,000522,000525,000528,00052B,00052E,000531,000534,000537,00053A,00053D,000540,000543,000546,000549,00054C,00054F r11 dest  # walks the work RAM from GAME_VARIABLES, 4 bytes per write
local delay_after_reset 00058A,00058F r1 count  # 8000 down to 0: the busy-wait loop
local run_game_mode 000738,000742 r0 next_game_mode  # game_mode_start_game: 1B (character select) in a game, 2 (start_stage) in the demo
local run_game_mode 000F74,000F7F r3 credits_needed  # after game over: credits_per_start, 0 in free play; p1/p2/p3_credits must reach it for a start to begin a new game
local run_game_mode 0014D7,0014E0,0014EC r3 new_stage  # stage select: stage + read_trackball_step's step, wrapped to 0..0x11, stored in stage
local run_game_mode 094266,09427D r6 playing_count  # game mode 14 step 1: 3 minus the players whose player_status is not FF: how many are playing; 0 skips the icicle dropper and Eggman's speech
local run_game_mode 094045,094055 r12 status_cursor  # game mode 19: walks player_status, one byte per player
local run_game_mode 0009E5 t2 intro_init_flags  # game mode 1: stage_init_flags before bit 0x2000 is set (the intro is set up once)
local run_game_mode 000B01 t1 p2_status  # game mode 1: player_2_status (FF = playing), tested with player 2's button to end the intro
local run_game_mode 000B14 t1 p3_status  # game mode 1: player_3_status, tested with player 3's button
local run_game_mode 001474 t2 stage_select_init_flags  # game mode 8: stage_init_flags before bit 0x40 is set (the stage select text is printed once)
local run_game_mode 0946D0 t2 event_0d_timer  # game mode 0D: scene_timer before this frame's decrement (<= 1: time is up)
local run_game_mode 000BA5 t2 old_continue_count  # game mode 0E: continue_count before the decrement
local run_game_mode 094409 t1 event_12_status  # game mode 12: a player's player_status byte in the loop over the three players
local run_game_mode 0943BD t2 event_13_timer  # game mode 13: scene_timer before the decrement
local run_game_mode 094276 t1 event_14_status  # game mode 14 step 1: a player's player_status byte (FF = playing)
local run_game_mode 0942DD t2 event_14_timer  # game mode 14 step 2: scene_timer before the decrement
local run_game_mode 000D47 t2 stage_clear_init_flags  # game mode 17: stage_init_flags before bit 0x4000 is set
local run_game_mode 000E90 t2 old_clear_timer  # game mode 17: stage_clear_timer before the decrement
local run_game_mode 094055 t1 event_19_status  # game mode 19: a player's player_status byte
local run_game_mode 094FB3 t2 event_1a_timer  # game mode 1A: scene_timer before the decrement
local run_game_mode 000FFF t2 character_select_init_flags  # game mode 1B: stage_init_flags before bit 0x10000 is set
local run_game_mode 001105 t2 stage_results_init_flags  # game mode 1D: stage_init_flags before bit 0x20000 is set
local run_game_mode 000F35 t1 game_over_cnt_bits  # game over: io_cnt read before clearing bit 1 (display off) on the way back to the attract mode
local run_game_mode 0006C8 t1 new_game_cnt_bits  # game_mode_start_game: io_cnt read before clearing bit 1 (display off)
local start_stage 00090D,000916 r0 cabinet_event  # 14 or 15 for cabinet_events at a stage start, by bit 0 of stages_past_2
local start_stage 000905,00090A r1 stages_past_2  # cleared_stages - 2, 0 below 2: bit 0 set gives cabinet event 14, clear 15
local start_stage 000905 t1 cleared_count  # cleared_stages, read once for the cabinet event
local stage_clear_banner_hold 001747 t2 letters_left  # banner_letters_left before the decrement: the loop ends when it was <= 1
local apply_stage_layer_blending 001884 r1 blend_mask  # the record's first byte, put in the low 6 bits of the layer's mixer shadow (the sprite blend mask)
local apply_stage_layer_blending 001859,001865,001884,001887,00188A,0018A5 r10 cursor  # walks the 7 layer shadows from mixer_text_blend (clearing bit 12), then the stage's stage_layer_blending records
local apply_stage_layer_blending 001877 r10 cursor layer_blend_list  # stage_layer_blending[stage] + 2694 ("movea.b 2694[PC](R10), R10"): the stage's list, its count byte first
local apply_stage_layer_blending 001881 r10 cursor layer_blend_record  # past the count ("movz.bw [R10+], R8"): the first record
local apply_stage_layer_blending 00188D r11 layer_shadow  # GAME_VARIABLES + the record's offset: the layer's mixer shadow (20F1C0-20F1DE)
local check_zones 0018DC,0018F7 r0 box_start  # the zone's box start: x_int + hitbox_offset_x, then y_int + hitbox_offset_y
local check_zones 0018B6,0018CB r17 hitter  # walks hitter_list (20F07E = 20F016 + 68, linked by hit_list_next); a hit sets its flags bit 27 and the zone at 14
local check_stage_clear 0019D0,0019FE r0 next_game_mode  # 17 (stage clear), 1E on stages B-F: the game_mode once every player has finished
local clear_player_blocks 001A14 r0 fill_word  # the value written to every longword
local clear_player_blocks 001A08,001A17,001A1A,001A1D,001A20,001A23,001A26,001A29,001A2C r10 dest  # walks the player blocks from 208400, 0x20 bytes a round
local update_player_summary 001A7F t2 init_flags  # stage_init_flags before bit 0x800 is set (the first summary of a stage clears unused_summary_byte)
local update_player_summary 001AAE t1 drop_in_status  # a player's player_status in the loop that looks for 0x81 (dropping in from above)
local update_player_summary 001B08 t1 in_play_status  # a player's player_status in the loop that looks for a negative one (someone in play)
local update_player_summary 001B64 t1 status  # a player's player_status in the loops that count the negative ones into players_in_play
param continue_to_next_stage_slot R10 link_record  # the stage's stage_link_points record: from and to block offsets; load_stage_map_pointers' bounds list then lands in it and is handed back
local load_stage_map_pointers 001C0C r0 stage_index  # stage: the index into stage_floor_blocks and stage_floor_block_bounds
local run_stage_events 001C62 r0 distance_past  # scroll_position minus the event's position: the event starts only while it is under 0x100 (always on stage 0x11)
local run_stage_events 001C31 t2 init_flags  # stage_init_flags before bit 0x100 is set (the stage's event list is loaded once)
local run_stage_events 001C4F,001C62 r10 event  # stage_event_cursor (a stage_event), + 2 past its position once reached; handed back
local read_trackball_step 001CB4,001CBF,001CCC r3 step  # handed back in R3's low half: 1 once the sum reaches 0x40, FFFF (-1) at -0x40, else 0
local read_trackball_step 001C9C r11 step_sum  # address of the player's word in trackball_step_sums: cleared on a change of direction and after a step
local update_restart_point 001CDB r10 restart_point restart_point_record  # current_restart_point - 8 ("sub.w #8, R10"): the record before the current one, as the walk adds 8 first
local update_restart_point 001CE3 r10 restart_point  # walks the stage's restart point records (+ 8) to the first whose position is above scroll_position
local reset_objects_from_list 001D46,001DB0 r5 status  # player_status of the slot's player: 1, 2 or 3 restart it with player_resume instead of player_start_stage
local reset_objects_from_list 001D1A,001D6D,001DD8 r13 handlers_left  # the word before object_handler_list's handlers, counted down per object
local reset_objects_from_list 001D6D t1 handler_count  # handlers_left before its first decrement (<= 1: one object only)
local check_hit 001F2F,001F4C r0 box_start  # round hitter against a box: the other's box start, x_int + hitbox_offset_x then y_int + hitbox_offset_y
local check_hit 001F72 r6 diameter  # 2 * (radius sum) + 1: dx + radius sum (and dy + radius sum) must stay under it
param check_hit R8 centre_offset  # round hitboxes: the hitter's s8 centre offset x, then y (the two bytes of hitbox_size_y)
local check_hit 001FCB r12 slope_record floor_slope
local check_hit 001FD3 r12 slope_record  # floor_slopes + floor_slope * 8: flags (bit 1: slope along x), then the slope factor at +2
local add_to_hitter_list 0020A5 r0 old_head  # hitter_list before the push: becomes the object's hit_list_next
local add_to_rider_list 0020DB r0 old_head  # rider_list before the push: becomes the object's platform_list_next
local add_to_platform_list 0020F4 r0 old_head  # platform_list before the push: becomes the object's platform_list_next
local check_platforms 00212E,002142 r17 rider  # walks rider_list (20F090 = 20F01E + 72, linked by platform_list_next); a hit sets its flags bit 3 and the platform at 74
local compute_bridge_sag 00220B,00221A r3 smallest  # sorting the load list: the smallest plank number found so far, stored at the sorted position
local compute_bridge_sag 002246 r7 plank_total  # plank_count (R19) kept whole while middle is halved: a plank past the middle becomes plank_total - n
local compute_bridge_sag 00222D,002280 r12 sag_cursor  # walks the per-plank sag table (current_player = bridge_0_sag or bridge_1_sag), one word per plank
local add_to_wall_list 0022A6 r0 old_head  # wall_list before the push: becomes the object's platform_list_next
local check_walls 0022DE,0022F9 r0 box_start  # the wall's box start: x_int + hitbox_offset_x, then y_int + hitbox_offset_y
local check_walls 0022BA,0022CD r17 rider  # walks rider_list (20F090 = 20F01E + 72); a hit sets its flags bit 5 and the wall at 78
local blink_demo_prompt 00237D t2 shown  # demo_prompt_shown flipped every 0x20 frames (written back): 0 = the prompt is hidden
local blink_demo_prompt 0023FB t1 port_d  # io_port_d read before clearing bits 2 and 3 (prompt hidden, or not on a demo screen)
local player_reset 002B51 r10 stage_index  # stage, times 0xC (plus kind * 4) into player_palette_lists
local player_place 002C07,002C28,002C2E r1 entry_status  # 81 (drop in from above: no floor, touched or zone flag) or FF (playing), stored in entry_status; find_floor's and project_object's R1 land in it unread
local player_update 002D15 t3 invulnerable_left  # invulnerable_timer - 1, written back (below 0 it is set to -1)
local player_state_opening_scene 006DB7,006DC7,006DD9 r1 sprite_height  # opening scene: 30 for player 1, 2E for the others (with sprite_width 0/-20/20)
local player_state_in_zone 00623E,00624A,006254,00625F r2 next_state  # player_in_zone_steer: state 0, 1, 13 or 14 by the animation speed's whole part (0, up to 200, up to 4F0, above)
local player_state_climbing 004A5F,004A6D,004A7C,004A84,004A87 r9 side_step  # climbing side step: +-0x20000 (by the facing flag) added to screen_x, halved (negated on x walls) and added to projected_y: the stepped projected y
local player_state_climbing 004AA0 r9 wall_position  # whole part of the unprojected point after the side step (y, or x by the wall's orientation), kept inside the wall's start..start+length
local player_state_floor_8_sequence 0091DE r9 drift_byte  # 0: the R9 byte compute_scroll_drift stores in unused_drift_byte
local player_state_hurt 0056B1 t1 push_speed  # hurt knockback: the speed (4.0, half on floor type 4) moved into speed_y when the toucher's direction is 200
local player_state_hurt 0056A2 t1 push_speed_2  # hurt knockback: the speed moved into speed_y negated when the toucher's direction is 0
local player_state_opening_scene 006C7B t1 status  # opening scene: a player's player_status in the loop counting the playing ones
local player_state_tumbling 0065C9 t1 push_speed_3  # tumbling start: the speed moved into speed_y (direction 200)
local player_state_tumbling 0065BA t1 push_speed_4  # tumbling start: the speed moved into speed_y negated (direction 0)
local player_state_in_zone 006223 t2 target_speed  # player_in_zone_steer: (trackball magnitude)^2, at most 5000000, before speed approaches it
local player_state_knocked_off 00A4C3 t1 push_speed_5  # knocked off start: the speed moved into speed_y (direction 200)
local player_state_knocked_off 00A4B4 t1 push_speed_6  # knocked off start: the speed moved into speed_y negated (direction 0)
local player_animate_standing 0030D8,0030E4,0030F2 r10 animation_table player_facing_animation_offsets  # the character's standing animation table: per-facing offsets to the animations (0030FD "movz.hw [R10](R0), R1", R0 = facing)
local player_animate_run_cycle 00331B t3 run_toggle  # alternate_run_toggle + 2, written back: not negative picks the alternate run animations
local player_launch 005479,005485,005493 r10 animation_record player_facing_animation_offsets  # the character's attack jump table (sonic_/ray_/mighty_attack_jump_animations): per-facing offsets (00549E "movz.hw [R10](R0), R1")
local player_launch 0054A2 r10 animation_record flagged_step  # "add.w R1, R10": its take-off step for the facing: sprite flags (0054AE "or.h [R10+], R0"), first sprite frame (0054B5), then the animation (+4)
local player_launch 0054AE r10 animation_record  # the [R10+] step to the frame word
local player_stuck_shake 007851,007858,007864 r0 speed  # (trackball magnitude)^2, at most 5000000: the new speed
local player_stuck_shake 00783E,007873 r1 trackball_y  # the player's trackball_y for the magnitude and the angle; square_root's, animate's and play_sound's R1 land in it unread
local player_stuck_shake 0078B1,0078BD,0078CB,0078DD,0078E9,0078F7,00792D,007939,007947 r10 animation  # the character's struggle animations (3, by escape progress), escape animation or stuck animation
local player_stuck_shake 007903,00790F r11 struggle_index  # escape_progress / 0x5555, at most 2: picks the struggle animation
local player_stuck_jump_out 0079A6 r10 bounce_record player_bounce_record  # the bounce record for bounce_direction 1: direction, sprite flags (+2), animation offset (+4)
local player_stuck_jump_out 0079AA r10 bounce_record  # the [R10+] steps through the record
local player_stuck_jump_out 00797D,007989,007997 r12 bounce_records  # the character's bounce records table (sonic/ray/mighty_bounce_records); the animation offset is from it
local player_object_climb_begin 008A90,008A9A r10 animation  # 9CB3 (Sonic) or 9CDD: the climbing animation (Ray gets 9CCF afterwards)
local player_object_climb_move 008B93 t2 next_hand  # -climb_hand, written back: the hand that climbs next; negative picks animation 9CCF
local spawn_climb_hand 008C2E,008C35,008C3C,008C46 r1 sprite_frame  # the hand's sprite frame: 8300 (Sonic) or 8258 when climb_hand is 0, 82C8 when negative, 82C0 otherwise
local player_move_with_scroll 009798 r2 drift_speed  # scroll_drift_speed >> 14: the length of this frame's drift move; find_floor's R2 lands in it unread
local player_move_with_scroll 009769 r9 drift_byte  # 0: the R9 byte compute_scroll_drift stores in unused_drift_byte
local player_move_with_scroll 0098D6,0098E2,0098F0 r10 animation  # the character's drift animation (sonic/ray/mighty_drift_animation)
local scene_object_update 009BCB r1 radius  # scene_object_view.radius_x4 >> 2 (it grows by 4 a frame): the distance from (5D0, z 78) on the circle the object turns on
local player_stuck_on_object_begin 009D69,009D73,009D7F r0 sprite_frame  # 1A40 (Sonic), 5180 (Ray), 35C8 (Mighty)
local player_stuck_on_object_shake 009E3B,009E42,009E4E r0 speed  # (trackball magnitude)^2, at most 5000000: the new speed
local player_stuck_on_object_shake 009E28,009E5D r1 trackball_y  # the player's trackball_y for the magnitude and the angle; square_root's, animate's and play_sound's R1 land in it unread
local player_stuck_on_object_shake 009E9B,009EA7,009EB5,009EC7,009ED3,009EE1,009F17,009F23,009F31 r10 animation  # the character's struggle animations (3, by escape progress), escape animation or stuck animation
local player_stuck_on_object_shake 009EED,009EF9 r11 struggle_index  # escape_progress / 0x4000, at most 2: picks the struggle animation
local player_stuck_on_object_jump_out 009F90 r10 bounce_record player_bounce_record  # the bounce record for bounce_direction 1: direction, sprite flags (+2), animation offset (+4)
local player_stuck_on_object_jump_out 009F94 r10 bounce_record  # the [R10+] steps through the record
local player_stuck_on_object_jump_out 009F67,009F73,009F81 r12 bounce_records  # the character's bounce records table; the animation offset is from it
local spawn_skid_dust 00A6C4 r0 priority_bits  # the player's sprite_palette bits 13-14 (the mixer priority group), copied to the dust
local player_hud_start 00A8C5,00A8CE,00A8DD r0 init_bit  # bit of stage_init_flags cleared for the player outside a game: F, 12, 13 (players 1-3)
local player_hud_start 00A85E r10 sprite_record  # hud_portrait_sprites + kind * 4: the portrait's sprite frame and palette
local player_hud_update 00AAEE,00AAF8,00AB04,00AB5F,00AB69,00AB75,00ABB5,00ABBF,00ABCB r10 portrait_anim  # the portrait's palette animation: CD63/CD6B/CD73 by character, CD7B/CD89/CD97 at low health
local animate_hud_palette 00ACB3 r0 last_frame  # low byte of the animation's first word: the last frame; past it the animation wraps to 0
local animate_hud_palette 00ACAF r10 palettes hud_palette_cycle  # animate_hud.anim ("mov.w 28[R20], R10"): the palette cycle, its last index first (00ACB3 "mov.h [R10+], R0")
local animate_hud_palette 00ACB3 r10 palettes hud_cycle_palettes  # anim + 2: the palettes, one word per frame (00ACCF "mov.h [R10](R1), 6[R20]")
local animate_hud_tiles 00ACE8 r0 last_frame  # low byte of the animation's first word: the last frame; past it the animation wraps to 0
local animate_hud_tiles 00ACE4 r10 frame_list frame_animation  # animate_hud.anim ("mov.w 28[R20], R10"): a frame animation (the ring effect's ring_sparkle_animation), its last frame first (00ACE8 "mov.h [R10+], R0")
local animate_hud_tiles 00ACE8 r10 frame_list sprite_frame_numbers  # anim + 2: the sprite frames, one word per frame (00AD02 "mov.h [R10](R1), 4[R20]")
local keep_player_on_screen 00AF1A r0 slope_direction  # camera_slope_direction: the sine_table/cosine_table index of the push
local keep_player_on_screen 00AF2C r2 slope_speed  # camera_slope_speed >> 14: the length of the push; play_sound's R2 lands in it unread
local keep_player_on_screen 00ADAC r5 base_x  # screen_x - sprite_offset_x: the player's x on screen (off screen outside -10..150)
local keep_player_on_screen 00ADB7,00AE4D,00AE58 r6 base_y  # screen_y - sprite_offset_y (minus 9 in a jump, plus the height above the floor): the player's y on screen
local keep_player_on_screen 00AE36,00AE42,00AE62,00AE6F r8 edge  # the screen edge the player reached: 0 top, 1 right, 2 bottom, 3 left; stored in screen_edge
local push_player_from_screen_edge 00AF6F r0 relative_direction  # direction minus the edge's limit direction (& 3FF): from 101 on the player goes back to its previous position
local push_player_from_screen_edge 00AF60,00AF6C r10 push_record  # screen_edge_push_directions + screen_edge * 4: the limit direction, then the push angle
local compute_camera_ground_speed 00AFCD r0 vx  # camera_velocity_x >> 5, for the speed
local compute_camera_ground_speed 00AFD9 r1 vy  # camera_velocity_y >> 4, for the speed; square_root's R1 lands in it unread
local compute_camera_slope_speed 00B0AA,00B0F3 r0 vx  # camera_velocity_x >> 5, for the speed
local compute_camera_slope_speed 00B09A,00B0FF r1 vy  # camera_velocity_y >> 4, or on a slope (camera_velocity_y << 11) / (10000 + |slope factor|); square_root's R1 lands in it unread
local compute_scroll_drift 00B1D8,00B20F r0 vx  # camera_velocity_x >> 5, for the speed
local set_speed_from_trackball 00B2DA,00B2DD,00B2E9 r0 speed  # (trackball magnitude)^2, at most 5000000: the new speed
local set_speed_from_trackball 00B2C7,00B2F8 r1 trackball_y  # the player's trackball_y for the magnitude and the angle; square_root's R1 lands in it unread
local set_climb_speed_from_trackball 00B32D,00B330,00B33C r0 speed  # (trackball magnitude)^2, at most 3000000: the new speed
local set_climb_speed_from_trackball 00B31A,00B34B r1 trackball_y  # the player's trackball_y for the magnitude and the angle; square_root's R1 lands in it unread
local get_ledge_side 00B3BE t1 x_min  # the block's x min: an x below it is side 3
local get_ledge_side 00B3C8 t1 x_max  # the block's x max: an x above it is side 1
local get_ledge_side 00B3D2 t1 y_min  # the block's y min: a y below it is side 0
local check_player_floor_edge 00B413,00B459 r0 next_state  # 5, or 7 when faster than 200 off a block whose type is not 12, 23, 18 or 19
local start_jump_if_pressed 00B63F,00B64C,00B658,00B65E,00B66B,00B677 r0 button_bit  # the player's button bit in inputs_pressed: 10/18/12 in a game, 19/1A/1B (the demo players' buttons) in the demo
param push_player_out_of_wall R0 angle  # the bounce direction from the wall's direction (<< 8, + 200), or the angle between the (reversed) direction and the wall's push direction (< 200 picks the side); 0 on the stop paths, the caller's R0 on the early exit
param push_player_out_of_wall R1 push_direction  # the sonic_bounce_records word for the wall's direction byte: where the wall pushes; +-0x80 it becomes scroll_drift_direction
local push_player_out_of_wall 00B813,00B825,00B82C r3 new_direction  # push_direction +-0x80 (by the side): the player's new direction
param push_player_out_of_wall R11 record_index  # the wall's direction byte * 3: index of its word in sonic_bounce_records
local player_steer_in_air 00B8B6 r1 trackball_y  # the player's trackball_y for the magnitude; square_root's R1 lands in it unread
local player_steer_in_air 00B8E3 t2 target_speed  # the target speed before speed approaches it (all at once, or 1/32 of the gap when slowing)
local player_steer 00BB1F r1 animation_speed_int  # the whole part of animation_speed: picks state 1, 13 or 14
local player_steer 00BB1C,00BB28,00BB49,00BB5B r2 next_state  # state 0, 1, 13 or 14 by animation_speed_int (limits 200, or 0xC0 on floor type 4, and 4F0)
local player_steer 00BBD0 r3 speed_factor  # the slope_speed_direction_pairs word that scales ground_speed on a slope
local player_steer 00BBCC r10 slope_pair slope_speed_pair  # slope_speed_direction_pairs record for the slope and direction: speed factor, then the new direction
local player_steer 00BBD0 r10 slope_pair  # the [R10+] step to the direction
local player_steer 00BA55 t2 target_speed  # floor type 7: the target speed before speed approaches it (1/64 or 1/32 of the gap)
local player_steer 00BA39 t2 target_speed_2  # other floors: the target speed before speed approaches it (1/32 when slowing, at once when speeding up)
local player_steer 00BAE3 t2 target_animation_speed  # the target animation speed (magnitude^2 * 1.5, at most 5000000) before animation_speed approaches it
local set_target_heading_from_trackball 00BC26,00BC3C r1 trackball_y  # the player's trackball_y (times 1.5 unless flags bit 31), plus push_y: the vector's dy
local turn_player 00BC9B r6 max_turn  # 0x180000 / speed: the most the heading may turn this frame (<< 6)
local turn_player 00BCAC,00BCCD,00BCDA,00BCF9 r8 turn  # |target_heading - heading| << 6, at most max_turn; 4000 or more starts state 0C
local turn_player 00BCAC t3 heading_delta  # (target_heading - heading) << 6, signed: the side to turn to
local set_heading_direction 00BD14 r0 snapped_heading  # (heading + 20) & 3C0: stored in heading_snapped, + 80 gives the direction; set_facing's result lands in it unread
local compute_ground_speed 00BDB9,00BDBF r0 rounded_speed  # ground_speed >> 12, rounded with bit 11: times speed_factor it is the new ground_speed
local compute_ground_speed 00BDB2 r3 speed_factor  # the slope_speed_direction_pairs word that scales ground_speed on a slope
local compute_ground_speed 00BDAE r10 slope_pair slope_speed_pair  # slope_speed_direction_pairs record for the slope and direction: speed factor, then the new direction
local compute_ground_speed 00BDB2 r10 slope_pair  # the [R10+] step to the direction
local read_player_trackball 00BDD9,00BDEB,00BDF5,00BDFC,00BE0E,00BE18 r0 axis_value  # the player's trackball x, then y: bit 0 cleared and << 8, keeping the sign
local shadow_update 00C0E6,00C220,00C292 r0 priority_bits  # the player's sprite_palette bits 13-14 (the mixer priority group), copied to the shadow; update_screen_position's and queue_sprite_at_depth's R0 land in it unread
local shadow_update 00C1ED r7 point_x  # the player's x for project_point; animate's wrapped lands in it unread
local shadow_update 00C1F1 r8 point_y  # the player's y for project_point, which hands back the screen y stored in ground_clip_bottom; animate's at_last_step lands in it unread
local draw_attack_effect 00C3EC r0 priority_bits  # the player's sprite_palette bits 13-15, copied to the second effect sprite
local draw_attack_effect 00C34B,00C355,00C361 r2 palette  # the effect's palette: 8320 (Sonic), 8700 (Ray), 8710 (Mighty)
local draw_attack_effect 00C30A,00C318 r10 effect_record player_facing_animation_offsets  # attack_power_3 or _4_effect_animations (113848, 113918): per-facing offsets (00C323 "movz.hw [R10](R0), R1")
local draw_attack_effect 00C331 r10 effect_record flagged_step  # "movea.h 2[R10](R0), R10": the facing's animation + 2 + animation_frame x 4, the step to show: sprite flags (00C33F "or.h [R10+], R0"), sprite frame (00C346); census ROM 113868-1139E7
local side_of_box 00C414,00C437 r0 box_start  # the other's box start: x_int + hitbox_offset_x, then y_int + hitbox_offset_y
local set_speed_from_direction 00C479 r1 cos_speed  # cosine(direction) * speed_int >> 6: speed_y is its negation; handed back
local polar_to_xy 00C482 r0 angle_index  # angle, sign-extended: the sine_table/cosine_table index
local polar_to_xy 00C4A5 r1 cos_part  # cosine(angle) * length >> 6: dy is its negation; handed back in R1
local polar_to_xy 00C48D r2 magnitude  # length, zero-extended
local polar_to_xy 00C497 r5 dx  # sine(angle) * length >> 6: handed back in angle's place
local polar_to_xy 00C4A9 r6 dy  # -cos_part: handed back in length's place
local set_speed_from_player_speed 00C4D3 r1 cos_speed  # cosine(direction) * ground speed >> 6: speed_y is its negation; handed back
local set_speed_from_player_speed 00C4B9 r2 ground_speed_int  # the whole part of ground_speed
local project_object 00C4F3 r0 projected_x  # ((x + y) >> 8) * B5: projected_x in 16.16; update_screen_position's R0 (screen_y) lands in it and is handed back
local project_object 00C4FA r1 projected_y  # ((y - x) >> 9) * B5: stored in projected_y; update_screen_position's R1 lands in it and is handed back
local update_screen_position 00C529,00C52D,00C536 r0 screen_y  # (projected_y + z) >> 16 - view_y + sprite_offset_y: stored in screen_y and handed back
local project_point 00C552 r0 projected_x_fixed  # ((point_x + point_y) >> 8) * B5: the projected x in 16.16 (its whole part goes to point_to_screen)
local project_point 00C541 r1 world_y  # point_y, read once for both formulas
local project_object_2 00C593 r0 projected_x  # ((x + y) >> 8) * B5: projected_x in 16.16
local find_nearby_floor_blocks 00C672,00C678 r0 corner_x  # a corner's x from the bounds record, minus left (with corner_y: inside the 240 x 1E0 area?)
local find_nearby_floor_blocks 00C635,00C69F r7 blocks_left  # the count at floor_block_bounds, counted down per block
local find_nearby_floor_blocks 00C61E,00C626 r12 top  # view_y - 0x80 (low half)
local find_nearby_floor_blocks 00C623,00C62B r13 bottom  # view_y + 0x160 (low half)
local find_nearby_floor_blocks 00C649 r15 bounds_cursor floor_outline  # bounds_list + bounds_offset (00C649 "add.w R4, R15"): the block's outline, its box tested first
local find_nearby_floor_blocks 00C64C,00C651,00C654,00C659,00C65C,00C672,00C675 r15 bounds_cursor  # walks the block's 16-byte record in floor_block_bounds (four corners)
local find_nearby_floor_blocks 00C66C r15 bounds_cursor floor_outline_corner  # bounds_list + bounds_offset again (00C66C): its four corners, 4 bytes each
local find_nearby_floor_blocks 00C638,00C692 r16 list_cursor  # write position in nearby_floor_blocks
local find_nearby_floor_blocks 00C630,00C635 r17 bounds_list  # floor_block_bounds, + 2 past its count: the records start
local find_nearby_floor_blocks 00C64C t2 box_left  # the record's first word (corner 0 x): not past right
local find_nearby_floor_blocks 00C654 t2 box_right  # the word at +4 (corner 1 x): not before left
local find_nearby_floor_blocks 00C65C t2 box_top  # the word at +A (corner 2 y): not past bottom
param find_floor R1 y  # the object's y, tested against the blocks' y min/max; the caller's R1 handed back on the other paths
param find_floor R2 block_offset  # the found block's offset from nearby_floor_blocks, stored in floor_block; the caller's R2 otherwise
local find_floor 00C763,00C78F r7 blocks_left  # nearby_floor_block_count, counted down per block
param find_floor R11 cursor  # walks nearby_floor_blocks, then the slope's floor_slopes record (flags, factor at +2)
local find_floor 00C77B t2 x_min  # the candidate block's x min
local find_floor 00C780 t2 x_max  # the candidate block's x max
local find_floor 00C785 t2 y_min  # the candidate block's y min
param check_player_step R0 height_diff  # z_int minus the new floor's z_int (+8 for the -8..8 test): 0 stays, below 0 falls (state 2), above 8 blocks; the caller's R0 on the early exits
local check_player_step 00CA18 r3 opposite_side  # get_floor_block_side's side + 2: & 3 it is the bounce_direction
local check_player_step 00C971 t1 floor_z_int  # step_floor_z_int: the new floor's height
local check_player_step 00C971 t2 z_int  # the object's z_int, before the difference
local check_player_step 00C991 t2 height_diff_2  # height_diff after the +8 is taken back off: negative = the floor dropped away
local bounce_off_wall_in_air 00CA70,00CA87,00CA8E r3 new_direction  # (side << 8) + 200 & 3FF from get_floor_block_side, then +-180 toward the current direction
local get_floor_block_side 00CAB5 t2 x_min  # the block's x min: an x below it is side 3
local get_floor_block_side 00CABF t2 x_max  # the block's x max: an x above it is side 1
local animate_sign 00CB2E r0 last_index  # low byte of the palette list's first word: the last palette; past it the index wraps to 0
local animate_sign 00CB36,00CB45 r1 next_index  # palette_index + 1, 0 past last_index: stored back and used to pick the palette
local animate_sign 00CB2E r10 palettes sprite_cycle_palettes  # palette_list + 2: the palettes, one word each
local cycle_part_palettes 00CB71 r0 last_index  # low byte of the palette list's first word: the last palette; past it the index wraps to 0
local cycle_part_palettes 00CB6C r10 palettes hud_palette_cycle  # cycle_part_palettes.palette_list ("mov.w 80[R20], R10"): the cycle, its last index first (00CB71 "mov.h [R10+], R0"); census ROM lava_rock_glow_palettes
local cycle_part_palettes 00CB94 r2 palette  # the palette written to every part (at +6 of each 8-byte part)
local cycle_part_palettes 00CB71 r10 palettes hud_cycle_palettes  # (00CB94 "mov.h [R10](R1), R2") palette_list + 2: the palettes, one word each
local place_player_at_restart_point 00CC56,00CC60 r0 spacing_step  # the player number, 2 turned into FFFF: 0, +1 or -1 times 2.0 pixels of x
local place_player_at_restart_point 00CBDB r2 offset_shift  # joining a game in progress: the word 6 bytes before the stage's restart points (low byte): player << it (sha) is added to x
local place_player_at_restart_point 00CBD8 r10 record stage_start_point  # joining: stage_restart_points + the stage's offset - 6 ("sub.w #6, R10"): the stage's start (shift, x, y)
local place_player_at_restart_point 00CBDB,00CC04,00CC07,00CC19,00CC1B,00CC46 r10 record  # walks the record: [R10+] through the start's fields, "add.w #2, R10" to the point's kind, then [R10+]
local screen_point_to_ground 00CC7A r8 x  # (point_x + view_x) << 16, which unproject_point turns into the world x
local screen_point_to_ground 00CC8A r9 y  # ((point_y + view_y) << 16 - z) * 2, which unproject_point turns into the world y
local unproject_point 00CC9E r0 new_x  # ((point_x - point_y) >> 8) * B5: the world x, kept aside while point_y is computed
local aim_at_screen_centre 00CCD3 r1 centre_dy  # (the screen centre's ground y - y) >> 12: dy for angle_of_vector; polar_to_xy's R1 lands in it and is handed back
local flash_hurt_palette 00CCF7,00CD08 r0 palette  # the palette list's palette (+2), or 83B0 while the flash is on
local flash_hurt_palette 00CCFE t2 flashing  # ~flash_on, written back: not 0 shows 83B0
local pay_and_join 00D566,00D573,00D595,00D5A2,00D5C2,00D5CF,00D616,00D61F r0 bit  # a bit number: the player's start bit in inputs_pressed (cabinets 2 and 3: 4/5/7, or 10/18/12), then the io_port_d bit cleared for player 1 (2) or player 2 (3)
local pay_and_join 00D5E2,00D5EB,00D5F6 r0 cabinet_event  # 1, 3 or 5 (player 1-3 pays) for cabinet_events
local pay_and_join 00D512,00D51B,00D524,00D52D,00D536,00D53F r1 start_bit  # cabinets 0, 1, 4: the player's start bit in inputs_pressed (4/5/7, or 10/18/12)
local pay_and_join 00D622 t2 port_d  # io_port_d (read as a long by clr1) before clearing the player's bit
local set_fall_sprite_priority 00D82E,00D837,00D860,00D869 r0 side  # index into the fall_sprite_priorities record: 3 or 1 by the half of the block the player is on, else ledge_side (ledge_direction for 4)
local set_fall_sprite_priority 00D86E r1 priority  # the record's byte: not negative, it goes into sprite_palette bits 13-14
local start_climb_at_spawn 00D904,00D90E r1 distance  # the distance from the wall's base: x - the block's x min, or the block's y max - y (by the wall's orientation)
local start_climb_at_spawn 00D8DF r12 attribute_record floor_attribute_record  # floor_types + floor_attribute: the wall's orientation (+0), climb height (+6), slope factor (+E)
local start_climb 00D948,00D954,00D962 r10 animation  # the character's climb animation (sonic/ray/mighty_climb_animation)
local apply_floor_push 00DADD r12 attribute_record floor_attribute_record  # floor_types + floor_attribute: the floor's push x (+0) and push y (+2)
local spawn_slip_pieces 00DC61 t2 pieces_left  # slip_pieces_left before the decrement: the loop ends when it was <= 1
local follow_player_path 00DD82,00DD89,00DDF6 r10 point  # path_pointer + 2: the path record's point, x then y
local animate_step_with_flags 00DE64 r0 last_frame  # low byte of the animation's first word: the last frame; past it the animation wraps to 0
local animate_step 00DEB7 r0 last_frame  # low byte of the animation's first word: the last frame (backwards: where it wraps to)
local animate_and_move 00DF0F r0 last_frame  # low byte of the animation's first word: the last frame
local animate_and_move 00DF4F,00DF59,00DF60,00DF6A r0 delta  # the step's s8 dx, then dy (negated by the x/y flip flags), added to projected_x/projected_y
local animate_and_move 00DF1D,00DF2B,00DF30,00DF3F r1 frame  # the next animation frame (+1, or -1 backwards; wraps)
local animate_around_anchor 00DF84 r0 last_frame  # low byte of the animation's first word: the last frame
local animate_around_anchor 00DF72,00DF9A,00DFB1 r8 at_last_step  # handed back: FFFFFFFF when the animation reached its last step, else 0
local set_step_around_anchor 00DFCA,00DFD4,00DFDF,00DFE9 r0 delta  # the step's s8 dx, then dy (negated by the flip flags), added to anchor_x/anchor_y
local animate_with_screen_offset 00E007 r0 last_frame  # low byte of the animation's first word: the last frame
local animate_with_screen_offset 00E015,00E023,00E028,00E037 r1 frame  # the next animation frame (+1, or -1 backwards; wraps)
local add_step_screen_offset 00E058,00E062,00E069,00E073 r0 delta  # the step's s8 dx, then dy (negated by the flip flags), added to screen_x/screen_y
local animate_part_list_step 00E090 r0 max_frame  # low byte of the animation's first halfword: the last frame number (the next frame wraps to 0 past it, or to it below 0)
local animate_part_list_step 00E09E,00E0AC,00E0B5,00E0C4 r1 next_frame  # animation_frame + 1 (- 1 with flags bit 7), wrapped; stored back to animation_frame
local animate_all_parts 00E0F3 r0 max_frame  # as in animate_part_list_step: the last frame number (equal: at_last_step FF)
local animate_all_parts 00E132 r2 frame_sprite  # the frame table's entry for the new frame: written to the sprite frame (first halfword) of every part
local player_select_character 00E1C7,00E1DA r1 playing_count  # player_status bytes that are negative, 1 or 4 (still playing, as record_player_game counts them); 1 (only this player) restarts game_frames
local player_select_character 00E1C4,00E1DE r8 players_left  # 3, counted down over the player_status bytes
local player_select_character 00E1BF,00E1DC r11 status_cursor  # walks player_status, one byte per player
local give_result_bonus 00E236 r0 joined_setting  # the player's byte 75 (joined_health_setting): index into max_health_by_setting
local give_result_bonus 00E23D r1 max_health  # max_health_by_setting[joined_setting x 2]: caps the health it just raised by 8
local give_result_bonus 00E22D r15 player_object  # the player's object (player 14, low half): its health (7E) + 8
local update_hurt_output 00E3C9 t1 port_value  # what io_port_d reads back
local update_hurt_output 00E3C9,00E3DF,00E40A r3 outputs  # port D's bits with this player's bit cleared, set again while hurt (states D 20 28 2C 33); written back to io_port_d
local update_hurt_output 00E3D0,00E3D9 r8 seat_bit  # this player's port D bit: 4 (player 0) or 8 (player 1); only with setting_moving_seat
local check_player_contacts entry,00E783,00E78D,00EBD9 r0 move_angle  # 0-3FF: where a hit sends an attacking player (direction + 200 when moving, else the object's direction) and, for kind 28, the angle to the object less 80 for push_x/push_y
local check_player_contacts 00E8B3,00E8BD,00E8C9 r0 held_frame  # sprite_frame of a player caught by kinds 8/10 (state 15): 1B10, 5230 or 3678 by character (kind)
local check_player_contacts 00E60B,00E616,00E61B,00E620,00E631,00E86C,00E871 r3 new_substate  # the substate a contact sets: hurt 0, 1 (kind 11) or 7 (kinds 14, 21); 0 or 1 with state F (kinds 6, 7)
local check_player_contacts 00E986 r3 saved_sides  # contact_box_side read back: 0 and 8 move the player with the object
local vine_swing 00EEC0 r1 segment_depth  # what each segment sorts by: screen_y - (x_int - base_x_int) + depth_offset (also takes update_screen_position's and project_point's R1, not read)
local vine_swing 00EE1C,00EE31 r12 in_front  # 1 when object_cursor's segment_angle_int is 100-2FF: the segments then go in bucket 0 (drawn last), else by depth
local vine_bar_update 00EF48 r13 screen_dy  # the old screen_y less the new one after project_object: added to projected_y_int (and the x difference to projected_x) so the bar stays where it was drawn
local vine_scroll_stop_hold 00EFFD,00F034 r0 all_clear  # 1 unless a player's object (flags bits A and 10 clear) is still left of x_int -120: then the scroll restarts (scroll_mode 0, scroll_speed 8)
local vine_scroll_stop_hold 00F00F,00F015 r1 player_object  # the player block from vine_scroll_stop_player_blocks, then its object (player 14)
local vine_scroll_stop_hold 00EFFA,00F000 r4 player_index  # 2, 1, 0: index into vine_scroll_stop_player_blocks (FFFFFFFF: all checked)
local spike_panel_init 00F0DD,00F115 r0 parts_left  # 2, counted down over the two middle parts
local electric_pole_rise 00F5B8 t3 odd_frame  # frame_toggle + 1 & 1: the rise advances a frame only when it is 0 (every other frame)
local electric_pole_update 00F6BF,00F712 r1 spark_y  # a spark's screen_y: the pole's screen_y - A (front) or - 10 (sides); its depth is this + B / + 11
local electric_pole_update 00F693 t3 front_odd_frame  # the front spark's toggle (high byte of its direction) + 1 & 1: its frame advances when 0
local electric_pole_update 00F6D9 t3 side_odd_frame  # the same toggle on each side spark
local metal_wall_draw 00FA75,00FAC2,00FAD9,00FAE2,00FAFD r1 dust_y  # a dust piece's y offset from the wall (6, less the rise for the last two), then its screen_y (+ the rise) for its depth
local metal_wall_draw 00F9ED r4 part_dy  # the part's dy (the signed byte after dx in its metal_wall_part_tables record): added to screen_y
local metal_wall_draw 00FA86,00FA8D r5 dust_frame  # the dust piece's frame (its direction + 1, 0 after 7): metal_wall_dust_sprites index
local metal_wall_draw 00F9BC,00FA54,00FA7B,00FB03 r10 parts_left  # the 3 wall parts, then the 4 dust pieces, counted down
local metal_wall_draw 00F9C4 r11 flip_offset  # flipped << 4: 10 picks the flipped half of the part offset tables
local flame_machine_init 00FCB3 r13 back_wall  # the back wall (handler flame_machine_back_wall_update): parent of both arms
local flame_machine_update 00FFD4 r3 random_scale  # next_random's value & 3FF: added to the explosion's sprite_scale; its low 7 bits move it down
local flame_machine_update 00FE8E t3 flash_count  # flash_frame_count + 1 (16-bit): the hit flash steps on while it is not 0
local flame_machine_update 00FE94 t2 flash_steps_left  # flame_machine_flash_step before its decrement: at 0 the flash ends (flag 1000 cleared), else flame_machine_hit_palettes[step] and sound 1E
local flame_machine_update 00FEE7 t3 odd_frame  # the capsule's toggle (high byte of its direction) + 1 & 1: its frame advances when 1
local flame_machine_update 00FFB1 t2 explosion_phase  # destroyed_frames & 7: an explosion spawns when 0 (every 8 frames)
local flame_machine_back_wall_update 0100E3 r2 offset_record  # the frame's (dx, dy) record in flame_machine_back_wall_offsets (move_object_projected's R2 before it is not read)
local flame_machine_back_wall_update 0100CD r2 rod_sprite  # flame_machine_rod_sprites[frame]: the sprite of both rods (the next two parts)
local flame_machine_back_wall_update 01007C t3 next_phase  # frame_phase + 1 & 3: the next frame when 0 (the phase then restarts at 1: every 3 frames)
local flame_machine_arm_update 01027E r1 frame_number  # flame_machine_arm_frames[frame_index]: C starts the sway, D ends the animation, else the arm's and its part's sprite index
local flame_machine_arm_update 01026C t3 odd_frame  # frame_toggle + 1 & 1: the arm's next frame comes when it is 0
local flame_machine_arm_update 010204 t3 odd_sway_frame  # sway_toggle + 1 & 1: a sway step when it is 0
local flame_machine_arm_draw 0102EB r1 sway_offset  # flame_machine_arm_sway_offsets + sway_step x 2: the step's (dx, dy) bytes, each times side
local flame_machine_arm_draw 0102FE r4 sway_dy  # the step's dy x side: added to projected_y_int
local flame_machine_cannon_update entry,010450 r1 offset_record  # flame_machine_flame_offsets + spawn_arg_1 x 4: the flame's (dx, dy) from the cannon (spawn_object_with_pieces' piece count before it is not read)
local flame_machine_flame_burn 0104E8 r1 flame_frame  # flame_machine_flame_frames[frame_index]: kept in spawn_arg_2, the sprite index; B removes the flame
local flame_machine_flame_burn 0104D7 t3 odd_frame  # frame_toggle + 1 & 1: the next flame frame when 1
local flame_machine_arm_take_disc 01067C r0 disc_flag_bit  # arm_index + A: the flame_machine_flags bit (take a disc) it clears
local spiked_disc_hold 0106B5 r0 arm_frame_index  # its arm's frame_index (80): kept in arm_frame, spiked_disc_hold_offsets index
local spiked_disc_hold 0106BF r1 offset_record  # spiked_disc_hold_offsets + arm frame x 4: the disc's (dx, dz) from the arm
local spiked_disc_spin 010777 t3 odd_frame  # spin_toggle + 1 & 1: a spin step when 0 (which sets the toggle back to 1)
local treadmill_init 010A30 r15 gate  # the gate (handler treadmill_gate_update): parent of the gate half
local treadmill_update 010AEE,010B11 r4 player_index  # 2, 1, 0: index into treadmill_player_blocks
local treadmill_update 010AF4,010B0D r6 total_speed  # sum of the speeds (object 6E) of the players in state 22 (on the treadmill): its bits 24-27 less spawn_arg_1 pick treadmill_lift_forces
local treadmill_gate_update 010D5B r0 draw_bucket  # bucket 0 for the gate's top part (queue_sprite_in_bucket); before it only move and fall outputs
local treadmill_gate_half_update 010DBA r0 draw_bucket  # bucket 0 for the top part; before it unproject_object's and queue_sprite_at_depth's outputs
local flame_jet_init 010F16,010F4D r15 piece_scale  # 180 less 18 per flame piece: each piece's sprite_scale (the flame tapers)
local flame_jet_init 010F1B,010F46 r16 piece_frame  # 1, 2, 3, 0...: each piece's first flame frame (projected_y byte 1, flame_jet_flame_sprites index)
local flame_jet_burn 011052 r0 flame_angle  # the flame front's angle (flame_jet.angle) for direction_to_speed; later only call outputs
local flame_jet_burn 011125,011131,01113D,011143 r15 flame_step_y  # y step between pieces: (nozzle_y - projected_y_int) / A; each piece's offset_y is it x piece_number (r14 holds the x step)
local flame_jet_burn 01114A,011197 r16 piece_number  # 1, 2...: multiplies the steps for each piece behind the front
local flame_jet_burn 011134 t2 nozzle_x  # the nozzle's projected_x (- 6 for nozzle kind 0): where the pieces lead back to
local flame_jet_burn 01113D t2 nozzle_y  # the nozzle's projected_y_int + 5 (kind 0) or + 18
local push_crate_break 0112DC t3 odd_frame  # frame_count + 1 & 1: the next break frame when 0
local push_crate_move 011355 t3 ring_delay  # ring_timer - 1 while the crate moves: below 0 (and rings left) a ring drops and it restarts at 10
local push_crate_release_rings 0113D8 r1 opposite_quadrant  # push_quadrant + 2 (& 3): the rings fly the other way from the push (push_crate_push_angles)
local handle_rod_init 01148F,0114FB r0 handle_index  # handle_count, counted down: each handle's index (80), angle offset and height (x 12)
local handle_rod_init 0115A8 r1 rod_dx  # sine_of_300 x 4 x the rod length: the far post's x offset (projected_x += it >> 16, projected_y -= it >> 1)
local handle_rod_init 011499 r2 angle_step  # 400 / handle_count (divide_s16): the angle between handles
local handle_rod_init 0115AB r2 rod_dz  # cosine_of_300 x 4 x the rod length: the far post's z offset
local handle_rod_init 0114DD r3 angle_offset  # angle_step x handle_index: the handle's spawn_arg_1 = 400 - it, spawn_arg_2 += it
local handle_rod_handle_update 011837 r0 rod_angle  # the rod's angle (its 82): sine and cosine index for the handle's place (also takes call outputs, not read)
local handle_rod_handle_update 011864 r1 rod_dx  # sine x 4 x handle_distance: speed_x, and speed_y = -(it >> 1)
local handle_rod_handle_update 011867 r2 rod_dz  # cosine x 4 x handle_distance: speed_z
local handle_rod_handle_update 011851 r3 handle_distance  # the handle's index x 12: its distance along the rod
local ice_launcher_update 011D4B r2 offset_record  # ice_launcher_hitbox_offsets + frame x 2: the hitbox's (x, y) offset bytes (unproject_object's R2 before it is not read)
local padded_wall_init 011F4A,011FD9 r4 pads_left  # 4, counted down over the pads
local padded_wall_init 011F4D,011F6C r5 pad_offset_x  # + 26 per pad: added to the pad's projected_x
local padded_wall_init 011F50,011F71 r6 pad_offset_y  # + 13 per pad: added to the pad's projected_y_int
local wall_pad_update 012030 r0 pad_step  # frame_step after + 1 mod B: wall_pad_frames and wall_pad_frame_durations index
local cactus_update 01213E r1 spin_speed  # cactus.spiral_speed (+ 18 a frame): direction_to_speed's length (fall_to_floor's over_pit later is not read)
local whirlwind_aim entry,0121D8,0121EC r0 farthest_distance  # the largest distance_to_player_n (after + 1) so far: the whirlwind takes the farthest player's direction (for player 3 it stores direction_to_player_2: original code)
local whirlwind_move 0122BB r1 aim_speed  # its speed (6E: distance x 3 from whirlwind_aim): direction_to_speed's length along direction
local whirlwind_vanish 012254 t3 odd_frame  # timer + 1 & 1: a vanish frame when 0
local centipede_init 012340,01236C r15 follow_delay  # 5, A, F...: each segment's path_index, the frames before it starts following
local centipede_init 012343 r16 previous_segment  # the piece before each new segment; at the end the one before the tail (kept in the tail's 92-95)
local centipede_move 0124A3 r1 saved_direction  # direction kept across the division by 92, put back for direction_to_speed
local centipede_move 0124BF,0124C2,0124C7 r1 chase_speed  # (nearest_player_distance x 2) & 3FF + 100: x 2 along direction, x 1 for the wobble
local centipede_tail_follow 0125D4,0125D9,0125DB r0 tail_frame  # direction / 92 + 5: centipede_tail_frames index (& 7); otherwise call outputs passed on
local sand_puff_animate 012702,012709 r0 next_frame  # frame_index + 1, 0 after 2: sand_puff_frames index (call outputs before it are not read)
local crawler_init 012819,01283E,012844,012849,012874,01289E,0128A4,0128A9 r0 rom_record  # the variant's record in crawler_tail_offsets, crawler_collision_boxes (size x, size y, offset bytes) and crawler_head_offsets
local crawler_init 012754,012796 r15 step_stagger  # 4, 8, C...: each segment's first step_timer (a wave down the body)
local crawler_init 012757,012785,01278C r16 segment_walk_step  # 1, 2... 5, 0: each segment's walk_step
local crawler_init 01275A,012777 r17 segment_x  # running sum of crawler_segment_offsets' dx: each segment's projected_x offset (current_player keeps the dy sum)
local crawler_segment_update entry r1 is_over_pit  # fall_to_floor's over_pit: no floor under the piece (the tail draws at its platform's depth)
local draw_eggman_booth 012BE8 r1 booth_frame  # eggman_booth_steps[step]: eggman_booth_frames index; frame B plays sound 41 (not on stages 0 and 5)
local wall_piece_init 012CAB,012CB1,012CB6 r1 box_record  # wall_piece_boxes + spawn_arg_1 x 6: size x, size y, then the offset x and y bytes
local shaking_block_init 012D47,012D4D,012D52 r1 box_record  # shaking_block_boxes + spawn_arg_1 x 6: size x, size y, then the offset x and y bytes
local eggman_teleport_animate 01349F t3 odd_frame  # beam_phase + 1 & 1: the beam is drawn when 1
local eggman_teleport_animate 0134CF t3 flicker_step  # flicker_phase + 1 & 3: Eggman is drawn when it is not 0
local sand_arch_init 01368E r16 phase_spacing  # spawn_arg_2 / (ball_count + 1): the bob phase between balls
local sand_arch_init 013681,0136DD r17 ball_phase  # 0 less phase_spacing per ball, & 3FF: each ball's bob_phase
local sand_arch_init 013679,013709 r19 balls_left  # ball_count, counted down
local sand_arch_ball_init 01374A r0 ball_direction  # its direction: direction_to_speed's angle (move_object_projected's R0 after it is not read)
local sand_arch_ball_move 0137EE r0 below_floor  # z_int - floor_z_int: 10 or more sunk in (gone), under -8 above the sand (call outputs otherwise)
local fence_post_init 0138FC,01390C,013986 r0 rom_record  # the row direction's record in fence_post_boxes, fence_post_box_offsets and fence_post_offsets
local fence_post_init 013944 t1 posts_left  # extra_posts (in r14) before its decrement: at 0 no more posts
local fence_post_update 013A0A t3 odd_frame  # the arc's toggle (low half of its handler field) + 1 & 1: a new arc frame when 0
local mine_fall 013CC9 t3 odd_frame  # the shadow's toggle (its direction) + 1 & 1: the shadow is drawn when 0
local mine_explode 013D60 r0 sound  # sound 24, played when the explosion ends (otherwise call outputs, not read)
local mine_explode 013D4D t3 next_frame  # frame_index + 1 mod 17: 0 ends the explosion
local draw_mine_with_bubbles 013DAB t3 shadow_odd_frame  # the shadow's toggle (its direction) + 1 & 1: drawn when 0
local draw_mine_with_bubbles 013DD7 t3 bubble_1_odd_frame  # the first bubble's toggle + 1 & 1: its next frame (mine_bubble_frames) when 1
local draw_mine_with_bubbles 013E1A t3 bubble_2_odd_frame  # the second bubble's toggle: its next frame (mine_bubble_frames_2) when 1
local draw_mine_with_bubbles 013E5E t3 bubble_3_odd_frame  # the third bubble's toggle: its next frame (mine_bubble_frames) when 1
local button_box_rise 014310 r1 box_frame  # button_box_steps[step]: button_box_frames index
local button_box_press 014310 r1 box_frame  # button_box_steps[step]: button_box_frames index
local sliding_door_wait 01445E,01446B r2 done_players  # playing players (status negative) whose stage_b_path_done (player 140) is set
local sliding_door_wait 01445B,01446D r3 ready_players  # players not playing or with their path done: the door opens when all 3 are and done_players is not 0
local sliding_door_wait 014458,014476 r8 players_left  # 3, counted down
local sliding_door_wait 01444E,014461 r10 status_cursor  # walks player_status, one byte per player
local sliding_door_wait 014461 t1 status  # the player's player_status byte (negative: playing)
local turtle_wait 01472D t3 head_step  # the head's step (projected_y byte 1) + 1 & F: turtle_head_steps index; at 0 the head rests for 1E0 frames
local turtle_swim 0148A8 r1 height_above_floor  # cursor's floor_z_int - z_int: added to the head's depth, and (in r14) the shadow's offset_y
local turtle_swim 014817 t3 wrapped_stroke_step  # turtle.stroke_step after ++ and & 0xF; 0 sets the 0x3C-frame pause
local turtle_swim 014888 t3 wrapped_head_step  # the head's step (byte at projected_y + 1, index into turtle_head_steps) after ++ and & 0xF; 0 sets the 0x78 wait
local eel_init 014A17 r15 explode_delay_step  # 0x30: added to r14, the explode delay each further segment gets (eel_segment.explode_delay)
local eel_init 014A1A,014B07 r16 segments_left  # eel_init.piece_count of the anchor; one segment spawned per pass, the loop ends at 0
local eel_init 014A1F,014B04 r19 segment_amplitude  # starts at the anchor's byte 95 (eel_init.wave_step); each segment's spawn_arg_1 (place_eel_piece.wave_amplitude), height_offset = 0x4000 * it; grows by byte 95 of current_object (014AFF movz.bw 95[R20]), which spawn_object_with_pieces leaves at object_cursor, the anchor: its wave_step again
local eel_anchor_update 014C14 r1 speed_step  # the anchor's speed: each segment down the eel gets this much more speed than the one before
local eel_head_move 014C72 r0 explosion_delay  # 11 * (head speed / parent speed), stored in eel_head.explode_delay before eel_head_explode_delay
local eel_segment_move 014D7C r0 explosion_delay  # 11 * (segment speed / parent speed), stored in eel_segment.explode_delay
local place_eel_piece 014EC6 r1 wave_offset  # sine(wave_angle) * wave_amplitude: added to z and (twice) to speed_y
local white_puff_animate 015222 t3 wrapped_frame_index  # white_puff.frame_index after ++ and % 0xA; 0 sets the 0x5A-frame pause
local spike_gate_beam_rise 0155A5 r0 height_above_floor  # floor_z_int - z_int after rising 3: over 0x20 the beam turns harmful (kind 0x24), over 0x50 it holds
local init_stage_view 015C84 r0 direction_y_list section_directions  # stage_section_directions_y[stage] + the table: the first section's y direction, +2 kept in section_direction_y_list
local init_stage_view 015B4D,015B69 r2 camera_origin  # the stage's camera origin x, then y (word tables at 01975A / 019780), into camera_origin_x/_y and camera_x/_y
local init_stage_view 015C8B r3 direction_x  # the first section's x direction (section_direction_x), handed back
local init_stage_view 015B2C,015B3F r5 table_offset  # the stage number for the byte table at 017C49, then doubled for the word tables (origins, view offsets); kept as stage_offset
local init_stage_view 015BA7 r6 stage_number  # stage, the index of the stage_section_* tables
local init_stage_view 0158E2 t1 io_cnt_bits  # io_cnt read to clear bit 1
local init_stage_view 015A06 t2 unrotated_sets  # loaded_tile_sets with the new set added to its low byte, before the rotate left by 8
param update_stage_tile_layers R1 shifted_tile_set  # a block map byte rotated into its byte of wanted_tile_sets; animate_stage_palette's R1 before
param update_stage_tile_layers R3 columns_left  # 2 block columns per row, counted down
param update_stage_tile_layers R5 stage_number  # stage, the index of stage_block_maps
param update_stage_tile_layers R8 row_skip  # stage_block_map_width - 2: added to the block map cursor to reach the next row
local reload_changed_tile_set 015DD7,015E27 r2 slots_left  # 4 loaded slots compared with the wanted set; at 0 the set is loaded
local reload_changed_tile_set 015DDD r6 shift_per_slot  # 0xF8 (-8): times tile_set_slot it shifts the wanted set's byte down
local reload_changed_tile_set 015DE4,015E24 r8 loaded_shift  # shift that brings the compared slot of loaded_tile_sets down (0, -8, -16...)
local get_tile_set_script_bank 015E79 r7 stage_number  # stage, doubled for the switch on the stage
local get_tile_set_list_bank 015EB4 r7 stage_number  # stage, doubled for the switch on the stage
local update_scroll 0175AC r2 layer_scroll_x  # camera_x_pixels for nbg0_scroll_x_int (stage 3 ending)
local update_scroll 0161EA,0161FC,016238 r3 push_speed  # the pushing player's speed / 64 (+5 past the leading edge, 0 when nobody pushes), added to follow_base_speed
param update_scroll R10 end_script  # stage_03_end_script_2, then _3, run through run_vram_script; otherwise only passed through the scroll routines
local is_player_pushing_edge 016436 r5 current_position  # scroll_position: stage 3 skips the diagonal edge between 87A9 and 96F1
local is_player_at_diagonal_edge_30 016488,0164DD r4 passes_left  # 2 passes, the second 10 pixels further in; a hit on it sets player_past_leading_edge
local is_player_at_diagonal_edge_f0 0164E2,01653D r4 passes_left  # 2 passes, the second 10 pixels further in; a hit on it sets player_past_leading_edge
local is_player_at_diagonal_edge_c0 016542,01659D r4 passes_left  # 2 passes, the second 10 pixels further in; a hit on it sets player_past_leading_edge
local is_player_at_diagonal_edge_60 0165A2,0165F7 r4 passes_left  # 2 passes, the second 10 pixels further in; a hit on it sets player_past_leading_edge
local is_player_facing_scroll 016849 r5 sine_offset  # sine_entry - sine_table: the scroll direction as an offset into the sine table (0x200 a quadrant)
local is_direction_in_quadrant 016908 r5 direction_y  # section_direction_y, compared with consecutive sine entries
local is_direction_in_quadrant 01691A,01691F r6 entry_at_least  # 1 when the sine entry is >= direction_y
local is_direction_in_quadrant 016910 r7 next_entry  # the next sine entry
local is_direction_in_quadrant 016928,01692D r7 next_at_least  # 1 when the next sine entry is >= direction_y; differing from entry_at_least means found
local advance_scroll 016B0B r0 direction_y_list  # section_direction_y_list: the new section's y direction, then advanced by 2
local advance_scroll 016AA8,016AB8,016B06 r1 list  # cursor into section_speed_list, follow_base_speed_list, then section_direction_x_list
param advance_scroll R2 other_list  # cursor into section_limit_y_list, section_limit_x_list, then scroll_mode_script; set_camera_velocity's R2 after
local advance_scroll 016B13 t1 direction_y  # the new section's y direction read before section_direction_x is stored
local set_camera_velocity 016B40 r4 speed_value  # the speed's low 16 bits: times section_direction_x/_y gives camera_velocity_x/_y
local update_background_scroll 016B9E r0 section_start  # word before section_end_list: where the current section starts; the fast range must begin after it
local update_background_scroll 016B92 r10 range fast_scroll_starts  # stage_fast_scroll_ranges[stage] + 1A128 ("add.w #1A128, R10"): the stage's list, walked by [R10+] (016BAB)
local update_fade 016ECE r0 jump_offset  # fade_step * 3, the offset into the jump table of 3-byte branches; its upper bytes stay in the fade_kind switches
local update_stage_00_exit_scene 01742B r1 layer_zoom  # 0x2000000 / stage_00_exit_zoom >> 8, written to the nbg0 and nbg1 zoom registers
local update_stage_00_exit_scene 0173D9 r2 layer_scroll_x  # camera_x_pixels for nbg0_scroll_x_int; then only passed through update_background_scroll
local update_stage_00_exit_scene 0170E1 t2 scroll_y_offset  # nbg0_scroll_y_int - 0xF6: within 0..7 the beam scripts run
local update_camera_with_shake 01763D,01764D,01765B r0 shaken_y  # wave sample * 0x20 + camera_shake_base_y (base alone on odd frames), the y for update_camera_view_y
local update_camera_with_shake 017633 r1 wave_table  # 101632 + camera_shake_wave: the shake wave read at camera_shake_step
local set_nbg0_scroll_from_camera 017690 r1 layer_scroll_y  # camera_y_pixels into nbg0_scroll_y_int and tile_block_row
local set_nbg0_scroll_from_camera 0176CE t2 block_column  # tile_block_column (camera x & ~1FF) before its rotate into the block column
local set_nbg0_scroll_from_camera 0176D5 t2 block_row  # tile_block_row (camera y & ~FF) before its rotate into the block row
local move_camera_along_path 01773E,017793 r0 next_offset  # where camera y, then x, gets this frame minus camera_origin_y/_x, compared with section_limit_y/_x
local move_camera_along_path 017743,017798 r1 limit_list  # section_limit_y_list, then section_limit_x_list: the two limits before it give the direction
local move_camera_along_path 0177D8 r5 current_position  # scroll_position; + speed is next_scroll_position
local snap_camera_to_section_end 01788F r2 section_end_y  # the section's last y limit + camera_origin_y, << 16: the new camera_y
local update_stage_06_background 017AE1,017AF0 r0 shake_y  # stage_06_shake's high byte, negated every other frame: nbg0_scroll_y_int and nbg3_scroll_y
local update_stage_06_background 017B18,017B4C r0 cycle_set  # palette_cycle_set (209090): the two 0x10-byte palette cycle records filled
param update_stage_06_background R4 rolls_left  # 2 random rolls; any over 2 skips cycling palette set 0
local update_stage_06_background 017AD1 t2 previous_shake  # stage_06_shake before - 0x80 (cleared when it was under 0x80)
local update_stage_06_background 017AE6 t2 flipped_sign  # ~stage_06_shake_sign, stored back; when set the shake goes up
local load_camera_start_section 01A99A t1 start_y  # the start section's first y limit: camera_y before camera_origin_y is added
local load_camera_start_section 01A9D1 t1 limit_y  # the start section's y limit (section_limit_y)
local load_camera_start_section 01AA0B t1 direction_y  # the start section's y direction (section_direction_y)
local animate_palette_stage_10 01ADAE r4 palette_count_1  # 1: palette_count of the second cycle_palette (palette_cycle_1)
local animate_palette_stage_10 01ADA4 r9 cycle_1  # palette_cycle_1: the cycle of the second cycle_palette
local animate_palette_stage_10 01ADB1 r10 source_1  # stage_10_cycle_palettes_1: source of the second cycle_palette
local animate_palette_stage_10 01ADA7 r11 dest_1  # PALETTE_RAM + 8940: dest of the second cycle_palette
local cycle_palette_set 01AF69 r15 cycle_set  # palette_cycle_set: the record read (+0 source, +4 dest, +8 delay, +9 rows left, +A, +B, +C frame, +D frames)
local load_palette_set_and_backdrop 01B004 r2 palette_set  # the stage's entry of stage_palette_sets: the palette list handed to load_palette_list
local init_nbg0_picture_screen 01B096 t1 io_cnt_bits  # io_cnt read to clear bit 1
local set_stage_layer_mixing 01B1E2,01B1F4 r10 mixer_register  # mixer_nbg0 and the next 3 layers' mixer registers, one priority byte + 0x200 each
local set_nbg1_scroll_to_camera 01B3F3 r1 layer_scroll_y  # camera_y_pixels into nbg1_scroll_y_int (work list entry skip_nbg1_scroll)
local init_nbg2_layer 01B4B7 r1 wave_index  # row_scroll_wave_step & 0x7F: the sine entry (* 0x10) of the table entry written
local init_nbg2_layer 01B49E,01B4E1,01B4E5 r4 entries_left  # 0x100 halfwords per VRAM table, counted down
local init_nbg2_layer 01B4AC,01B4F3 r5 tables_left  # 2 tables: VIDEO_RAM + A800, then + A000
local init_nbg2_layer 01B4A5,01B4DA,01B4EC r10 table_entry  # the VRAM halfword written, from VIDEO_RAM + A800, then A000
local set_nbg2_scroll_to_camera 01B57F r1 layer_scroll_y  # camera_y_pixels into nbg2_scroll_y
local set_nbg2_scroll_to_camera_2 01B595 r1 layer_scroll_y  # camera_y_pixels into nbg2_scroll_y (work list entry skip_nbg2_scroll)
local set_nbg2_scroll_stages_03_07 01B5BD,01B5E9 r1 layer_scroll_y  # camera y (+ 0x10 from scroll position 9159 on) into nbg2_scroll_y
local set_nbg2_scroll_stages_03_07 01B5CB r3 current_position  # scroll_position, compared with 9159 and returned
local set_nbg2_scroll_stage_04 01B608 r1 layer_scroll_y  # camera_y_pixels into nbg2_scroll_y
local update_nbg3_scroll 01B8FB r1 layer_scroll_y  # camera_y_pixels into nbg3_scroll_y (stages 9 and 20)
local select_nbg3_pages 01B9E4 r1 block_row  # nbg3_block_row (times nbg3_block_map_width - 1): its parity picks the quadrant list row
local select_nbg3_pages 01BA18,01BA2A,01BA48,01BA5A r1 byte_shift  # 0, 8, 16, 24: rotates each byte read into its place
local select_nbg3_pages 01BA76,01BA91 r6 pages  # the 4 pages combined from block_pages and quadrants, into nbg3_pages
local select_nbg3_pages 01BA97 t4 next_mask  # the byte mask moved up 8 bits; the loop ends when it is 0
local build_ending_scene 01BCDD t1 io_cnt_bits  # io_cnt read to clear bit 1
local ending_tower_animate 01BF7E r10 frame_record ending_tower_frame  # "movea.b 1C4F8, R10": ending_tower_frames' first record
local ending_tower_animate 01BF96 r10 frame_record  # "add.w #6, R10": the next record
struct ending_tower_frame 6 rom       # a record of ending_tower_frames (01C4F8): ending_tower_animate shows the first whose time is not below the tower's timer; census ROM 01C4F8-01C537 (0258 9AC8 10F0, then 025D-0285 9B00-9B40 1260, FFFF FFFF FFFF)
field ending_tower_frame 00 end_time s16        # compared signed with the timer (32); FFFF ends the list
field ending_tower_frame 02 sprite_frame u16    # -> sprite_frame (04)
field ending_tower_frame 04 sprite_palette u16  # -> sprite_palette (06); FFFF in the end record: the tower is removed
local ending_big_explosion 01C286 r10 frame_record ending_explosion_frame  # ending_big_explosion_frames + frame_index * 4: the frame and flags of the main sprite
local ending_big_explosion 01C28E r11 second_frame_record ending_explosion_frame  # ending_big_explosion_frames_2 + frame_index * 4: the sprite of the object kept in speed_x
struct ending_explosion_frame 4 rom   # a record of ending_big_explosion_frames (01E282) or ending_big_explosion_frames_2 (01E300), by frame_index: "movea.w 1E282(R10), R10" / "movea.w 1E300(R11), R11" (01C286, 01C28E); FFFF ends; census ROM 01E282-01E2FD, 01E300-01E37D
field ending_explosion_frame 00 sprite_frame u16  # -> sprite_frame (04): 9670-9708 / 9670-9710; FFFF ends ("cmp.h #FFFF, [R10]" 01C296)
field ending_explosion_frame 02 sprite_flags u16  # -> sprite_flags (02): 0200, then 0201 / 0202
local run_timed_spawns_2 01C45C r10 spawn_record timed_spawn  # timed_spawns_2 + list_index * 4: the time, then the handler offset (+ 01BFC4)
local ending_dust_cloud_rise 01C61C r10 frame_pointer  # the path cursor kept in z, + 2: the record's frame word; + 2 more is the next record
local ending_music_play 01C99A r10 cue_record ending_music_cue  # ending_music_cues + list_index * 4: the time, then the sound queued
struct ending_music_cue 4 rom         # a record of ending_music_cues (01C9CA): "movea.w 1C9CA(R10), R10" (01C99A); census ROM 01C9CA-01C9D1 (0E9C 0096, FFFF FFFF)
field ending_music_cue 00 time s16    # "cmp.h [R10], 32[R20]; blt" (01C9B1, signed); FFFF ends ("cmp.h #FFFF, [R10]" 01C9A2)
field ending_music_cue 02 sound_id u16  # "mov.h 2[R10], R0" (01C9B9): queue_sound's id (96: control 80 01)
local ending_portrait_show 01C9EF r10 timed_record ending_timed_frame  # "movea.b 1D2E4, R10": ending_portrait_frames' first record
local ending_portrait_show 01CA57 r10 timed_record ending_timed_palette  # "movea.b 1D318, R10": ending_portrait_backdrop_palettes' first record
local ending_portrait_show 01CA05,01CA66 r10 timed_record  # "add.w #4, R10": the next record
local ending_portrait_show 01C9F6 r11 palette_record ending_timed_palette  # "movea.b 1D308, R11": ending_portrait_palettes' first record
local ending_portrait_show 01CA08 r11 palette_record  # "add.w #4, R11": in step with the frame list
struct ending_timed_frame 4 rom       # a (time, sprite) record of ending_portrait_frames (01D2E4), ending_name_plate_frames (01D2F4) or ending_eggman_turn_frames (01D957): shown while the object's timer has not passed its time; FFFF FFFF ends; census ROM 01D2E4-01D307, 01D957-01D96E
field ending_timed_frame 00 end_time s16      # "cmp.h [R10], 32[R20]" against the timer (signed); FFFF ends ("cmp.h #FFFF, [R10]" 01CA0B, 01CB03, 01D91C)
field ending_timed_frame 02 sprite_frame u16  # -> sprite_frame (04)
struct ending_timed_palette 4 rom     # a (time, palette) record of ending_portrait_palettes (01D308) or ending_portrait_backdrop_palettes (01D318): walked in step with the portrait's frames or on its own; census ROM 01D308-01D327
field ending_timed_palette 00 end_time s16      # against the timer (signed, 01CA5E); FFFF ends ("cmp.h #FFFF, [R10]" 01CA69)
field ending_timed_palette 02 sprite_palette u16  # -> sprite_palette (06): 1040 1050 1060, 1080 1070 1090
local ending_name_plate_show 01CAF1 r10 frame_record ending_timed_frame  # "movea.b 1D2F4, R10": ending_name_plate_frames' first record
local ending_name_plate_show 01CB00 r10 frame_record  # "add.w #4, R10": the next record
local ending_credits_team_logo_scroll 01CBE1,01CBE6,01CBEB r10 field_cursor  # the credit line record (from the cursor kept in speed_x) + 2: its y, palette, then link
local ending_credits_director_scroll 01CC94,01CC99,01CC9E r10 field_cursor  # the credit line record (from the cursor kept in speed_x) + 2: its y, palette, then link
local ending_credits_program_scroll 01CD60,01CD65,01CD6A r10 field_cursor  # the credit line record (from the cursor kept in speed_x) + 2: its y, palette, then link
local ending_credits_design_scroll 01CE5E,01CE63,01CE68 r10 field_cursor  # the credit line record (from the cursor kept in speed_x) + 2: its y, palette, then link
local ending_credits_sound_scroll 01CF75,01CF7A,01CF7F r10 field_cursor  # the credit line record (from the cursor kept in speed_x) + 2: its y, palette, then link
local ending_credits_voice_actor_scroll 01D073,01D078,01D07D r10 field_cursor  # the credit line record (from the cursor kept in speed_x) + 2: its y, palette, then link
local ending_credits_special_thanks_scroll 01D18A,01D18F,01D194 r10 field_cursor  # the credit line record (from the cursor kept in speed_x) + 2: its y, palette, then link
local run_timed_spawns_3 01D32C r10 spawn_record timed_spawn  # timed_spawns_3 + list_index * 4: the time, then the handler offset (+ 01C82F)
local ending_eggman_fly_in 01D71F r10 y_entry  # the fly-in path cursor kept in speed_y, + 2: the y word; + 2 more is the next point
local ending_eggman_turn 01D914 r10 frame_record ending_timed_frame  # ending_eggman_turn_frames + step * 4: the time, then the frame
local ending_eggman_fly_away 01DC95,01DCAA r0 half_width_squared  # circle_wipe_radius squared minus line_dy squared; its square root is the half width of the line
local ending_eggman_fly_away 01DC9F r1 line_dy  # circle_wipe_line - 0x70: the line's distance from the circle's centre line
local ending_eggman_fly_away 01DC8E,01DD70 r5 lines_left  # 0x70 lines per frame, counted down
local ending_eggman_fly_away 01DC62,01DC78,01DD60 r10 left_entry  # VRAM halfword of the line's left edge (from VIDEO_RAM + A000 or A0E0); its offset / 2 is the line
local ending_eggman_fly_away 01DDCB,01DDD8 r10 scale_step  # 3, or 2 under scale 0x100: taken off both sprites' sprite_scale each frame
local ending_eggman_fly_away 01DC69,01DC7F,01DD66 r11 right_entry  # VRAM halfword of the line's right edge (from VIDEO_RAM + A400 or A4E0)
local animate_ending_runner_across 01DFD3 r11 second_frame_record  # the list kept in y + cycle_count * 2: the frame of the object kept in speed_y
param spawn_ring_rows R1 stage_list_offset  # stage_template_spawns[stage]: the stage's list in that table; then only passed through find_floor and project_object
local spawn_ring_rows 01E39C,01E3AF,01E3CE,01E3D4,01E3DA,01E3E0 r10 row_cursor  # ring_spawn_cursor: the row's position, then template, count, x, y, dx and dy
local spawn_ring_rows 01E37E t2 old_init_flags  # stage_init_flags before bit 2 is set: the list cursor starts when it was clear
local spawn_ring_rows 01E4DF t2 rings_left  # ring_row_rings_left before the decrement: another ring while it was over 1
local carried_ring_spin 01E690 r0 platform_z  # the z of the platform the ring rides: its floor_z and z; afterwards only passed through
local move_scattered_ring 01E928 r0 rebound_speed  # -speed_z when the ring lands; speed_z becomes it minus a quarter
local draw_child_shadow 01EFEC t2 shadow_blink  # the shadow's hitbox_size_x low byte inverted each frame while its parent rides (flags 0x10); drawn when 0
local check_left_screen 01F0F2,01F100 r1 edge_margin  # 0x20 once the object has been on screen (flags 0x800), else 0: added around the screen
local spawn_objects_by_handler 080000 t2 old_init_flags  # stage_init_flags before bit 0 is set: the list cursor starts when it was clear
local spawn_objects_by_handler 08001E,08004A,08004D,080057,080061,080066 r10 record  # handler_spawn_cursor: position, then from + 2 x, y, handler_arg, handler (a handler_spawn_record)
local stage_03_floor_block_start 080072 r0 block_kind  # the kind the spawn left in sprite_frame: kept in speed_x_int, index of stage_03_floor_block_kinds
local stage_03_floor_block_start 08007A r10 kind_entry  # stage_03_floor_block_kinds + kind * 4: the record's offset, then the fall flag index (speed_x_fraction)
local stage_03_floor_block_start 080083 r11 kind_record stage_03_floor_block_shape  # the kind's record (080083 "add.w #82284, R11"): sprite offsets x and y, depth offset (speed_y_fraction), then the animation
local stage_03_floor_block_start 08008A,08008F r11 kind_record  # past the sprite offsets ([R11+])
local spiked_roller_start 080316 t2 parts_left  # roller_parts_left before the decrement: another part while it was over 1
local spiked_roller_chase 08046F,080499,08049F,0804A5,080702,080710,080723,08072C,080735,08073E r10 record_cursor  # cursor into the chase path record (threshold, catch-up, base speed, wait), then into the event record
local spiked_roller_chase 0806FB r10 record_cursor spiked_roller_event  # spiked_roller_events + event_offset (0806FB "add.w #82306, R10"): the next event record
local spiked_roller_chase 08046F t1 record_position  # the path record's scroll position: it is read once scroll_position reaches it
local spiked_roller_chase 080702 t1 event_position  # the event record's scroll position: no event before it
local spiked_roller_chase 080723 t1 max_y  # event 0/8: fires once y_int <= it
local spiked_roller_chase 08072C t1 min_x  # event 2: fires once x_int >= it
local spiked_roller_chase 080735 t1 min_y  # event 4: fires once y_int >= it
local spiked_roller_chase 08073E t1 max_x  # event 6: fires once x_int <= it
local spiked_roller_drop_in 0808F0 r0 sound_number  # 0x20 for play_sound on landing; then only passed through project_object
local spiked_roller_ram 08091D,080922,08092A,080932 r0 speed_level  # chaser_speed / 16 clamped to 0..4: index of spiked_roller_rumble_delays
local spiked_roller_sink_and_jump 080BD3,080C81 r0 sound_number  # 0xD for play_sound (sinking starts, landing); then only passed through project_object
local spiked_roller_burst_out 080CCF,080D4B r8 fragments_left  # roller_wall_fragment_count, one fragment allocated per pass
local spiked_roller_burst_out 080CCF r12 fragment_cursor rock_debris_piece  # past roller_wall_fragment_count (080CCF "movz.hw [R12+], R8"): the first roller_wall_fragments record
local spiked_roller_burst_out 080CDC,080CE5,080CEE,080CF3,080CF8,080CFD r12 fragment_cursor  # moving through the record ([R12+]: flags, x and y offsets, speeds, animation); 080CFD reaches the next
local spiked_roller_smash_wall 080D6A r1 x_margin  # 0x68: is_off_screen_margin's margin_x; then only passed through update_screen_position
local catapult_wait_for_platform 080F54 r1 launch_delay  # catapult_launch_delays[sprite_frame]: added to launch_position; 0 launches at once
local catapult_update 08108A r0 platform_z  # the z of the platform the catapult rides: its floor_z and z; then only passed through
local catapult_base_update 0811E4 r0 parent_z  # z of the parent (the catapult), copied to floor_z and z; then only update_screen_position's unused outputs
local arrow_board_ride 082403 r0 platform_z  # z of the platform it rides (74), copied to floor_z and z; then only update_screen_position/queue_sprite_at_depth leftovers
local propped_board_start 082583,082590 r0 prop_dy  # FF90 (-70) for the first prop, 70 for the second: added to its y_int; before that only project_object/allocate_object leftovers (the free count is not tested)
local propped_board_start 0825A0 t2 props_left_before  # board_props_left before the dec.h: the loop goes on while it was > 1
local propped_board_update 082632,082640 r0 shake_dx  # shake_strength (34) >> 4, negated on odd shake steps (36): added to screen_x and stored in 80 (shake_dx) for the props; also update_screen_position/queue_sprite_at_depth leftovers
local release_board_rings 082702 t2 rings_left_before  # board_rings_left before the dec.h: eight scatter_rings_fixed calls while it was > 1
local door_wall_wait_to_burst 082929,08292E,082935,08293C,082946,08294B r3 random_bits  # next_random's value worked into the debris speed: low byte + 4 = speed_x_fraction, bit 16 the sign and bits 17-24 the size of speed_y_fraction
local spawn_door_rollers 0829E8 r10 layout_record door_roller_spawn  # door_roller_layout + 6 x door_rollers_left (0829E8 "movea.h 833EF(R0), R10"): x_int, y_int, then the starting frame byte
local spawn_door_rollers 0829F0 r10 layout_record  # past x ([R10+])
local spawn_door_rollers 082A8C t2 rollers_left_before  # door_rollers_left before the dec.h: four rollers, while it was > 1
local fireball_emitter_start 082B89 r11 list_cursor fireball_emitter_list  # fireball_emitter_lists + fireball_emitter_lists[04] (082B89 "add.w #83185, R11"): x_int, y_int, floor_z_int, then the spawn records start (stored in 48); then find_floor's unused R11
local fireball_emitter_start 082B90,082B9A r11 list_cursor  # moving through the list's head ([R11+])
local fireball_emitter_update 082BF7 r0 slope_direction  # camera_slope_direction: the sine/cosine index for the x/y slide; also project_object/find_floor/allocate_object leftovers
local fireball_emitter_update 082BDE r1 floor_slope  # its floor_slope, index into floor_slope_factors; also project_object/find_floor leftovers
local fireball_emitter_update 082C09 r2 slope_speed  # camera_slope_speed >> E: the slide distance multiplied by sine/cosine; then find_floor's unused R2
local fireball_emitter_update 082C81,082C84,082C8E,082C93 r10 list_record  # the current spawn record (field 48) past its scroll word: x_int, y offset, speed_y, speed_z, then the fireball_types offset
local fireball_emitter_update 082CAF r12 type_record fireball_type  # fireball_types + the shot's type offset (082CAF "add.w #832B9, R12"): sprite flags, palette, scale, height, width, offsets x/y, hitbox bytes, animation
local fireball_emitter_update 082CB6,082CBB,082CC0,082CC5,082CCA,082CCF,082CD4,082CD9 r12 type_record  # moving through the type record ([R12+])
local bouncing_fireball_update 082DD6,082DE3 r8 shadow_dx  # 8 (0 when hitbox_size_x is 0): added to the shadow's screen x
local bouncing_fireball_update 082DD9,082DE6 r9 shadow_dy  # FFFD (-3): added to the shadow's screen y
local bouncing_fireball_update 01F052 t2 blink_phase  # the shadow's hitbox_size_x low byte, inverted every frame on a platform: drawn when it comes back to 0
local chaser_back_zone_update 082F20 r0 direction  # the parent's direction: >> 8 picks the side the 200 box extends to; then add_to_zone_list's ignored argument and unused result
local roller_back_zone_update 08302A r0 direction  # the parent's direction: >> 8 picks the limit test and the side of the 200 x 1A2 box; then add_to_zone_list's ignored argument and unused result
local machine_fire_update entry,08390F r3 random_bits  # next_random's value, & 1F: the lane's new fireball timer; also find_floor's unused slope height
local machine_fire_update 083735,083745,08374B,083751 r10 path_cursor  # machine_fire_script record past its height word: screen threshold, catch-up factor, base speed, wait; the next record is stored back in path
local machine_fire_update 083670 r12 spawn_record machine_fireball_spawns  # spawns_with_next_handler (083670 "movea.b 8530D[PC], R12"): the count word
local machine_fire_update 08367D r12 spawn_record machine_fireball_spawn  # past the count (08367D [R12+]): the first 14-byte record (x, y, 96, 9A, z, next handler); 0836C1's [R12+] reaches the next
local machine_fire_update 083697,0836A1,0836AB,0836B1,0836B7,0836C1 r12 spawn_record  # moving through the record ([R12+])
local machine_fire_update 0836C7 t2 spawns_left_before  # temp_0 (the record count) before the dec.h: loops while it was > 1
local machine_fire_update 083735 t2 record_height  # the script record's height: the fire climbs on until its top (z_int - hitbox_height) reaches it, then loads the record's settings
local machine_fire_update 083901 t1 lane_timer_address  # fireball_lane_timer_cursor: the lane's timer byte (current_object + 72 + lane)
local machine_fire_update 083901 t2 lane_timer_before  # that timer before the dec.b: a fireball when it was <= 1
local machine_fire_update 083976 t2 lane_before  # fireball_lane (3, 2, 1) before the dec.b: next lane while it was > 1
local init_fireball_flight 083A5C r1 flight_frames  # flight_timer (36, 24 in stage C): the distance to target_x/target_y is divided by it; then play_sound's unused R1
local how_to_play_trackball_hand_update 083F5B r0 last_step  # low byte of the animation's first word (its last step number)
local how_to_play_trackball_hand_update 083F62,083F69 r1 next_step  # animation_frame + 1, 0 past last_step: the new animation_frame and the step it shows
local opening_machine_approach 0841E0 t2 shake_before  # stage_06_shake before it goes down by 80 (0 once it was below 80)
local draw_opening_machine_shadow 08439C r2 scaled_dy  # sprite_height x the machine's sprite_scale >> 8: added to the shadow's screen y (screen x gets the same with sprite_width)
local player_pacer_update 084410,08441A r3 playing_count  # how many of the three player_status bytes are FF (playing): 0-1 nothing to pace, 2 the pair, 3 all three
local player_pacer_update 08440D,08441C r8 players_left  # 3, counted down over player_status
local player_pacer_update 084408,084413 r10 status_cursor  # walks player_status
local player_pacer_update 0844A4,0844BA r15 leader_object  # the object of the block in front so far (player): its axis coordinate is compared
local player_pacer_update 0844AB,0844BE r16 other_object  # the object of the block compared with it (player 2's, then player_4's)
local player_pacer_update 084413 t1 status  # one player_status byte
local player_pacer_update 084475 t1 swap  # swaps player and player_2 when player_2 is further along the axis
local player_pacer_update 0844B7 t1 swap_2  # swaps player and middle_player
local player_pacer_update 0844CA t1 swap_3  # swaps player and player_4
local player_pacer_update 0844DD t1 swap_4  # swaps middle_player and player_4
local bubble_pop 08473F,084746 r3 jitter  # next_random's value & F, negated when the value is negative: added to base_x and base_y for the bubble's new spot
local spiked_press_update 084955,084969,084973,08497F,0849D8 r0 sound  # play_sound's sound: 34 at frame 14, at frame 50 3F / 5C / 40 by caged_character, D every 3C frames while it moves; also project_object/queue_sprite_at_depth leftovers
local spiked_press_update 0848F2,084901 r7 player_index  # player_status bytes before the first FF: the first player playing (0-2), block 208400 + it << 9
local spiked_press_update 0848EF,084903 r8 players_left  # 3, counted down over player_status
local spiked_press_update 0848F5,0848FA r10 status_cursor  # walks player_status
local spiked_press_update 0848FA t1 status  # one player_status byte
local cage_start 084ABF t2 bars_left_before  # cage_bars_left before the dec.h: ten bars while it was > 1
local cage_bar_update 084B11 r10 step_list sprite_frame_numbers  # animation + 2: the steps' frame words
local cage_bar_update 084B11 t2 last_step  # the animation's first word: the hit frame is shown while animation_frame is below it
local spreading_flame_burn 084F58,084F64 r3 target_dy  # next_random's value & 3F, at least flame_chaser_gap: taken off the fireball's target y (9A)
local eggman_speech_run 085781 r12 script_cursor  # the speech record past its delay word
local eggman_speech_run 085789 r12 script_cursor eggman_speech_arguments  # past the command word: the command's arguments (sound, or x, y, kind, frames); the next record is stored back
local eggman_speech_run 0857B4,0857B7,0857BA r12 script_cursor  # the [R12+] steps through the message box's arguments
local eggman_speech_run 0857B4 r5 screen_x  # command 2's first argument: spawn_message_box's screen_x (A0)
local eggman_speech_run 0857B7 r6 screen_y  # command 2's second argument: spawn_message_box's screen_y (40)
local eggman_speech_run 0857BA r7 kind  # command 2's third argument: spawn_message_box's message kind (11, 14, 9)
local eggman_speech_run 0857BD r8 frames  # command 2's fourth argument: spawn_message_box's frames (88)
local eggman_speech_run 0857D1 r0 sound_id  # command 3's argument: play_sound's sound (74, 3B, 7A)
local eggman_speech_run 085781 t1 delay  # the record's first word: its command runs once frame_count reaches it
local scenery_sprite_start 0857E5 r0 sprite_kind  # sprite_frame as the scenery kind: into scenery_sprite_kind and the index of scenery_sprite_kinds; then find_floor/project_object leftovers
local scenery_sprite_start 0857F4 r10 kind_record scenery_kind_record  # scenery_sprite_kinds + scenery_sprite_kinds[sprite_frame]: the kind's record
local scenery_sprite_start 0857FB,085800,085805 r10 kind_record  # the [R10+] steps: sprite flags, frame, palette, depth offset, handler
struct scenery_kind_record C rom             # a record of scenery_sprite_kinds (085D1F, data line record=12): scenery_sprite_start 0857EE-0857F4 "movz.hw 85D1F[PC](R0), R10; add.w #85D1F, R10", then [R10+] (0857FB-085810); census ROM 085D3B-085DE2 (14 kinds)
field scenery_kind_record 00 sprite_flags u16  # "mov.h [R10+], 2[R20]": 1004 ...
field scenery_kind_record 02 sprite_frame u16  # "mov.h [R10+], 4[R20]": 6468, 65B0, 65B8 ...
field scenery_kind_record 04 sprite_palette u16  # "mov.h [R10+], 6[R20]": C560, 86E0 ...
field scenery_kind_record 06 depth_offset s16  # "mov.h [R10+], 8A[R20]" (scenery_sprite depth_offset, added to screen_y for the depth): FFF0, FF60, FFD8 ...
field scenery_kind_record 08 handler u32      # "mov.w [R10+], 1C[R20]": 08585F, 085880 ...
local fire_vent_spawner_update 085936,08593B,085959,085963 r12 vent_record  # fire_vent_cursor's record past its scroll word: x_int, y_int, z_int, blast delay (an end mark at +6 removes the spawner)
local fire_vent_spawner_update 08593B t1 spawn_position  # the record's first word: the vent is spawned once scroll_position reaches it
local fire_vent_update 085A4D t3 next_delay  # blast_delay - 1, stored back: the vent blasts when it was 1; a negative result is set back to -1 (stopped)
local group_leader_hold_start 085B7F r10 hold_record group_leader_hold_record  # group_leader_hold_records + group_leader_hold_records[04]: the record
local group_leader_hold_start 085B86,085B8C,085B91 r10 hold_record  # the [R10+] steps: direction byte, coordinate, end position, which flag
struct group_leader_hold_record 8 rom        # a record of group_leader_hold_records (085F4D, after its 5 offsets): group_leader_hold_start 085B79-085B7F "movz.hw 85F4D[PC](R0), R10; add.w #85F4D, R10", then [R10+] (085B86-085B9D); census ROM 085F57-085F7E
field group_leader_hold_record 00 leader u8     # "mov.b [R10+], 80[R20]" (group_leader_hold leader): 1, 0, 2, 2, 2
field group_leader_hold_record 01 direction u8  # "movz.bh [R10+], 24[R20]": 1, 2, 1, 2, 2
field group_leader_hold_record 02 coordinate s16  # "mov.h [R10+], 82[R20]" (compared signed by group_leader_hold_wait): 1020, 1840, 1190, 1430, 1430
field group_leader_hold_record 04 end_position u16  # "mov.h [R10+], 84[R20]": 9200, 9500, 9B00, 3900, 3900
field group_leader_hold_record 06 which_flag u16  # "mov.h [R10+], 86[R20]": 0, 1, 0, 0, 1
local group_leader_hold_start 085BA3,085BAE r11 held_flag  # group_leader_0_held, or group_leaders_1_3_held when which_flag is set: set to FF
local group_leader_hold_wait 085C18,085C23 r11 held_flag  # group_leader_0_held, or group_leaders_1_3_held when which_flag is set: cleared at the end
local collapsing_bridge_plank_update 086019,086024 r10 sag_table  # bridge_0_sag, or bridge_1_sag for the second bridge: indexed by the plank's number
local burning_boulder_start 086276 r10 boulder_record burning_boulder_record  # burning_boulder_records + 8 x sprite_frame ("movea.w 87B61[PC](R0), R10", R0 = sprite_frame x 2): x_int, start delay, path read with [R10+]
local burning_boulder_start 08627C r10 boulder_record  # burning_boulder_records + 8 x kind: x_int, start delay, speed script
local burning_boulder_start 0863DA t2 flames_left_before  # boulder_flames_left before the dec.b: four flames while it was > 1
local burning_boulder_update 0865B9,0865C4,0865CF r1 roll_speed  # base_speed (10 while slow_timer runs) or the catch-up speed from camera_slope_speed: into speed_fraction
local burning_boulder_update 0864E9,0864F1,0864F6,0864FE,086504,08650A r10 path_cursor  # burning_boulder_speed_script record: scroll position, y, then threshold, factor, base speed, wait; the next record goes back to path
local burning_boulder_update 0864E9 t1 record_scroll  # the record's scroll position: loaded once scroll_position reaches it
local burning_boulder_update 0864F6 t1 record_y  # the record's y: loaded once the boulder's y_int reaches it
local burning_boulder_update 086570 t3 next_slow_timer  # slow_timer - 1, stored back (0 when it goes negative)
local burning_boulder_update 08660F t2 rumble_before  # rumble_timer before the decrement: when it was 0 or less it reloads (animation speed x A) and sound D plays
local burning_boulder_update 08660F t3 rumble_after  # rumble_timer after the decrement (stored back; its zero test skips the sound)
local burning_boulder_flame_start 086840 r10 layout_record burning_boulder_flame_offset  # burning_boulder_flame_layout + 6 x kind ("mul.w #6, R10; add.w #87AA3, R10"): offset x, offset y, animation offset read with [R10+]
local burning_boulder_flame_start 086847 r10 layout_record  # burning_boulder_flame_layout + 6 x kind: offset x, offset y, animation offset
local ember_emitter_move 08697C r1 floor_slope  # its floor_slope, index into floor_slope_factors; also find_floor/project_object leftovers
local ember_emitter_move 08699A r2 move_step  # camera_slope_speed >> 7: added to or taken from x or y by travel direction; then find_floor's unused R2
local ember_emitter_move entry,086A3D,086A46,086A4A r3 random_bits  # next_random's value: bits 0-6 / 16-22 the ember's x / y offset, & 3F the next spawn_timer; also compute_camera_slope_speed/find_floor leftovers
local falling_ember_update 086B10,086B21 r10 burst_animation  # falling_ember_burst_animation, or the slope one on a sloped floor: its new animation
local scene_carrier_start 086B78 r12 carrier_record scene_carrier_record  # scene_carrier_records + scene_carrier_records[sprite_frame] ("add.w #87BD9, R12"): x, y, z, speeds, end position read with [R12+]
local scene_carrier_start 086B7F,086B89,086B93,086B9D,086BA2 r12 carrier_record  # scene_carrier_records[04]: x, y, z, speed x, y, z, end position
local seesaw_tilt 086CA5 r0 next_step  # animation_frame + 1: the new frame and the part list frame word; stops (speed FF) at last_step; also update_screen_position/queue_sprite_at_depth leftovers
local seesaw_tilt 086CA7 r10 step_list frame_animation  # seesaw.animation ("mov.w 48[R20], R10": seesaw_animation 087BC5, last frame 8): "cmp.h [R10+], R0" (086CAB) compares the next step with its last_frame, as animate_step 00DEB3
local seesaw_tilt 086CAB r10 step_list sprite_frame_numbers  # animation + 2 (the [R10+] of "cmp.h [R10+], R0"): the frame words, "mov.h [R10](R0), A[R11]" (086CBE) by next_step into the part list's second part
local seesaw_tilt 086CAB t2 last_step  # the animation's first word: animation_speed FF once next_step reaches it
local spawn_fire_trench_flames 087171 t2 flames_left_before  # trench_flames_left before the dec.h: three flames while it was > 1
local bridge_fire_start 0872FC t2 flames_left_before  # bridge_flames_left before the dec.h: three rising flames while it was > 1
local bridge_fire_rise 087427,087432 r1 climb_speed  # base_speed or the catch-up speed from camera_ground_speed: into speed_fraction; also update_screen_position/play_sound leftovers
local bridge_fire_rise 087373,08738A,0873A2,0873A8,0873AE r10 path_cursor  # bridge_fire_script record past its height: scroll position, then threshold, factor, base speed, wait; the next record goes back to path
local bridge_fire_rise 087373 t2 record_height  # the record's first word: used once the fire's top (z_int - hitbox_height) reaches it
local lava_flow_advance 0876F1,0876FC r1 flow_speed  # base_speed or the catch-up speed from camera_ground_speed: into chaser_speed; also project_object/play_sound leftovers
local lava_flow_advance 087641,087654,08766C,087672,087678 r10 path_cursor  # lava flow script record: scroll position, x, then threshold, factor, base speed, wait; the next record goes back to path
local lava_flow_advance 087641 t1 record_scroll  # the record's scroll position: loaded once scroll_position reaches it (or x reaches the next word)
local spawn_lava_steam 0879B2,087A09 r8 puffs_left  # lava_steam_puff_count, counted down
local spawn_lava_steam 0879B2 r13 offset_cursor lava_steam_puff_offset  # lava_steam_puff_offsets (87D4F + 2: "movz.hw [R13+], R8" takes the count lava_steam_puff_count): the first puff's offsets; [R13+] walks on (4 bytes a puff)
local spawn_lava_steam 0879FB,087A00 r13 offset_cursor  # walks lava_steam_puff_offsets: anchor x, anchor y per puff
local spawn_ice_platform_leader 087EFA r0 reference_x  # the record's reference x: travel = x - it
local ice_floor_start 08809F,088141 r8 cubes_left  # 7 cubes a row, counted down (the first of each row, 7, gets palette bank 2000)
local ice_floor_start 08809C,088153 r9 rows_left  # 5 rows, counted down
local icicle_dropper_start 0882AB r10 dropper_record icicle_dropper_record  # "movea.b 88F35[PC](R10), R10": icicle_dropper_records' record for sprite_frame: x, y, z, direction, 96/97 flags, script offset
local icicle_dropper_start 0882B1,0882BB,0882C5,0882D5 r10 dropper_record  # the [R10+] steps through it
local icicle_dropper_move 0885F7,088602 r0 row_spacing  # 400 (300 on the ice platform): travel between icicle rows
local icicle_dropper_move 088371,088386 r1 hold_threshold  # FFE0, or 160 for directions below 100 or above 200: screen_threshold while holding back; also find_floor/project_object leftovers
local icicle_dropper_move 08852C,088537 r1 move_speed  # base_speed or the catch-up speed from camera_slope_speed: into chaser_speed
local icicle_dropper_move 0883A0 r10 path  # "mov.w 80[R20], R10": the dropper's path, the icicle_dropper_step to load next (the running step's end coordinate is the word before it)
local icicle_dropper_move 0883F4,0883F9,08841A,088420,088426 r10 path_cursor  # icicle_dropper_scripts record past its scroll word: threshold, factor, base speed, wait; the next record (+ 4) goes back to path
local icicle_dropper_move 0883F9 t1 record_scroll  # the record's scroll position: loaded once scroll_position reaches it
local drop_icicle_row 088722 r1 end_coordinate  # the script's end coordinate (path - 2): the row lines up on it when the dropper is within 40
local drop_icicle_row 088729 r2 line_coordinate  # a copy of it: the icicle's x or y is set to it +- 20
local drop_icicle_row 088640,08882D r8 icicles_left  # 3 icicles a row, counted down
local icicle_update 088AAD r3 platform_shift  # moving_floor_object's speed_x x (timer + 19): how far the platform moves before the icicle lands; added to x (less the bounce past the swing limit)
local icicle_update 088AB9,088AC3 r4 swing_limit  # 87, then -87: the platform's travel limits
local icicle_buried_part_draw 088D1C r1 part_depth  # buried_part_depth: the sort depth it queues itself at; then project_point's unused R1
local tornado_start 0891B5,0891FB r5 angle_offset  # 0, CC, 198...: each twister's speed_z_int, its angle offset around the tornado (tornado_column_circle)
local tornado_start 0891BD,0891F2 r7 radius  # 4000, 6000...: each twister's speed_z_fraction, its starting radius
local tornado_start 0891AD,089249 r8 columns_left  # 5 twisters, counted down
local tornado_start 0890FE r10 tornado_record tornado_start_record  # tornado_records + tornado_records[04]: the record
local tornado_start 089104,08910E,089118 r10 tornado_record  # the [R10+] steps: y, hazard list offset, whirlwind list offset, then the script (80)
struct tornado_start_record 8 rom            # the record tornado_records[kind] leads to (08A6C0 + the offset): tornado_start 0890F8-0890FE "movz.hw 8A6C0[PC](R0), R10; movea.b 8A6C0[PC](R10), R10", four [R10+] (089104-08912D); its path (tornado_path_step records) follows (08913B "mov.w R10, 80[R20]"); census ROM 08A6C4-08A6CB, 08A738-08A73F
field tornado_start_record 00 x u16           # "mov.h [R10+], 4E[R20]" (x_int): 114B ...
field tornado_start_record 02 y u16           # "mov.h [R10+], 52[R20]" (y_int): 1509 ...
field tornado_start_record 04 hazard_list_offset u16  # "movz.hw [R10+], R11; add.w #8A79E, R11": into animation (48), its list in tornado_hazard_lists
field tornado_start_record 06 whirlwind_list_offset u16  # "movz.hw [R10+], R11; add.w #8A7E4, R11": into part_list (04), its list in tornado_whirlwind_lists
struct tornado_path_step E rom               # a record of the tornado's path (after its tornado_start_record, 7 words): tornado_move reads the one tornado.path (80) points at (0892E1 "mov.w 80[R20], R10", 0892E6 "cmp.h [R10+], 6435[R25]", 0892F3 "mov.h [R10+], R1"), tornado_load_record the rest (089330-089379) and moves path past it (08937E); census ROM 08A6CC-08A737, 08A740-08A79D
field tornado_path_step 00 position u16       # "cmp.h [R10+], 6435[R25]; bl" (unsigned): the record loads once scroll_position reaches it
field tornado_path_step 02 coordinate s16     # "mov.h [R10+], R1", compared signed with y_int / x_int by direction (08930E-08932D): it loads once the tornado passes it
field tornado_path_step 04 screen_threshold s16  # "mov.h [R10+], 84[R20]": 1E0, FF60, 20
field tornado_path_step 06 catch_up_factor s16   # "mov.h [R10+], 86[R20]"; negative ends the path ("test.h 2[R10]; bp" 089330, R10 at +4)
field tornado_path_step 08 base_speed s16     # "mov.h [R10+], 88[R20]"; negative with the path's end: goal_unlocked ("test.h 4[R10]; bp" 089335)
field tornado_path_step 0A wait u16           # "mov.h [R10+], 90[R20]" (wait_timer): A0, 10, 0
field tornado_path_step 0C direction u16      # "mov.h [R10+], 24[R20]": 300, 200, 0
local tornado_start 0891B0,089216 r12 column_slot  # current_object + 96: where each twister's address goes (96-9F)
local tornado_move 089464 r1 hazard_coordinate  # the hazard list's coordinate: a hazard box is placed once y/x passes it (by direction)
local tornado_move 089383 r5 box_across  # F0: the hitbox side across the travel direction (size_x for directions 0/2/4, size_y for 1/3)
local tornado_move 089388 r6 box_along  # 80: the hitbox side along the travel direction
local tornado_move 08934C,089357 r8 columns_left  # 5 twisters flagged 400 (removed), counted down
local tornado_move 08945B t1 hazard_scroll  # the hazard list's scroll position: its coordinate is checked once scroll_position reaches it
local tornado_move 0892E6 t1 record_scroll  # the script record's scroll position: loaded once scroll_position reaches it
local tornado_column_circle 0896BD,0896D4 r2 radius_limit  # 1000 or 6800: the radius (speed_z_fraction) it bounced off; then find_floor's unused R2
local tornado_column_circle entry,0896BA,0896CF r3 radius_rate  # next_random's value + 100 & 3FF (negated at the top): the new radius change (hitbox_size_y); then find_floor's unused slope height
local spawn_tornado_hazard 0897AB r0 direction  # the hazard's direction: >> 8 picks F0 x 80 or 80 x F0; then project_object's unused R0
local tornado_rear_zone_follow 0898B7 r0 direction  # the chaser's direction: >> 8 picks the side the 200 box extends to; then add_to_zone_list's ignored argument and unused result
local quicksand_swirl 089A5F r3 step_frame  # the step's frame word: written into every part of the child's part list
local quicksand_swirl 089A6E,089A77 r8 parts_left  # the part list's count word, counted down
local quicksand_swirl 089A53 r10 step_list sprite_frame_numbers  # animation + 2: the steps' frame words
local quicksand_swirl 089A53 t2 last_step  # the animation's first word: the step wraps to 0 past it
local sand_block_leader_move 089FA5,089FB1 r1 hold_threshold  # FFE0, or 160 for odd directions: screen_threshold while holding back; also find_floor/project_object leftovers
local sand_block_leader_move 08A0A0,08A0AB r1 move_speed  # base_speed or the catch-up speed from camera_ground_speed: into chaser_speed
local sand_block_leader_move 089FCE,089FE1,089FE7,089FED r10 path_cursor  # the script record past its scroll word: threshold, factor, base speed, wait; the next record goes back to path
local sand_block_leader_move 089FCE t1 record_scroll  # the record's scroll position: loaded once scroll_position reaches it
local sand_block_update 08A2CE,08A2D9 r2 stream_parts_left  # the stream part list's count word, counted down; also allocate_object_2's free stack and play_sound's leftover
local sand_block_update 08A2C5 r3 part_scale  # the face's part list word at 8: written at +6 of every stream part
local sand_block_update 08A2E5,08A326 r8 blocks_left  # 2 pool-2 blocks chained under the block, counted down
local sand_streams_update 08A4C6 r1 stream_frame  # sand_stream_frames[the face's animation_frame]: written into every part
local sand_streams_update 08A4D0,08A4DA r2 parts_left  # the part list's count word, counted down
local spawn_sink_swirl 08A4FD,08A58E r8 swirls_left  # 2 swirls, counted down
local spawn_sink_swirl 08A4F6,08A51E,08A52D,08A575 r12 effect_record  # walks sink_swirl_effects: depth offset, animation, the second part's animation
local brick_wall_wait 08A8B2,08A92D,08A93E,08A99A r8 pieces_left  # brick_wall_bricks, then brick_wall_dust, counted down
local brick_wall_wait 08A8E3 r10 brick_animation brick_look  # brick_animations + offset: the palette word, then the animation (+ 2)
local brick_wall_wait 08A8B2 r12 piece_record brick_wall_brick  # brick_wall_brick_records (8BF01 + 2: "movz.hw [R12+], R8" takes the count brick_wall_bricks): the first brick; [R12+] then walks the record and on into the next (C bytes a brick)
local brick_wall_wait 08A93E r12 piece_record brick_dust_puff  # brick_wall_dust_records (8BFAB + 2, past the count brick_wall_dust): the first puff; [R12+] walks on (8 bytes a puff)
local brick_wall_wait 08A8BF,08A8C8,08A8D1,08A8D6,08A8DB,08A8E0,08A94B,08A954,08A95D,08A962 r12 piece_record  # walks brick_wall_brick_records (flags, dx, dy, speeds, animation), then brick_wall_dust_records (flags, dx, dy, delay)
local biting_head_init 08A9E6 r10 head_record biting_head_path_start  # biting_head_paths + biting_head_paths[sprite_frame] ("movea.b 8BE25[PC](R10), R10"): the path's x, y; biting_head.path = what follows (+ 4)
local biting_head_init 08A9EC r10 head_record  # biting_head_paths[04]: x, y, then the script (path)
local biting_head_chase 08ABB3,08AC9A,08AD32 r0 direction  # its direction: >> 8 picks the record test, the threshold test and the move; also project_object/add_to_zone_list/queue_sprite_at_depth leftovers
local biting_head_chase 08ADB0 r2 move_frame  # biting_head_move_frames[step]: written into two parts of the part list
local biting_head_chase 08ABA3 t1 record_scroll  # the record's scroll position: loaded once scroll_position reaches it
local biting_head_chase 08ADC1 t3 bob_phase  # bob_counter + 1 & 3, stored back: screen_y - 1 when it is 0
local fire_front_init 08AF79,08AFA5 r8 flames_left  # 4, decremented until 0 (dbr): the spreading flames spawned beside the fire front
local fire_front_init 08AF7C,08AFA0 r9 flame_offset_x  # 0x28, + 0x38 per flame: added to each new spreading flame's x_int
local fire_front_init 08AEE7 r10 path fire_front_path_start  # fire_front_paths + fire_front_paths[sprite_frame] ("movea.b 8BDA1[PC](R10), R10"): x, y, floor_z, direction; fire_front.path = what follows (+ 8)
local fire_front_init 08AEED,08AEF7,08AF01 r10 path  # the fire_front_paths entry for sprite_frame: x, y, floor_z and direction read in turn; fire_front.path = what follows
local fire_front_chase 08B168,08B173 r1 speed  # base_speed, or camera_ground_speed_int * catch_up_factor >> 8 when that is larger and the front is past its screen threshold; stored in chaser_speed
local fire_front_chase 08B038,08B040,08B045,08B0A2,08B0A8,08B0AE,08B0B4 r10 cursor  # walks the path script: trigger word, then threshold, catch-up factor, base speed, wait time; saved back to fire_front.path
local fire_front_chase 08B038 t1 trigger_scroll  # first word of the path record: the record loads once scroll_position reaches it
local fire_front_chase 08B014 t3 hold_threshold  # direction ^ (direction >> 1): the screen_threshold while holding back (0x160 when 0)
local rising_saw_init 08B3CB r12 path rising_saw_path_start  # rising_saw_paths[sprite_frame] + rising_saw_paths ("add.w #8C001, R12"): x, y, z; rising_saw.path = what follows (+ 6)
local rising_saw_init 08B3D2,08B3DC r12 path  # the rising_saw_paths entry for sprite_frame: x, y and z read in turn; rising_saw.path = what follows
local rising_saw_init 08B54F t2 blades_left  # saw_blades_left before the decrement: the loop repeats while it was above 1
local rising_saw_rise 08B6AA r0 slope_step  # the floor attribute's x_per_rise * speed_fraction >> 4: projected_y -= half of it, fixed_projected_x moves by it (update_screen_position's result sits here before, only handed to add_to_touchable_list)
local rising_saw_rise 08B6AE,08B6B5 r1 projected_dx  # slope_step, negated when attribute_record's first byte is set: added to rising_saw.fixed_projected_x (update_screen_position's unread result lands here first)
local rising_saw_rise 08B668,08B673 r1 speed  # base_speed, or camera_ground_speed_int * catch_up_factor >> 8 when that is larger and the saw is below its screen threshold; stored in speed_fraction
local rising_saw_rise 08B5D5,08B5E3,08B5E9,08B5EF r10 cursor  # walks the path record past its two trigger words: threshold, catch-up factor, base speed, wait time; saved back to rising_saw.path
local rising_saw_rise 08B698 r12 attribute_record floor_attribute_record  # floor_types + floor_attribute (a byte offset): byte 0 = which way the slope shifts x (axis), word 4 = x_per_rise
local saw_blade_update 08B7ED r7 clip_x  # screen x (project_point) of the blade's x at the saw's y, + sprite_offset_x: into the first clip entry's palette word (the line the blade is clipped at)
local saw_blade_update 08B72B,08B76C r8 clips_left  # 2, decremented until 0 (dbr): the two clip entries (sprite flags 5000) chained behind the blade
local build_grate_steps 08BAF5 t2 rows_to_build  # grate_rows_to_build before the decrement: the row loop repeats while it was above 1
local grate_step_update 08BB37 t2 saw_top_z  # z_int - 0x20 of this column's rising saw (rising_saws[kind]): once at or above cut_z the step is cut
local spawn_grate_step_debris 08BC45 t2 debris_left  # grate_debris_left before the decrement: the loop repeats while it was above 1
local build_saw_shaft_base 08BD09,08BD21 r0 piece_frame  # sprite_frame of the piece: 0x7FE8 for the end pieces (0 and 2, the last one mirrored), 0x7FB8 between
local river_raft_spawner_init 08C152 r10 record object_start_place  # river_raft_spawners + the offset for sprite_frame (08C152 "add.w #8D9AA, R10"): x, y, floor_z, end_position read in turn; the raft list (river_raft_entry) follows (entry, list_start)
local river_raft_spawner_init 08C159,08C163,08C16D,08C177 r10 record  # the [R10+] steps through it
local river_raft_update 08C3C9,08C3D5 r1 draw_depth  # screen_y - sprite_offset_y - 0x10, at least -0x0F: stored in river_raft_update.draw_depth for the raft and its foam
local geyser_front_init entry r11 hitbox_object  # the object take_free_object hands out: hitbox sizes, flags 2, direction 0x300, kind 0x29; becomes the front's child (find_floor's unread result lands here first)
local geyser_front_init 08C6EB r12 path geyser_front_record  # geyser_fronts + the offset for sprite_frame (08C6EB "add.w #8D89A, R12"): z, top_height, next listed geyser read in turn; geyser_front.path = what follows (chase_step_with_limit)
local geyser_front_init 08C6F2,08C6FC r12 path  # the [R12+] steps through it
local geyser_front_wait 08C7B5,08C7BF r0 start_position  # 0x6500 (0x8400 when sprite_frame is not 0): the front starts rising once scroll_position reaches it
local geyser_front_rise 08C84A,08C862,08C868,08C86E r10 cursor  # walks the path record past its two trigger words: threshold, catch-up factor, base speed, wait time; saved back to geyser_front.path
local geyser_front_rise 08C91F r12 attribute_record floor_attribute_record  # floor_types + floor_attribute (a byte offset): byte 0 = which way the slope shifts x (axis), word 4 = x_per_rise
local geyser_front_rise 08CA37 t2 geysers_remaining  # geysers_left before the decrement: the row loop repeats while it was above 1
local spawn_listed_geyser 08CA6A,08CA6D,08CA74,08CA7B r10 record  # walks the geyser list entry past its scroll position: x, y, z, wait; next_listed_geyser = what follows
local waterfall_move 08CE3C,08CE47 r1 speed  # base_speed, or camera_ground_speed_int * catch_up_factor >> 8 when larger: stored in chaser_speed (find_floor/project_object/play_sound results land here too, unread)
local waterfall_move 08CD85,08CDA2,08CDA8,08CDAE r10 cursor  # walks the path record past its trigger word: threshold, catch-up factor, base speed, wait time; saved back to waterfall.path
local waterfall_move 08CD85 t1 trigger_scroll  # first word of the path record: the record loads once scroll_position reaches it
local river_water_update 08CF4B,08CF8C r8 clips_left  # 2, decremented until 0 (dbr): the two clip entries (sprite flags 5000) chained behind the water
local stone_block_init 08D053 r10 record stone_block_position  # "movea.w 8DAF0[PC](R0), R10" with R0 = sprite_frame x 2: the stone_block_positions entry (8 bytes): x, y, z, then depth_offset
local stone_block_init 08D059,08D063 r10 record  # the [R10+] steps through it
local spawn_geyser_line 08D0E2 r10 record geyser_line_start  # sprite_frame << 3 + 8DB10 (08D0DF-08D0E2): the geyser_line_starts entry (8 bytes): x, y, z, then the script offset (geyser_line_scripts)
local spawn_geyser_line 08D0E9,08D0F3 r10 record  # the [R10+] steps through it
local geyser_line_move 08D181,08D189,08D18E,08D1A4 r10 cursor  # walks the path script: trigger words, then speed and wait time; saved back to geyser_line.path
local geyser_line_move 08D181 t1 trigger_scroll  # first word of the path record: the record loads once scroll_position reaches it
local geyser_line_move 08D18E t1 trigger_y  # second word of the path record: or once y_int is at or above it
local propeller_machine_init entry r11 parts  # the block take_free_object hands out: 0x2D halfwords of propeller_machine_parts copied in, then the machine's part_list
local propeller_machine_chase 08D541 r2 part_frame  # first word of the animation step: the sprite of parts 6 and 9 (part_list + 0x32, + 0x4A) (play_sound/find_floor results land here too, unread)
local propeller_machine_chase 08D44E,08D456,08D45B,08D478,08D47E,08D484 r10 cursor  # walks the path script: trigger words, then threshold, catch-up factor, base speed, wait time; saved back to propeller_machine.path
local propeller_machine_chase 08D53C r11 frame_entry propeller_machine_animation_step  # animation + 2 + frame * 4: the step's two words (parts 6/9, part 4 at part_list + 0x22) (find_floor's unread result lands here later)
local propeller_machine_chase 08D44E t1 trigger_scroll  # first word of the path record: the record loads once scroll_position reaches it
local propeller_machine_chase 08D45B t1 trigger_y  # second word of the path record: or once y_int reaches it
local scene_anchor_init 08D686 r10 record object_start_place  # sprite_frame x 8 + 8DC1C (08D683-08D686): the scene_anchor_positions entry (8 bytes): x, y, floor_z, then end_position
local scene_anchor_init 08D68D,08D697 r10 record  # the [R10+] steps through it
local railing_spawner_update 08D7C0 t2 segments_left  # railing_segments_left before the decrement: the loop repeats while it was above 1
local start_spawns_by_type 08DC3C r0 stage_number  # stage: stored in type_spawn_stage, indexes stage_spawns_by_type
local spawn_objects_by_type 08DC8C r6 spawn_z  # fourth word of the record (signed): the new object's z (<< 16)
local spawn_objects_by_type 08DCA4 r8 object_type object_type_record  # object_types + the record's type x 8 ("add.w R7, R8"): part_count, then (+ 4, after spawn_object_with_pieces) the handler
local spawn_objects_by_type 08DC6D,08DC75,08DC86,08DC89,08DC8C,08DC97,08DCD6 r10 record  # type_spawn_cursor: scroll position, projected x, projected y, z, type, spawn_arg_1, spawn_arg_2 (14 bytes)
local count_active_players 08DCEC,08DD10 r0 players_left  # 3, decremented until 0: the loop over the three player slots
local spawn_object_with_pieces 08DD71 r1 pieces_to_add  # spawn_count - 1: the pieces after the first object, handed to add_object_pieces and back in R1
local spawn_object_with_pieces 08DD47 t2 free_blocks  # free_object_stack_2[0]: the second pool's free blocks, which must cover piece_count
local spawn_object_with_pieces 08DD71 t1 spawn_total  # spawn_count: the object and its pieces; 1 = no pieces
local add_object_pieces 08DD94 t2 free_blocks  # free_object_stack_2[0]: the second pool's free blocks, which must cover piece_count
local unproject_object 08DFEC r1 scaled_y  # projected_y (whole) << 9: x = (scaled x - it) * 0xB5, y = (it + scaled x) * 0xB5 (find_floor's unread result lands here after)
local update_screen_position_no_offset 08E029 r0 unscrolled_y  # (projected_y + z) >> 16: the screen y before view_y is taken off
local face_nearest_player 08E1CA,08E1D0,08E1F2,08E1F8 r1 player_object  # the player's block (player_block_offsets), then its object (block + 14) (square_root's unread result lands here too)
local face_nearest_player 08E237,08E240 r2 abs_dy  # the y difference made positive (neg.h) for the distance
local face_nearest_player 08E246 r2 abs_dy_long  # abs_dy sign-extended (movs.hw) for the multiply: dx^2 + dy^2
local face_nearest_player 08E1BC,08E1BF,08E1C3 r4 player_index  # 2, 1, 0 (dec.w before each round, exits below 0), then nearest_player for the final direction
local face_nearest_player 08E22B r5 player_dx  # player projected_x - object projected_x, kept signed: nearest_player_dx, angle_of_vector's dx
local face_nearest_player 08E23B r6 player_dy  # player projected_y - object projected_y, kept signed: nearest_player_dy, angle_of_vector's dy
local face_nearest_player 08E1BF t1 players_left  # player_index before the decrement: the loop ends when it was 0
local face_nearest_player 08E227 t3 diff_x  # the x difference, tested for the sign
local face_nearest_player 08E237 t3 diff_y  # the y difference, tested for the sign
local find_nearest_player_x 08E2BA,08E2C3 r0 distance_x  # |player x_int - object x_int|: the smallest goes into nearest_player_distance
local find_nearest_player_x 08E2A2,08E2A8 r1 player_object  # the player's block (player_block_offsets), then its object (block + 14)
local find_nearest_player_x 08E29C,08E2D0 r4 player_index  # 2, 1, 0
local find_nearest_player_x 08E2BA t3 diff_x  # player x_int - object x_int, tested for the sign
local find_nearest_player_x 08E2D0 t1 checked_index  # player_index before the decrement: the loop ends after index 0
local face_object 08E2FD r0 dx  # projected x of the object at R14 - ours: angle_of_vector's dx, which it replaces with the angle (into direction)
local distance_to_object 08E31D,08E328,08E339 r0 distance  # |dx| (projected), then dx^2 + dy^2, then its square root: nearest_player_distance and the result
local distance_to_object 08E320,08E330 r1 dy  # |dy| (projected) (square_root's unread result lands here after)
local distance_xy_to_object 08E36F t3 dx  # x_int of the object at R14 - ours, tested for the sign
local distance_xy_to_object 08E383 t3 dy  # y_int of the object at R14 - ours, tested for the sign
local direction_to_speed 08E405 r0 angle  # direction & 0x3FF: indexes sine_table/cosine_table; handed back in R0
local explosion_animate 08E4A5 t3 toggle  # explosion.frame_toggle + 1, bit 0: the frame index steps every other frame
local drop_rings 08E5A2 r0 random_bits  # random_value & 0x7F: added to the ring's speed (6E), and (& 0x3F) * 2 - 0x40 to its direction
local spawn_debris 08E7B1 r0 random_byte  # the random value & 0xFF: added to each piece's direction; its low 6 bits to its speed (bit 0 picks var_80/var_81 = 0A/13)
local debris_fly 08E847 t3 phase  # debris.frame_timer + 1, & 3: the frame steps when it comes back to 0
local draw_queued_strings 08E926,08E98D,08E9AD r1 char_x  # the record's x, + advance per character (leading blanks added once the size is known): each sprite entry's x word
local draw_queued_strings 08E929 r2 string_y  # the record's y: each sprite entry's y word
local draw_queued_strings 08E92C r3 palette  # the record's palette: each sprite entry's last word
local draw_queued_strings 08E935,08E9B0 r6 char_code  # the string's next byte: indexes text_frames; 0 ends the string
local draw_queued_strings 08E911 r7 shift  # 0xF8 (-8): the shift_by/rotate_by count, (size * scale + 0x80) >> 8
local draw_queued_strings 08E907,08E974 r11 advance  # (low byte of the frame record's second word * scale + 0x80) >> 8: the scaled character size, how far x moves per character
local draw_queued_strings 08E914,08E93B,08E94E,08E956,08E962,08E983 r12 frame_record  # sprite_frames base | text_frames[char_code]: the character's frame record (width, height, bank bits, address)
local draw_queued_strings 08E90A,08E946,08E990 r13 leading_blanks  # characters with no frame (text_frames 0) counted before the first visible one: x += leading_blanks * advance once advance is known
local draw_queued_strings 08E90D r28 rounding  # 0x80: added before the >> 8 of the scaled sizes
local start_ranking_entry 08EB05,08EB14 r0 x_index  # the player, + 3 when cabinet_config bit 0 is clear: indexes ranking_entry_x
local start_ranking_entry 08EB44,08EB5C r1 next_block  # the block after the current one (+ 0xA0 after the entry block, + 0x30 after each piece): stored in its link (+28)
local start_ranking_entry 08EAFF,08EB3B,08EBD9,08EC05,08EC31,08EC5D r3 block  # ranking_entry_blocks[player]: the entry block, then each piece along the links (+28), set up in turn
local start_ranking_entry 08EB56,08EB6E r4 pieces_left  # 4, decremented until 0: the pieces linked behind the entry block
local start_ranking_entry 08EBD6,08EC02,08EC2E,08EC5A r11 previous_block  # the block before r3: its x and y are copied into the next piece (x + 0x0D)
local start_ranking_entry 08EAD7 r13 ranking_size  # 0x0F: the player gets an entry when r14 (15, minus one per record the rings reach) ends below it
local enter_ranking_initials 08ED39,08ED44 r3 abs_dx  # |the trackball's first word|: compared with abs_dy to pick the move
local enter_ranking_initials 08EE94 r3 record_initials  # each record's second long (initials and the byte after), moved down with it
local enter_ranking_initials 08ED3C r4 dx  # the trackball's first word, signed: added to the slot position (spawn_arg_1)
local enter_ranking_initials 08ED47,08ED52 r5 abs_dy  # |the trackball's second word|
local enter_ranking_initials 08ED4A r6 dy  # the trackball's second word, signed: added to the selected letter (its piece's projected_x)
local enter_ranking_initials 08ED85,08ED98 r10 slot  # the selected letter slot (spawn_arg_1's high byte, 3 at most): ranking_initial_fields[slot]; 3 = done
local enter_ranking_initials 08EDF8,08EE2A r19 letters_left  # 3, decremented until 0: the three letter pieces drawn
local enter_ranking_initials 08EF3E t2 out_of_rank_timer  # var_82 before the decrement: the out-of-rank text blinks until it was 0
local enter_ranking_initials 08ECCA t2 time_left  # var_84 (0x4B0 from start_ranking_entry) before the decrement: when it was 0 the record is saved
local enter_ranking_initials 08ED5A t4 slot_position  # spawn_arg_1 + dx: the slot position (8.8), not below 0
local enter_ranking_initials 08EDB1 t4 letter_position  # the letter piece's projected_x + dy: the letter (8.8, 0-0x2B wrapping)
local reset_ranking 08F11E r0 read_cursor default_ranking_record  # "movea.b 8F1B5[PC], R0": default_ranking, its first record
local reset_ranking 08F12D,08F131,08F135,08F13E,08F147,08F150 r0 read_cursor  # walks default_ranking ([R0+]): two words, three ASCII letters, one byte per record
struct default_ranking_record 8 rom   # a record of default_ranking (08F1B5, 15, lowest first), ranking_record's layout with ASCII letters: reset_ranking reads it with [R0+] (08F12D-08F150); census ROM 08F1B5-08F22C (012C 02 04 "div" 00 ... 03E8 00 08 "son" 00)
field default_ranking_record 00 rings u16           # "mov.h [R0+], [R1+]" (08F12D): the score, 012C up to 03E8
field default_ranking_record 02 character_and_stages u16  # "mov.h [R0+], [R1+]" (08F131): the character (0-2) and stages_reached (04-08) bytes as one halfword, into ranking_record's character and stages_reached
field default_ranking_record 04 first_letter u8     # "movz.bw [R0+], R3" (08F135), ASCII, through ascii_to_ranking_letter
field default_ranking_record 05 second_letter u8    # (08F13E)
field default_ranking_record 06 third_letter u8     # (08F147)
field default_ranking_record 07 letters_pad u8      # (08F150) 00, copied as it is to initials_pad
local reset_ranking 08F12A,08F154 r2 records_left  # 15, decremented until 0
local reset_ranking 08F13E t1 second_letter  # the record's second ASCII letter
local reset_ranking 08F147 t1 third_letter  # the record's third ASCII letter
local reset_ranking 08F150 t1 last_byte  # the record's eighth byte, copied as it is
local build_ranking_display 08F26D,08F473 r17 rows_left  # 15, decremented until 0: one row per ranking record
local ranking_row_update 08F7BA r1 portrait_frames  # ranking_portrait_frames[character]: the portrait's frame list
local ranking_row_update 08F7C0,08F7CC r2 frame_index  # the portrait's animation step (projected_y high byte), + 0x14 on row 1: indexes portrait_frames (delete_object_and_children's result lands here on the other path, unread)
local start_character_chooser 08FA8E r7 chooser_x  # chooser_x_by_player[player (+3)]: the screen x of the portraits, the name and the labels
local start_character_chooser 08FA49,08FA5E,08FA69,08FAC5,08FACD,08FB0D,08FB15 r8 frame_index  # character * 4: indexes chooser_portrait_frames; + 4 (back to 0 after 8) for the side portraits
local start_character_chooser 08FA43,08FA59,08FA66,08FAD0,08FAD2,08FB18,08FB1A r9 character  # the player's character, moved on past taken ones (character_taken_bits); + 1 % 3 for each side portrait
local character_chooser_wait 08FD2A r0 idle_frame  # chooser_idle_steps[idle_step]: the frame offset added to character * 4
local character_chooser_wait 08FCAB r2 trackball_x  # the player's trackball_N_x (chooser_trackball_offsets): negative counts left turns, positive right ones
local character_chooser_wait 08FCE6 t2 right_steps  # right_count's whole steps; the same as the left ones = no turn
local character_chooser_confirm 08FC55 r1 chosen_character  # the chosen character again: indexes chooser_portrait_palettes (play_sound's unread results land here after)
local character_chooser_turn 08FF35 r15 turn_step  # turn_direction (+1/-1, sign-extended): moves the portraits' x and y each frame
local character_chooser_turn 08FE06 t4 next_character  # character + turn_direction: 2 when below 0, then % 3
local character_chooser_turn 08FF3F t3 scale_step  # turn_direction * 2: taken off (or added to) the sprite scales
local character_select_countdown_update 090004,090009 r0 seconds  # character_select_timer / 0x3C: the digit shown (ranking_letter_sprites + 0x20)
local character_select_countdown_update 08FFF8 t2 time_left  # character_select_timer before the decrement: kept at 1 once it was 0
local start_join_character_chooser 0900A9,090135 r3 block  # join_chooser_blocks[player]: the chooser block, then the name block linked after it (+ 0xA0) (count_free_characters' unread result lands here first)
local start_join_character_chooser 0900E9 r7 chooser_x  # chooser_x_by_player[player (+3)]: the screen x of the portrait and the name
local start_join_character_chooser 0900A0,0900C7 r9 character  # the joining player's kind, moved on past taken ones (% 3): picks the portrait, palette and name
local join_character_chooser_update 0901E4 r1 taken_bit  # character_taken_bits[character]: set in characters_taken when the player takes it
local join_character_chooser_update 090240 r1 left_steps  # spawn_arg_2's whole steps (left turns)
local join_character_chooser_update 090217 r2 trackball_x  # the player's trackball_N_x (chooser_trackball_offsets): negative counts left turns, positive right ones
local join_character_chooser_update 090174 t2 time_left  # var_82 before the decrement: when it was 0 the shown character is taken
local join_character_chooser_update 090251 t2 right_steps  # spawn_arg_1's whole steps; the same as the left ones = no turn
local join_character_chooser_update 090260 t4 next_character  # var_81 + the turn: 2 when below 0, else % 3
local count_free_characters 0902F9,090313 r0 characters_left  # 3, decremented until 0 (handed back as 0)
local count_free_characters 0902FC,090311 r1 free_characters  # characters whose bit in characters_taken is clear: into free_character_count
local count_free_characters 090302 r2 character  # characters_left - 1: 2, 1, 0 (the last one handed back)
local count_free_characters 090304 r3 taken_bit  # character_taken_bits[character] (the last one handed back)
local spawn_walls 090DAB,090DB3 r0 blocks_needed  # the wall's part count (speed_x_fraction), doubled when its kind has a second sprite: must be below the second pool's free blocks (project_object's unread result lands here later)
local spawn_walls 090DD0 r10 kind_record wall_kind_offsets  # wall_kinds + sprite_frame: the kind's record
local spawn_walls 090DD7,090DDC,090DE1 r10 kind_record  # the [R10+] steps: hitbox offset y, sprite offsets, direction
struct wall_kind_offsets 6 rom               # the first 6 bytes of a wall_kinds record (090A72 + the kind's offset, kind x 14): spawn_walls 090DCC-090DD0 "movz.hw 4[R20], R10; add.w #90A72, R10", then [R10+] (090DD7-090DEB); the rest of the record (from +6) is a wall_kind_parts, read through another pointer (build_wall_parts); census ROM 090A72-090C79 (26 kinds)
field wall_kind_offsets 00 hitbox_offset_x s8   # "mov.b [R10+], 7C[R20]": 0
field wall_kind_offsets 01 hitbox_offset_y s8   # "mov.b [R10+], 7D[R20]": 0, F0
field wall_kind_offsets 02 sprite_offset_x s8   # "mov.b [R10+], 64[R20]": 04, 0A, 0C, 13, 23, 43
field wall_kind_offsets 03 sprite_offset_y s8   # "mov.b [R10+], 65[R20]": 0, FA, FC
field wall_kind_offsets 04 direction u8         # "movz.bh [R10+], 24[R20]": 0-3 (byte 5 is 0 in every kind; nothing reads it)
local spawn_walls 090D3F,090D42,090D48,090D52,090D5C,090D6C r12 record  # the wall spawn record past its position: end_position, x, y, z, kind, part count, depth_from_next
local spawn_walls 090CFA t2 init_flags  # stage_init_flags before bit 5 is set: the first call points wall_spawn_cursor at the stage's list
local spawn_walls 090D22 t1 spawn_position  # first word of the spawn record: the scroll position the wall appears at
local spawn_walls 090D22 t2 current_scroll  # scroll_position: at least spawn_position, and less than 0x100 past it
local wall_update 090ECD,090F06 r1 depth_y  # a part's screen y (the next part's when depth_bias is positive): its depth is depth_y - depth_bias
local wall_update 090EA5,090EAF r2 depth_bias  # 0x10, or -0x10 when depth_from_next is 0
local wall_update 090E9A,090ED4 r15 part_y  # the running screen y: the wall's, + each part's step (hitbox_size_y high byte)
local build_wall_parts 09117A r6 part_sprite  # the word at 90A78 + kind + 8 (wall_kinds' first part sprite): each part's sprite_frame; negative = an invisible wall
local build_wall_parts 09116B,091210 r9 parts_left  # the part count (speed_x_fraction), decremented per part
local build_wall_parts 091126 r10 record wall_kind_parts  # 90A78 + sprite_frame: the kind's wall_kinds record from its byte 6
local build_wall_parts 09112D,091132,09116F,091174,091177,09117A,091194 r10 record  # the [R10+] steps: sizes, palette, steps, sprite, second sprite's offsets and frame
struct wall_kind_parts E rom                 # a wall_kinds record (090A72 + kind x 14) from its byte 6 on, after its wall_kind_offsets: build_wall_parts 091122-091126 "movz.hw 4[R20], R10; add.w #90A78, R10", then [R10+] (09112D-09119A); spawn_walls tests its second_sprite at 90A84 + the kind (wall_kind_second_sprites, 090DA4-090DAF); census ROM 090A78-090C79 (26 kinds)
field wall_kind_parts 00 size_x u16             # "mov.h [R10+], 10[R20]": hitbox_size_x; bit 15 set: the size of one part (& 7FFF, times the part count): 800C, 8017, 805B, 80B5, 0010
field wall_kind_parts 02 size_y u16             # "mov.h [R10+], 12[R20]": hitbox_size_y, the same way: 0010, 802E, 805B, 80B5
field wall_kind_parts 04 palette u16            # "mov.h [R10+], 6[R20]": 83E0
field wall_kind_parts 06 part_step_x u8         # "mov.b [R10+], R15": each part's hitbox_centre_x (its x step to the next part): 10, 40, 7F, 0
field wall_kind_parts 07 part_step_y u8         # "mov.b [R10+], R16": each part's hitbox_centre_y (y step): F8, F0, E0, C0, 0
field wall_kind_parts 08 part_sprite s16        # "mov.h [R10+], R6; test.h R6; bp": each part's sprite_frame (73A0); negative (FFFF): no parts, an invisible wall
field wall_kind_parts 0A second_offset_x u8     # "mov.b [R10+], R17": the second part's hitbox_centre_x: 0C, 0
field wall_kind_parts 0B second_offset_y u8     # "mov.b [R10+], R18": the second part's hitbox_centre_y: 04, 06, 07, 0
field wall_kind_parts 0C second_sprite s16      # "mov.h [R10+], R5; test.h R5; bn": the second part's sprite (73A0, palette 9FFE); negative (FFFF): none
local build_wall_parts 091174 r15 part_step_x  # each part's x step to the next (its hitbox_size_y low byte)
local build_wall_parts 091177 r16 part_step_y  # each part's y step to the next (its hitbox_size_y high byte)
local build_wall_parts 091194 r17 second_offset_x  # the second sprite's x offset from its part (its hitbox_size_y low byte)
param spawn_object_groups R3 slope_z  # how far the member is along its floor block (y, or x when the slope's flag bit 1 is set) * the slope factor: added to floor_z
local spawn_object_groups 091220 t2 init_flags  # stage_init_flags before bit 9 is set: the first call points group_spawn_cursor at the stage's list
local spawn_object_groups 091244 t1 spawn_position  # first word of the spawn record: the group appears once scroll_position reaches it
local spawn_object_groups 091360 t1 slot_taken  # each group_slots word (0 = free) while looking for a free slot
local spawn_object_groups 0914C0 t2 members_left  # group_member_count before the decrement: the loop repeats while it was above 1
local group_leader_move 0917F9,091805 r1 hold_threshold  # the screen_threshold while holding back: 0xFF90, or 0x1B0 when direction bit 0 is set (project_object's unread result lands here after)
local group_leader_move 0918CF,0918D9 r1 speed  # base_speed, or camera_ground_speed_int * catch_up_factor >> 8 when that is larger and the leader is behind its threshold: into chaser_speed
local group_leader_move 091820,091833,091838,09183D r10 cursor  # walks the path record past its trigger word: threshold, catch-up factor, base speed, wait time; saved back to group_leader_move.path
local group_leader_move 091820 t1 trigger_scroll  # first word of the path record: the record loads once scroll_position reaches it
local group_slot_trigger_update 091F89 r0 slot  # the trigger record's slot number: indexes group_slots
local group_slot_trigger_update 091F68,091F89 r10 cursor  # walks the trigger record past its scroll position: slot, new slot value; next_trigger = what follows
local group_slot_trigger_update 091F68 t1 trigger_scroll  # first word of the trigger record: it fires once scroll_position reaches it
local group_slot_trigger_update 091F9A t1 slot_address  # group_slots + slot * 2: bit 15 set there
local spawn_object_groups_2 09383A t2 init_flags  # stage_init_flags before bit 4 is set: the first call clears group_slots and points group_spawn_cursor_2 at the stage's list
local spawn_object_groups_2 093892 t1 spawn_position  # first word of the spawn record: the group appears once scroll_position reaches it
local spawn_object_groups_2 093980 t1 slot_taken  # each group_slots byte (0 = free) while looking for a free slot
local spawn_object_groups_2 093AB2 t2 members_left  # group_member_count before the decrement: the loop repeats while it was above 1
local rope_bridge_plank_update 093C74,093C8A,093C90,093CA6 r1 slot_mark  # 0xFF, or 0x80 when the rider (the platform object of kind 0x0F) is past the plank's middle: stored in its group slot
local rope_bridge_plank_update 093B11,093B59 r8 ropes_left  # 2, decremented until 0: the rope pieces allocated behind every fourth plank
local rope_bridge_plank_update 093BFA,093C0A r11 last_plank  # bridge_0_last_plank or bridge_1_last_plank: when it is this plank, the bridge's plank count is cleared
local rope_bridge_plank_update 093CBA r11 slot_address  # group_slots + group_slot: the byte saying which half the rider is on
local rope_bridge_plank_update 093D4F r12 hitbox_record hitbox_size_pair  # rope_plank_hitbox_size: the plank's hitbox_size_x and hitbox_size_y
local rope_bridge_plank_update 093B17,093B24 r13 rope_offsets rope_bridge_rope_offset  # rope_bridge_rope_offsets (093B17 "movea.b 94001[PC], R13"), wide_rope_bridge_rope_offsets for kind 0x30 (093B24): one record per rope piece
local rope_bridge_plank_update 093B44,093B49 r13 rope_offsets  # the [R13+] steps: each rope piece's x and y offset in turn
local draw_rope_bridge_ropes 093E7A r11 slot_address  # group_slots + group_slot: rope 1 hides when it holds 0x80, rope 2 when it holds 0xFF
local draw_rope_bridge_ropes 093E68,093EC1 r19 ropes_left  # 2, decremented until 0: the two rope pieces after the plank
local event_0f_show_messages 09465D,094662,09466A,09466D,094670 r10 cursor  # event_0f_message_cursor: time, screen x, screen y, kind, frames; saved back past the record
local event_0f_show_messages 094662 t1 show_time  # first word of the record: the message box opens once scene_timer reaches it
local event_0c_first_quake 0948D0 t2 time_left  # scene_timer before the decrement: the next step starts when it was 1 or less
local event_0c_second_quake 094902 t2 time_left  # scene_timer before the decrement: the next step starts when it was 1 or less
local event_0c_finish 09491C t2 time_left  # scene_timer before the decrement: play resumes (game_mode 3) when it was 1 or less
local update_stage_05_event 094B56,094B5F,094B68,094B6D,094B72,094B77 r12 piece_record  # walks stage_00_rock_pieces: sprite flags, x and y offsets, speeds, animation, one record per rock piece
local update_stage_05_event 094B49 r12 piece_record rock_debris_piece  # past stage_00_rock_count ("movz.hw [R12+], R8"): the first stage_00_rock_pieces record
local stage_00_geyser_rise 094DB6 r10 cursor  # the rise script pointer (kept in floor_z) past the record's frame count: its speed word (<< 12 into speed_z); floor_z = the next record
local event_1c_stop 095158 t2 time_left  # scene_timer before the decrement: the riding step starts when it was 1 or less
local spawn_stage_06_lightning 09532E t2 init_flags  # stage_init_flags before bit 12 is set: the first call points stage_06_lightning_cursor at stage_06_lightning_strikes
local spawn_stage_06_lightning 095345 t1 strike_time  # first word of the strike record: the bolt appears once scene_timer reaches it
local spawn_stage_06_lightning 095340,095345,095373,095378,09537D r10 record  # stage_06_lightning_cursor (a stage_06_lightning_strike), read with [R10+]; handed back
local place_message_box 0953E7 r7 kind message_box_kind  # message_box_kinds + kind x 8 ("mul.w #8, R7", "add.w #9571A, R7"): the kind's record, read with [R7+]
local spawn_message_box 0953E7,0953EE,0953F3 r7 kind_record  # message_box_kinds + kind * 8: sprite, palette, full width (into speed_x_int), height
local start_escape_countdown 095463,095490 r12 digit_slot  # escape_tens_digit, then escape_units_digit: where each digit object is kept
local update_escape_countdown 095527 t4 tens  # escape_seconds_bcd >> 4: the tens digit, drawn only when not 0
local map_intro_update 095CCF r1 icon_y  # offset_y (whole) + base_y: the icon's y, the map screen object's base_y
local map_intro_update 095AF7 r11 stage_number  # stage: indexes stage_map_icons
local map_intro_update 095BB7 t1 io_cnt_value  # io_cnt as read: written back with bit 1 clear
local move_map_stage_icon 095B5E r0 icon_x  # offset_x (whole) + base_x: the icon's screen x
local move_map_stage_icon 095B1F,095B62 r1 icon_y  # offset_y (whole) + base_y: the icon's screen y (the move stops at 0x30, 0x10)
local map_screen_update 09609C r0 next_x  # map_stage_positions[marker] x: map_screen.target_x
local map_screen_update 0960C7 r1 step_y  # (next_y - base_y) << 16 / 0x3C: map_screen.step_y (update_screen_wipe's unread result lands here in the wipe steps)
local map_screen_update 09609F r1 next_y  # map_stage_positions[marker] y: map_screen.target_y
local map_eggman_update 096348 r0 gesture  # the gesture list byte (sign-extended; negative steps kind back): sprite_frame = 0x5760 + (gesture - 1) * 8
local start_map_trail 0964F7 r10 trail  # stage_map_trails[stage]: the segment frames word, then the first trail point (none when not above 0)
local next_map_trail_segment 09650F r0 start_x  # the trail point's x: base_x
local next_map_trail_segment 096512 r1 start_y  # the trail point's y: base_y
local next_map_trail_segment 096563 r1 step_y  # (base_y - target_y) << 16 / segment_frames: next_map_trail_segment.step_y
local next_map_trail_segment 096515 r2 end_x  # the next point's x: target_x; negative = the trail is done
local next_map_trail_segment 096518 r3 end_y  # the next point's y: target_y
local draw_map_trail_dot 0965AD r1 dot_y  # the trail's y offset (whole) + its base y: the dot's screen y
local draw_map_stage_markers 096637 r1 current_from_end  # 7 - current_marker: the markers before the current one (markers_left above it) take their cleared palette
local draw_map_stage_markers 096631,0966DB r4 markers_left  # 7, decremented until 0: the markers of one group
local draw_map_stage_markers 09661F r5 current_marker  # stage_map_markers[stage]: negative = no markers
local draw_map_stage_markers 09662E,0966DF r6 groups_left  # 2, decremented until 0: the two groups of seven markers
local blink_current_map_marker 09670B r5 current_marker  # stage_map_markers[stage]: negative = none to blink
local start_window_wipe 0967BF,0967FB r1 left  # first word of the record (window_wipe_default_left when not above 0): window_wipe_left (update_screen_wipe's unread result lands here after)
local start_window_wipe 0967C2,096804 r2 top  # second word of the record (window_wipe_default_top when not above 0): window_wipe_top (update_screen_wipe's unread result lands here after)
local start_window_wipe 0967CE,0967E4 r3 right  # left + 0x100, at most 0x140: window_wipe_right (update_screen_wipe's unread result lands here after)
local start_window_wipe 0967D5,0967F1 r4 bottom  # top + 0x80, at most 0xE0: window_wipe_bottom
local start_window_wipe 09684E r4 bottom_speed  # (0xE0 - bottom) << 16 / frames: window_wipe_bottom_speed, and the result
local update_screen_wipe 096932,096967,0969D1 r0 clip_left  # window_wipe_left_edge >> 16, clamped; written to video_bitmap_clip_left
local update_screen_wipe 096936,09697C,096985,0969E4 r1 clip_right  # window_wipe_right_edge >> 16, clamped; written to video_bitmap_clip_right (handed back in R1)
local update_screen_wipe 09693A,09699A,0969A3,0969FB r2 clip_top  # window_wipe_top_edge >> 16, clamped; written to video_bitmap_clip_top (handed back in R2)
local update_screen_wipe 09693E,0969B8,0969C1,096A0E r3 clip_bottom  # window_wipe_bottom_edge >> 16, clamped; written to video_bitmap_clip_bottom (handed back in R3)
local run_title_screen 096E3B t1 cnt_bits  # io_cnt read before clearing bit 1 (display off)
local title_emblem_animate 0970BB,0970EA r0 sonic_y  # Sonic's screen_y: 0x70 - title_sonic_rise_heights[step] while he rises, then 0x46
local title_emblem_animate 097099 r1 sonic_step  # (u8) title_sonic_step; sonic_step - 0x14 indexes the frame loop at 096E02
local title_emblem_animate 0970C3,0970F2 r3 sonic_frame  # the rise step (0-13), then the loop byte - 1; sprite_frame = 5300 + 8 * it
local title_emblem_animate 0970A2 r9 loop_entry title_sonic_loop_codes  # 096E02 + step - 0x14: the entry of Sonic's frame loop (FF sends the step back to 0x14)
elements title_sonic_loop_codes u8    # title_sonic_loop_frames (096E02): Sonic's frame codes from title_sonic_step 14 on, "cmp.b #FF, [R9]" (0970A5) loops back, else sprite 5300 + (code - 1) x 8; census ROM 096E02-096E0E (15 16 17 18 19 1A 19 18 17 16 15 14 FF)
local timed_spawner_run 0973FA r1 spawned_handler  # the record's second word + 0x973B4: the handler of the object it spawns
local timed_spawner_run 0973CD r10 spawn_record timed_spawn  # "movea.w 97419(R0), R10": timed_spawns + list_index * 4, the current record (FFFF ends the list)
local timed_spawner_run 0973E5 r10 spawn_record  # "cmp.h [R10+], 32[R20]": past the time, at the handler offset
struct timed_spawn 4 rom              # a record of timed_spawns (097419, the title), timed_spawns_2 (01C49C, the tower) or timed_spawns_3 (01D372, the credits): the frame it starts on, then its handler's offset; FFFF FFFF ends; census ROM 097419-097434, 01C49C-01C4F7, 01D372-01D399
field timed_spawn 00 time s16         # "cmp.h [R10+], 32[R20]; ble" (timed_spawner_run 0973E5) and "cmp.h [R10], 32[R20]" (run_timed_spawns_2/3): against the object's timer, signed; FFFF ends ("cmp.h #FFFF, [R10]" 0973D5)
field timed_spawn 02 handler_offset u16  # added to timed_spawner_run (0973B4), ending_debris_1_fly (01BFC4) or ending_credits_fade_in (01C82F) for the new object's handler: "movz.hw [R10+], R1" (0973F0), "movz.hw 2[R10], 40[R20]" (01C464, 01D334)
local timed_spawner_run 0973E5 t1 spawn_frame  # the record's first word: its handler is spawned once timed_spawner.timer passes it
local title_smoke_puff_animate 097515 r0 puff_frame title_smoke_puff_sprites  # 0x97538 + frame_index * 2: the puff's frame list entry (FFFF deletes it)
elements title_smoke_puff_sprites u16  # title_smoke_puff_frames (097538): "movea.h 97538(R0), R0" (097515), "mov.h [R0], 4[R20]" (09751D) into sprite_frame, FFFF removes the puff ("cmp.h #FFFF, [R0]" 097522); census ROM 097538-09754B (BB68-BBA0, FFFF)
local title_sky_wave 097EF3,097F18 r4 wave_rows_left  # 0x80 rows of the wave loop
local title_sky_wave 097EE5,097F12 r11 row_scroll  # walks NBG2's row-scroll table (300800, video_rowscroll_control = 401) from line 0x80: each line gets its offset >> 16
local title_sky_wave 097EEC,097F0B r12 row_offset  # walks 302100: one 16.16 offset per row, += sine * 8 every frame, wrapped at 0x2000000
local run_sega_logo 09817B t1 cnt_bits  # io_cnt read before clearing bit 1 (display off)
local start_ring_tally 098434 r0 stage_rings_1  # player_1_stage_rings: summed for the percentage; | 0x100 gives player 1's bonus word
local start_ring_tally 098439 r1 stage_rings_2  # player_2_stage_rings: summed; | 0x100 gives player_2_rings
local start_ring_tally 09843E r2 stage_rings_3  # player_3_stage_rings: summed; | 0x100 gives player_3_rings
local start_ring_tally 098495,0984B0,09856F r2 player_3_rings  # player 3's rings | 0x100 (bonus flag) for pick_ring_bonus_players, as player_2_rings; >> 8 = his panel's bonus byte (hitbox_height + 1), handed back in R2
local start_ring_tally 09846E,09847B r5 percent_fixed  # (rings of all three << 16) / stage_ring_counts[stage] * 100: the ring percentage in 16.16 (+0x100 when bit 11 is set)
local start_ring_tally 098482 r5 percent  # its integer part: ring_tally_percent
local start_ring_tally 098459 r6 bonus_percent  # stage_ring_bonus_percents[stage]: under it every player's bonus flag is cleared
local start_ring_tally 09854A t2 out_flags  # player 3's flags & 0x10000400 (bit 10: out of the game); his panel is made only when 0
local ring_tally_percent_update 0987FF r0 percent_bcd  # value & 0x1FF: the percentage (or the rolling random number) in BCD
local ring_tally_percent_update 098806,098809,09881D,098820,098825,09883A,09883D r1 percent_digit  # one BCD digit of it at a time (ones, tens, hundreds): index of the digit frames at 098908
local wait_for_frame 098ED6 t1 frames_left  # frames_to_wait as the wait loop reads it; the loop ends when it goes negative (irq_vblank_end counts it down)
local play_demo_buttons 098FC9 r13 demo_cursor  # demo_input: the demo record whose bits 0-2 become input bits 25-27 (move_bits_down)
local square_root 09904C,099071,099079 r1 steps_left  # 0x10 steps of the bit-by-bit root (two radicand bits each); 0 when handed back
local square_root 099049,099056,09905C r2 radicand  # the value, shifted left two bits a step into the remainder
local square_root 099050,099059,09905F,09906C r3 root_remainder  # the partial remainder: takes the radicand's top bits, loses the trial value when it fits
local angle_of_vector 0990A3 t1 abs_dx  # |dx| kept while the two components swap (|dy| >= |dx|)
local convert_to_bcd 0990F4,0990FD,099100,099110 r2 bcd  # the BCD result, built a digit at a time (add the digit, rotate right 4)
local convert_to_bcd 0990F1,099106,099114 r3 digits_left  # 8 digits; the remaining rotations when the value runs out early
local reset_object_list 0991EE,09920A r0 object_offset  # the low half of each object address (0x200000 + n * 0xA0) pushed on the free stack
local reset_object_list 099200,09920F r1 objects_left  # 0x80 objects; 0 when it is stored as object_count
local reset_object_list 099204,099207 r3 stack_slot  # walks the free object stack from 20E902 (as reset_object_pool_2's stack_slot)
local reset_object_pool_2 099321,099330 r1 pool_blocks_left  # 0xFF blocks of the second pool
local load_text_font 099387 r27 dest_end  # where the MOVCU of the 0x9A0 font halfwords left off in VRAM: expand_font writes on from there
local load_text_font 099387 r28 source_end  # where it left off in the ROM: the 1 bpp font expand_font reads
local expand_font 0993E5 r0 pixel_mask  # pixels * 0xF: a nibble mask over the row's set pixels (clears the shadow under them)
local expand_font 0993B9,099403 r1 chars_left  # count characters
local expand_font 0993C5,0993D7 r2 pairs_left  # 4 passes of two bits per source byte
local expand_font 0993BC,0993FF r3 char_rows_left  # 8 rows per character
local expand_font 0993BF,0993FB r6 shadow_pixels  # (pixels * colour + set_pixels) >> 4: the previous row's pixels in colour + 1, one pixel over, drawn under the next row
local expand_font 0993E2 r7 set_pixels  # the row's pixels as a 1 per nibble, before pixels is multiplied by the colour
local init_video_registers 0994E0 r0 right_bottom  # DF013F: right 13F and bottom DF of each of the five clip rectangles
local init_video_registers 0994E7,0994F1 r1 rects_left  # 5 clip rectangles (31FF60-87)
local init_video_registers 0994DC r10 defaults_cursor  # video_register_defaults after the 31FF5C word: the two words for 31FF88-8F
local init_video_registers 0994DC,0994EA,0994EE r11 register_cursor  # walks 31FF60-8F (clip rectangles, then 31FF88); the last assignment only keeps clear_page_0_and_scroll's leftover
local set_screen_mode 099583,099586,09958C r0 flip_bits  # mode bit 1 (flip) moved to bit 9 of video_control; * 3, rotated, gives 3 for sprite_flip
local set_screen_mode 099573,099589 r1 control_bits  # mode bit 0 (wide screen, also written to sprite_wide_screen) moved to bit 15, plus the flip bit: OR'd into video_control
local set_screen_mode 099562 r11 control_register  # video_control (31FF00), the register the asm works through; returned in R11
local draw_fraction 0995CC r0 cell_attribute  # attribute << 8: the high byte of both cells
local draw_fraction 0995CF,0995DD r1 numerator_cell  # the numerator digit as the upper fraction character (0x80 + n) with the attribute; handed back
local draw_fraction 0995D2,0995E0 r2 denominator_cell  # the denominator digit as the lower fraction character (0x90 + n) with the attribute; handed back
local print_text_here 09960E t1 next_char  # the next byte of the text: replaces the cell's low byte, 0 ends it
local erase_text_here 09962F t1 first_char  # the text's first character (after the attribute byte): 0 = nothing to erase
local erase_text_here 099636 t1 next_char  # each following character: 0 ends the erase
local print_text_lines 099641,099649 r0 text_cell  # attribute (the text's first byte) << 8 | character; a character with bit 7 starts a new line
local print_text_lines 09963B,099652 r13 line_start  # text position of the current line; + 0x80 per new line
local erase_text_lines 099662 r0 blank_cell  # 0: written over each character
local erase_text_lines 09965F,099672 r13 line_start  # text position of the current line; + 0x80 per new line
local erase_text_lines 09966C t1 text_char  # each text byte: 0 ends, bit 7 starts a new line
local run_vram_script 099870,099877 r0 script_code  # the script byte: under 0x80 an index of vram_script_dictionary, else & 0x7F the command
local vram_script_fill 0998DB r0 tile  # the script's map word, written count times
local vram_script_count_up 0998E9,0998EF r0 tile  # the script's map word, + 1 after each cell
local vram_script_count_down 0998F9,0998FF r0 tile  # the script's map word, - 1 after each cell
local vram_script_bytes 09990C,09990F r0 tile  # map word: high byte from the script's first byte, low byte from each following byte
local vram_script_words_to_dictionary 099929 r0 tile  # each script word: written to VRAM and to the next vram_script_dictionary slot
local vram_script_clear_pages 099948 r0 halfword_count  # the script byte << 9: 0x200 halfwords per page set to 0
local vram_script_fill_down 099958 r0 tile  # the script's map word, written count times 0x40 bytes apart going down
local vram_script_fill_up 09996A r0 tile  # the script's map word, written count times 0x40 bytes apart going up
local vram_script_fill_down_wide 09997C r0 tile  # the script's map word, written count times 0x80 bytes apart going down
local vram_script_repeat 09998F r0 tile  # the script's map word, written count times at the same address
local draw_hex_2 099A57 r0 hex_value  # the value drawn; draw_hex_digits shifts it right a digit at a time and returns what is left
local draw_hex_4 099A5F r0 hex_value  # the value drawn; draw_hex_digits shifts it right a digit at a time; handed back
local draw_hex_digits 099A8F t4 remaining  # value >> 4: the digits still to draw; the loop stops at 0 and blanks the rest
local draw_credit_digits 099AA3,099AD4 r0 bcd_value  # the BCD credit count; shifted right a digit at a time
local draw_credit_digits 099AA6,099AD7,099AE2 r2 digits_left  # 2 digits; the rest are blanked when the value runs out
local draw_credit_digits 099AC6 r3 shift  # 0xFC = -4: shift right one digit
local draw_credit_digits 099AD4 t5 remaining  # bcd_value >> 4: the digits still to draw
local init_sprites 099F4D,099F54 r1 registers_left  # 4 sprite control registers set from sprite_control_init
local init_sprites 099F47,099F50 r11 control_cursor  # SPRITE_CONTROL + 6 onwards (500006-0D)
local init_sprites 099F5F r27 dest_end  # where the MOVCU of the sprite list header (0x10 halfwords at 400000) left off: the first bucket, back buffer and list start
local clear_sprite_buckets 099FAF,099FB7 r1 buckets_left  # 0x100 bucket heads, each set to point at itself (empty)
local queue_sprite_at_floor 09A032,09A036 r1 floor_y  # (projected_y + floor_z) >> 16 - view_y: the floor's screen y; + 0x10, clamped, is the depth
local queue_sprite_at_previous_floor 09A07C,09A080 r1 floor_y  # (projected_y + previous_floor_z) >> 16 - view_y; + 0x10, clamped, is the depth
local queue_sprite_at_previous_floor_2 09A174,09A180,09A18D r1 floor_depth  # (projected_y + previous_floor_z) >> 16 - view_y + 0x10, clamped to 0-FE
local swap_sprite_buffers 09A20B r0 swap_entry sprite_buffer_swap  # sprite_buffer_swaps + (sprite_buffer_flip & 6) * 2: the back buffer and the jump for the list just built
local swap_sprite_buffers 09A21F t1 status  # sprite_status, read and dropped ("test.b 500002")
local build_sprite_list 09A2A0 r0 flags_word  # sprite_flags widened; rotated left 6 and & 0x34 it picks the entry kind (jump table at 09A2E0)
local build_sprite_list 09A3AB,09A3B2 r1 part_scale  # sprite_scale (or 0x100) for a multi-part sprite: scales each part's size and offset (scale_x/scale_y are it negated per flip)
local build_sprite_list 09A31F,09A350,09A370,09A409,09A42A r2 source_width  # word 0 of the frame (or part) header: width in pixels; in the part loop it then becomes the packed size word (width >> 2, height) before the byte swap
local build_sprite_list 09A33A,09A353 r3 dest_width  # source_width * scale >> 8 (unscaled for kinds 3-4); OR'd with frame word 2 into the entry's width word
local build_sprite_list 09A33D,09A427 r5 dest_height  # (source height byte) * scale >> 8: the low byte of the entry's height word
local build_sprite_list 09A28E r7 scale_shift  # 0xF8 = -8: the shift/rotate count that undoes the 8.8 scale and swaps the size word's bytes
local build_sprite_list 09A3F6,09A471 r13 parts_left  # the part list's count (0x11 or more halts in int_default)
local build_sprite_list 09A28A r28 rounding  # 0x80: added before the >> 8 of every scaled size
local build_sprite_list 09A409 t1 part_width  # the part frame's word 0 (width)
local build_sprite_list 09A415 t1 part_height  # the part frame's word 1 (high byte kept, low byte = height)
local build_sprite_list 09A436 t1 width_bits  # the part frame's word 2: OR'd with the scaled width into the entry's width word
local load_palette_list 09A4CC r0 copy_mode  # the entry's copy mode (0 pen 0 + copy, 2 copy, 4 repeat, 6 as 0): jsr 9A4DF[PC](R0); also the pen 0 colour copy_palettes_with_pen_0 writes
local load_palette_list 09A4BC,09A4DA r13 entries_left  # the list's first halfword: number of entries
local copy_palettes_with_pen_0 09A4EC t1 pens_1_2  # colours 1-2 of the palette (one long)
local copy_palettes_with_pen_0 09A4F0 t1 pens_3_4  # colours 3-4
local copy_palettes_with_pen_0 09A4F4 t1 pens_5_6  # colours 5-6
local copy_palettes_with_pen_0 09A4F8 t1 pens_7_8  # colours 7-8
local copy_palettes_with_pen_0 09A4FC t1 pens_9_10  # colours 9-10
local copy_palettes_with_pen_0 09A500 t1 pens_11_12  # colours 11-12
local copy_palettes_with_pen_0 09A504 t1 pens_13_14  # colours 13-14
local copy_palettes_with_pen_0 09A508 t1 pen_15  # colour 15 (one halfword)
local copy_palettes_with_pen_0 09A50C t1 halfwords_left  # size before the - 15 of this palette: the loop goes on while it was over 15
local copy_palette_block 09A516 r27 dest_end  # where the MOVCU left off in palette RAM: handed back as dest
local copy_palette_block 09A516 r28 source_end  # where it left off in the source: handed back as source
local repeat_palette_block 09A52D,09A539 r2 repeats_left  # copies of the 0x10-colour palette still to make
local repeat_palette_block 09A530 r27 dest_end  # where each MOVCU left off: the next copy goes there
local repeat_palette_block 09A530 r28 source_end  # where the last MOVCU left off in the source: the next palette
local init_sound 09A577 t1 cnt_bits  # io_cnt read before setting bit 2 (sound CPU runs)
local init_sound 09A57F t1 port_d_bits  # io_port_d read before clearing bit 3
local queue_sound 09A5E3,09A5EA,09A5EC r1 write_index  # sound_queue_write: where the id goes; + 1 & 0x1F is stored back (and handed back)
local load_stage_palette 09A609 r1 halfword_count  # the stage_palettes record's second long: halfwords copied to PALETTE_RAM + 0xB800
local load_stage_palette 09A603,09A606 r9 palette_record  # stage_palettes + stage x 8 (a stage_palette record), + 4 past its source; handed back
local clear_bitmap_layer 09A707,09A712 r0 clip_right_bottom  # DF013F (right 19F with video_control bit 15): right and bottom of the bitmap clip, written to 31FF84
local clear_bitmap_layer 09A71F r1 window_top  # 0: set_bitmap_window's top
local get_bitmap_address 09A738 r2 x_shift  # bitmap_8bpp - 1: x >> 1 at 4 bpp, x at 8 bpp; + 9 is the y shift
local set_bitmap_window 09A785 t1 old_left  # left kept while left and right swap (right < left)
local set_bitmap_window 09A78D t1 old_top  # top kept while top and bottom swap
local draw_line 09A9AA r1 top_row  # end_y: an 8 bpp vertical line going up starts from its top end
local draw_line 09AA42,09AA53,09AA70,09AA86,09AA9D,09AAB7 r2 fraction_4bpp  # 4 bpp: the 0.16 accumulator; its carry steps the minor axis
local draw_line 09AA25 r3 low_nibble_color  # 4 bpp: color & 0xF, the even pixel's colour (color itself becomes the high nibble)
local draw_line 09A8E4,09AA3F r5 slope  # (minor + 1) << 16 / (major + 1): added to the accumulator every pixel (4 bpp shallow lines, 8 bpp)
local draw_line 09AA83 r6 steep_slope  # 4 bpp steep lines: (|dx| + 1) << 16 / (|dy| + 1), added every row
local draw_line 09A891,09A8A2,09A8B2 r7 direction_index  # 8 bpp: + 1 if dy < 0, + 2 if dx < 0: index of the step pairs at 09C4D2
local draw_line 09A8E7,09A8F0 r7 fraction_8bpp  # 8 bpp: the 0.16 accumulator; its carry adds the second step
local draw_line 09AA2B r7 high_nibble_mask  # 0xF0: keeps the odd pixel when the even one (low nibble) is written
local draw_line 09A9E8 t2 end_y_4bpp  # end_y (16 bits) for dy and its flags
local draw_line 09A9E8 t3 dy_4bpp  # end_y - start_y
local draw_line 09A9F9 t2 end_x_4bpp  # end_x (16 bits) for dx and its flags
local draw_line 09A9F9 t3 dx_4bpp  # end_x - start_x
local draw_line 09AAD9 t1 pixels_left  # 4 bpp diagonal: the pixel count before its decrement (1 = last pixel)
local draw_line 09A897 t2 end_y_8bpp  # end_y (16 bits) for dy and its flags
local draw_line 09A897 t3 dy_8bpp  # end_y - start_y
local draw_line 09A938 t2 span_end_x  # 8 bpp horizontal line: end_x, to get the span length
local draw_line 09A8A7 t2 end_x_8bpp  # end_x (16 bits) for dx and its flags
local draw_line 09A8A7 t3 dx_8bpp  # end_x - start_x
local draw_line 09A8D6 t1 saved_step  # the second step from the table, kept while the steps swap (|dx| < |dy|)
local draw_line 09A8D9 t1 saved_dy  # |dy|, kept while the lengths swap
local fill_span_8bpp 09A991 r3 tail_count  # length & 3 as a byte: 2-3 = a halfword, odd = a last byte
local fill_span_8bpp 09A94B t2 length_left  # length - 1 after an odd first pixel: negative = done
local fill_span_8bpp 09A962 t4 block_count  # (length + 1) >> 4: 16-pixel blocks (four longs each)
local fill_span_8bpp 09A991 t2 tail_pixels  # length & 3: pixels after the longs
local fill_span_4bpp 09AB08,09AB13,09AB25,09AB30,09AB56,09AB71 r1 pixel_address  # start_y * 0x100 + bitmap_address + x / 2: the byte written
local fill_span_4bpp 09AB86 r2 keep_mask  # -1 << (4 * pixels left): the nibbles of the last long that stay
local fill_span_4bpp 09AB6D,09AB74 r3 longs_left  # length >> 3: longs of 8 pixels
local fill_span_4bpp 09AAF4 t2 end_column  # end_x (16 bits) for the length and the swap test
local fill_span_4bpp 09AB28 t2 length_left  # length - 1 after the odd first pixel: negative = done
local fill_span_4bpp 09AB78 t2 tail_pixels  # length & 7: pixels after the longs
local fill_span_4bpp 09AB3A t1 pixels_before_byte  # the pixel count before the two-pixel byte at an odd address: 1 = only one pixel left
local fill_span_4bpp 09AB59 t1 pixels_before_second  # the count after that byte's first pixel: 1 = the byte ends the span
local draw_vertical_span_4bpp 09AB9F,09ABAC,09ABDA,09ABE9 r1 pixel_address  # start_y * 0x100 + x / 2 + bitmap_address; + row_step per row
local draw_vertical_span_4bpp 09ABB3,09ABC1 r2 keep_mask  # 0xF0 (even x) or 0x0F (odd x): the nibble that stays
local fill_rect_unclipped 09AF3E,09AF4B,09AF6D,09AF93,09AFD4,09B002,09B025,09B047,09B04D,09B053,09B085,09B091,09B0C6,09B102,09B134,09B15B,09B17E r1 pixel_address  # top * 0x200 + left (8 bpp) or top * 0x100 + left / 2 (4 bpp) + bitmap_address: the next column to fill
local fill_rect_unclipped 09B0A6 t2 columns_left  # 8 bpp: width - 1 after the odd first column: negative = done
local fill_rect_unclipped 09B0B4 t4 block_count  # 8 bpp: (width + 1) >> 4: 16-pixel blocks per row
local fill_rect_unclipped 09B14E t2 tail_columns  # 8 bpp: width & 3: columns after the longs
local fill_rect_unclipped 09AF2F t2 bottom_row  # 4 bpp: bottom (16 bits): equal to top = a horizontal span
local fill_rect_unclipped 09AF38 t2 right_column  # 4 bpp: right (16 bits): equal to left = a vertical span
local fill_rect_unclipped 09AF57 t1 rect_dx  # 4 bpp: right - left: 1 = two pixels wide
local fill_rect_unclipped 09AFD7 t2 tail_pixels  # 4 bpp: width & 3: pixels after the halfwords
local get_window_outcode 09C383 r0 point_x  # the old end x, now the start: compared with bitmap_window_left/right (outcode bits 1, 2)
local get_window_outcode 09C386 r1 point_y  # the old end y, now the start: compared with bitmap_window_top/bottom (bits 4, 8)
local get_window_outcode 09C383 r2 old_start_x  # the old start x, handed back as the end (xch R2, R0)
local get_window_outcode 09C386 r3 old_start_y  # the old start y, handed back as the end (xch R3, R1)
local get_window_outcode 09C383 t1 old_end_x  # end_x kept for the swap
local get_window_outcode 09C386 t1 old_end_y  # end_y kept for the swap
local clip_line_to_window 09C3C3 t1 old_start_x  # start_x kept while the two ends swap (xch R0, R2)
local clip_line_to_window 09C3C6 t1 old_start_y  # start_y kept while the two ends swap (xch R1, R3)
local clip_line_at_x 09C40F r6 line_dx  # end_x - start_x: the divisor (no change to y when 0)
local clip_line_at_x 09C418 r7 dy_scaled  # (end_y - start_y) * dx: / line_dx is the y change at the edge
local clip_line_at_y 09C451 r6 dx_scaled  # (end_x - start_x) * dy: / line_dy is the x change at the edge
local clip_line_at_y 09C44E r7 line_dy  # end_y - start_y: the divisor (no change to x when 0)
local clip_rect_to_window 09C479,09C4A3 r5 window_min  # bitmap_window_left, then bitmap_window_top
local clip_rect_to_window 09C47E,09C4A8 r6 window_max  # bitmap_window_right, then bitmap_window_bottom
local clip_rect_to_window 09C46B t1 old_left  # left kept while left and right swap
local clip_rect_to_window 09C473 t1 old_top  # top kept while top and bottom swap
local attract_warning 09C528 r0 new_mode  # 1: set_screen_mode's mode
local attract_warning 09C50D t1 cnt_bits  # io_cnt read before clearing bit 1 (display off)
local load_settings 09C6DD t1 port_f_bits  # io_port_f: bit 0 (DIP 1) copied to moving_seat_dip
local eeprom_read_all 09C72D,09C73C r5 eeprom_words_left  # 0x40 EEPROM words
local eeprom_read_all 09C724,09C739 r11 word_dest  # walks eeprom_magic onwards: each word read goes there
local eeprom_save 09C77F,09C78F r10 word_source  # walks eeprom_magic onwards: each word written
local eeprom_read_word 09C7B4 r2 clocked_lines  # lines ^ 0x40: the lines with the clock flipped for the second half of each bit
local eeprom_read_word 09C7A7,09C7BF r3 bits_left  # 0x12 clocks: the data bits shift into data
local eeprom_write_word 09C7D4,09C7F5,09C7F9,09C80A r3 clocks_left  # 0x10 data bits, then 2 more clocks
local eeprom_write_word 09C7CB,09C7D1,09C7DF r6 data_bits  # the word, bytes swapped, rotated left a bit per clock: its bit 7 goes out on the data line
local eeprom_send_command 09C82B,09C85E r1 command_bits  # the command, shifted left a bit per clock: bit 7 goes out on the data line
local eeprom_send_command 09C858,09C876 r3 bits_left  # 8 command bits
local compute_eeprom_crc 09C8B8,09C8C3,09C8CB,09C8D6 r1 bits_left  # 8 bits per byte (and 8 more at the end)
local start_test_mode 09C8EC t1 cnt_bits  # io_cnt read before clearing bit 1 (display off)
local start_test_mode 09C931 t1 port_d_bits  # io_port_d read before clearing bit 3
local start_test_mode 09C9A1 t1 seat_port_bits  # io_port_d read before clearing bits 2-3 (the moving seat outputs)
local draw_test_menu 09CA7C r11 print_position  # TEXT_CELL(31, 26) for the write count; otherwise the position clear_text_layer and print_text_list leave
local test_memory 09CBCA t1 cnt_bits  # io_cnt read before clearing bit 1 (display off)
local check_rom_checksums 09CD5C r10 stored_crc rom_chip_checksums  # "cmp.h [R10+], R0" (09CD5C) from 0FF08C (09CD41 "mov.w #FF08C, R10", the mirror of rom_checksums): past rom_checksums[0], at rom_checksum_ic8 (IC8, IC18, IC9)
local check_rom_checksums 09CD7C,09CD9C r10 stored_crc  # walks on ([R10+])
local check_rom_checksums 09CD7C t2 ic8_crc  # rom_checksums[1]: compared with the CRC of 080000-09EFFF (memory_test_errors bit 2)
local check_rom_checksums 09CD9C t2 ic18_crc  # rom_checksums[2]: compared with the even-byte CRC at 100000 (bit 1)
local test_work_ram 09CDEC,09CE13,09CE30,09CE6F r4 pattern_index  # 4 down to 1: the pattern address at 09ED5C + 4 * it
local test_work_ram 09CDFA,09CE0F,09CE3E,09CE6B r5 bytes_left  # 0xF9 bytes per pattern
local test_work_ram 09CDE5,09CE17,09CE29,09CE73 r6 passes_left  # 0x42 passes of the four patterns (stops at 210000)
local test_work_ram 09CDF7,09CE01,09CE3B,09CE45 r10 pattern  # the pattern's bytes: written, then compared
local test_work_ram 09CDDE,09CE01,09CE22,09CE45 r11 ram_cursor  # walks 200000-20FFFF
local test_work_ram 09CE45 t1 expected  # the pattern byte
local test_work_ram 09CE45 t2 actual  # the byte read back
local test_video_ram 09CEA2,09CEC9,09CEDF,09CF7F r4 pattern_index  # 4 down to 1: the pattern address at 09ED5C + 4 * it
local test_video_ram 09CEB0,09CEC5,09CEED,09CF7B r5 bytes_left  # 0xF9 bytes per pattern
local test_video_ram 09CE9B,09CECD,09CED8,09CF83 r6 passes_left  # 0x84 passes of the four patterns (stops at 320000)
local test_video_ram 09CEAD,09CEB7,09CEEA,09CEF4 r10 pattern  # the pattern's bytes: written, then compared
local test_video_ram 09CE94,09CEB7,09CED1,09CEF4 r11 ram_cursor  # walks 300000-31FFFF
local test_video_ram 09CEF4 t1 expected  # the pattern byte
local test_video_ram 09CEF4 t2 actual  # the byte read back
local test_sprite_ram 09CF96,09CFBD,09CFD3,09D06F r4 pattern_index  # 4 down to 1: the pattern address at 09ED5C + 4 * it
local test_sprite_ram 09CFA4,09CFB9,09CFE1,09D06B r5 bytes_left  # 0xF9 bytes per pattern
local test_sprite_ram 09CF8F,09CFC1,09CFCC,09D073 r6 passes_left  # 0x84 passes of the four patterns (stops at 420000)
local test_sprite_ram 09CFA1,09CFAB,09CFDE,09CFE8 r10 pattern  # the pattern's bytes: written, then compared
local test_sprite_ram 09CF88,09CFAB,09CFC5,09CFE8 r11 ram_cursor  # walks 400000-41FFFF
local test_sprite_ram 09CFE8 t1 expected  # the pattern byte
local test_sprite_ram 09CFE8 t2 actual  # the byte read back
local test_palette_ram 09D082,09D0A9,09D0BB,09D121,09D133,09D15A,09D16C,09D1DA r4 pattern_index  # 4 down to 1: the pattern address at 09ED5C + 4 * it
local test_palette_ram 09D090,09D0A5,09D0C9,09D11D,09D141,09D156,09D17A,09D1D6 r5 halfwords_left  # 0xF9 halfwords per pattern
local test_palette_ram 09D07F,09D0AD,09D0B8,09D125,09D130,09D15E,09D169,09D1DE r6 passes_left  # 8 passes of the four patterns per half
local test_palette_ram 09D08D,09D0C6,09D13E,09D177 r10 pattern ram_test_pattern_words  # "mov.w [R10], R10": the pattern's address from ram_test_patterns, read here as halfwords
local test_palette_ram 09D097,09D0D0,09D148,09D181 r10 pattern  # the pattern's halfwords: written, then compared ([R10+])
local test_palette_ram 09D078,09D097,09D0B1,09D0D0,09D129,09D148,09D162,09D181 r11 ram_cursor  # walks 600000-603FFF, then 604000-607FFF
local test_palette_ram 09D0D0 t1 expected_first  # first half: the pattern halfword
local test_palette_ram 09D0D0 t2 actual_first  # first half: the halfword read back
local test_palette_ram 09D181 t1 expected_second  # second half: the pattern halfword
local test_palette_ram 09D181 t2 actual_second  # second half: the halfword read back
local test_sound_ram 09D202,09D229,09D23B,09D26B r4 pattern_index  # 4 down to 1: the pattern address at 09ED5C + 4 * it
local test_sound_ram 09D210,09D225,09D249,09D267 r5 bytes_left  # 0xF9 bytes per pattern
local test_sound_ram 09D1FF,09D22D,09D238,09D26F r6 passes_left  # 9 passes of the four patterns (stops at 702000)
local test_sound_ram 09D20D,09D217,09D246,09D250 r10 pattern  # the pattern's bytes: written, then compared
local test_sound_ram 09D1F8,09D217,09D231,09D250 r11 ram_cursor  # walks 700000-701FFF
local test_sound_ram 09D250 t1 expected  # the pattern byte
local test_sound_ram 09D250 t2 actual  # the byte read back
local test_inputs 09D4C0 r3 input_index  # count - 1: index of the input's bit number and its ON/OFF text
local test_inputs 09D456,09D475,09D490,09D4AF r5 on_texts  # input_test_on_texts_* for the cabinet: texts shown when the input is active
local test_inputs 09D45D,09D47C,09D497,09D4B6 r6 off_texts  # input_test_off_texts_*: texts shown when it is not
local test_inputs 09D44F,09D46E,09D489,09D4A8 r7 input_bits input_test_bit_numbers  # input_test_bits_*: each input's bit in input_ports_active
elements input_test_bit_numbers u8    # an input_test_bits_* table: "movea.b [R7](R3), R10; movz.bw [R10], R10" (09D4C2, 09D4C6), the bit "test1 R10, 701C[R25]" tests in input_ports_active; census ROM 117080-117090 (07 12 04 10 05 18 00 0C 01 0D 12 10 18 00 0C 01 0D)
local test_crt 09D997,09D9B0,09D9C0,09D9EA,09D9EE,09DA18 r8 lines_left  # 4 border lines, 0x1C horizontal, 0x28 vertical; draw_line may change it when it clips (its R8)
local test_crt 09D8DC r12 bar_record crt_test_color_bar_row  # "movea.b 115D3F, R12": crt_test_color_bars, the first row's record
local test_crt 09D8E6,09D8EC,09D8F2 r12 bar_record  # walks crt_test_color_bars ([R12+]): x, y, first colour per row of bars; the third leaves it at the next row
struct crt_test_color_bar_row 5 rom   # a row of C.R.T. TEST 1/2's colour bars (crt_test_color_bars 115D3F, 4 rows): test_crt reads it with [R12+] (09D8E6-09D8F2); census ROM 115D3F-115D52
field crt_test_color_bar_row 00 x u16           # "mov.h [R12+], 4A29[R25]" (09D8E6): crt_test_x, the first bar's left (30)
field crt_test_color_bar_row 02 y u16           # "mov.h [R12+], 4A2B[R25]" (09D8EC): crt_test_y, the row's top (38, 58, 78, 98)
field crt_test_color_bar_row 04 first_color u8  # "mov.b [R12+], 4A2D[R25]" (09D8F2): crt_test_color of the first bar, + 1 per bar (10, 30, 50, 70)
local test_game_assignments 09DAF6 r0 cabinet_type  # setting_cabinet_type: & 1 picks cabinet_type_texts
local test_coin_assignments 09DFEE r11 print_position  # the text position of the prints; TEXT_CELL(31, 9) for the setting number (text_position is used for the other numbers)
local apply_coin_setting 09E24C,09E256,09E25B,09E296 r0 coin_setting  # setting_coin_number + 1 or individual_coin_setting_numbers[...]: - 1 indexes coin_setting_values
local apply_coin_setting 09E29B,09E2A3,09E2A9 r10 values_record  # coin_setting_values + (setting - 1) * 4: coins per credit, bonus adder, chute 1 and 2 multipliers
local draw_coin_credit_cell 09E638 r0 coin_count  # coins (16 bits): times the multiplier
local draw_coin_credit_cell 09E65D,09E66E,09E67F r0 credits_fixed  # credits for those coins in 16.16; then only its fraction (divided into a numerator)
local draw_coin_credit_cell 09E6BE r5 column  # the cell's column byte: TEXT_CELL(column, row)
local test_bookkeeping 09E7C0 r2 chute_3_coins  # bookkeeping +8: chute 3's coins, added to the total when that chute exists
local test_outputs 09EB76 t1 port_d_bits  # io_port_d read before clearing bits 2-3 (both seat outputs off)
local test_outputs 09EBAB t1 port_d_bits_1p  # io_port_d read before setting bit 2 (1P seat)
local test_outputs 09EBD4 t1 port_d_bits_2p  # io_port_d read before setting bit 3 (2P seat)
local draw_blinking_test_cursor 09ECC7 t2 blink_before  # test_cursor_blink before its decrement: under 1 draws a space
local check_moving_seat_dip 09ED84 r0 seat_dip_on  # (io_port_f & 1) ^ 1: DIP 1 on; compared with moving_seat_dip, copied to setting_moving_seat
local check_moving_seat_dip 09ED77 r11 dip_flag_address  # moving_seat_dip (701FFE): where the new state is written
local check_moving_seat_dip 09ED70 t1 port_f_bits  # io_port_f
local irq_vblank_start 0FF534 t3 frames_left  # display_off_frames - 1: 0 sets io_cnt bit 1 (display on)
local irq_vblank_start 0FF53C t1 cnt_bits  # io_cnt read before setting bit 1
local run_vblank_copies 0FF5DC r1 copy_halfwords  # the entry's halfword count (+8)
local run_vblank_copies 0FF5E2,0FF5EB r5 repeats_left  # the destination's top byte: how many copies, each continuing where the last ended
local run_vblank_copies 0FF5E5 r27 dest_end  # where the MOVCU left off: the next copy goes there
local run_vblank_copies 0FF5EB t1 repeats_before  # repeats_left before its decrement: 1 or less ends the entry
local send_sound_command 0FF602 r0 queue_base  # sound_queue (the asm's [R0+] walk past the read and write indexes)
local send_sound_command 0FF5FF r1 read_index  # sound_queue_read; + 1 & 0x1F is stored back
local send_sound_command 0FF60B r10 command_entry sound_command_bytes  # sound_commands + id * 2: the group and command bytes sent
struct sound_command_bytes 2 rom      # an entry of sound_commands (115A32) by sound id, as send_sound_command sends it: "movea.h 115A32(R10), R10" (09F60B), then "mov.b [R10+], 700701 / 700700" (09F613, 09F61B); census ROM 115A32-115B31
field sound_command_bytes 00 group u8   # -> sound_command_group (700701): 80 control, 81 music, 82 effects, 83 a third set
field sound_command_bytes 01 number u8  # -> sound_command (700700): the number in the group
local count_frames_and_check_test_switch 0FF660 t2 old_frames  # power_on_frames before + 1: its carry bumps power_on_frames_high
local count_frames_and_check_test_switch 0FF66A t2 old_divider  # power_time_save_divider before - 4: under 4 saves the power-on time
local apply_settings 0FF6CF r0 vitality  # setting_initial_vitality: & 3 indexes starting_health_by_vitality
local apply_settings 0FF739 r0 credit_cap  # 0x900: at most 9 credits restored
local apply_settings 0FF74D,0FF750,0FF75A r1 credits  # each saved halfword & 0xFF00 (credits; coins dropped), capped
local apply_settings 0FF74A,0FF760 r2 credit_blocks_left  # 3 credit blocks
local apply_settings 0FF73E,0FF74D r12 saved_cursor  # walks saved_credits (701F2E)
local update_coins 0FF76A r0 changed_flag  # 0: written to byte 3 ("changed") of each credit block every frame
local update_coins 0FF7FC r6 coin_inputs  # inputs_released & 0x4C: coins 1 (4), 2 (8) and 3 (0x40)
local update_coins 0FF76D,0FF778 r8 credit_blocks_left  # 3 credit blocks
local update_coins 0FF7FC t2 coins_released  # inputs_released & 0x4C, tested before it goes to coin_inputs
local pulse_coin_counter 0FF8DA r0 pulse_timers  # the counter's halfword: on frames (low byte), off frames (high byte); 0 = idle
local pulse_coin_counter 0FF8FD t2 port_word_before_on  # the long at io_port_d before its counter bit is set (set1 on a word)
local pulse_coin_counter 0FF8E5 t2 on_frames  # the on frames before their decrement: 1 = switch the counter off
local pulse_coin_counter 0FF8EA t2 port_word_before_off  # the long at io_port_d before its counter bit is cleared
local add_coin 0FF959 r1 chute_multiplier  # the chute's multiplier (chute_1_multiplier + chute_offset): coins counted per coin
local add_coin 0FF95F r2 coins_per_bonus  # bonus_adder: every so many coins add a free one
local add_coin 0FF97F r2 coin_cost  # credit_coin_cost: coins per credit
local add_coin 0FF96C,0FF976,0FF987,0FF98F r3 coin_tally  # bonus coins + multiplier (less coins_per_bonus per free coin), then coins + multiplier (less coin_cost per credit)
local add_coin 0FF946 t1 counter_pending  # GAME_VARIABLES + pending_offset: coin_counter_1_pending or coin_counter_2_pending, + 1 (pulse_coin_counter counts it down)
local load_bookkeeping 0FFA15,0FFA3D r2 copy_source  # move_halfwords' source: the main block or bookkeeping_backup
local load_bookkeeping 0FFA12,0FFA36 r3 copy_dest  # move_halfwords' destination: the backup (data after the CRC) or bookkeeping_coin_credits
local reset_bookkeeping 0FFA88 r0 rom_stamp  # rom_checksums[0] + [1]: written to bookkeeping_rom_stamp and its backup
local compute_crc16 0FFAC4,0FFACF,0FFAD7,0FFAE2 r1 bits_left  # 8 bits per byte (and 8 more at the end)
local compute_crc16_alternate_bytes 0FFAF2,0FFAF9,0FFB01,0FFB06,0FFB14,0FFB19 r0 shift_register  # seeded DEBDEB00, a byte at a time in the low byte, shifted with polynomial; >> 16 is the CRC
local compute_crc16_alternate_bytes 0FFAFE,0FFB09,0FFB11,0FFB1C r1 bits_left  # 8 bits per byte (and 8 more at the end)
local compute_crc16_alternate_bytes 0FFAEB r4 crc_polynomial  # 0x10210000
local add_play_time 0FFB2C r0 game_time  # game_frames: added to bookkeeping_play_time
local add_play_time 0FFB25 r11 bookkeeping_base  # bookkeeping_coin_credits (701F02): +1A is the play time's high halfword
local add_play_time 0FFB31 t3 play_time  # bookkeeping_play_time before the add: the carry bumps +1A
local record_player_game 0FFB6B r1 player_time  # game_frames - the player's join_time: added to the total, kept as longest (+C) and shortest (+22)
local record_player_game 0FFB40,0FFB55 r8 players_left  # 3 player_status bytes
local record_player_game 0FFB70 t3 total_time  # bookkeeping_player_time before the add: the carry bumps +20
param draw_attract_credits R10 message_text  # blink_start_prompt's text, two_credits_one_play_text for print_text; draw_credit_count may leave a credit block there
local blink_start_prompt 0FFD17 r0 credits  # the block's credits (+1): compared with credits_per_start
local blink_start_prompt 0FFD75 t1 port_d_bits  # io_port_d read before clearing bits 2-3
local draw_credit_count 0FFD90,0FFD9D,0FFDAE r0 drawn_bit  # 0xF, 0x12 or 0x13 for the credit block: its bit in stage_init_flags
local draw_credit_count 0FFDB5 t3 old_flags  # stage_init_flags before the bit is set: clear = draw now
local draw_credit_count_now 0FFDEA,0FFDED,0FFDF3 r0 tens  # the credit count over 10 (in place), then shifted to the high nibble; its high byte is the attribute print_text_here left
local draw_credit_count_now 0FFDF3 t4 tens_nibble  # tens << 4: 0 blanks the tens cell

# ======== tables the C still read by address (13-10-2026) ========
09ED5C  table ram_test_patterns u32 ram_test_pattern_bytes      # entries 1-4 (0 is 0): ROM data test_work_ram copies over the work RAM, 249 bytes at a time, and reads back
elements ram_test_pattern_bytes u8   # what a ram_test_patterns entry points at, walked a byte at a time: "mov.b [R10+], [R11+]" (test_work_ram 09CE01, test_video_ram 09CEB7, test_sprite_ram 09CFAB, test_sound_ram 09D217), "cmp.b [R10+], [R11+]" and "-1[R10]" in their verify steps (09CE45, 09CEF4, 09CFE8, 09D250); 0xF9 bytes of sine_table, cosine_table, sine_of_300 or cosine_of_300 (census ROM 101632-10172A, 101832-10192A, 101C32-101D2A, 101E32-101F2A)
elements ram_test_pattern_words u16  # the same patterns walked a halfword at a time by the palette RAM test: "mov.h [R10+], [R11+]" (test_palette_ram 09D097, test_palette_ram_fill_second_half 09D148), "cmp.h [R10+], [R11+]" then "mov.h -2[R10], R0" (09D0D0/09D0D7, 09D181/09D188); 0xF9 halfwords from each (census ROM 101632-101823, 101832-101A23, 101C32-101E23, 101E32-102023)
098908  table percent_digit_sprites u16      # sprite frames of the digits 0-9 (9DE8, then + 8): the ring tally's percent (ring_tally_percent_view)
09036F  table chooser_voice_sounds chooser_voice_row  # [character x 4 + random 0-3]: the sound the character chooser plays for the one chosen (Sonic 50 5D 48 53, Ray 6F 4B 68 4F, Mighty 62 49 67 6C)
struct chooser_voice_row 4 rom  # a character's four voices: the chooser plays chooser_voice_sounds + character x 4 + random 0-3
field chooser_voice_row 00 sounds u8[4]  # the sound ids (Sonic 50 5D 48 53, Ray 6F 4B 68 4F, Mighty 62 49 67 6C)
08E2E1  table distance_to_player_offsets u16  # 22 24 26: R25 offsets of distance_to_player_1..3 (face_nearest_player stores through it)
# memory_test_errors' low byte has a second use in a game: the scroll code's count of frames a player spends at the back edge
scoped global update_scroll 20CA50 back_edge_frames u8     # +1 a frame while a player is at the back edge (016221/01622B); at 0x40 follow_base_speed = 6
scoped global advance_scroll 20CA50 back_edge_frames u8    # cleared at every new section (016AA2)
scoped global init_stage_view 20CA50 back_edge_frames u8   # cleared at the stage start (015D0B)
# player animations in the code area after 009BF6 (a count, then frames; player_animate picks Sonic's, Ray's or Mighty's by object kind)
009C05  data  floor_8_idle_animation_sonic       # state 0x25 (floor type 8 sequence) while escape_progress is 0
009C1D  data  floor_8_idle_animation_ray
009C35  data  floor_8_idle_animation_mighty
009C13  data  floor_8_escape_animation_sonic     # state 0x25: the frame by escape_progress >> 8 (at most the count)
009C2B  data  floor_8_escape_animation_ray
009C43  data  floor_8_escape_animation_mighty
009C4D  data  flail_animation_sonic              # 7 steps, mirrored on the way back; probably flailing: the drift, swept-away and tripped states use it
009C6F  data  flail_animation_ray
009C91  data  flail_animation_mighty

# ======== local variables, second round (local lines, from the helpers) ========
local boot 000022,00002D r1 pairs_left  # 8 down to 0: the loop copying byte pairs from 00007E to E00000 (dbr R1)
local boot 00001B,000025,000029 r11 dest  # E00000 on: where the first copy loop writes its byte pairs (unmapped on this board)
local boot 000031,00003B r11 dest  # D00000 on: the irq_vectors registers the second copy loop fills
local boot 000038,00003F r1 bytes_left  # 0xC down to 0: the loop copying 12 bytes to the interrupt controller at D00000
local run_game_mode 0014BA r0 fill_word  # stage select: 0, written as four longwords per round over VIDEO_RAM + 0xE000
local run_game_mode 0014AC,0014BD,0014C0,0014C3,0014C6 r10 clear_dest  # stage select: VIDEO_RAM + 0xE000 on, 16 bytes a round for 0xE00 rounds
local run_game_mode 09404A,09405C r10 player_object  # game mode 19: the player's object (player block +14, low 16 bits over 0x20xxxx); its x_int > 0x17C closes the barred gate
local run_game_mode 0952EE,0952FA,0952FE r10 finished_flags  # stage exit: players_finished as one longword, a byte forced to FF per player whose player_status is not negative, rotated per player; all FF = everyone done
local run_game_mode 000AEE,000B01,000B14 r10 intro_status_cursor  # game mode 1: walks player_2_status/player_3_status ([R10+] at 000AEE/000B01/000B14); handed back
local run_game_mode 000C82 r10 count_text  # string record 25C4: erase_text_here clears the continue count digits at TEXT_CELL(24, 14)
local run_game_mode 094F86 r11 scroll_script event_1a_scroll_step  # game mode 1A: event_1a_scroll_script + scene_step * 6 ("movea.h 9593E[PC](R0), R11", R0 = 3 x scene_step): the scroll position to reach, then the scroll mode and speed
local run_game_mode 094F94,094F97,094F9D,094FBF,094FC2,094FC8 r11 scroll_script  # moved through the record ("add.w #2, R11", [R11+])
local run_game_mode 0952E9,095302 r11 exit_status_cursor  # stage exit: walks player_status, one byte per player
local run_game_mode 094052,094070 r8 players_left  # game mode 19: 3 down to 0, one round per player
local update_player_summary 001AA6,001AAE r10 status_cursor  # walks player_status looking for 0x81 (a player dropping in from above)
local update_player_summary 001B00,001B08 r10 status_cursor  # walks player_status looking for a negative status (someone in play)
local update_player_summary 001AAB,001AB3 r8 players_left  # 3 down: the loop looking for a dropping-in player
local update_player_summary 001B05,001B0A r8 players_left  # 3 down: the loop looking for a player in play
local update_player_summary 001B1F,001B3A r8 players_left  # 3 down: the loop over the player blocks
local continue_to_next_stage_slot 001B93 r0 from_block  # first word of the stage's stage_link_points record: offset of the floor block the stage leaves from (stage_link_from_block)
local continue_to_next_stage_slot 001BB6 r0 current_stage  # stage, + 1 indexes the next stage's stage_floor_blocks
local continue_to_next_stage_slot 001BC7 r0 to_block  # second word of the link record: offset of the next stage's floor block (stage_link_to_block)
local continue_to_next_stage_slot 001B88 r10 link_point stage_link_point  # stage_link_points + stage_link_points[stage]: the from and to block offsets
local continue_to_next_stage_slot 001B96 r11 from_record floor_block  # floor_blocks + 2 + from_block: the block's x (+0), y (+8) and height (+0x10) go to stage_link_from_*
local continue_to_next_stage_slot 001BCA r11 block floor_block  # the next stage's floor_blocks + 2 + to_block ("add.w R0, R11"): the block it reaches, its x (+0), y (+8) and height (+0x10) go to stage_link_to_*
local reset_objects_from_list 001D9E,001DAD r3 player_number  # 1 or 2 (player block 2 or 3): indexes player_status for the objects after the first; reset_objects' R3 lands in it first; handed back
local check_hit 001FD6 r0 slope_kind  # first byte of the floor_slopes record x 2: 0/2/8 take the slope along dy, 4/6 along dx (switch at 001FDE)
local check_hit 001FF5,001FFD r0 slope_factor  # the slope record's longword (+2), made positive: scales the slope correction
local check_hit 002015,00201C,002023 r0 quadrant  # 2 if dx < 0, + 1 if dy < 0: picks whether the correction is added to dx or dy
local compute_bridge_sag 002240,002252,002255 r0 curve_index  # a load entry's plank, mirrored past the middle (plank_total - plank): index into bridge_sag_curve
local compute_bridge_sag 00225F,002267,00226A r0 next_curve_index  # the next load entry's plank, mirrored past the middle: the sag at the far end of the span
local compute_bridge_sag 00229B r0 fill_word  # 0: written over the sag table when only the two end loads are on the bridge
local compute_bridge_sag 00220E,002220 r10 scan  # the entries after slot, compared against the smallest so far (selection sort of the load list)
local compute_bridge_sag 002200,002203,002226 r11 slot  # load_list + 1 on: the entry the sort fills with the smallest remaining plank
local compute_bridge_sag 002238 r11 sag_curve bridge_sag_depths  # bridge_sag_curve: sag per plank from the bridge end; handed back
local compute_bridge_sag 002298,00229E r11 clear_dest  # the sag table (R18) on, 4 bytes a round when the bridge carries nothing
local compute_bridge_sag 00225C,00227A,002286 r3 span  # the next load's plank, then the planks between the two loads: divides the sag difference and counts the writes
local compute_bridge_sag 002217 r4 swapped  # the previous smallest, written where the new smallest was found
local compute_bridge_sag 002208,002222 r8 compares_left  # passes_left down: entries left to compare in one sort pass
local player_start 002872,0028A3 r8 records_left  # 2 down: the two 8-halfword records written from player block + F2 (5000, E0, 0, 140, then zeros)
local player_state_falling_off_edge 003C9C,003CAC r0 x_shift  # 0x18, 0x14 for the later animation tables (>= 1128EE): subtracted from projected_x
local player_state_over_edge 003EE1,003EFA r0 slip_limit  # 0x20000000 (/4 on floor type 9 or with flags bit 17): the slip_distance that ends the slide
local player_state_over_edge 0040D2,0040DD r0 new_status  # FF, or 1 when health <= 8; stored in player_status
local player_state_climbing 004B97,004BAA r0 step_target  # the z after a climb step (z +- 0x18), then its screen y: past climb_base_z or off 0x10..0xD0 blocks the step
local player_state_climbing 004BD7,004BDF,004BE7 r0 climb_speed  # |speed_y_int| capped at 3: 4 minus it is the climb animation speed
local player_state_climbing 004C56 r0 screen_shift  # attribute record +4 x z_step: added to screen_x (sign by wall side) and half of it to projected_y
local player_state_climbing 004E0B,004E14 r0 new_heading  # 0x80 or 0x380 by the wall's axis: target_heading after climbing over
local player_animate_skidding 004F1F,004F23 r0 skid_facing  # facing + 8 (the opposite of the 16 directions): picks the skid animation
local player_animate_skidding 004F4C,004F57 r0 speed_slowdown  # speed / 8 (/32 on floor type 7): skid_speed_slowdown
local player_animate_skidding 004F63,004F6E r0 ground_slowdown  # ground_speed / 8 (/32 on floor type 7): skid_slowdown
local player_state_dying 005303,00530D,005319 r0 character_frame  # sprite frame by character: 1B50 / 5270 / 36B8
local player_state_hurt 005766,005783 r0 gravity  # hurt flight: 0x6000 (/4 on floor type 4 unless the hitter is kind 0x27) added to speed_z
local player_state_bouncing 005CCB,005CD9 r0 gravity  # bounce: 0x8000 (/4 on floor type 4) added to speed_z
local player_state_bouncing 005D20 r0 rebound_speed  # speed_z / 2 on landing a bounce: speed_z and bounce_speed_z become -it, bounce_step 2/3 of it
local player_state_bouncing 005DD2 r0 new_heading  # target_heading + 0x200 (turned around): heading, facing_angle and set_facing
local player_state_out_of_game 005E77 r0 player_number  # the object's player: indexes hud_game_over_positions_3p/2p for the GAME OVER text
local player_state_continue 005F73 r0 player_number  # the object's player: indexes hud_continue_positions_3p/2p for the continue text
local player_state_continue 005FA3,005FAC,005FB7 r0 cabinet_event  # 0, 2 or 4 by player: written to cabinet_events
local player_state_continue 005FC1,005FC9,005FD3 r0 character_bit  # 0, 1 or 2 by character: the bit cleared in characters_taken
local player_state_in_zone 006223 r0 speed_diff  # target speed (trackball magnitude squared, capped) - speed: added whole when speeding up, / 32 when slowing
local player_state_tumbling 00672F,006739 r0 pose_frame  # 5, or 6 while speed_z_int >= 7: the animation frame held in the air
local player_state_opening_scene 006CF1,006CFC r0 shake  # byte 1 of stage_06_shake, negated by stage_06_shake_sign: added to screen_y
local player_state_opening_scene 006D95,006D9F,006DAB r0 character_frame  # scene step 3: sprite frame by character, 1B58 / 5278 / 36C0
local player_state_opening_scene 006DB4,006DC2,006DD4 r0 width  # scene step 3: sprite_width 0 / -0x20 / 0x20 by player
local player_state_stage_scene 007097,0070A0,0070AD r0 y_shift  # 0 / 0x20 / -0x20 by player: added to y_int at the geyser start
local player_state_stage_scene 007141,007149,007157 r0 x_shift  # geyser ride: 0, -1.0 or +1.0 by character, subtracted from screen_x each frame
local player_state_stage_exit 007570 r0 goal_dx  # 0x1225 - x_int: with goal_dy, the distance to the point that ends the goal run (squared sum <= 0x6400)
local player_state_trapped 00830B,008315,008321 r0 character_frame  # sprite frame by character, B4E8 / B580 / B468 (then only handed to face_movement_direction)
local player_state_riding 00853F,008549,008555 r0 character_frame  # sprite frame by character, B2B8 / B288 / B2E8
local player_animate_falling 0034CA,0034D5 r0 new_status  # falling off a ledge: FF, or 1 when health <= 8; stored in player_status
local player_state_climbing_object 008961,008971 r0 x_shift  # 0x18, 0x14 for the later animation tables (>= 1128EE): subtracted from projected_x
local player_state_pulled 008D3C r0 pull_distance  # (0x5D00000 - x) >> 12: pull_start_distance; (0x1200 - it) >> 5 is pull_distance_factor
local player_state_pulled 008EDC r0 new_heading  # heading + 0x200 (turned around): target_heading and facing_angle
local player_state_floor_8_sequence 0091E7 r0 drift_direction  # scroll_drift_direction: sine/cosine index of the drift added to x and y
local player_state_caught 00A14E r11 effect object.player_effect  # spawn_player_effect's effect (substate 2): palette bit 0x4000 cleared, depth_offset 0x70
local player_state_caught 00A1DF r0 sound_number  # 0x87 for play_sound, then only handed to find_floor
local player_state_sinking 00A242,00A24B r0 edge_direction  # ledge_side, or ledge_direction when it is 4: which way the player is moved 0xC off the edge
local player_state_sinking 00A27A,00A295 r0 sink_z  # 0x1D80000 on floor blocks of kind 0x22, else 0x200000: floor_z and z of the sinking player
local player_state_knocked_off 00A49E r0 hit_direction  # the hitter's direction, copied to the player: picks how the knockback speed goes into speed_x/speed_y
local player_animate_jumping 00360B,003612 r0 new_attack_power  # 4 (constant: the test of #4 never takes the 1); stored in attack_power, sound 1F when it was not 4 already
local player_state_knocked_off 00A566,00A574 r0 gravity  # 0x6000 (/4 on floor type 4) added to speed_z
local player_animate_jumping 0036E8,0036F6 r0 gravity  # jump: 0x9000 (0x2400 on floor type 4) added to speed_z while the jump's first tile shows
local player_state_teetering 0038DE,0038E5,0038F7 r0 edge_direction  # teetering at a block corner: (direction + 0x280) >> 8, or byte 15 of the previous floor block; picks the teeter side and ledge_direction
local player_state_teetering 003A91,003A9C r0 new_status  # teetering falls off: FF, or 1 when health <= 8; stored in player_status
local player_move 002F3A r10 state_entry  # floor_type_player_states + floor_type: nonzero is the state the floor puts the player in
local player_state_over_edge 003FA8,003FBB,003FD0 r10 far_hang_animations  # sonic_/ray_/mighty_far_edge_hang_animations by kind: ledge sides 0, 1, 4
local player_state_climbing 0047AF,0047B7 r10 along_wall  # pointer to the object's y (0x50), or x (0x4C) when the attribute record's byte 0 is set: clamped to the wall's extent
local player_state_climbing 004821,00482D,00483B r10 climb_over_animation  # sonic_/ray_/mighty_climb_over_animation by the object's kind (character)
local player_state_climbing 004871,00487D,00488B r10 climb_animation  # sonic_/ray_/mighty_climb_animation by the object's kind (character)
local player_state_climbing 0048E7,0048F3,004901 r10 climb_animation  # sonic_/ray_/mighty_climb_animation by the object's kind (character)
local player_state_climbing 004A2F,004A3B,004A49 r10 climb_sideways_animation  # sonic_/ray_/mighty_climb_sideways_animation by the object's kind (character)
local player_state_climbing 004B49,004B55,004B63 r10 climb_animation  # sonic_/ray_/mighty_climb_animation by the object's kind (character)
local player_animate_walking 0031DB,0031E7,0031F5 r10 walking_animations player_facing_animation_offsets  # sonic_/ray_/mighty_walking_animations by the object's kind (character)
local player_state_climbing 004C8C,004C94 r10 across_wall  # pointer to x_int (0x4E), or y_int (0x52) when the record's byte 0 is set: moved 2 back when the climb is over
local player_state_climbing 004D51,004D5D,004D6B r10 climb_over_animation  # sonic_/ray_/mighty_climb_over_animation by the object's kind (character)
local player_animate_skidding 004EFE,004F0A,004F18 r10 skid_animations player_facing_animation_offsets  # sonic_/ray_/mighty_skid_animations by the object's kind (character); read at (facing + 8) & F
local player_state_short_animation 0050F9,005105,005113 r10 state_1b_animation  # sonic_/ray_/mighty_state_1b_animation by the object's kind (character)
local player_animate_hopping 0051C1,0051CD,0051DB r10 running_animations player_facing_animation_offsets  # sonic_/ray_/mighty_running_animations by the object's kind (character)
local player_state_hurt 0056F6,005702,005710 r10 hurt_animations player_facing_animation_offsets  # sonic_/ray_/mighty_hurt_animations by the object's kind (character)
local player_state_hurt 005B39,005B45,005B53 r10 shocked_animations player_facing_animation_offsets  # sonic_/ray_/mighty_shocked_animations by the object's kind (character)
local player_animate_falling 00346E,00347A,003488 r10 running_animations player_facing_animation_offsets  # sonic_/ray_/mighty_running_animations by the object's kind (character)
local player_state_bouncing 005C47 r10 bounce_record player_bounce_record  # bounce_records + bounce_direction x 6 ("movea.h [R12](R0), R10"): direction, sprite flags, animation offset
local player_state_bouncing 005C4B r10 bounce_record  # the [R10+] steps through the record
local player_state_tumbling 00661D,006629,006637 r10 tumble_animations player_facing_animation_offsets  # sonic_/ray_/mighty_tumble_animations by the object's kind (character)
local player_state_tumbling 006910,006921,006934 r10 knocked_out_animations  # sonic_/ray_/mighty_knocked_out_animations by the object's kind (character)
local player_state_tumbling 006953 r11 knocked_out_entry knocked_out_entry  # knocked_out_animations + (facing >> 2) x 4 ("movea.w [R10](R0), R11"): sprite flags, then the animation's offset ([R11+] 00695E, 006963)
local player_state_blown_away 006A6A,006A76,006A84 r10 tumble_animations player_facing_animation_offsets  # sonic_/ray_/mighty_tumble_animations by the object's kind (character); its facing 2 entry (+4)
local player_state_opening_scene 006C38,006C44,006C52 r10 opening_run_animation  # sonic_/ray_/mighty_opening_run_animation by the object's kind (character)
local player_state_opening_scene 006C6C,006C7B r10 status_cursor  # walks player_status, counting the players with FF
local player_state_opening_scene 006D0D,006D19,006D27 r10 opening_voice_animation  # sonic_/ray_/mighty_opening_voice_animation by the object's kind (character)
local player_state_stage_scene 006FD4,006FE0,006FEE r10 standing_animations player_facing_animation_offsets  # sonic_/ray_/mighty_standing_animations by the object's kind (character)
local player_state_stage_scene 00703A,007046,007054 r10 geyser_hop_animation  # sonic_/ray_/mighty_geyser_hop_animation by the object's kind (character)
local player_animate_falling 0034FF,003512,003527 r10 far_hang_animations  # sonic_/ray_/mighty_far_edge_hang_animations by kind: ledge sides 0, 1, 4
local player_state_stage_scene 007192,00719E,0071AC r10 shrink_away_animation  # sonic_/ray_/mighty_shrink_away_animation by the object's kind (character)
local player_state_stage_scene 0071D1 r10 position_record  # shrink_away_positions + kind x 4: screen x and y (minus A0/70) where the character shrinks away
local player_state_stunned 007370,00737C,00738A r10 stun_recover_animation  # sonic_/ray_/mighty_stun_recover_animation by the object's kind (character)
local player_state_stage_b_sequence 007B23,007B2F,007B3D r10 standing_animations player_facing_animation_offsets  # sonic_/ray_/mighty_standing_animations by the object's kind (character)
local player_state_scene_thrown 007C6E,007C7A,007C88 r10 hurt_animations player_facing_animation_offsets  # sonic_/ray_/mighty_hurt_animations by the object's kind (character)
local player_state_scene_thrown 007D37,007D43,007D51 r10 hurt_animations player_facing_animation_offsets  # sonic_/ray_/mighty_hurt_animations by the object's kind (character)
local player_state_carried_off 007E28,007E34,007E42 r10 near_edge_fall_animation  # sonic_/ray_/mighty_near_edge_fall_animation by the object's kind (character)
local player_state_carried_off 007F7A,007F86,007F94 r10 hurt_animations player_facing_animation_offsets  # sonic_/ray_/mighty_hurt_animations by the object's kind (character)
local player_animate_jumping 00364D,003660,003675 r10 jump_animation player_facing_animation_offsets  # the attack jump table by kind (sonic_/ray_/mighty_attack_jump_animations): per-facing offsets of the take-off steps (003691 "movz.hw [R10](R0), R1")
local player_animate_jumping 00368A r10 jump_animation  # "xch.w R11, R10": the plain jump table instead when attack_power < 1
local player_animate_jumping 003695 r10 jump_animation flagged_step  # "add.w R1, R10": the facing's take-off step: sprite flags (0036AC "or.h [R10+], R0") and frame (0036B3 "mov.h [R10+], 4[R20]"); census ROM 11232E-1128AD
local player_animate_jumping 0036AC,0036B3,0036C1 r10 jump_animation  # the [R10+] steps past the take-off step's flags and frame (or "add.w #4, R10"): the animation that follows it, into 48
local player_state_carried_off 008010,00801C,00802A r10 seesaw_launch_animation  # sonic_/ray_/mighty_seesaw_launch_animation by the object's kind (character)
local player_state_trapped 008134,008140,00814E r10 trapped_animation  # sonic_/ray_/mighty_trapped_animation by the object's kind (character)
local player_state_trapped 0082C0,0082CC,0082DA r10 trap_struggle_animations struggle_animation_pointers  # sonic_/ray_/mighty_trap_struggle_animations by the object's kind (character)
local player_state_trapped 00837B,008387,008395 r10 running_animations player_facing_animation_offsets  # sonic_/ray_/mighty_running_animations by the object's kind (character)
local player_state_tripped 008645,00864F,00865B r10 flail_animation  # probably: the 7-frame animations at 009C4D / 009C6F / 009C91 by kind (no symbol), frames that go and come back mirrored; drifting, swept away, tripped
local player_state_tripped 0086BD,0086C9,0086D7 r10 hurt_animations player_facing_animation_offsets  # sonic_/ray_/mighty_hurt_animations by the object's kind (character)
local player_state_swept_away 00878F,008799,0087A5 r10 flail_animation  # probably: the 7-frame animations at 009C4D / 009C6F / 009C91 by kind (no symbol), frames that go and come back mirrored; drifting, swept away, tripped
local player_state_climbing_object 00890A,008916,008924 r10 near_edge_fall_animation  # sonic_/ray_/mighty_near_edge_fall_animation by the object's kind (character)
local player_state_pulled 008E7B,008E85,008E91 r10 flail_animation  # probably: the 7-frame animations at 009C4D / 009C6F / 009C91 by kind (no symbol), frames that go and come back mirrored; drifting, swept away, tripped
local player_state_teetering 003841,003858,00386F,003886,00390B,00391B,003932,003942 r10 teeter_variant  # 0 or 1 by the ledge side or edge direction: which of the character's two teetering animations
local player_state_floor_8_sequence 008FE5,008FF1,008FFF r10 hurt_animations player_facing_animation_offsets  # sonic_/ray_/mighty_hurt_animations by the object's kind (character)
local player_state_floor_8_sequence 00927D,009289,009297 r10 floor_8_ride_animation  # sonic_/ray_/mighty_floor_8_ride_animation by the object's kind (character)
local player_state_floor_8_sequence 00932F,00933B,009349 r10 drift_animation  # sonic_/ray_/mighty_drift_animation by the object's kind (character)
local player_state_floor_8_sequence 009481,00948B,009497 r10 floor_8_idle_animation  # 009C05 / 009C1D / 009C35 by kind (no symbol): animated while escape_progress is 0
local player_state_floor_8_sequence 0094A8,0094B2,0094BE r10 floor_8_escape_animation frame_animation  # 009C13 / 009C2B / 009C43 by kind (no symbol): escape_progress >> 8, capped at its count, picks the frame
local player_state_floor_8_sequence 009564,009570,00957E r10 running_animations player_facing_animation_offsets  # sonic_/ray_/mighty_running_animations by the object's kind (character)
local player_state_floor_8_sequence 0096A4,0096B0,0096BE r10 floor_8_landing_animation  # sonic_/ray_/mighty_floor_8_landing_animation by the object's kind (character)
local player_state_caught 00A026,00A032,00A040 r10 near_edge_fall_animation  # sonic_/ray_/mighty_near_edge_fall_animation by the object's kind (character)
local player_state_teetering 003963 r10 teeter_animation teeter_record  # teetering_animations + its entry for kind and variant: first word the sprite frame, the animation follows
local player_state_caught 00A17E,00A18A,00A198 r10 hurt_animations player_facing_animation_offsets  # sonic_/ray_/mighty_hurt_animations by the object's kind (character)
local player_state_sinking 00A2A4,00A2B0,00A2BE r10 pit_sinking_animation  # sonic_/ray_/mighty_pit_sinking_animation by the object's kind (character)
local player_state_knocked_off 00A508,00A514,00A522 r10 hurt_animations player_facing_animation_offsets  # sonic_/ray_/mighty_hurt_animations by the object's kind (character)
local player_animate_falling 003506,003519,00352E r11 near_hang_animations  # sonic_/ray_/mighty_near_edge_hang_animations by kind: ledge sides 2 and 3
local player_animate_jumping 003654,003667,00367C r11 plain_jump_animations player_facing_animation_offsets  # sonic_/ray_/mighty_plain_jump_animations by kind: used instead of the attack ones when attack_power < 1
local player_state_climbing 004726 r12 attribute_record floor_attribute_record  # floor_types + floor_attribute: byte 0 the wall's axis, +4 screen shift, +6 climb height, +8 top z, +0xA/+0xC the wall's extent
local player_state_bouncing 005C1E,005C2A,005C38 r12 bounce_records  # sonic_/ray_/mighty_bounce_records by kind
local player_state_hurt 005AD3 r1 knock_speed  # 0x40000: speed_x or speed_y (sign by the hitter's spawn_arg_2 direction) when shocked
local player_state_tumbling 0067D6 r1 sprite_depth  # queue_sprite_at_base's depth, kept in draw_depth (before it, project_object's R1 handed to keep_player_on_screen)
local player_state_stage_scene 007276 r1 sprite_zoom  # sprite_scale: multiplies screen_x and projected_y (>> 11) around the screen centre A0/70
local player_state_stage_exit 00757F r1 goal_dy  # 0x1439 - y_int: with goal_dx, the distance to the point that ends the goal run
local player_state_swept_away 008777 r1 chaser_dy  # y_int - the chaser's y_int: with dx, angle_of_vector gives the direction away from it
local player_state_floor_8_sequence 009200 r1 drift_x  # sine[drift_direction] x drift speed >> 8: added to x
local player_state_floor_8_sequence 009219 r1 drift_y  # cosine[drift_direction] x drift speed >> 8: subtracted from y
local player_state_floor_8_sequence 00943E r1 input_dy  # trackball_y: with trackball_x, angle_of_vector gives target_heading
local player_state_climbing 0047BB,0047C5 r3 wall_limit  # attribute record +0xA (wall start), then + record +0xC (wall end): the along-wall coordinate is clamped to them
local player_state_climbing 0049FA,004A03 r3 side_threshold  # 2 (8 with fast_climb): side_step_accumulator past +- it starts a sideways climb step
local player_state_climbing 004AA4 r3 wall_start  # attribute record +0xA: the sideways step must stay between it and it + record +0xC
local player_state_hurt 005672,005689,0056A5,0056B1 r3 knock_speed_y  # the knockback speed's y part (0, or +- the speed by the hitter's direction): speed_y
local player_state_continue 0060A4,0060AD,0060B6 r3 start_bit  # bit of inputs_pressed for the player's start (0x10 / 0x18 / 0x12): pressing it restarts the continue countdown
local player_state_tumbling 0065A1,0065BD,0065C9 r3 knock_speed_y  # the knockback speed's y part (0, or +- the speed by the hitter's direction): speed_y
local player_state_opening_scene 006C74,006C82 r3 layout_row  # -1 + the players whose status is FF: row (0x12 bytes) of opening_scene_player_positions
local player_state_knocked_off 00A49B,00A4B7,00A4C3 r3 knock_speed_y  # the knockback speed's y part (0, or +- the speed by the hitter's direction): speed_y
local player_state_climbing 004A74 r8 step_x  # screen_x + the side step (saved in temp_0), then the world x unproject_point gives back
local player_state_climbing 004C2F,004C39 r8 z_step  # 2 climbing down (object flag 7), -2 climbing up: added to z_int and climb_remaining
local player_animate_run_from_table 003386,003394,0033B4 r0 walk_cycle_limit  # 4F0 in state 14, else 600 (x4 on floor 4 slow steps): when walk_cycle reaches it, its high byte is cleared and the next frame steps (flags 4000)
local player_stuck_begin 007718,007722,00772E r0 stuck_frame  # sprite_frame of the stuck pose by character: 1A40 (kind 0), 5180 (kind 1), 35C8
local player_move_with_scroll 009786 r0 drift_angle  # scroll_drift_direction (set to 300 first in state 26 when drift_turned_back): indexes sine_table/cosine_table for the drift step
local player_move_with_scroll 0097D9,0097E6 r0 nudge_x  # A, negated when drift_turned_back: added to x_int when the drift stops in state 26
local player_move_with_scroll 00986F r0 heading_16  # direction << 6 (the angle in 16 bits): +7000 and -1000 against 6040 pick the drift turn animation
local player_move_with_scroll 00979F r1 drift_dx  # sine(drift_angle) x (scroll_drift_speed >> 14) >> 8: added to x
local player_hud_init 00A730,00A740 r10 gauge_position  # hud_gauge_positions_3p entry for the player (2p with cabinet_config bit 0 clear): the gauge's x, y
local player_hud_init 00A790,00A7A0 r10 portrait_position  # hud_portrait_positions_3p/2p entry for the player: the portrait's x, y
local player_hud_init 00A7B6 r10 portrait_sprite  # hud_portrait_sprites entry for the character: the portrait's tile and palette
local player_hud_init 00A7E2,00A7EC,00A7F8 r10 portrait_anim  # CD63/CD6B/CD73 by character: written to hud_portrait_anim
param player_hud_update R1 digit_attribute  # 800/A00/C00 by character: the attribute draw_hex_4 prints the ring count with (also takes convert_to_bcd's and the HUD animators' R1, only passed on)
local keep_player_on_screen 00AE84 r10 voice_range  # edge_voice_scroll_ranges entry for the stage: start and length of the scroll range where the edge voice (sound 39/43/4C) plays
local keep_player_on_screen 00AEEF,00AEF6,00AEFD r10 direction_row  # camera_scroll_direction's high byte, + 4 when its low byte is not 0: x 4 + screen_edge indexes screen_edge_scroll_carry_flags
local keep_player_on_screen 00AEEA r1 scroll_direction  # camera_scroll_direction
local compute_scroll_drift 00B19F r1 slope  # the block's slope number (byte 13): indexes floor_slope_factors
local compute_scroll_drift 00B1B9,00B1CD r1 vy  # camera_velocity_y << B / divisor: the y velocity along the slope, squared into the speed
local compute_scroll_drift 00B21B r1 vy  # camera_velocity_y >> 4 on a flat block, squared into the speed
local compute_scroll_drift 00B1AE,00B1B6,00B1BE r8 divisor  # floor_slope_factors entry, made positive, + 10000 (kept in unused_drift_divisor)
local check_player_floor_edge 00B4FB,00B4FF,00B537,00B53C r1 draw_depth  # screen y of the old floor ((projected_y + previous_floor_z) >> 16 - view_y), or the platform's draw_depth - 1: stored to fall_draw_depth and handed back
local push_player_out_of_wall 00B7C1,00B7D8,00B7DF r1 drift_bounce_direction  # sonic_bounce_records' direction for the wall's direction byte, -80 or +80 by the side: the new scroll_drift_direction (states 25, 26)
local player_steer_in_air 00B8E3 r0 speed_diff  # target speed - speed: added whole when speeding up, >> 5 when slowing down
local player_steer 00BA39 r0 speed_diff  # target speed - speed: added whole when speeding up, >> 5 when slowing down
local player_steer 00BA55 r0 speed_diff  # the same on floor type 7 (no path): >> 5 up, >> 6 down
local player_steer 00BAE3 r0 animation_speed_diff  # trackball target - animation_speed: added whole up, >> 5 down
local player_steer 00BB2B,00BB40 r0 run_threshold  # 200 (x 60 >> 8 on floor type 4): above it the next state is 13 (14 over 4F0)
local player_steer 00BBD3,00BBD7,00BBDD r0 rounded_speed  # ground_speed >> 12, rounded: x the slope's speed factor
local player_steer 00B990 r1 input_y  # trackball_y + push_y, squared into the target speed
local player_steer 00BB86 r1 rounded_heading  # (heading + 20) & 3FF: >> 6 indexes heading_speed_factors
local compute_ground_speed 00BD68 r1 rounded_heading  # (heading + 20) & 3FF: >> 6 indexes heading_speed_factors
local shadow_update entry,00C11A r1 offset_y  # sprite_offset_y (what update_screen_position also hands back in R1): added to screen_y in the per-player view (also animate's frame, passed on)
local shadow_update 006E65,006E6B r10 scene_player  # the player's block, then its object (scene steps 0, 1, 4): the shadow takes its screen position
local shadow_update 006EB4,006EBA r19 scaled_player  # the player's block, then its object (scene step 3): the shadow takes its sprite_scale and screen position
local draw_attack_effect 00C36E,00C3EC r0 priority_bits  # the player's sprite_palette & 6000: put into the effect's palette (as priority_bits further down)
local draw_attack_effect 00C323 r1 facing_offset  # the effect table's offset for the player's facing (first halfwords of the table)
local project_object 00C509 r0 screen_y  # update_screen_position's R0: the new screen_y, handed back
local find_floor 00C6EE r0 floor_type_index  # the platform's block's byte 12: indexes floor_types
local find_floor 00C713 r0 slope_index  # the block's slope number (byte 13): indexes floor_slopes
local find_floor 00C71A r11 cursor floor_slope  # floor_slopes + slope_index x 8 ("add.h R0, R0; movea.w 10DF44(R0), R11" 00C717-00C71A): flags, then the factor at +2 (00C73C "mov.w [R11+], R0")
local find_floor 00C74A r0 base_z  # the block's floor height (halfword 10) << 16, added to the slope height
local find_floor 00C810 r0 slope_index  # the block's slope number (byte 13): indexes floor_slopes
local find_floor_set_floor_z 00C817 r11 cursor floor_slope  # floor_slopes + slope_index x 8 ("add.h R0, R0; movea.w 10DF44(R0), R11" 00C814-00C817): flags, then the factor at +2 (00C839 "mov.w [R11+], R0")
local find_floor 00C839 r0 slope_factor  # the floor_slopes record's second word (floor_slope_factors): multiplies the distance into the slope height; handed back
local cycle_part_palettes 00CB74,00CB79,00CB88 r1 next_index  # palette_index + 1, wrapped to 0 past the list's last index
local cycle_part_palettes 00CB9C,00CBA7 r1 parts_left  # the part list's count: one palette write per part
local place_player_at_restart_point 00CBC5 r0 stage_index  # stage: indexes stage_restart_points
local place_player_at_restart_point 00CC0A r0 kind_offset  # the restart point's kind byte x 3: offset into the jump table (0, 3, 6)
local aim_at_screen_centre 00CCDD r5 dx  # the angle to the screen centre's ground point, then polar_to_xy's x step (length 200), handed back
local aim_at_screen_centre 00CCE0 r6 dy  # 200 (the length), then polar_to_xy's y step, handed back
local start_climb_at_spawn 00D88D,00D899,00D8A7 r10 climb_animation  # sonic/ray/mighty_climb_animation by character
local slip_pieces_update 00DC8A r1 margin_x  # 20: the x margin for is_off_screen_margin (then animate's frame, passed on)
local follow_player_path 00DD8C r0 x_offset  # x_int - the path point's x + 8: 11 or more when farther than 8 from it
local animate_with_flags entry,00DE5A r1 frame  # animate_step_with_flags' frame, handed back like the R1 it took
local animate entry,00DEAD r1 frame  # animate_step's frame, handed back like the R1 it took
local animate_all_parts 00E0F6,00E101,00E10F,00E118,00E127 r1 next_frame  # animation_frame + 1 (- 1 with flags bit 7), wrapped: stored to animation_frame
local animate_all_parts 00E13A,00E144 r1 parts_left  # the part list's count: one sprite frame write per part
local player_select_character 00E201,00E20A,00E215 r0 player_bit  # 0, 1 or 2 by player: the player's bit in players_in_join_chooser
local intro_marker_update 00E398 r1 scale  # the marker's sprite_scale (copied from its parent): scales sprite_offset_y (then animate's frame, passed on)
local check_player_contacts 00EB2B,00EB2F r1 draw_depth  # screen y of the old floor ((projected_y + previous_floor_z) >> 16 - view_y): stored to fall_draw_depth (kind 27) and handed back
local check_player_contacts 00EC06 r1 push_cos  # cosine(move_angle) x 6000 >> 16: push_y is its negative (kind 28); handed back
local vine_bar_update 00EF19 r0 vine  # 200000 | direction: the vine (set by vine_init) whose x, y and z the bar copies
local vine_bar_update 00EF61 r0 height  # (floor_z_int - z_int) & 7F of the vine: the hitbox height (8 over 50)
local spike_panel_update 00F280,00F308 r0 frame_index  # spike_panel_frame_sequence entry for the step: stored to spike_panel.frame_index, read back as frame_index below
local metal_wall_draw 00F9CE r0 part_index  # parts_left - 1 (2, 1, 0): picks the part's offset and sprite tables in metal_wall_part_tables
local metal_wall_draw 00FA70,00FA95,00FAF8 r0 dust_x  # -8 for the first two dust sprites, 2C for the others: added to screen_x
local metal_wall_draw 00FA78,00FA9A,00FB00 r2 dust_sprite_base  # 0 or 8: added to the dust frame to index metal_wall_dust_sprites
local metal_wall_draw 00F9EA,00FABF r3 screen_offset  # the part's x offset (signed byte), then the dust's rise 20 - frame x 4 for the last two dust sprites
local flame_machine_update entry,00FF0F,00FF25,00FF38,00FF4E r1 depth  # the capsule's screen_y, + 1: the depth of the next part, as depth above
local flame_machine_back_wall_update 01008F,010099 r0 frame_index  # the back wall's frame_index (wraps to 0 past 9): indexes flame_machine_back_wall_frames
local treadmill_update 010AFA r1 player_block  # GAME_VARIABLES + treadmill_player_blocks entry: a player on the treadmill (state 22) adds its object's speed
local treadmill_update 010B18,010B2B,010B33 r1 speed_level  # total_speed's top byte & F less spawn_arg_1 (0 below): indexes treadmill_lift_forces, x 9 added to belt_phase
local push_crate_move 011328 r0 deleted  # delete_if_off_screen's flag (1: gone); 0 here, + FF makes it bucket FF for the broken crate
param push_crate_release_all_rings R16 random_value  # drop_rings' random_value, handed in and back
param push_crate_release_all_rings R17 ring  # drop_rings' ring (the last ring object it released), handed in and back
param push_crate_release_rings R16 random_value  # drop_rings' random_value, handed in and back
param push_crate_release_rings R17 ring  # drop_rings' ring (the last ring object it released), handed in and back
local handle_rod_init 011595 r3 rod_length  # var_80 (handle_count) of the rod's first handle * 0x12 (the spacing of the handles): scales sine/cosine of 0x300 into rod_dx / rod_dz for the post
local ice_launcher_update 011D2D r0 frame_index  # ice_launcher_frames[step]: indexes ice_launcher_sprites, ice_launcher_hitbox_sizes (*4) and ice_launcher_hitbox_offsets (*2)
local whirlwind_move 0122CD,0122D2,0122D4 r0 frame_index  # whirlwind.frame_index + 1, & 3: stored back and indexes whirlwind_sprites; then left for add_to_touchable_list
local centipede_move 01249F,0124A6,0124AB r0 head_frame  # direction / 0x92 + 1 (& 7): index into centipede_head_frames (centipede_tail_follow's tail_frame does the same + 4)
local centipede_move 0124F8 r2 wobble_speed_x  # direction_to_speed(wobble_angle, chase_speed / 2)'s speed_x, added to speed_x; afterwards only passed through centipede_hop and spawn_centipede_sand_puff
local centipede_move 0124F8 r3 wobble_speed_y  # direction_to_speed(wobble_angle, chase_speed / 2)'s speed_y, added to speed_y; afterwards only passed to spawn_centipede_sand_puff
local centipede_hop 012645 r0 fall_height  # fall_to_floor's height (floor_z - z, 0 once landed), handed back through *p_r0
local sand_rock_update 012F30,012F47 r0 crumble_frame  # sand_rock.crumble_frame: indexes sand_rock_crumble_frames for the rock, then + 1 for its child's frame (stops past 0xD)
local snow_mound_update 0130F2,013109 r0 burst_frame  # snow_mound.burst_frame: indexes snow_mound_burst_frames for the mound, then + 2 for its first child's frame (stops past 0xE)
local snow_mound_update entry,013147,01314B,01317C,013180 r1 depth  # screen_y + 0x12 (the mound) and + 0x11 (its first child) for queue_sprite_at_depth; first fall_to_floor's unused over_pit flag
local eggman_teleport_animate 013475 r1 frame_index  # eggman_teleport_steps[step], stored in eggman_teleport.frame_index; 0xFF deletes the object, else indexes eggman_teleport_frames and eggman_teleport_durations
local fence_post_update 0139F5 r0 arc_offset_record  # fence_arc_offsets + row_direction * 4 (0139F5 movea.w 13A95[PC](R1)): its two halfwords are place_sprite_near_cursor's offset_x / offset_y for the arc
local fence_post_update 0139FB r8 offset_x  # the arc's fence_arc_offsets dx: place_sprite_near_cursor's offset_x
local fence_post_update 0139FE r9 offset_y  # its dy: place_sprite_near_cursor's offset_y
local eel_anchor_update 014C10,014C4E r0 segment_speed  # starts at the anchor's speed and grows by it (speed_step) per segment down the eel: each segment's speed
local eel_head_move 014C90,014CCF r0 head_offset_x  # 0xB (facing) or -0xC: added to projected_x before move_object_projected
local eel_head_move 014C95,014CD2 r1 head_offset_y  # 0 (facing) or 3: added to projected_y_int before move_object_projected
local spike_gate_pillar_update 015552 r0 head_distance_x  # distance_xy_to_object's distance_x to biting_head (r14): <= 0x20 breaks the pillar into debris; then left for spawn_debris
local spike_gate_beam_update 01563E r0 head_distance_x  # distance_xy_to_object's distance_x to biting_head (r14): <= 0x20 breaks the beam into debris; then left for spawn_debris
local spike_gate_beam_update 01567A,0156A5,0156CA r1 depth  # cursor_floor_screen_y - 0x10 / - 0x14 / - 0x10: queue_sprite_at_depth's depth for the beam and its two pieces (first distance_xy_to_object's unused distance_y)
local init_stage_view 01595C,0159FE r2 tile_set_cursor  # stage_initial_tile_sets[stage] (01595C), then + 4 per entry ([R2+] 0159FE) over the stage's 4 initial tile sets for load_tile_set
local init_stage_view 015964 r2 tile_set_cursor stage_start_tile_sets  # stage_initial_tile_sets[stage] + 0x19712 ("add.w #19712, R2"): the stage's list of 4 tile set numbers
local init_stage_view 015C8E r2 direction_y  # first halfword of stage_section_directions_y: section_direction_y, handed back through *p_r2
local init_stage_view 01596B,015A12 r3 sets_left  # 4, counted down once per tile set loaded
local update_stage_tile_layers 015D6F,015D90 r10 block_cursor  # walks the 2x2 blocks under the view (+1, then + map width - 2 per row); handed back through *p_r10
local update_stage_tile_layers 015D5A r10 block_cursor block_tile_sets  # stage_block_maps[stage] + 17C5C + tile_block_row + tile_block_column ("add.w R2, R10"): the block under the camera
local update_stage_tile_layers 015D79 r1 shifted_tile_set  # the block's tile set byte rotated by set_shift, summed into wanted_tile_sets; handed back through *p_shifted_tile_set
local update_stage_tile_layers 015D60,015D96 r2 rows_left  # 2, counted down per block row
local update_stage_tile_layers 015D63,015D81 r4 set_shift  # 0, + 8 per block: the rotation that puts each block's tile set in its own byte of wanted_tile_sets
local reload_changed_tile_set 015E18 r5 slot_tile_set  # byte of loaded_tile_sets at loaded_shift: the tile set in the slot compared with the wanted one
local update_scroll 016039 r0 check_result  # 0 (set_screen_push_edges' quadrant bits), then is_direction_in_quadrant's found flag and each is_player_facing_scroll / is_player_pushing_edge result tested for players_facing_scroll / players_pushing_edge
local update_scroll 01603C r11 sine_entry  # set_screen_push_edges' / is_direction_in_quadrant's sine table entry of the section direction, given to is_player_facing_scroll; later advance_scroll's R11 handed back
local update_scroll 01694B,016950 r2 mode_script_cursor  # scroll_mode_script: reads the next scroll_mode and scroll_mode_timer, + 4 stored back (scroll mode 5)
local update_scroll 016978,01697D r2 mode_script_cursor  # scroll_mode_script: reads the next scroll_mode and scroll_mode_timer, + 4 stored back (scroll mode 6)
local update_scroll 016A50,016A55 r2 mode_script_cursor  # scroll_mode_script: reads the next scroll_mode and scroll_mode_timer, + 4 stored back (scroll mode 2)
local update_scroll 0175DB r2 velocity_y  # section_direction_y * speed: stored in camera_velocity_y and handed back (stage 03's end)
local update_scroll entry,015FA9,016245,016259,01625F,016A1A,016A4A,0174EF,017532 r3 speed  # the scroll speed scroll_at_scroll_speed / scroll_slowing_down leave, handed back through *p_speed: the same as speed
local update_scroll 0175D5 r4 speed_value  # speed (16 bits): times section_direction_x / _y for camera_velocity_x / _y (as set_camera_velocity's speed_value)
local update_scroll 0161CC r5 player_speed  # the pushing player's object speed >> 16: above 0x500 or negative keeps speed 5, else * 0x400 >> 16 is push_speed
local scroll_slowing_down 0169C1 r3 speed  # scroll_at_scroll_speed's speed (scroll_speed), handed back through *p_speed
local advance_scroll 016AC8 r2 limit_y_cursor  # section_limit_y_list: reads the next section_limit_y, + 2 stored back
local advance_scroll 016AD8 r2 limit_x_cursor  # section_limit_x_list: reads the next section_limit_x, + 2 stored back
local advance_scroll 016AE8,016AED r2 mode_script_cursor  # scroll_mode_script: reads the next scroll_mode and scroll_mode_timer, + 4 stored back
local set_camera_velocity 016B46 r2 velocity_y  # section_direction_y * speed_value, stored in camera_velocity_y
local update_background_scroll 016B82,016BAB r1 fast_scroll_start  # the first entry of stage_fast_scroll_ranges past the section start (0xFFFF ends the list): scroll_position at or past it sets follow_base_speed 0x10; first the stage number that picks the list
local update_background_scroll 016BB4 r2 current_position  # scroll_position, compared with fast_scroll_start and handed back
local run_stage_05_switch 016E25 r2 layer_scroll_x  # camera_x_pixels: stored in nbg0_scroll_x_int, then passed through update_background_scroll and handed back
local update_stage_00_exit_scene 017359,01742B r1 layer_zoom  # 0x2000000 / stage_00_exit_zoom >> 8, written to nbg0/nbg1 zoom x and y (steps 9 and 10): the same as layer_zoom
local update_stage_06_background 017911 r1 palette_stage  # 1: load_palette_set_and_backdrop's stage_index (stage 1's palette set); afterwards run_vram_script's count
local update_stage_06_background 01797A,017987,0179E6,0179F3,017A00,017A0D,017BB5,017BC2 r10 script  # stage_06_forest / lower_jaw / island_tower VRAM scripts for run_vram_script, handed back: the same as script
local load_camera_start_section 01A994 r2 limit_y_cursor section_camera_limits  # stage_section_limits_y + section offset: the start section's y (camera_y), then its section_limit_y; + 2 is section_limit_y_list
local load_camera_start_section 01A99A r2 limit_y_cursor  # moved past the start's y ([R2+])
local load_camera_start_section 01AA02 r2 direction_y_cursor section_directions  # stage_section_directions_y + section offset: section_direction_y; + 2 is section_direction_y_list
local load_camera_start_section 01AA4A r2 section_index  # section_offset / 2 (stage_start_section): offset into the byte lists stage_section_speeds and stage_section_follow_speeds
local cycle_palette 01AF14 r1 frame_offset  # cycle byte 3 (the current palette frame) << 5: added to source to pick the frame's 16 colours
local load_stage_palette_set 01AFD7 r11 palette_dest  # load_palette_set_and_backdrop's dest (palette RAM after the last palette loaded), handed back as p_dest
local load_stage_palette_set 01AFD7 r3 tile_bank_bits  # load_palette_set_and_backdrop's tile bank bits (stage_tile_bank_bits << 10, ORed into video_control), handed back
local set_stage_layer_mixing 01B1DC r1 priority_cursor nbg_layer_priorities  # stage_layer_priorities + stage * 4 ("movea.b 1B231[PC](R1), R1"): the stage's four priority bytes
local set_stage_layer_mixing 01B1EC r1 priority_cursor  # ++ per layer: one priority byte (+ 200) per NBG mixer register
local set_stage_layer_mixing 01B1FB r1 stage_index  # stage: indexes stage_layer_disables and stage_rowscroll_settings
local update_nbg3_scroll 01B987 r2 half_view_y  # view_y >> 1: the NBG3 scroll y before + 80 (stages 0, 3, 5, 6, 7, 10)
local update_nbg3_scroll 01B99D r2 block_pages  # select_nbg3_pages' block_pages (the four page bytes of the NBG3 block), handed back
local select_nbg3_pages 01B9E9 r0 block_column  # nbg3_block_column: its low bit (with the row's) picks the quadrant table entry
local select_nbg3_pages 01BA15,01BA3E r0 bytes_left  # 4, counts the page bytes gathered from the block map (at 3 the cursor jumps to the next map row)
local select_nbg3_pages 01BA45,01BA65 r0 bytes_left  # 4, counts the quadrant bytes gathered (at 3 the cursor skips one byte)
local select_nbg3_pages 01BA1E,01BA27 r4 gathered_pages  # the four block map bytes ORed in at shifts 0, 8, 16, 24: copied to block_pages
local select_nbg3_pages 01BA4E,01BA57 r4 gathered_quadrants  # the four quadrant table bytes ORed in at shifts 0, 8, 16, 24: copied to quadrants
local select_nbg3_pages 01BA6C,01BA97 r4 byte_mask  # FF << 8 per byte: picks one byte of block_pages and quadrants; the loop ends when it shifts out
local select_nbg3_pages 01B9AB r5 stage_index  # stage: indexes stage_nbg3_block_maps
local select_nbg3_pages 01BA73,01BA94 r5 shift  # 0, -8, -16, -24: shift_by count that brings the masked byte down to bit 0
local build_ending_scene 01BEAA,01BEBC,01BECE,01BEE0,01BEF2,01BF04 r11 scene_actor  # each object allocate_object_outside_loop hands back: gets the tower, horizon, Sonic, Ray, Mighty and road handlers and init_ending_scene_object; the last one is handed back
local spawn_ring_rows 01E3AF r0 scroll_past_row  # scroll_position - the row's trigger position: the row spawns while it is below 100
local allocate_object_outside_loop 01F1E4 r0 free_count  # allocate_object's free count (0: nothing was free), handed back as p_free_count
local spiked_roller_start 080160 r0 roller_kind  # sprite_frame of the spawn record: stored in platform, indexes spiked_roller_events and spiked_roller_kinds; then only find_floor's R0 leftover
local spiked_roller_chase 080713 r0 event_kind  # event record's kind * 2 (0/8 max y, 2 min x, 4 min y, 6 max x): the switch index
local spiked_roller_chase 0804E0 r0 height_above_floor  # floor_z_int - z_int: at most 8 snaps the roller to the floor, more makes it fall (state 1)
local spiked_roller_chase 0805F1 r0 move_step  # chaser_speed << 12: subtracted from / added to y or x by path direction and stored as the speed
local spiked_roller_chase 080608 r0 move_step  # chaser_speed << 12 (direction 0: y -= it)
local spiked_roller_chase 08061F r0 move_step  # chaser_speed << 12 (direction 1: x += it)
local spiked_roller_chase 080636 r0 move_step  # chaser_speed << 12 (direction 3: x -= it)
local spiked_roller_chase 080651,08065A r0 speed_level  # chaser_speed >> 4, at most 5: indexes spiked_roller_animation_speeds
local spiked_roller_chase 0806CD,0806D2,0806DA,0806E2 r0 speed_level  # chaser_speed >> 4, clamped to 0..4: indexes spiked_roller_rumble_delays (as speed_level in spiked_roller_ram)
local spiked_roller_chase 080599,0805A4 r1 chase_speed  # base_speed, or camera ground speed * catch_up_factor >> 8 when that is more: stored in chaser_speed; also animate / play_sound leftovers
local spiked_roller_follow_leader 08082E,080837 r0 speed_level  # chaser_speed >> 4, at most 5: indexes spiked_roller_animation_speeds
local spiked_roller_follow_leader 08084E r0 leader_reverse_bit  # leader's flags & 80 (animate steps the frames backwards): copied into ours
local spiked_roller_follow_leader 080874 r0 leader_hidden_bit  # leader's flags & 1 (not drawn, see spiked_roller_update): copied into ours
local spiked_roller_crash_into_machine 080AEC,080AF1,080AFA r0 speed_level  # chaser_speed >> 4, at most 5: indexes spiked_roller_animation_speeds
local spiked_roller_crash_into_machine 080B3B,080B40,080B48,080B50 r0 speed_level  # chaser_speed >> 4, clamped to 0..4: indexes spiked_roller_rumble_delays
local catapult_update 0810AD,0810B7 r0 depth_offset  # FFEC (-20), or 14 in states 1-3 (swinging): added to screen_y for the sprite depth
local boulder_roll 0827F4 r0 sound_id  # F, played when the boulder first rolls in (x_int <= 180); then only find_floor's R0 leftover
local door_wall_wait_to_burst 082913 r0 random_offsets  # next_random's bits with the low half cut to 0-3F (added to the piece's screen_x); its high half & 3F is added to screen_y
local door_wall_wait_to_burst 08295C r10 fragment_animation  # door_wall.fragment + 8: the record's animation offset (into rock_debris_animations); + 2 is the next fragment record
local spawn_door_rollers 0829D2 r0 free_count  # allocate_object_outside_loop's free count: 0 stops spawning; also find_floor's R0 leftover
local machine_fire_update 08380B r0 projected_dx  # the floor attribute's x_per_rise * speed_fraction >> 4: added to fixed_projected_x, half of it taken from projected_y (as in bridge_fire_rise)
local machine_fire_update 0837CA,0837D5 r1 climb_speed  # base_speed, or camera ground speed * catch_up_factor >> 8 when that is more: stored in speed_fraction (as climb_speed in bridge_fire_rise); also update_screen_position / play_sound leftovers
local fireball_fly 083AB5,083AC0 r1 arc_step  # 9 (E on stage 0C): added to arc_angle every frame of the flight
local how_to_play_trackball_hand_update 083F76,083F84 r0 child_palette  # 8750, or 8550 when the hand's palette is not 8760: the second sprite's palette
local opening_ground_run 084048,084053 r0 shake_dy  # high byte of stage_06_shake, negated when stage_06_shake_sign is negative: added to screen_y
local opening_machine_update 08412C,084137 r0 shake_dy  # high byte of stage_06_shake, negated when stage_06_shake_sign is negative: added to screen_y
local bubble_pop 084749 r0 jitter_y  # the random jitter (0-F) copied before base_x is added to it: added to base_y for y_int
local spreading_flame_burn 084F0D,084F21 r0 chaser_gap  # our y_int - the chaser's (+ 40 on stage 0C above chaser speed 8): stored in flame_chaser_gap
local fire_vent_update 085A0A r1 margin  # 40: the margin handed to check_left_screen_margin; then only play_sound / update_screen_position leftovers
local collapsing_bridge_plank_update 085FE2 r0 start_frame  # plank_index & 7: the plank's first animation frame; & 3 is its first animation_timer
local collapsing_bridge_plank_update 086029,086037 r1 sag  # bridge_0_sag / bridge_1_sag [plank_index], 0 when under 100: << 8 added to previous_floor_z; also update_screen_position / play_sound leftovers
local collapsing_bridge_plank_update 0861F8 r1 swing_dz  # cosine_table[angle] * 40: taken from the previous plank's z; also spawn_sign / play_sound / animate leftovers
local collapsing_bridge_plank_update 0861DF r1 swing_dx  # sine_table[angle] * 40: added to the previous plank's x
local burning_boulder_start 086349 r1 zone_index  # 2: spawn_chaser_back_zone's zone_index for the pathless boulder; then only find_floor's R1 leftover
local burning_boulder_update 0865E7,0865EF,0865F8 r0 speed_level  # speed_fraction, then >> 4 at most 5: indexes burning_boulder_roll_rates; also find_floor's R0 leftover
local burning_boulder_update 08668C,086698,0866A1 r0 speed_level  # the lead boulder's speed_fraction (copied to ours), then >> 4 at most 5: indexes burning_boulder_roll_rates
local burning_boulder_update 0866F9,08670A r0 drop_step  # 60000, or 0 from scroll position 4800: added to z each frame of state 3
local burning_boulder_update 0865DC r1 step_y  # speed_fraction << 12: added to y and stored in speed_y; also find_floor / play_sound / animate leftovers
local burning_boulder_update 086671,08667D,086681 r1 step_y  # the lead boulder's speed_fraction (ours when its var_96 >= 3) << 12: added to y and stored in speed_y
local burning_boulder_update 086753,08675F r8 flames_left  # 4, counts the boulder's flame children marked 400 (to delete themselves)
local ember_emitter_move 086947 r0 travel_direction  # the floor block's direction byte (+15): stored in ember_emitter.travel_direction and switched on; also find_floor's R0 leftover
local ember_emitter_move 086A56 r10 speed_entry falling_ember_speed  # falling_ember_speeds + (random & 3) * 4: the ember's speed_z and speed_y (<< 8)
local ember_emitter_move 086A3A r9 random_low  # low byte of next_random's bits: & 3 picks the falling_ember_speeds entry
local bridge_fire_rise 087468 r0 projected_dx  # the floor attribute's x_per_rise * speed_fraction >> 4: added to fixed_projected_x, half of it taken from projected_y
local spawn_lava_flame 087907 r3 flame_random  # lava_flame_random reloaded: bit 0 picks the second animation, the low byte * C0C1 gives anchor_y, byte 2 gives anchor_x
local ice_block_update entry,087E68,087E7A r0 push_speed  # 10000, or the toucher's speed_fraction << 8 when it is no player (kind > 2): stored in speed_x; also the caller's R0 and add_to_touchable_list's leftover
local spawn_ice_platform_leader 087EDE r0 free_count  # allocate_object_outside_loop's free count: 0 hands back spawned = 0 at once
local icicle_dropper_move 0883B6,0884B0,088562 r0 direction  # the direction again while testing the script record's coordinate (>> 8 picks y or x and the comparison); one variable with direction (find_floor's unread r0 lands here too)
local icicle_dropper_move 08857A,088584,088598,0885A2,0885B6,0885C0,0885D4,0885DE r0 step  # chaser_speed: added to travel, then << 12 to platform_offset and to y or x by direction
local icicle_update 088AAA,088AB4 r0 landing_offset  # moving_floor_object's speed_x x (timer + 0x19), then >> 16 + its var_8C: where the platform is when the icicle lands; compared with +/-0x87 (swing_limit)
local icicle_update 088862,088A2B,088C05 r1 margin_x  # 0x50 handed to is_off_screen_margin in state 1 (0x30/0x28 in states 3/7); then update_screen_position's leftover handed to queue_sprite; one variable with margin_x
local icicle_update 088918,088956 r8 parts_left  # 2, counted down: the two clip parts (sprite flags 5000) chained behind the icicle
local tornado_move 0892F6,08938D,089467,089535,0895D6,089624 r0 direction  # the direction while testing the record's coordinate, then the record's new direction (picks the hitbox sides); one variable with direction (find_floor's unread r0 lands here too)
local tornado_move 0892E1,0892E6,0892EE,0892F3,089361,089367,08936D,089373 r10 path_cursor  # walks the path script: scroll word, coordinate, threshold, catch-up factor, base speed, wait, direction; saved back to tornado.path
local tornado_move 089347,08934F r10 slot_cursor  # current_object + 0x96, + 2 a slot: the five twister columns flagged 400 (removed)
local tornado_move 0895AB,0895B6,0895C7 r1 move_speed  # base_speed or the catch-up speed from camera_slope_speed (x 4 once all players finished): into chaser_speed (play_sound's unread r1 lands here too)
local quicksand_swirl 089A4D,089A51,089A58 r1 next_step  # animation_frame + 1, back to 0 past last_step: the new step, stored in animation_frame and indexing step_list
local rimmed_sinkhole_start 089CAE,089D88 r0 free_count  # allocate_object's free count for the sinkhole (0: stop); one variable with allocate_object_2's free_count (find_floor's unread r0 lands here too)
local rimmed_sinkhole_open 089DBB r1 margin  # 0x58: check_left_screen_margins' margin; then play_sound/animate/update_screen_position leftovers
local rimmed_sinkhole_open 089DC2 r2 unseen_margin  # 0x20: check_left_screen_margins' unseen margin; then play_sound's leftover
local spawn_sand_block_leader 089EB0 r0 free_count  # allocate_object_outside_loop's free count: 0 is handed back as spawned (no leader)
local sand_block_update 08A357 r0 span_y  # parent's y_int - the block's y_int: stored in hitbox_size_y; the block (face, streams) is removed once it is no longer positive
local biting_head_chase 08AB6F,08AB84 r1 hold_threshold  # 0xFFE0, or 0x160 for directions below 100 or above 200: screen_threshold while holding back
local biting_head_chase 08AB9E,08ABA3,08ABAB,08ABB0,08AC0C,08AC12,08AC18,08AC1E r10 path_cursor  # walks the path script: scroll word, coordinate, threshold, catch-up factor, base speed, wait, direction; saved back to path
local biting_head_chase 08AD9B r10 move_frames sprite_frame_numbers  # biting_head_frames (8BE5F, a frame_animation: last index 3) + 2 after its "mov.h [R10+], R0": the frames 7A78-7A90, "mov.h [R10](R1), R2" (08ADB0) by the next frame (biting_head_move_frames, written into parts 0 and 4); the instruction is in biting_head_move_at_speed
local biting_head_chase 08ADD5 r10 bite_record biting_head_bite_step  # biting_head_bite_steps (8BE69: last index 5) + 2 after its "mov.h [R10+], R0": biting_head_bite_step_records; 08ADEA "movea.w [R10](R1), R10" picks the step (x 4); the instruction is in biting_head_move_at_speed
local biting_head_chase 08ADEA r10 bite_record  # biting_head_bite_step_records + step * 4: the bite part's frame word, then its timer byte
local biting_head_chase 08ABB0 r1 record_coordinate  # the record's second word: the record loads once y or x (by direction) passes it
local biting_head_chase 08AD16,08AD21 r1 move_speed  # the catch-up speed from camera_slope_speed, or base_speed when larger: into chaser_speed
local biting_head_chase 08AD9E,08ADA2,08ADA9 r1 next_frame  # animation_frame + 1, back to 0 past biting_head_frames: stored back, indexes move_frames
local biting_head_chase 08ADD8,08ADDC,08ADE3 r1 next_bite_step  # bite_step + 1, back to 0 past biting_head_bite_steps: stored back, indexes bite_record
local biting_head_chase 08AE11 r1 bounce_dy  # biting_head_bounce[bounce_index]: added to screen_y; 7F ends the bounce
local fire_front_chase 08B1A0,08B1B7,08B1CE,08B1E5 r0 step  # chaser_speed << 12: moves y or x by direction and is the matching speed (negated up and left)
local fire_front_chase 08B014,08B019 r1 threshold  # hold_threshold, or 0x160 when it is 0: the screen_threshold while holding back
local fire_front_chase 08B045 r1 trigger_coordinate  # the path record's second word (0 = none): the record loads once y (plus hitbox_offset_y) or x passes it
local build_grate_steps 08BADA r0 slope_shift  # the floor attribute's x_per_rise * 0x1E: added to row_projected_x, half of it to row_projected_y, for each row
local grate_step_update 08BB37 r0 cut_gap  # saw_top_z - cut_z: once the saw reaches the cut line, debris and sound 0x28 while it is within 0x20 of it
local build_saw_shaft_base 08BD5A r0 slope_shift  # the floor attribute's x_per_rise * 0x1D: added to fixed_projected_x, half of it to projected_y, for each piece
local river_raft_spawner_update 08C24A,08C255,08C260 r10 raft_entry  # the raft list entry: y offset, speed, floor block; the next entry (+ 4) goes back to entry
local geyser_front_rise entry,08C932 r0 slope_step  # the floor attribute's x_per_rise (attribute_record + 4) * chaser_speed >> 4: the projected x shift (signed by the attribute) and twice the projected_y rise; also update_screen_position's leftover handed to add_to_touchable_list
local spawn_geyser 08CAA0 r0 free_count  # allocate_object's free count: 0 is handed back as spawned (no geyser)
local spawn_waterfall 08CC66 r0 free_count  # allocate_object_outside_loop's free count: 0 is handed back as spawned (no waterfall)
local propeller_machine_chase 08D596 r0 sound_id  # 0x0B for play_sound (the machine's sound every 0x3C frames); then find_floor's unread r0
local barred_gate_update 08D630 r1 margin  # 0x50: check_left_screen_margin's margin; then animate/play_sound/update_screen_position leftovers
local move_object_projected 08DFD7 r0 screen_y  # unproject_object's r0 (update_screen_position_no_offset's screen y): handed back as screen_y
local move_object_projected 08DFD7 r3 slope_height  # unproject_object's slope height (find_floor's): handed back as slope_height
local unproject_object 08DFE9 r0 scaled_x  # projected_x << 8: x = (scaled_x - scaled_y) * 0xB5 (find_floor's unread r0 lands here after)
local unproject_object 08E00E r0 screen_y  # update_screen_position_no_offset's screen y: handed back in p_r0
local unproject_object 08E00E r3 slope_height  # find_floor's slope height: handed back as slope_height
local delete_object_and_children 08E0F5 r0 free_blocks  # free_object_stack_2[0] after freeing a pool-2 child: above 0x100 jumps to int_default (stack overflowed); int_default's r0 is handed back
local released_ring_move 08E6D2,08E6E1 r0 bounce_speed  # bounce_speed - bounce_speed / 4 + 0x10000 on landing: stored back and as speed_z
local released_ring_move 08E6F5 r1 speed  # the ring's speed, handed to direction_to_speed; then animate's unread frame
local draw_queued_strings 08E932,08E935,08E9B0 r5 text  # the queued record's string address, + 1 a character: each byte indexes text_frames; 0 ends it
local start_ranking_entry 08EB59 r2 piece  # the piece block being linked (next_block before it moves on): its link (+28) points at the next one; the last one ends the chain
local start_ranking_entry 08EB81,08EB8E r2 entry_x_index  # the player, + 3 when cabinet_config bit 0 is clear: indexes ranking_entry_x for the entry block's x
local enter_ranking_initials 08ECA3 r0 button_bit  # var_80: the player's button bit in inputs_pressed (moves to the next letter slot)
local enter_ranking_initials 08ED80,08ED88,08ED95,08ED9F r0 links_left  # slot + 1 (4 at most): how many child links to follow to the selected letter piece
local enter_ranking_initials 08EEFC,08EF06,08EF0A r1 copy_dest  # walks saved_ranking: the copy of ranking
local enter_ranking_initials 08ED35,08ED39 r2 trackball  # the trackball address in speed_z: its first word is dx, the second dy
local enter_ranking_initials 08EE8C r2 record_rings  # each record's first long (rings word, then character and stages): compared with the rings, moved up with record_initials
local enter_ranking_initials 08EF03,08EF0E r2 records_left  # 15, counted down: the records copied into saved_ranking
local build_ranking_display 08F2C7 r0 label_second_char  # the rank label's second character (rank_label + 1): its ranking_letter_sprites entry is the second piece's frame
local build_ranking_display 08F2D9,08F2DC,08F31A,08F356,08F38A r0 remainder  # the record's rings word & 0x7FFF, less the thousands, hundreds and tens shown: its last digit is the units piece
local build_ranking_display 08F3AB,08F3AE r0 character  # the record's character byte % 3: portrait palette, name sprite, kept in the portrait's projected_y_int
local build_ranking_display 08F3E9,08F3EC r0 stages  # the record's stages byte % 9: ranking_stage_sprites[stages - 1]
local build_ranking_display 08F416 r0 first_initial  # the record's first initial: ranking_letter_sprites[it % 0x2C]
local build_ranking_display 08F437 r0 second_initial  # the record's second initial: ranking_letter_sprites[it % 0x2C]
local build_ranking_display 08F2EF,08F2F6 r1 thousands_digit  # remainder / 1000 + 0x20: the thousands piece's ranking_letter_sprites index (0x20 = digit 0)
local build_ranking_display 08F32B,08F332 r1 hundreds_digit  # remainder / 100 + 0x20: the hundreds piece's ranking_letter_sprites index
local start_character_chooser 08FA7E,08FA8B r0 x_index  # the player, + 3 when cabinet_config bit 0 is clear: indexes chooser_x_by_player
local character_chooser_wait 08FCD0 r0 right_turns  # right_count's whole steps (its high byte, cleared after)
local character_chooser_wait 08FCE6 r0 turn  # right_turns - left_turns: 0 = no turn, else stored in turn_direction
local character_chooser_wait 08FCD5 r1 left_turns  # left_count's whole steps (its high byte, cleared after)
local character_chooser_confirm 08FC30,08FC3E r0 x_index  # kind, + 3 when cabinet_config bit 0 is clear: indexes chooser_x_by_player
local character_chooser_confirm 08FC50,08FC58 r0 frame_index  # the chosen character, then * 4: chooser_portrait_frames[frame_index + 3] is the chosen portrait
local character_chooser_turn 08FD2A r0 idle_frame  # chooser_idle_steps[idle_step]: the frame offset added to character * 4
local character_chooser_turn 08FDDD,08FDEB r0 x_index  # kind, + 3 when cabinet_config bit 0 is clear: indexes chooser_x_by_player
local start_join_character_chooser 0900D9,0900E6 r0 x_index  # the player, + 3 when cabinet_config bit 0 is clear: indexes chooser_x_by_player, then returned; count_free_characters' r0 (0) is returned when no character is free
local join_character_chooser_update 0901DB,090275 r0 character  # var_81: the character shown (taken when its bit is free), and after the turn the one whose portrait is drawn
local join_character_chooser_update 09023B r0 right_turns  # spawn_arg_1's whole steps (its high byte, cleared after)
local join_character_chooser_update 090251 r0 turn  # right_turns - left_steps: stored in hitbox_size_y's high byte and added to var_81
local spawn_walls 090D22 r0 past_spawn  # scroll_position - spawn_position: 0x100 or more skips the record
param build_wall_parts R2 part_size  # hitbox_size_x (or _y) & 0x7FFF when its bit 15 is set: one part's size, times the part count (speed_x_fraction) is the wall's size; the caller's r2 otherwise
local spawn_object_groups 091256 r0 kind_index  # the record's kind byte * 2: the switch (leader, slot trigger, ice platform, sand block, waterfall, group)
local spawn_object_groups 091483 r0 slope_number  # the member's floor block slope byte: indexes floor_slopes (8 bytes an entry)
local spawn_object_groups 091354,091360 r10 slot_cursor  # walks group_slots, 2 bytes a slot, until a free one (0) is found and marked 1
local spawn_object_groups 09148A r12 slope_record floor_slope  # floor_slopes + slope_number * 8: flag bit 1 (x or y), then the slope factor at +2
local spawn_group_leader 0916F1 r0 free_count  # allocate_object_outside_loop's free count: 0 is handed back as spawned (no leader)
local spawn_group_leader 0916FE r0 leader_index  # the record's first byte & 3: the leader's sprite_frame and its group_leaders slot
local lava_rock_update 091ABF r0 height  # floor_z - z while rising: at 0x20000 or more the rise turns back (and floor_z may creep up)
local lava_rock_update 091A33 r11 slot_entry  # group_slots + group_slot * 2: negative = the slot was triggered, its low 15 bits are the erupt delay
local spawn_lava_column 091D0B,091D17 r0 side  # 1 when the column's x_int is 0xC97 or more, else 0: lava_column.side (its lava_columns slot)
local spawn_group_slot_trigger 091F1B r0 free_count  # allocate_object_outside_loop's free count: 0 is handed back as spawned (no trigger)
local spawn_group_slot_trigger 091F51 r0 fill_word  # 0: written to every longword of group_slots
local spawn_group_slot_trigger 091F45,091F54 r10 slot_cursor  # walks group_slots, 4 bytes a write (0x20 writes)
local spawn_object_groups_2 093883 r0 fill_word  # 0: written to every longword of group_slots on the first call
local spawn_object_groups_2 09389E r0 kind_index  # the record's kind byte * 2: the switch (bridge, leader, group on bridge 0 or 1)
local spawn_object_groups_2 0938E5,0938F1 r0 bridge_number  # 0, or 1 when bridge 0 already has planks: group_bridge_number
local spawn_object_groups_2 093877,093886,093974,093980 r10 slot_cursor  # walks group_slots: 4 bytes a write while clearing them, then 1 byte a slot until a free one (0) is found and marked 1
local spawn_object_groups_2 093AC0,093ACB r10 last_plank_var  # the address of bridge_0_last_plank (or bridge_1_ when group_bridge_number is set): the last member goes there
local spawn_object_groups_2 0938C7,0938D4,0938E8,0938F4 r12 plank_count_var  # the address of bridge_0_plank_count or bridge_1_plank_count: + 1 for a new bridge, + group_member_count for its members
local spawn_object_groups_2 093A83 r12 floor_type_record floor_block_type  # floor_types + floor type byte * 4: floor_type byte, floor_attribute word at +2
local rope_bridge_plank_update 093B6A,093B75 r10 sag_table  # bridge_0_sag, or bridge_1_sag when field 95 is set: indexed by the plank number (6F)
local rope_bridge_plank_update 093BFF,093C0F r10 plank_count_address  # &bridge_0_plank_count or &bridge_1_plank_count: written 0 when this plank is the bridge's last one
local rope_bridge_plank_update 093B7A,093B88 r1 sag  # sag_table[plank number] (0 when under 0x100); << 8 added to previous_floor_z
local rope_bridge_plank_update 093D4F r1 odd_plank_animation  # the record's second animation (rope_plank_animation_2): planks with an odd number (6F bit 0) take it
local update_stage_05_event 094B49,094BC3 r8 rocks_left  # stage_00_rock_count (0x10), counted down (dbr) once per rock_debris_fly piece spawned; handed back
local update_escape_countdown 095522,095571 r1 seconds_bcd  # escape_seconds_bcd: high nibble the tens digit (0 hides its sprite), low nibble the units digit
local update_escape_countdown 095527 r1 tens_digit  # seconds_bcd >> 4: index into escape_countdown_digits for the tens sprite
local map_intro_update 095AC5 r1 step_y  # ((0x10 - map_intro.base_y) << 16) / 0x3C: the icon's per-frame y step (map_intro.step_y), 60 frames to y 0x10
local map_screen_update 095F15 r11 stage_number  # stage, * 2 indexes next_stage_map_icons (the next icon, map_stage_icon)
local map_screen_update 096099 r11 marker_position map_icon_position  # map_stage_positions + stage_map_markers[stage] * 4: the x, y the icon moves to (target_x, target_y)
struct map_icon_position 4 rom        # a record of map_stage_positions (096B9B), by stage_map_markers[stage]: where the icon flies to, "add.w R10, R11" (096099), then "movz.hw [R11+], R0 / R1" (09609C, 09609F, zero-extended); census ROM 096B9B-096BB6 (0030 0010, 000D 002D, 0026 0020 ...)
field map_icon_position 00 x u16      # -> map_screen.target_x (4D)
field map_icon_position 02 y u16      # -> map_screen.target_y (4F)
local run_title_screen 096EE5 r10 background_script  # title_waving_background_script: run_vram_script advances it; handed back in R10
local start_ring_tally 09854A r3 player_3_out_flags  # flags & 10000400 of player 3's object (R9): 0 spawns its tally panel; handed back in R3
local start_ring_tally 09844C r4 stage_index  # stage: indexes stage_ring_bonus_percents and stage_ring_counts
local wait_for_frame 098ECE,098EDA r1 poll_count  # starts at 0, dbge counts it down once per poll of frames_to_wait; neg.w hands it to read_controls, which overwrites it
local read_controls 098F3D,098F60 r10 trackball_port  # C00040 + 4 per axis: the six trackball bytes (kept when within -0x60..0x60)
local read_controls 098F6A,098F7A r10 trackball_port  # C00040 + 8 per trackball: three halfwords written 0 after the reads; handed back (play_demo_trackballs' demo cursor in the demo)
local read_controls 098F49,098F66 r8 axes_left  # 6: the trackball bytes read (three trackballs, x and y)
local angle_of_vector 099082,09908F,09909B,0990A6 r3 octant_index  # 0, + 1 when dx < 0, + 2 when dy < 0, + 4 when |dy| >= |dx| (swapped): indexes angle_octants
local allocate_object 099246 r0 free_count  # the free stack's count: 0 = no free object; handed back in R0
local expand_font 0993C2,0993CB,0993D1 r0 font_row  # one byte of the 1bpp font: add.b shifts its pixels out into the carry, two per step
local vram_script_set_address 0998BF r11 dest  # 300000 + the script's halfword * 2: where the next cells go; handed back
local init_sprites 099F82 r0 end_mark  # FFFF written at the start of each of the four sprite list buffers; handed back in R0
local init_sprites 099F3B r10 control_init sprite_control_settings  # sprite_control_init ("movea.b 9A6B2[PC], R10"): the four bytes for SPRITE_CONTROL + 6...; handed back in R10 (the code is init_sprite_hardware's)
local init_sprites 099F50 r10 control_init  # walks sprite_control_init ("movz.bh [R10+], [R11+]")
local init_sprites 099F67 r11 list_start  # move_string's end after the 0x10-byte header at 400000: sprite_back_buffer and sprite_list_start
local build_sprite_list 09A27B,09A2B6,09A2B9,09A2BC,09A2BF,09A2C2,09A2C7,09A2CB,09A38D,09A390,09A393,09A396,09A399,09A39E,09A3A3,09A43F,09A442,09A445,09A448,09A463,09A466,09A469,09A46D,09A47C,09A480,09A484,09A488,09A49B,09A4A3 r19 sprite_entry  # walks the sprite list from sprite_list_start, 0x10 bytes per sprite or part; handed back in R19
local build_sprite_list 09A38A r3 sized_width  # the object's sprite_width (1A) | the frame's width bits: the width word of a sprite drawn at the object's own size (flags 23/24)
local build_sprite_list 09A424 r3 part_dest_width  # (part width * scale + 0x80) >> 8, | the frame's width bits (09A436): a part's scaled width word
local play_sound 09A5CE r1 mask_bit  # sound_id & 0x1F: its bit in attract_sound_mask[mask_index]
local play_sound 09A5C8,09A5D2 r2 mask_index  # sound_id, then >> 5: the word of attract_sound_mask to test
local queue_sound 09A5F0 r2 write_index_address  # &sound_queue_write: [R2+] reads the index, [-R2] writes it back; handed back
local split_frame_count 09A62B r0 hours_dividend  # the frames left over from the days (divx remainder): the low half divx #346A7 divides, its quotient = hours
local split_frame_count 09A63B r0 minutes_dividend  # the frames left over from the hours: divx #DFB turns it into the minutes
local split_frame_count 09A64B r0 seconds_dividend  # the frames left over from the minutes: divx #3C turns it into the seconds
local split_frame_count 09A62E r1 hours_dividend_high  # 0, the high half of the hours dividend; divx leaves the remainder in it
local split_frame_count 09A63E r1 minutes_dividend_high  # 0, the high half of the minutes dividend; divx leaves the remainder in it
local split_frame_count 09A64E r1 seconds_dividend_high  # 0, the high half of the seconds dividend; divx leaves the remainder in it
local clear_bitmap_layer 09A6F3 r1 video_base  # video_control's address (31FF00): + 0x84 is video_bitmap_clip_right/bottom
local draw_line 09A8C3 r0 step_record  # 9C4D2 + direction_index * 4 (8bpp): the two signed address steps for the line's direction
local draw_line 09A8CC,09A8D6 r0 major_step  # the record's second step, swapped with minor_step when |dx| < |dy|: added for every pixel
local draw_line 09A8C9,09A8D6,09A902 r2 minor_step  # the record's first step (swapped when |dx| < |dy|): added when the fraction carries; both steps together on a diagonal (09A902)
local draw_line 09A935 r3 span_end_copy  # movz.hw of end_x for a horizontal 8bpp line: sub.h turns it into the span length
local draw_line 09A9E5 r5 end_y_copy  # movz.hw of end_y (4bpp): sub.h turns it into dy (slope)
local draw_line 09A8A4,09A8A7,09A8AF,09A8D9,09A8DE,09A8F3 r6 steps_left  # |end_x - start_x| (8bpp), swapped with |dy| when shorter: the longer axis; + 1 = pixels to draw, counted down (dbnl)
local draw_line 09A9F6 r6 end_x_copy  # movz.hw of end_x (4bpp): sub.h turns it into dx (steep_slope)
local fill_span_4bpp 09AAF1 r5 end_x_copy  # movz.hw of end_x: sub.h turns it into the span length
local fill_rect 09AF18 r10 unclipped_edges  # clip_rect_to_window's R10: 0 = the rectangle is outside the window (left = -1, nothing drawn); handed to fill_rect_unclipped
local fill_rect_unclipped 09AF67 r2 high_nibble_color  # color << 4: the odd left column's pixel (kept low nibble)
local fill_rect_unclipped 09AF8A,09AF90 r2 byte_color  # color | color << 4: two pixels for the byte-wide column
local fill_rect_unclipped 09AFB9,09AFC6,09AFC9 r2 block_cursor  # top + rows * 0x100, - 0x100 per row (bottom row first), + 2 per halfword of the 4bpp block fill
local fill_rect_unclipped 09AFED r2 tail_keep_mask  # FFFF << (columns left * 4): the pixels of each row's last halfword that stay
local fill_rect_unclipped 09B014 r2 straddle_keep_mask  # F00F: the outer pixels kept when a two-pixel column starts on an odd pixel
local fill_rect_unclipped 09B02D r2 color_low  # color before << 4: or'ed back so both pixels of the byte get it
local fill_rect_unclipped 09B14E r2 tail_width  # right & 3 (8bpp): the last columns, filled two (halfword) and one (byte) at a time
local fill_rect_unclipped 09AF35 r3 right_copy  # movz.hw of right: sub.h turns it into last_column
local fill_rect_unclipped 09AF38 r3 last_column  # right - left (4bpp): 0 = a vertical span, 1 = two pixels wide
local fill_rect_unclipped 09AF57,09AF5C,09AF83,09AFA3,09AFD7 r3 pixel_columns  # last_column + 1 = the width, less the odd left column and the byte column; & 3 = the tail columns
param fill_rect_unclipped R5 inner_count  # the inner loop's count (8bpp): row pairs of a column strip, or 16-byte blocks of a row (dbr)
local fill_rect_unclipped 09AF29 r5 bottom_copy  # movz.hw of bottom (4bpp): sub.h turns it into rows_left
local fill_rect_unclipped 09AF2F,09AF55,09B005,09B028 r5 rows_left  # bottom - top (4bpp), + 1 = the rows; counted down by the last loops (dbr) and handed to the vertical span routines
local fill_rect_unclipped 09AF6A,09AF7F r6 rows_to_fill  # rows_left: the rows of the odd left column (dbr)
local fill_rect_unclipped 09AF96,09AF9F r6 rows_to_fill  # rows_left: the rows of the byte-wide column (dbr)
local fill_rect_unclipped 09AFB3,09AFD0 r6 rows_to_fill  # rows_left: the rows of the halfword block fill (dbr)
local fill_rect_unclipped 09B06A,09B127 r6 row_pitch_8bpp  # 0x200: one 8bpp row (two rows are filled per pass: + 0x200 and + 0x400)
local fill_rect_unclipped 09B10C r6 pair_skip  # 0x3F8: from the end of an 8-byte piece to the next row pair (0x400 - 8)
local fill_rect_unclipped 09AF6D,09AF7C r7 column_cursor  # walks the odd left column down, + 0x100 per row
local fill_rect_unclipped 09AF93,09AF9C r7 column_cursor  # walks the byte-wide column down, + 0x100 per row
local fill_rect_unclipped 09B075 r7 first_row  # the first row's address, kept across fill_span_8bpp when the row count is odd
local fill_rect_unclipped 09B0CC,09B0D3,09B0D6,09B0D9,09B0DC r7 upper_cursor  # the upper row of the pair in the 16-byte block fill, + 4 per long
local fill_rect_unclipped 09B108,09B117,09B120 r7 lower_cursor  # the lower row of the pair in the 8-column fill (+ 0x200)
local fill_rect_unclipped 09B13A r7 row_offset_longs  # 0x80: the next row as a long index ([R0](R7) = + 0x200)
local fill_rect_unclipped 09B15E r7 row_offset_halfwords  # 0x100: the next row as a halfword index ([R0](R7) = + 0x200)
param fill_rect_unclipped R8 row_pairs_left  # (rows + 1) >> 1 (8bpp): row pairs of the block fill, counted down (dbr)
local fill_rect_unclipped 09AF50 r8 row_pitch_4bpp  # 0x100: one 4bpp row
local clip_line_to_window 09C3C3 r0 swapped_start_x  # xch with R2: the line's end x, the start the second clip moves; handed back as start x
local clip_line_to_window 09C3C6 r1 swapped_start_y  # xch with R3: the line's end y, the start the second clip moves; handed back as start y
local clip_line_to_window 09C3C3 r2 swapped_end_x  # xch with R0: the (clipped) start x, now the far end; handed back as end x
local clip_line_to_window 09C3C6 r3 swapped_end_y  # xch with R1: the (clipped) start y, now the far end; handed back as end y
local eeprom_save 09C775,09C784,09C795 r4 command  # 0x30 (write enable), then 0x40 + the word address, + 1 per word written; handed back
local compute_eeprom_backup_checksum 09C897 r0 crc  # compute_eeprom_crc's CRC of the backup copy (20F64A), returned
local compute_eeprom_checksum 09C899 r0 crc  # compute_eeprom_crc's CRC from eeprom_write_count, handed back in R0
local start_test_mode 09C976 r1 scroll_y  # 0 in the low half (clear_bitmap_layer's top above it): set_bitmap_scroll's y; handed on to run_test_mode
local test_memory 09CCD2,09CCE5 r10 offset_entry memory_test_result_offsets  # memory_test_good_offsets or memory_test_bad_offsets (memory_test_errors bit set) + result_index * 2
elements memory_test_result_offsets u16  # an entry of memory_test_good_offsets or memory_test_bad_offsets: "movea.h 11775A(R5), R10" / "movea.h 117780(R5), R10" (09CCD2, 09CCE5), "movz.hw [R10], R10" (09CCED), + memory_test_result_texts (1175DE) for print_text_list; census ROM 11775A-1177A5 (0000, 000A ... 0172: 10 bytes apart)
param test_memory R4 results_left  # 0x13 result lines, counted down (dbr); handed back
local test_memory 09CBAD,09CBB4 r4 delay_count  # 0x1000: an empty delay loop after holding the sound CPU
local test_memory 09CBE6,09CBED r4 delay_count  # 0x3C: an empty delay loop after the tests
local test_memory 09CCCF,09CD00 r5 result_index  # 0 up: the bit of memory_test_errors and the result line
local test_inputs 09D30F,09D33A,09D36C r0 left  # 0xA0: the left edge of each panel fill_rect paints (flip_test_coordinates mirrors it)
local test_inputs 09D506,09D516,09D531,09D541 r10 coin_text  # the coin 2 / coin 3 ON or OFF text, printed; handed on to move_input_test_cursors
param test_inputs R4 panel_color  # 2, 0xE, 6: the color of each panel fill_rect paints; handed back
local test_inputs 09D44C,09D46B,09D486,09D4A5,09D4E5 r4 inputs_left  # 6, 7, 8 or 0xA inputs by setting_no_start_buttons/cabinet type, counted down
local move_input_test_cursors 09D6D7,09D6F2 r4 cursors_left  # 3 cursors (0x10 apart from screen_sprite_block): x at least 0xA5
local move_input_test_cursors 09D6FB,09D716 r4 cursors_left  # 3 cursors: x at most 0x132
local move_input_test_cursors 09D71F,09D73E r4 cursors_left  # 3 cursors: y limited by bottom_limit
local move_input_test_cursors 09D747,09D766 r4 cursors_left  # 3 cursors: y limited by top_limit; handed back
local test_crt 09D8C4,09D8FF,09D99A,09D9C7,09D9F5 r0 start_x  # crt_test_x: each color bar's left for fill_rect, then each line's start x for draw_line
local test_crt 09D8F8,09D93A r11 bars_left  # 0x20 bars (8 pixels, color + 1 each) per row of the color bar screen
local test_crt 09D8E3,09D93E r13 rows_left  # 4 rows of color bars (crt_test_color_bars records)
local test_crt 09D990 r13 line_record crt_test_line  # "movea.b 115D53, R13": crt_test_border_lines, the first line
local test_crt 09D99A,09D99D,09D9A0,09D9A3 r13 line_record  # walks crt_test_border_lines ([R13+]): start x, start y, end x, end y of the four border lines
struct crt_test_line 8 rom            # a line of crt_test_border_lines (115D53, 4 lines): test_crt "mov.h [R13+], R0 / R1 / R2 / R3" (09D99A-09D9A3), then draw_line in colour 7E; census ROM 115D53-115D72 (top 0,0-13F,0; bottom 0,DF-13F,DF; left 0,0-0,DF; right 13F,0-13F,DF)
field crt_test_line 00 start_x u16    # R0 (09D99A): draw_line's start x
field crt_test_line 02 start_y u16    # R1 (09D99D): its start y
field crt_test_line 04 end_x u16      # R2 (09D9A0)
field crt_test_line 06 end_y u16      # R3 (09D9A3)
local test_coin_assignments 09E000 r0 credits_to_start  # setting_credits_to_start, >> 1 indexes credits_to_start_texts
local test_coin_assignments 09E1CC r0 credits_to_start  # setting_credits_to_start after the change, >> 1 indexes credits_to_start_texts
local draw_coin_credit_cell 09E640 r3 adder_setting  # setting_bonus_adder: 0 = credits are coins * multiplier / coins per credit
local draw_coin_credit_cell 09E674,09E6B5 r3 whole_credits  # the 16.16 credit count >> 16, at most 9 (then no fraction)
local test_bookkeeping 09E904 r1 time_high  # the high halfword of the 48-bit total player time (+20): divx by the games count (+0A) for the average
local compute_bookkeeping_crc 0FFAAF r0 crc  # compute_crc16's CRC of the 0x40 bookkeeping bytes, handed back in R0
local record_player_game 0FFB3B,0FFB53 r11 status_cursor  # walks player_status (3 bytes): negative, 1 or 4 = still playing (no add_play_time)
local record_player_game 0FFB5C r11 bookkeeping_block  # 701F02: games (+0A), longest (+0C), player time (+1C, high +20), shortest (+22); copied to +40
local draw_attract_credits 0FFBDA r10 message_text  # two_credits_one_play_text, printed by print_text; handed back
local blink_start_prompt 0FFCFF r10 prompt_index  # 0: the first prompt_texts entry (erased while blinking off, or printed)
local blink_start_prompt 0FFD22,0FFD36 r10 prompt_index  # 1, or 3 on the alternate frames when two credits are needed
local blink_start_prompt 0FFD41 r10 prompt_index  # 2: no coins and no credits
local blink_start_prompt 0FFD05,0FFD2D r2 frame_bit  # 5 - frames_per_wait: the frame_counter bit that blinks the prompt; + 1 for the two-credit alternation
local draw_credit_count 0FFD93,0FFDA4 r10 compared_block  # p1_coins, then p2_coins: compared with credit_block to pick the drawn bit; handed back when nothing is drawn
param draw_credit_count_now R1 credits  # the block's credits (+1), then their units digit (remu #A), then both digits in BCD for draw_credit_digits
local draw_credit_count_now 0FFE1A r1 coins  # the block's coins (+0): the fraction's numerator (0 = no fraction)
local draw_credit_count_now 0FFE0F r2 coin_cost  # credit_coin_cost: coins per credit, the fraction's denominator (1 = no fraction)
# the INPUT TEST keeps each trackball's quadrants in the stage 3 end bytes
scoped global test_inputs 20906B trackball_1_quadrants u8              # the quadrants trackball 1 has turned through (bits; F = GOOD)
scoped global test_inputs 20906C trackball_2_quadrants u8
scoped global test_inputs 20906D trackball_3_quadrants u8
scoped global move_input_test_cursors 20906B trackball_1_quadrants u8
scoped global move_input_test_cursors 20906C trackball_2_quadrants u8
scoped global move_input_test_cursors 20906D trackball_3_quadrants u8

# ======== local variables, third round ========
local run_frame 0000F9,000104 r3 credits_needed  # mov.h credits_per_start into R3's low half (0 on free play at 000104): p1/p2/p3_credits below it cannot start; handed to attract_demo/run_game_mode as R3
local stage_clear_banner_reveal 00168C r8 at_last_step  # animate's at_last_step: set, the banner switches its handler to stage_clear_banner_hold and marks cleared_shown/stage_name_shown
local banner_sparkle_animate 0017B6 r7 wrapped  # animate's wrapped flag: set when the sparkle animation ran out, the sparkle deletes itself
local banner_letter_spin 00183E r7 wrapped  # animate's wrapped flag: set when the spin animation ran out, the letter deletes itself
local add_to_zone_list 001956 r0 old_head  # zone_list before this object is pushed: becomes its hit_list_next (as add_to_hitter_list/add_to_rider_list)
local check_hits 001DE8,001DF8,001E09 r10 other  # the object tested against the hitter: the next hitter in the list (001E09), else the touchable_list head (001DF8); check_hit's 'other'
local add_to_touchable_list 0020C2 r0 old_head  # touchable_list before this object is pushed: becomes its hit_list_next
local player_update 002D36 r0 jump_offset  # state * 3 (movea.h [R0](R0) at player_dispatch_state): the offset into the 3-byte branch table; handed to player_state_over_edge/player_state_game_over
local player_move 002D36 r0 jump_offset  # state * 3 at the re-dispatch (player_dispatch_state): offset into the branch table, handed to player_state_over_edge/player_state_game_over
local player_animate 002D36 r0 jump_offset  # state * 3 (player_dispatch_state): offset into the branch table, handed to player_state_over_edge/player_state_game_over
local player_animate_run_cycle_alternate 003322,00332E,00333C r10 alternate_run_animations  # sonic_/ray_/mighty_alternate_run_animations by the object's kind: the table handed to player_animate_run_from_table
local player_animate_jumping 0036C8,0036D2 r0 animation_rate  # 1, or 2 when attack_power is not 4: written to animation_speed for the jump
local player_animate_jumping 003715,003719 r0 text_x  # screen_x - 0x18: x of the ATTACK text (attack_text) queue_string_at prints on stage 5
local player_animate_jumping 0035F6 r8 z_int  # z_int of the player: the sign's z for spawn_sign_2 on stage 5 (beside x_int, y_int, kind 4); animate_with_flags' at_last_step lands in it unread
local player_state_falling_off_edge 003D11,003D22 r1 sink_threshold  # 0x1DC0000 on floor blocks of kind 0x22, 0x240000 on kind 0x26: compared (cmp.h, its low half) with z_int to switch to state 0x33 (sinking); play_sound's/update_screen_position's R1 land in it unread
local player_state_falling_off_edge 003DB1 r8 at_last_step  # animate_and_move's at_last_step: set, animation_speed becomes FF (the fall animation stops)
local player_state_over_edge 004272 r8 at_last_step  # animate's at_last_step after the slip-off push: set, animation_speed becomes FF
local player_state_falling_into_pit 00462F,004640 r1 sink_threshold  # 0x1DC0000 on floor blocks of kind 0x22, 0x240000 on kind 0x26: compared (cmp.h, its low half) with z_int to switch to state 0x33 (sinking)
local player_state_climbing 002D36 r0 jump_offset  # state * 3 (player_dispatch_state): offset into the branch table, handed to player_state_over_edge/player_state_game_over
local player_state_climbing 004867,004D95 r8 at_last_step  # animate_around_anchor's at_last_step: while 0 the climb over the edge goes on, then the climb animation restarts at frame 6
local player_state_climbing 004CB5 r8 world_x  # screen_x of the climber, turned back into the world x by unproject_point: the object's x
local player_state_climbing 004CBD r9 world_y  # projected_y * 2, turned back into the world y by unproject_point: the object's y
local player_state_not_playing 004EA4,004EB4 r11 text_position  # 0x31 << 16 | hud_text_positions_3p/2p[player]: where erase_text_lines clears the HUD message lines
local player_state_short_animation 005142 r8 at_last_step  # animate's at_last_step: set, animation_frame becomes FF (the animation stops)
local player_state_hurt 00566F,005677,005685,0056A2,0056B1,0056BD r2 knock_speed_x  # 0x40000 (/2 on floor type 4), then its x part by the hitter's direction (0 or -it, swapped with knock_speed_y): speed_x; play_sound's/find_floor's R2 land in it unread
local player_state_hurt 00596F,005B9F r8 at_last_step  # animate_with_flags' at_last_step: 0 keeps the shocked flash going; set, the getting-up animation ends (substate + 1)
local player_state_bouncing 005DEF r8 at_last_step  # animate's at_last_step: set, animation_speed becomes FF
local player_state_out_of_game 005E41,005E51,005E7C,005E8C,005EFA,005F0A r11 text_position  # 0x31 << 16 | the player's HUD position (hud_name_positions_3p/2p, then hud_game_over_positions_3p/2p): where erase_text_here/print_text_here write
local player_state_continue 005F4B,005F5B,005F78,005F88,00601A,00602A,00604C,00605C,006064,00612F,00613F r11 text_position  # 0x31 << 16 | the player's HUD position (ring count, continue text, credit count, continue digits + 0x14, message lines) for the text routines
local player_state_in_zone entry,002D36 r0 jump_offset  # state * 3 at the re-dispatch, handed to player_state_over_edge/player_state_game_over; also the dispatcher's R0 handed to face_movement_direction
local player_state_in_zone 0061F2,006200,006209,006210,00621C r0 trackball_speed  # trackball_x^2 + trackball_y^2 through square_root, squared and capped at 0x5000000: the target speed
local player_state_stage_exit 0074FD r0 goal_angle  # 0x1225 - x_int (dx to the goal point), turned by angle_of_vector into the direction of the run: object direction and face_movement_direction
local player_state_tumbling 00659A,0065BA,0065C9,0065D5 r2 knock_speed_x  # 0x40000, then its x part by the hitter's direction (0 or -it, swapped with knock_speed_y): speed_x; play_sound's/find_floor's R2 land in it unread
local player_state_tumbling 006984,006A2F r8 at_last_step  # animate_with_flags'/animate's at_last_step: set, animation_frame becomes FF, or the knocked-out player goes to state 0x16
local player_state_opening_scene 006D56,006D69,006E26 r1 depth  # screen_y (capped at 0xED but for player 1): the queue_sprite_at_depth depth
local player_state_opening_scene 006C71,006C84 r8 players_left  # 3 down to 0: the loop over player_status counting the players with FF (animate's unread at_last_step lands in it later)
local player_state_stunned 0073D0 r8 at_last_step  # animate's at_last_step in substate 2: set, the player recovers (state 0, control_lock 4)
local player_state_held 002D36 r0 jump_offset  # state * 3 at the re-dispatch, handed to player_state_over_edge/player_state_game_over
local player_state_stuck entry,0076CE,0076D6,0076FE r1 depth  # screen_y - sprite_offset_y kept in draw_depth while the board stands still, then the queue_sprite_at_depth depth
local player_stuck_shake 00786F r0 trackball_angle  # trackball_x, turned by angle_of_vector (with trackball_y) into the stick's direction: target_heading
local player_stuck_jump_out 00796D r8 at_last_step  # animate's at_last_step: set, the player bounces out of the board (state 0xE)
local player_state_carried_off 007E5D,007E66,007E71 r0 sound_number  # 0x36 / 0x37 / 0x77 by character for play_sound, then only handed to find_floor
local player_state_carried_off 007E8D r8 at_last_step  # animate_and_move's at_last_step: set, animation_speed becomes FF
local player_state_trapped 002D36 r0 jump_offset  # state * 3 at the re-dispatch, handed to player_state_over_edge/player_state_game_over (find_floor's R0 lands in it unread)
local player_state_trapped 008264,008272,00827B,008282,00828E r0 trackball_speed  # trackball_x^2 + trackball_y^2 through square_root, squared and capped at 0x5000000: the object's speed
local player_state_trapped 0082E1,0082EB r1 struggle_index  # high half of escape_progress, capped at 4: picks the trap_struggle_animations entry
local player_state_trapped 008213 r8 at_last_step  # animate's at_last_step of the trapped animation: set, the struggle starts and the sign spawns
local player_state_swept_away 00876F r0 away_angle  # x_int - the chaser's x_int, turned by angle_of_vector (with chaser_dy) into the angle away from the chaser: direction = it - 0x100
local player_state_swept_away 0087E7 r0 chaser_angle  # the chaser's x_int - x_int, turned by angle_of_vector into the angle toward the chaser: the direction turns toward it by swirl_turn_rate
local player_object_climb_move 008B84 r8 at_last_step  # animate's at_last_step: set, the climb step is over (substate 1, the other hand, screen_x + 0x12)
local player_object_climb_move 008BE0 r8 world_x  # screen_x of the climber, turned back into the world x by unproject_point: the object's x
local player_object_climb_move 008BE8 r9 world_y  # projected_y * 2, turned back into the world y by unproject_point: the object's y
local player_state_pulled 002D36 r0 jump_offset  # state * 3 at the re-dispatch, handed to player_state_over_edge/player_state_game_over (find_floor's R0 lands in it unread)
local player_state_floor_8_sequence entry,002D36 r0 jump_offset  # state * 3 at the re-dispatch, handed to player_state_over_edge/player_state_game_over; also the dispatcher's R0 handed to face_movement_direction
local player_state_floor_8_sequence 009405,009413,00941C,009423,00942F r0 trackball_speed  # trackball_x^2 + trackball_y^2 through square_root, squared and capped at 0x5000000: the object's speed while struggling
local player_state_floor_8_sequence 0091F9 r2 drift_speed  # scroll_drift_speed >> 14: multiplies sine/cosine[drift_direction] into drift_x/drift_y
local player_state_floor_8_sequence 0093BD r7 kind  # 0xC: the sign kind for spawn_sign_2 (beside x_int, y_int, z); animate's unread wrapped flags land in it
local player_state_floor_8_sequence 0093C4 r8 z  # z_int - 0x28: the sign's z for spawn_sign_2; the animate* at_last_step flags (never read) land in it
local player_state_floor_8_sequence 0091DA r8 floor_block_record  # floor_blocks + 2 + floor_block: handed to compute_scroll_drift
local player_move_with_scroll 0099DC,0099E0 r1 depth  # screen_y - 0x20: the queue_sprite_at_depth depth in floor 8 step A, handed back
local player_effect_update 009A6F r8 at_last_step  # animate's at_last_step: set, the effect deletes itself
local player_state_stuck_on_object 002D36 r0 jump_offset  # state * 3 at the re-dispatch, handed to player_state_over_edge/player_state_game_over
local player_stuck_on_object_shake 009E59 r0 trackball_angle  # trackball_x, turned by angle_of_vector (with trackball_y) into the stick's direction: target_heading
local player_stuck_on_object_jump_out 009F57 r8 at_last_step  # animate's at_last_step: set, the player bounces off the object (state 0xE)
local player_state_caught 00A0B7,00A0C0,00A0CB,00A1DF r0 sound_number  # 0x36 / 0x37 / 0x77 by character, or 0x87, for play_sound; then only handed to find_floor
local player_state_caught 00A0A7 r8 at_last_step  # animate_and_move's at_last_step: set, animation_speed becomes FF and the voice plays
local player_state_sinking 00A305,00A336 r8 records_left  # 2 down to 0: the two 8-halfword clip records rewritten from player block + F0 (animate's unread at_last_step lands in it later)
local player_state_sinking 00A3A5 r8 point_y  # y, with point_x and floor_z through project_point: the screen y kept in ground_clip_bottom
local sinking_effect_update 00A401 r8 at_last_step  # animate's at_last_step: set, the effect deletes itself
local player_state_knocked_off 002D36 r0 jump_offset  # state * 3 (0x16 * 3 when out of health), handed to player_state_over_edge/player_state_game_over
local player_state_knocked_off 00A489,00A497,00A4B4,00A4C3,00A4CF r2 knock_speed_x  # 0x40000 (/2 on floor type 4), then its x part by hit_direction (0 or -it, swapped with knock_speed_y): speed_x; play_sound's/find_floor's R2 land in it unread
local skid_dust_update 00A6D5 r7 wrapped  # animate's wrapped flag: set when the dust animation ran out, the dust deletes itself
param player_hud_update R1 digit_attribute  # 800/A00/C00 by character (00ABF4, 00ABFE, 00AC0A): the attribute draw_hex_4 prints the ring count with; the HUD animators' R1 lands in it and it is handed back (the in/out R1: names the parameter)
local player_hud_update 00A98B,00A99B r11 message_position  # 31xxxx | hud_text_positions_3p/_2p[player]: where erase_text_lines blanks the HUD message before the text at CDA5 is printed (joining closed)
local draw_player_start_prompt 00A9FF,00AA0F r11 prompt_position  # 31xxxx | start_prompt_positions_3p/_2p[player]: where blink_start_prompt prints the start prompt (cabinets 0, 1, 4); returned
local draw_player_start_prompt 00AA36,00AA46 r11 prompt_position  # the same for cabinets 2 and 3 (one coin block per player); returned
local blink_player_hud_message 00AD2E,00AD3E r11 prompt_position  # 31xxxx | hud_prompt_positions_3p/_2p[player]: where the text is printed or erased every 1E frames; returned
local keep_player_on_screen 00ADA8 r1 offset_x  # sprite_offset_x (low half): screen_x minus it is base_x
local push_player_from_screen_edge 00AF93 r5 push_x  # screen_edge_push_directions' second halfword for the edge: polar_to_xy's angle, back as the x step (length 200) added to x
local push_player_from_screen_edge 00AF96 r6 push_y  # 200: polar_to_xy's length, back as the y step added to y
local compute_camera_ground_speed 00AFFD r0 ground_angle  # camera_velocity_x >> 9 as angle_of_vector's dx, back as the angle: + 80 & 3FF is camera_ground_direction
local compute_camera_ground_speed 00B024 r0 map_angle  # camera_velocity_x >> 8 as angle_of_vector's dx, back as the angle stored in camera_map_direction
local compute_camera_slope_speed 00B0CE r0 slope_angle  # on a slope: camera_velocity_x >> 5 as angle_of_vector's dx (dy slope_velocity_y), back as the angle: + 80 & 3FF is camera_slope_direction
local compute_camera_slope_speed 00B123 r0 slope_angle  # flat (slope_factor 0): camera_velocity_x >> 9 as dx, the same angle for camera_slope_direction
local compute_camera_slope_speed 00B14A r0 scroll_angle  # camera_velocity_x >> 5 as angle_of_vector's dx, back as the angle stored in camera_scroll_direction
local compute_scroll_drift 00B23F r0 drift_angle  # flat block: camera_velocity_x >> 9 as angle_of_vector's dx, back as the angle: + 80 & 3FF is scroll_drift_direction
local compute_scroll_drift 00B266 r0 scroll_angle  # camera_velocity_x >> 5 as angle_of_vector's dx, back as the angle stored in camera_scroll_direction
local set_speed_from_trackball 00B2F4 r0 trackball_angle  # trackball_x as angle_of_vector's dx (dy trackball_y), back as the angle: + 80 & 3FF is the new direction
local set_climb_speed_from_trackball 00B347 r0 trackball_angle  # trackball_x as angle_of_vector's dx (dy trackball_y), back as the angle: & 3FF is the new direction (no + 80 here)
local check_player_floor_edge 00B4EC r3 side  # get_ledge_side's side (0-3), or for 4 (inside the block) the block's byte 15 + 2 & 3: stored in ledge_side
local check_player_floor_edge 00B4A5 r5 edge_speed_x  # the object's direction: polar_to_xy's angle, back as the x step (length 100) stored in speed_x (slow fall off an edge)
local check_player_floor_edge 00B4A9 r6 edge_speed_y  # 100: polar_to_xy's length, back as the y step stored in speed_y
local push_player_out_of_wall 00B875,00B879,00B87C,00B881 r0 rebound_direction  # the wall's direction byte << 8, + 200, & 3FF: stored in direction (states 4 and 17 bounce off the wall); face_movement_direction's facing comes back in it and is handed back as angle
local player_steer_in_air 00B8B2,00B8C0,00B8C9,00B8D0,00B8DC r0 input_speed  # trackball_x^2 + trackball_y^2, square-rooted and squared again, at most 5000000: the target speed speed moves toward
local player_steer 00BAA8,00BAB6,00BABF,00BAC6,00BAD0,00BADC r0 input_animation_speed  # trackball_x^2 + trackball_y^2, square-rooted, squared, x 1.5, at most 5000000: the target animation_speed
local set_target_heading_from_trackball 00BBF1 r0 trackball_angle  # trackball_x as angle_of_vector's dx (dy trackball_y x 1.5), back as the angle stored in facing_angle
local set_target_heading_from_trackball 00BC21 r0 push_angle  # trackball_x + push_x as dx (dy trackball_y, x 1.5 unless flag 80000000, + push_y), back as the angle stored in target_heading
local set_facing 00BD2C,00BD31,00BD36,00BD3A r0 new_facing  # (facing_angle + 20) >> 6 & F: the 16-way facing stored in facing; returned
local face_movement_direction 00BD42,00BD46,00BD4B r0 new_heading  # direction - A0, & 3FF: stored in target_heading, heading and facing_angle; then set_facing's facing, returned
local shadow_update 006EEA r1 scaled_offset_y  # sprite_height x sprite_scale >> 8 (scene step 3): added to screen_y, then queue_sprite's R1
local draw_attack_effect 00C38D,00C391 r1 depth  # the effect object's screen_y + 8: queue_sprite_at_depth's depth; its key comes back and goes on to queue_sprite for the second effect
local project_object_2 00C580,00C59A r1 rotated_y  # the object's y, then ((y - x) >> 9) x B5: the 45-degree turn stored in projected_y
local project_object_2 00C5B6 r1 offset_x  # sprite_offset_x: added to projected_x - view_x for screen_x
local project_object_2 00C5DC r1 offset_y  # sprite_offset_y: added for screen_y (per-player view past scroll A5F0); returned
local project_object_2 00C5F9 r1 offset_y  # sprite_offset_y: added for screen_y (shared view); returned
local check_player_step 00C96D r0 object_z  # the object's z_int: step_floor_z_int is subtracted from it into height_diff
local place_player_at_restart_point 00CC49 r8 restart_x  # the restart point's halfword (a screen x): screen_point_to_ground's point_x, back as the ground x stored in x (+ the player's spacing)
local place_player_at_restart_point 00CC46 r9 restart_y  # the restart point's byte (a screen y): screen_point_to_ground's point_y, back as the ground y stored in y
local aim_at_screen_centre 00CCC8 r0 centre_angle  # (the ground point's x - x) >> 12 as angle_of_vector's dx, back as the angle toward the screen centre, given to polar_to_xy
local aim_at_screen_centre 00CCB0 r8 centre_x  # A0 (the screen centre's x): screen_point_to_ground's point_x, back as the ground x
local aim_at_screen_centre 00CCB5 r9 centre_y  # 70 (the screen centre's y): screen_point_to_ground's point_y, back as the ground y
local start_climb_at_spawn 00D8EC,00D915 r0 climbed_height  # the floor attribute record's halfword E x (distance >> 8): the height already climbed at the spawn point, taken off z and (>> 16) off climb_remaining
local follow_player_path 00DDFC,00DE12 r0 path_heading  # the path point's x - x_int as angle_of_vector's dx, back as the angle; - 80 & 3FF: target_heading / facing_angle (and heading) toward the point
local animate_around_anchor 00DF87,00DF92,00DFA3,00DFA8,00DFBA r1 frame  # animation_frame + 1 (- 1 with flag 80), wrapped: stored back and given to set_step_around_anchor
local check_player_contacts 00EB69 r0 distance  # touch_distance: square-rooted and stored back (8 or less starts the sinking)
local check_player_contacts 00E9A2 r8 carried_x  # the touched object's speed_x: unproject_point's point_x (with speed_y x 2), back as the ground x step added twice to x
local check_player_contacts 00E9AA r9 carried_y  # the touched object's speed_y x 2: unproject_point's point_y, back as the ground y step added to y
local vine_swing 00EE75 r7 point_x  # the segment's x: project_point's x, back as the segment's screen_x
local vine_swing 00EE90 r8 point_y  # the segment's y: project_point's y, back as the segment's screen_y
local spike_panel_init entry r0 spawned  # spawn_objects' spawned: 0 when the free stack is too short (the init stops)
local spike_panel_update 00F386 r0 part_2_frame  # the panel's frame_index (byte 80 of object_cursor): indexes spike_panel_part_2_offsets / _sprites, 3 skips the draw; >= 5 picks the wide hitbox, but after queue_sprite_at_depth it holds that call's bucket head (00F3B6)
local electric_pole_update 00F678,00F680 r1 charging_spark_y  # object_cursor's screen_y: the side spark's screen_y while frame_index is 2-9, + 1 its queue_sprite_at_depth depth
local flame_machine_update 00FFB1 r0 spawned  # destroyed_frames & 7 (only 0 goes on, into spawn_object_with_pieces), then its spawned flag: 0 means no explosion was made
local flame_machine_back_wall_update entry,0100C2 r1 wall_frame  # flame_machine_back_wall_frames[frame_index]: stored in frame, indexes flame_machine_rod_sprites and flame_machine_back_wall_offsets; 5 sets flags 10 and 20; then queue_sprite's R1
local flame_machine_arm_draw 010328 r1 depth  # the rod's last piece's screen_y - 1 (also kept in R14): queue_sprite_at_depth's depth
local treadmill_update 010B60 r0 bucket  # FF: queue_sprite_in_bucket's bucket; what it leaves is handed to add_to_zone_list
local handle_rod_wait 011752 r1 depth  # screen_y: stored in hitbox_size_x and queue_sprite_at_depth's depth
local handle_rod_swing 011752 r1 depth  # screen_y: stored in hitbox_size_x and queue_sprite_at_depth's depth
local handle_rod_hang 01178D r1 depth  # screen_y: stored in hitbox_size_x and queue_sprite_at_depth's depth
local handle_rod_handle_update 0118A4,0118A9,0118AC,0118AF r1 depth  # the handle's index x 4 + the rod's hitbox_size_x (its screen_y): queue_sprite_at_depth's depth; its key goes on to queue_sprite for the shadow
local ice_launcher_update 011D3B r1 size_record  # ice_launcher_hitbox_sizes + frame_index x 4: hitbox_size_x and hitbox_size_y; spawn_object_with_pieces' piece_count lands in it unread
local padded_wall_init entry r0 spawned  # spawn_objects' spawned: 0 when the free stack is too short (the init stops)
local wall_pad_update entry,012035 r1 frame_index  # wall_pad_frames[pad_step]: indexes wall_pad_sprites; then queue_sprite's R1
local whirlwind_move 012298 r0 spin_angle  # (spin_angle + spin_step) & 3FF: stored back and given to direction_to_speed (speed 100) for the spin's speed_x / speed_y
local crawler_init 012771 r0 offset_record  # crawler_segment_offsets + variant x 4: the x / y step from one segment to the next; spawn_object_with_pieces' spawned lands in it unread
local wall_piece_draw 012CD7,012CE4,012CF0 r1 depth  # screen_y, + 14 (at most EE) when it is E0 or less: queue_sprite_at_depth's depth
local bubble_column_animate entry,013B10,013B15 r1 frame_base  # spawn_arg_1 x 13: where this column's run starts in mine_bubble_frames (frame_index is added); then queue_sprite's R1
local eggman_scene_talk 014126 r8 text_duration  # eggman_scene_text_durations[direction]: the step_timer and spawn_message_box's frames
local turtle_swim 0148EA r0 bucket  # FF: queue_sprite_in_bucket's bucket for the shadow (queue_sprite_at_depth's bucket head when there is none); handed on to add_to_touchable_list
local turtle_swim 0148D0 r9 shadow_offset_y  # the turtle's height above the floor (R14): place_sprite_near_cursor's offset_y for the shadow, also taken off its sprite_scale
local stepped_wall_draw 0158CD r0 bucket  # FF: queue_sprite_in_bucket's bucket; what it leaves goes on to add_to_touchable_list
local init_stage_view 0159FE r7 tile_set  # the stage_initial_tile_sets word under tile_set_cursor: added into the low byte of loaded_tile_sets (rotated 8 per set) and loaded by load_tile_set
local reload_changed_tile_set 015E05 r7 wanted_tile_set  # the byte of wanted_tile_sets for tile_set_slot: compared with each slot of loaded_tile_sets, loaded by load_tile_set when none holds it
local is_player_at_right_edge 016606,01660B r0 leading_limit  # push_edge_right + A: a screen_x at or past it sets player_past_leading_edge; with its low byte set to 1 it is the at_edge returned
local is_player_at_left_edge 01662D,016632 r0 leading_limit  # push_edge_left - A: a screen_x at or before it sets player_past_leading_edge; with its low byte set to 1 it is the at_edge returned
local is_player_at_top_edge 016654,016659 r0 leading_limit  # push_edge_top - A: a screen_y at or above it sets player_past_leading_edge; with its low byte set to 1 it is the at_edge returned
local is_player_at_bottom_edge 01667B,016680 r0 leading_limit  # push_edge_bottom + A: a screen_y at or below it sets player_past_leading_edge; with its low byte set to 1 it is the at_edge returned
local run_stage_05_switch 016E1A r2 camera_view_y  # what update_camera_view hands back (view_y, 16 bits): its high half (0) stays under camera_x_pixels in layer_scroll_x
local update_stage_00_exit_scene 0170A5,0170E1 r1 scroll_y_past_trigger  # nbg0_scroll_y_int - 20 (tower wall scripts) or - F6 (beam scripts): the scripts run while it is 0-7; then run_vram_script's count
local init_nbg3_layer 01B622 r1 stage_index  # stage: indexes stage_nbg3_widths and picks the NBG3 scripts and pages; then run_vram_script's count
param build_ending_scene R11 scene_actor  # each object allocate_object_outside_loop hands back in step 1: gets the tower, horizon, Sonic, Ray, Mighty and road handlers and init_ending_scene_object; the last one is handed back
local ending_big_explosion entry r11 second_object  # the object allocate_object_2 hands back: kept in speed_x, drawn with ending_big_explosion_frames_2, freed with free_object_2 at the end
local animate_horizon_blast 01C369 r10 frame_entry ending_sprite_frames  # ending_horizon_blast_frames + frame_index * 2: FFFF ends the blast, otherwise draw_ending_blast_frame's frame_entry
local animate_tower_blast 01C3BD r10 frame_entry ending_sprite_frames  # ending_tower_blast_frames + frame_index * 2: FFFF ends the blast, otherwise draw_ending_blast_frame's frame_entry
local ending_portrait_show entry r11 backdrop_object  # the object allocate_object_2 hands back: kept in speed_y, drawn as frame 9880 with the ending_portrait_backdrop_palettes palette
local ending_sonic_run_across entry r11 second_object  # the object allocate_object_2 hands back: kept in speed_y, the runner's second sprite (A820) that animate_ending_runner_across draws
local ending_ray_run_across entry r11 second_object  # the object allocate_object_2 hands back: kept in speed_y, the runner's second sprite (A7F0) that animate_ending_runner_across draws
local ending_mighty_run_across entry r11 second_object  # the object allocate_object_2 hands back: kept in speed_y, the runner's second sprite (A7C0) that animate_ending_runner_across draws
local catapult_update 0810A2,0810BC r1 depth  # screen_y + depth_offset: the sort depth for queue_sprite_at_depth
local catapult_wait_for_rider 0810D4,0810DE r1 frame  # 0, or 1 while touched (flags bit 2): the animation_frame and set_step_around_anchor's frame
local catapult_hold_up 08116D,081177,08118D r1 frame  # 5, or 4 when the frame was not 4 (it shakes between 4 and 5), 4 once animation_speed is down to 1E: animation_frame and set_step_around_anchor's frame (one variable with frame)
local machine_fire_update 083854,083987,0839CC r0 sound_id  # 9, played every 3C frames while the fire climbs on screen (one variable with sound_id); update_screen_position's R0 lands in it and only its page counts for add_to_touchable_list
local machine_fire_update 083865 r8 point_x  # fixed_projected_x: unproject_point's point_x, comes back as the new x
local machine_fire_update 08386E r9 point_y  # projected_y * 2: unproject_point's point_y, comes back as the new y
local fireball_fly entry r8 at_last_step  # what animate_and_move hands back: set at the last frame of the landing, then the fireball takes its next_handler
local opening_machine_update 084352 r1 scale  # sprite_scale: y_int * it >> 10 + B8 is the screen_y; then handed to queue_sprite, which replaces its low half
local spreading_flame_burn entry r11 fireball  # the object allocate_object hands back: made current, placed at our x/y/z with a random target y and started as a flipped fireball (next handler spreading_flame_start)
local eggman_speech_run 08578C r0 command  # the record's command word * 2: 0/8 end, 2 booth (also spawn_eggman_booth's spawned), 4 message box, 6 sound, E nothing
local scenery_sprite_draw_wide_margin 0858A5 r1 margin  # 50: check_left_screen_margin's margin; then update_screen_position's R1 lands in it unread
local collapsing_bridge_plank_update 0860A0,08625F r1 depth  # (projected_y + floor_z) >> 16 - view_y + depth_offset: stored in draw_depth and handed to queue_sprite_at_depth (one variable with depth)
local collapsing_bridge_plank_update 086164 r7 kind  # 16: spawn_sign's kind, after the sixth swing (with x BA0, y EA4); then animate's wrapped lands in it unread
local collapsing_bridge_plank_update 086169 r8 z  # 660: spawn_sign's z; then animate's at_last_step lands in it unread
local burning_boulder_update 08646C,08647B,086659,0866E9,086776 r1 depth  # screen_y - depth_offset (- 20 more before the start): stored in draw_depth and handed to queue_sprite_at_depth (one variable with depth)
local burning_boulder_update 08658B,0865A2 r8 slope_factor  # floor_slope_factors[floor_slope], 0 on flat floor: compute_camera_slope_speed's slope_factor; animate's at_last_step lands in it unread
local burning_boulder_flame_flicker entry r3 random_bits  # next_random's value: & 3F is the new pause_timer
local seesaw_start entry r11 part_list  # the block allocate_object_2 hands back: D halfwords of seesaw_parts copied in, stored in part_list
local metal_platform_base_draw 086D16 r1 margin  # 40: check_left_screen_margins' margin; then update_screen_position's R1 lands in it unread
local fire_wall_start entry r11 part_list  # the block allocate_object_2 hands back: 9 halfwords of fire_wall_parts copied in, stored in part_list
local fire_trench_update 08708C r1 margin  # 70: check_left_screen_margin's margin; then update_screen_position's R1 lands in it unread
local bridge_fire_rise 0874B6 r0 sound_id  # 9, played every 3C frames while the fire is on screen; update_screen_position's R0 lands in it and only its page counts for add_to_touchable_list
local bridge_fire_rise 0874C8 r8 point_x  # fixed_projected_x: unproject_point's point_x, comes back as the new x
local bridge_fire_rise 0874D1 r9 point_y  # projected_y * 2: unproject_point's point_y, comes back as the new y
local lava_flow_start entry r11 part_list  # the block allocate_object_2 hands back: 9 halfwords of lava_flow_parts copied in, stored in part_list
local lava_steam_puff_update entry r3 random_bits  # next_random's value: & 3F is the new spawn_arg_2 (the wait before the next puff)
local ice_block_update 087E3F r1 margin  # 50: check_left_screen_margin's margin; then update_screen_position's R1 lands in it unread
local ice_platform_piece_update 088041,088074 r1 depth  # screen_y + depth_offset - sprite_offset_y: stored in draw_depth and handed to queue_sprite_at_depth (one variable with depth)
local ice_floor_block_update 0881EC,08823D,088286 r1 depth  # screen_y, or (projected_y + floor_z) >> 16 - view_y + sprite_offset_y: stored in draw_depth and handed to queue_sprite_at_depth
local icicle_update 0889F3,088A66,088A72,088A7E,088B80,088C60,088C76,088C9B r1 depth  # (projected_y + floor_z) >> 16 - view_y (- 10, kept within FFF0..EE), or the platform's draw_depth + 1: stored in draw_depth and handed to queue_sprite_at_depth (one variable with depth)
local icicle_buried_part_draw 088D04,088D1C r1 part_depth  # the parent's draw_depth + 1: stored in buried_part_depth, the depth both parts are queued at (one variable with part_depth)
local icicle_buried_part_draw 088D2B r8 point_y  # y: project_point's point_y; its low 12 bits go into the child's sprite_flags
local tornado_column_circle 0896FD r5 orbit_dx  # the angle around the parent (its spawn_arg_2 + speed_z_int, & 3FF) into polar_to_xy, which hands back sine * radius: added to x
local tornado_column_circle 089702 r6 orbit_dy  # the radius (speed_z_fraction) into polar_to_xy, which hands back -cosine * radius: added to y
local tornado_hazard_update 0897EF r1 margin_x  # F0: is_off_screen_margin's margin_x; then update_screen_position's R1 lands in it unread
local quicksand_swirl 0899EE r1 margin  # 70: check_left_screen_margin's margin; then update_screen_position's R1 lands in it unread
local sinkhole_open entry,089BA3 r0 sound_id  # F, played when the hole opens; otherwise update_screen_position's R0 lands in it and only its page counts for add_to_touchable_list
local sinkhole_open 089BB8 r1 scale  # sprite_scale: scales unscaled_offset_x / unscaled_offset_y (>> 8) into the sprite offsets; then animate's frame lands in it unread
local sand_patch_draw 089C51 r1 margin  # 38: check_left_screen_margins' margin; then update_screen_position's R1 lands in it unread
local rimmed_sinkhole_open entry,089E07 r0 sound_id  # F, played when the hole opens; then handed to add_to_touchable_list, which only uses its page
local rimmed_sinkhole_open 089E4E,089E5A,089E7F r1 depth  # screen_y - 30, at least FFF1: stored in draw_depth and handed to queue_sprite_at_depth (one variable with depth)
local sand_block_update 08A1F0,08A3E9,08A3F7,08A406 r1 depth  # screen_y + depth_offset - sprite_offset_y: stored in draw_depth and handed to queue_sprite_at_depth (one variable with depth)
local sand_block_update 08A39A r7 point_x  # x + hitbox_size_x << 16: project_point's point_x; the projected value becomes the child's sprite_scale
local biting_head_init entry r11 taken_object  # what take_free_object hands back: first the block for the part list (biting_head_parts and two clip entries copied in), then the child object
local biting_head_chase 08AE8F,08AEAD,08AEC3,08AED2 r1 depth  # screen_y - 50: stored in biting_head_depth and handed to queue_sprite_at_depth (one variable with depth)
local biting_head_chase 08AE61 r7 point_x  # 8210000: project_point's point_x; the projected value goes to the part at part_list + 48
local falling_grate_update 08B336,08B39C r1 depth  # screen_y + depth_offset - sprite_offset_y: stored in draw_depth and handed to queue_sprite_at_depth (one variable with depth)
local rising_saw_rise 08B6CC r8 point_x  # fixed_projected_x, unproject_point turns it into the world x (stored in x)
local rising_saw_rise 08B6D5 r9 point_y  # projected_y * 2, unproject_point turns it into the world y (stored in y)
local saw_shaft_update 08B976 r7 wrapped  # 0, then animate_step's wrapped flag: set = the shaft step is done (state + 1)
local grate_step_update 08BB58 r8 at_last_step  # 0, then animate_step's at_last_step: set = the cut animation ended (animation_frame = FF)
local spawn_grate_step_debris entry,08BC1A r3 random_value  # next_random's value: its low half is added to speed_x, its high half (sha #F0) to speed_z
local geyser_front_rise 08C9FD,08CA08 r11 row_delays  # the row pattern byte * 0xA + geyser_row_delays: the five wait words of the geyser row (into geyser_delay_cursor); then spawn_geyser's unread geyser
local geyser_front_rise 08C954 r8 point_x  # fixed_projected_x, unproject_point turns it into the world x (stored in x)
local geyser_front_rise 08C95D r9 point_y  # projected_y * 2, unproject_point turns it into the world y (stored in y)
local river_water_update 08CFE0 r7 clip_x  # the water's x, projected at floor_z by project_point: stored in the first clip block's sprite_scale word
local geyser_line_move 08D1C1,08D1C6 r0 hold_distance  # scroll_position - hold_start_position: 0x300 or more ends the hold (project_object's r0 before; its high half stays in speed)
local geyser_line_move 08D1F1 r0 speed  # geyser_line.speed (low half only, mov.h): added to travel, << 12 subtracted from y
local scene_anchor_update 08D6D4 r8 point_y  # the anchor's y, projected at floor_z by project_point: sets the z that keeps it at screen height 0x70
local railing_segment_draw 08D7E1 r1 margin  # 0x88: check_left_screen_margin's margin; then update_screen_position's unread leftover
local spawn_objects_by_type 08DC75 r1 trigger_scroll  # the record's first word: FFFF ends the list, otherwise the scroll position the record spawns at (also spawn_object_with_pieces' unread piece count after)
local spawn_object_with_pieces 08DD44 r2 pool_2_free  # free_object_stack_2[0]: the free blocks of pool 2; less the piece count must not borrow
local add_object_pieces 08DD91 r2 pool_2_free  # free_object_stack_2[0]: the free blocks of pool 2; less the piece count must not borrow
local delete_object_and_children 08E0DB,08E112 r0 child  # the child being deleted: the parent's first child, then each one's next child (FFFF ends); below 0x5000 a pool-1 object, else a pool-2 block
local face_nearest_player 08E227,08E230,08E243,08E24F r0 distance  # |dx|, then dx * dx + dy * dy, square_root makes it the player's distance (distance_to_player, nearest_player_distance)
local direction_to_object 08E357 r0 dx  # the projected x difference to the object (R14); angle_of_vector turns it into the direction stored in direction
local released_ring_move 08E6F1 r0 direction  # the ring's direction, direction_to_speed masks it (& 3FF) into speed_x, speed_y
local start_ranking_entry 08EB65 r11 piece_copy  # the piece block just linked, handed to init_object_2; its high half later builds previous_block
local enter_ranking_initials 08ED04,08ED08 r0 text_x  # screen_x + 0x20: the x of the second string (0x8EFF8 + direction * 0xA)
local enter_ranking_initials 08EF59 r0 blink_timer  # var_82 after the count down: the out-of-rank text is drawn when its low two bits are not 0
local reset_ranking 08F135 r3 letter  # the record's first initial from default_ranking, through ascii_to_ranking_letter into ranking
local reset_ranking 08F13E r3 letter  # the record's second initial, through ascii_to_ranking_letter into ranking
local reset_ranking 08F147 r3 letter  # the record's third initial, through ascii_to_ranking_letter into ranking
local build_ranking_display 08F458 r0 third_initial  # the record's third initial: ranking_letter_sprites[it % 0x2C]; then spawn_object_with_pieces' unread spawned
local build_ranking_display 08F367,08F36A r1 tens_digit  # remainder / 10 + 0x20: the tens piece's ranking_letter_sprites index (0x20 = digit 0)
local draw_ranking_title 08F5EF,08F5F3 r1 layout_offset  # speed_y_int * 2: added to screen_x (layout 0) or screen_y (layout 1) of the third piece
local draw_ranking_title 08F62C,08F630 r1 layout_offset  # speed_y_int * 2: subtracted from screen_x (layout 0) or screen_y (layout 1) of the fourth piece
local character_chooser_wait 08FD5E r1 taken_bit  # character_taken_bits[the side portrait's character]: set in characters_taken greys the portrait out
local character_chooser_confirm 08FC75 r3 random_value  # next_random's value: its low two bits pick one of the character's four chooser_voice_sounds
local character_chooser_confirm 08FC89 r0 sound_id  # the voice chooser_voice_sounds[character].sounds[random & 3] gives, for play_sound
local character_chooser_turn 08FD5E r1 taken_bit  # character_taken_bits[the side portrait's character]: set in characters_taken greys the portrait out
local join_character_chooser_update 09017B,090183 r0 text_x  # screen_x - 0x25: the x of the time-left string
local join_character_chooser_update 0901B1,0901B9 r0 text_x  # screen_x + 9: the x of the second string
local spawn_walls entry,090D0C r1 spawns_offset  # wall_spawns[stage]: this stage's records in wall_spawns (start of wall_spawn_cursor); otherwise the caller's r1 and project_point's leftover handed back
local spawn_walls 090E22 r7 corner_x  # wall_corner_x, projected by project_point: less screen_x is the corner's screen offset (speed_z_fraction)
local spawn_object_groups 09123F,091244,091253,0912F0,091302,091304,09130A,09131B,091321,091327,09132D r10 record  # walks the record at group_spawn_cursor: scroll position, kind byte, then the leader, slot flag, floor block, member count, steps, direction, member x/y and member record
local crumbling_floor_update 091678 r1 sprite_depth  # screen_y + depth_offset - sprite_offset_y: kept in draw_depth and handed to queue_sprite_at_depth
local crumbling_floor_update 0916A1 r8 at_last_step  # animate_with_screen_offset's result: negative stops the crack animation (animation_timer = -1)
local lava_rock_update 091AA6 r1 sprite_depth  # screen_y + depth_offset - sprite_offset_y: kept in draw_depth and handed to queue_sprite_at_depth
local lava_rock_update 091BC2 r1 sprite_depth  # the screen y of previous_floor_z + depth_offset: kept in draw_depth and handed to queue_sprite_at_depth
local lava_rock_update 091972 r3 random_value  # next_random's value: (it & 0xF) + 4 is the glow's palette_delay
local lava_rock_update 091C26 r8 at_last_step  # animate's or animate_all_parts' at_last_step: set = the eruption animation ended (animation_frame = FF)
local lava_column_rise 091E0E,091E1A r1 sprite_depth  # screen_y - 0x128 (at least FFF2): the rising column's depth for queue_sprite_at_depth
local spawn_object_groups_2 09388D,093892,09389B,09391C,09392E,093930,093936,09394A,093950,093956,09395C r10 record  # walks the record at group_spawn_cursor_2: scroll position, kind byte, then the leader, slot flag, floor block, member count, steps, direction, member x/y and member record
local rope_bridge_plank_update 093DC6 r1 sprite_depth  # the screen y of floor_z + depth_offset: kept in draw_depth and handed to queue_sprite_at_depth
local rope_bridge_plank_update 093E06 r8 at_last_step  # animate's or animate_all_parts' at_last_step: negative stops the falling plank's animation (animation_timer = -1)
local stage_00_platform_stand 094CB3,094CBF r0 bucket  # depth_to_bucket's bucket + 0x3C (at most 0xFF): handed to queue_sprite_in_bucket
local stage_00_platform_break 094D2B r0 bucket  # depth_to_bucket's bucket - 0x3C: handed to queue_sprite_in_bucket
local stage_06_lightning_animate 0953A9 r8 at_last_step  # animate's at_last_step: set = the bolt is deleted, else it is drawn
local update_escape_countdown 0954E0 r0 seconds  # escape_seconds, turned into BCD by convert_to_bcd (stored in escape_seconds_bcd)
local map_screen_update 095FB4 r5 edges_done  # update_screen_wipe's edges_done: low nibble F = all four window edges arrived
local start_window_wipe 0967C5 r5 frames  # the record's third word: the wipe's length in frames (window_wipe_frames), divisor of the four edge speeds; then update_screen_wipe's unread edges_done
local title_emblem_animate 096FA5 r11 emblem_part  # allocate_object_2's block, one per call: kept in the title object's speed_y, z, emblem_bottom_left, animation, x, y and floor_z as the seven emblem part blocks
local ring_tally_percent_update entry,0987CD r3 shown_percent  # the percent shown: the real one (hit_list_next = ring_tally_percent) once counted, next_random's value while it rolls; its low byte goes to BCD
local update_ring_tally_panel 098CFF r0 counted_bcd  # the panel's rings_counted, turned into BCD by convert_to_bcd: its three nibbles pick ring_tally_digits
local allocate_object_or_start_list 099234 r11 new_object  # the object popped from free_objects when the list is empty: first_object, last_object, linked and handed back
local draw_line 09A9E0,09A9F0,09AA0A r10 row_step_4bpp  # 0x100, negated when the line goes up and again when its ends are swapped: added to the byte address per row (4bpp)
local draw_line 09A86D r11 end_outcode  # get_window_outcode's first result (the end point's): ANDed with the start's (both off one edge: nothing drawn), handed to clip_line_to_window
local draw_line 09A938,09A93D r3 span_length  # span_end_x - start_x (negated when the line runs left): fill_span_8bpp's length for a horizontal 8bpp line
local draw_line 09A894 r5 end_y_copy_8bpp  # mov.w of end_y (8bpp): sub.h turns it into dy (slope)
local fill_rect_unclipped 09B0B1 r9 row_bytes  # right - left + 1: the row's width in bytes (8bpp); >> 4 is blocks_per_row
local eeprom_lock 09C61C r2 lines  # eeprom_select's EEPROM lines byte (io_port_d), through eeprom_send_command to eeprom_deselect
local eeprom_read_all 09C731 r2 lines  # eeprom_select's EEPROM lines byte (io_port_d), handed to eeprom_read_word for every word
local eeprom_read_all 09C729,09C737 r4 command  # 0x80 (read) + the word address, + 1 per word: eeprom_read_word's command
local eeprom_save 09C779 r2 lines  # eeprom_select's EEPROM lines byte (io_port_d), through eeprom_send_command and eeprom_write_word
local eeprom_send_command 09C85B,09C861 r0 data_bit  # the command's top bit (& 0x80, the data line): or'ed into lines for each of the 8 clocks
local draw_test_menu 09CA6A,09CA75 r0 write_count_bcd  # eeprom_write_count in BCD (convert_to_bcd, & FFFFF): printed by draw_hex_8 when show_eeprom_write_count is set
local test_inputs 09D591 r0 sprite_block  # screen_sprite_block: the cursor sprites' pool-2 block, freed (free_object_2) when leaving the screen
local move_input_test_cursors 09D66C r1 trackball_dx  # trackball_1_x: the quadrant test's dx, >> 2 moves cursor 1's x
local move_input_test_cursors 09D68E r1 trackball_dx  # trackball_2_x: the quadrant test's dx, >> 2 moves cursor 2's x
local move_input_test_cursors 09D6B0 r1 trackball_dx  # trackball_3_x: the quadrant test's dx, >> 2 moves cursor 3's x
local move_input_test_cursors 09D671 r2 trackball_dy  # trackball_1_y: the quadrant test's dy, >> 2 moves cursor 1's y
local move_input_test_cursors 09D693 r2 trackball_dy  # trackball_2_y: the quadrant test's dy, >> 2 moves cursor 2's y
local move_input_test_cursors 09D6B5 r2 trackball_dy  # trackball_3_y: the quadrant test's dy, >> 2 moves cursor 3's y
local move_input_test_cursors 09D676 r3 quadrant_bit  # get_trackball_quadrant's bit (1, 2, 4 or 8): or'ed into trackball_1/2/3_quadrants (F = all seen)
local test_crt 09D904 r1 top  # crt_test_y: each color bar's top for fill_rect (bottom = top + 0x1F)
local draw_vitality_setting 09DDEF r0 vitality_number  # vitality_setting_numbers[setting_initial_vitality]: printed by draw_hex_4
local draw_vitality_setting 09DE04 r0 left  # 0x78: the bar background's left for fill_rect (flip_test_coordinates mirrors it)
local draw_vitality_setting 09DE37 r0 left  # 0x78: the filled bar's left for fill_rect (right = (vitality + 1) * 0x20 + it)
local draw_vitality_setting 09DE18 r4 bar_back_color  # color with low byte 0x17: the bar background's color (fill_rect); 0x49 replaces it for the filled part
local draw_difficulty_setting 09DE7E r0 difficulty_number  # setting_difficulty + 1: printed by draw_hex_2
local draw_difficulty_setting 09DE92 r0 left  # 0x78: the bar background's left for fill_rect (flip_test_coordinates mirrors it)
local draw_difficulty_setting 09DEC5 r0 left  # 0x78: the filled bar's left for fill_rect (right = (difficulty + 1) * 0x20 + it)
local draw_difficulty_setting 09DEA6 r4 bar_back_color  # color with low byte 0x17: the bar background's color (fill_rect); 0x49 replaces it for the filled part
local test_coin_assignments 09DFA4,09DFE1,09DFE6 r0 setting_number  # individual_coin_setting_numbers[setting_coin_number_individual] or setting_coin_number + 1: printed in BCD by draw_hex_2
local test_coin_assignments 09DF37 r10 adder_setting  # &setting_bonus_adder: handed to clamp_coin_setting
local draw_coin_credit_cell 09E690,09E6A4,09E6A9,09E6B8 r1 fraction_coins  # 0x10000 / coins_per_credit (one coin's share of a credit), then the credit fraction / it: the coins toward the next credit (0 = no fraction), draw_fraction's numerator
local draw_coin_credit_cell 09E6D4,09E6E4,09E6E7 r11 text_position  # the cell's text address (column * 2 + row * 0x80): - 4 after the fraction, - 2 for the whole credits
local draw_coin_credit_cell 09E63B,09E660 r2 coins_per_credit  # setting_coins_per_credit: the credits' divisor and draw_fraction's denominator
local test_bookkeeping 09E7C4,09E7D5 r0 total_coins  # chute 1 + chute 2 (+ chute 3) coins: printed in BCD on the total coins line
local test_bookkeeping 09E9A3 r0 shortest_game_frames  # bookkeeping_shortest_game (+22): FFFFFFFF = none (zeros shown), else split into hours, minutes, seconds
local test_bookkeeping 09E8F1 r5 average_seconds  # 0, then split_frame_count's seconds of the average game time (player time / games)
local test_bookkeeping 09E997 r5 shortest_seconds  # 0, then split_frame_count's seconds of the shortest game time
local test_bookkeeping 09E8F4 r6 average_minutes  # 0, then split_frame_count's minutes of the average game time
local test_bookkeeping 09E99A r6 shortest_minutes  # 0, then split_frame_count's minutes of the shortest game time
local test_bookkeeping 09E8F7 r7 average_hours  # 0, then split_frame_count's hours of the average game time
local test_bookkeeping 09E99D r7 shortest_hours  # 0, then split_frame_count's hours of the shortest game time
local update_coins 0FF806,0FF882,0FF89E r10 coin_credit_block  # the credit block a coin goes to: p1_coins, + 4 per chute in the individual modes; add_coin's R10
local update_coins 0FF7AE r8 service_credits_added  # 0, then add_service_credit's count for each block: added to bookkeeping_service_credits
local update_coins 0FF7F4 r9 coin_credits_added  # 0, then add_coin's count for each chute: added to bookkeeping_coin_credits
local draw_credit_count_now 0FFDE2 r0 plural_cell  # print_text_here's last cell with its low byte set to 0x73 ('s'): written 4 bytes back, so CREDIT reads CREDITS from 2 credits on

# ======== unused code (from build/naming/dead_1..5.txt) ========
# Code nothing in the program reaches (no call, jump, table or stored pointer leads there): unused functions, decompiled like the rest
08341F  data  roller_back_zone_limits      # replaces data_08341F: object_steps records 3-7 (spawn_roller_back_zone reads object_steps[roller_back_zone_index * 3], indices 3-7 from spiked_roller_kinds): limit words 10A0, F74B, 0AF0, FA63, 0650 with no handler, then a 00 byte before machine_start
09A65C  data  pause_text                   # replaces data_09A65C: a print_text record (cell F7A4: row 15 column 18, attribute 0, "Pause", 0) and a 00 byte; no code found reads it
09EDC1  data  data_09EDC1    # data that decodes as instructions into invalid ones: zeros and FF after three RSR
09FEDD  data  data_09FEDD    # data that decodes as instructions into invalid ones: a table of halfwords

# Unused code, work list 1 (001459-082DF2): names, parameters, locals, labels, variables and tables

# ---- debug stage select shortcut ----
001459  func  check_stage_select_buttons      # unused: service held (input_ports_active bit 0) and player 1's jump just pressed (inputs_pressed bit 16): game_mode 8 (mov.h), the debug stage select (game_mode_stage_select, right after it)

# ---- returns nothing reaches: the rsr after a routine's tail jump ----
003DF8  func  player_state_falling_off_edge_spare_return    # unused: a lone rsr after player_state_falling_off_edge's last jmp (queue_sprite_at_depth); player_state_over_edge follows
00439A  func  player_state_dropping_spare_return            # unused: a lone rsr after player_state_dropping's jmp queue_sprite_at_previous_floor
0046EC  func  player_state_falling_into_pit_spare_return    # unused: a lone rsr after player_state_falling_into_pit's jmp queue_sprite_at_depth; player_state_climbing follows
005436  func  player_state_launched_spare_return            # unused: a lone rsr after player_state_launched's jmp queue_sprite_at_floor
0059F9  func  player_state_hurt_spare_return                # unused: a lone rsr after player_state_hurt's jmp queue_sprite_at_base (player_hurt_get_up); player_hurt_recover follows
006E2F  func  player_state_opening_scene_spare_return       # unused: a lone rsr after player_state_opening_scene's jmp queue_sprite_at_depth
006E8D  func  shadow_update_spare_return                    # unused: a lone rsr after the opening scene shadow code at 006E30 (shadow_update jumps there in game mode 10) and its jmp queue_sprite_at_depth
00FF9F  func  flame_machine_update_spare_return             # unused: a lone rsr after flame_machine_update's br spawn_flame_machine_wreck; flame_machine_remove follows
014C59  func  eel_anchor_wait_explode_spare_return          # unused: a lone rsr after eel_anchor_wait_explode's last br; eel_head_move follows
01DDC7  func  ending_eggman_fly_away_spare_return           # unused: a lone rsr after ending_eggman_fly_away's jmp delete_current_object
01E6F6  func  carried_ring_spin_spare_return                # unused: a lone rsr after carried_ring_spin's br draw_child_shadow; scatter_rings_fixed follows
082DF1  func  bouncing_fireball_update_spare_return         # unused: a lone rsr after bouncing_fireball_update's jmp into draw_child_shadow (01F007)
0169C7  func  scroll_slowing_down_spare_branch           # unused: a lone br set_camera_velocity after scroll_slowing_down's jmp scroll_at_scroll_speed
0169F7  func  scroll_speeding_up_spare_branch            # unused: a lone br set_camera_velocity after scroll_speeding_up's jmp advance_scroll_unless_ended

# ---- 00 bytes (halt) that put the next routine at an even address ----
005E23  data  player_state_out_of_game_padding  # a 00 byte (halt) after player_bouncing_end's rsr, aligning player_state_out_of_game (005E24): not code
007499  data  player_stage_exit_stage_4_padding  # a 00 byte (halt) after player_state_you_failed's rsr, aligning player_stage_exit_stage_4 (00749A): not code
007C09  data  player_state_scene_thrown_padding  # a 00 byte (halt) after player_state_stage_b_sequence's jmp queue_sprite, aligning player_state_scene_thrown (007C0A): not code
0158E1  data  init_stage_view_padding         # a 00 byte (halt) after stepped_wall_draw's rsr, aligning init_stage_view (0158E2): not code
01B403  data  init_nbg2_layer_padding         # a 00 byte (halt) after set_nbg1_scroll_to_camera's rsr, aligning init_nbg2_layer (01B404): not code

# ---- player tumbling (state 20): an earlier knockback and the start code it shares with the live state ----
0065E2  func  player_tumbling_knock_with_toucher_speed   # unused: speed_x/speed_y = the toucher's (R10) speed_x/speed_y x 2 (in place of the fixed 40000 by direction player_state_tumbling uses, whose br lands right after it), then player_tumbling_set_launch_speed
0065F8  func  player_tumbling_set_launch_speed           # player_state_tumbling's tumbling start with a knockback (and the unused 0065E2): speed = 4000000, then player_tumbling_launch
006601  func  player_tumbling_launch                     # the rest of the tumbling start (straight here when the toucher is kind 17): direction = the toucher's (100 on floor type D), face_movement_direction, the character's tumble animation, normal palette, hurt flash 3/3/off, the character's voice (3A/61/6E), spawn_tumble_effect, then player_tumbling_fly
param player_tumbling_set_launch_speed R10 toucher       # player_state_tumbling's LINKED(touched_by): handed to player_tumbling_launch
param player_tumbling_set_launch_speed R11 effect        # handed on to spawn_tumble_effect (its effect object)
param player_tumbling_launch R10 toucher                 # the object that touched the player: its direction becomes the player's (unless floor type D)
param player_tumbling_launch R11 effect                  # spawn_tumble_effect's effect

# ---- stage slots ----
00A607  data  move_object_to_next_stage_slot_padding  # a 00 byte (halt) that aligns move_object_to_next_stage_slot: not code
00A608  func  move_object_to_next_stage_slot  # unused: while next_stage_slot_request is set, moves the current object's x, y, z_int, previous_floor_z_int and previous_floor_block from this stage's link block to the next stage's (- stage_link_from_*, + stage_link_to_*), the object side of continue_to_next_stage_slot

# ---- HUD ----
00AD5C  func  draw_player_stage_rings          # unused: when stage_rings differs from player field 12C (stage_rings_shown, player_hud_init sets FFFF: the count shown), copies it there and draw_hex_2 (attribute 0) at 31 << 16 | hud_stage_rings_positions[player]
113F44  table hud_stage_rings_positions u16    # [player] FB8C, FBA6, FBC0: text row 23, columns 6, 19, 32 (5 cells right of hud_text_positions_3p): where draw_player_stage_rings (unused) draws stage_rings in hex

# ---- floor block lookups (earlier versions of find_floor's search) ----
00C850  func  get_floor_z_at_point             # unused: finds the nearby_floor_blocks entry whose box (x min/max, y min/max) holds the point R7 = x, R8 = y (else no_floor_block) and hands back R9 = its height (+10) << 16 plus, on a slope (+13), (x or y - the box start) >> 16 x the floor_slopes factor (bit 1 = along x): find_floor's floor z for any point (with no block nearby R10 = floor_blocks + 2 and R9 is left alone)
00C887  code  get_floor_z_at_point_next_block  # get_floor_z_at_point: the point is outside this block: count down to the next nearby block, no_floor_block after the last
local get_floor_z_at_point entry r7 x                    # the point's x (32-bit), compared with each block's x min/max
local get_floor_z_at_point entry r8 y                    # the point's y
local get_floor_z_at_point 00C864 r0 test_x              # copy of x the box tests use
local get_floor_z_at_point 00C8A4 r0 slope               # the block's slope type (+13): 0 flat, else its floor_slopes record
local get_floor_z_at_point 00C867 r1 test_y              # copy of y the box tests use
local get_floor_z_at_point 00C85D,00C86D r11 cursor      # walks nearby_floor_blocks (u16 block offsets)
local get_floor_z_at_point 00C86D r2 block_offset        # the nearby entry: the block's offset from floor_blocks + 2
local get_floor_z_at_point 00C85D,00C887 r6 blocks_left  # nearby_floor_block_count, counted down
local get_floor_z_at_point 00C873 t2 x_min               # the block's x min
local get_floor_z_at_point 00C878 t2 x_max               # its x max
local get_floor_z_at_point 00C87D t2 y_min               # its y min
00C8D9  func  find_floor_block_at_point        # unused: the same search for the point R8 = x, R9 = y: hands back R10 = the block holding it (floor_blocks + 2 + offset) or no_floor_block (floor_blocks + 2 with no block nearby)
local find_floor_block_at_point entry r8 x               # the point's x
local find_floor_block_at_point entry r9 y               # the point's y
local find_floor_block_at_point 00C8ED r0 test_x         # copy of x the box tests use
local find_floor_block_at_point 00C8F0 r1 test_y         # copy of y
local find_floor_block_at_point 00C8E6,00C8F6 r11 cursor # walks nearby_floor_blocks
local find_floor_block_at_point 00C8E6,00C910 r6 blocks_left  # nearby_floor_block_count, counted down
local find_floor_block_at_point 00C8FC t2 x_min          # the block's x min
local find_floor_block_at_point 00C901 t2 x_max          # its x max
local find_floor_block_at_point 00C906 t2 y_min          # its y min
00CAD7  func  get_floor_block_side_or_corner   # unused: as get_floor_block_side (just before it) for block offset R10 and point R11, R12, with the corners: outcode R0 (1 past x max, 2 before x min, 4 before y min, 8 past y max) and R3 = floor_block_side_codes[outcode]
113E95  table floor_block_side_codes u8        # [outcode: 1 past x max, 2 before x min, 4 before y min, 8 past y max] 0 1 3 0 0 5 4 0 2 6 7: get_floor_block_side's sides (0 y min side or inside, 1 x max, 2 y max, 3 x min) and 4-7 the corners (get_floor_block_side_or_corner, unused)

# ---- floor push ----
00D9F6  func  add_push_to_player_motion        # unused: when push_x/push_y (read as one word) is not 0: polar_to_xy(heading_snapped, speed_int) + the push words, each >> 4; speed = sqrt(dx^2 + dy^2) << 12 (0 while speed_int < 0, at most 5000000), heading = angle_of_vector; then push_x/push_y cleared (32-bit stores at 94 and 96)
local add_push_to_player_motion 00DA2B,00DA79 r0 sum_dx         # (the move's dx + push) >> 4; one C variable with sum_dx^2 + sum_dy^2 (square_root's value: the new speed comes back) and angle_of_vector's dx (the new heading comes back)
local add_push_to_player_motion 00DA3C r1 sum_dy                # (the move's dy + push) >> 4
local add_push_to_player_motion 00DA01 r5 angle                 # heading_snapped: polar_to_xy's angle (the move's dx comes back in it)
local add_push_to_player_motion 00D9FD r6 length                # speed_int: polar_to_xy's length (the move's dy comes back in it)
scoped global add_push_to_player_motion 20E500 push_motion_x s32  # the move's dx + the push word at 94, then >> 4 (sha): angle_of_vector's dx
scoped global add_push_to_player_motion 20E504 push_motion_y s32  # the move's dy + the push word at 96, then >> 4 (32-bit, as scratch_1's AS32)

# ---- sand rock ----
012F7D  func  sand_rock_draw_piece             # unused: probably drew the rock's second part: R20 = the child (28) of R23, put at its screen x/y and queued at screen_y + 1F (the rock itself queues at + 14) (right after sand_rock_update)

# ---- debug sprite mover (debug_sprite_x, debug_sprite_y, debug_sprite_spawned) ----
016C1D  func  spawn_debug_sprite_mover         # unused: once (20CA3A debug_sprite_spawned is 0, then + 1) allocate_object_outside_loop with handler debug_sprite_mover_update
016C3D  func  debug_sprite_mover_update        # unused: handler (only from spawn_debug_sprite_mover): sprite 8DB8, palette 130, layer 32 at 20CA36/20CA38 (debug_sprite_x, debug_sprite_y, init_stage_view sets them to 30); held 1P jump / 1P start (input_ports_active bits 16/4) move it right/left, 2P jump / bit 25 down/up, one pixel a frame; the two in hex at text cells (3,22) and (8,22)
016D02  func  call_run_map_screen              # unused: jsr run_map_screen, rsr (between start_stage_view and run_stage_05_switch)

# ---- "Scroll skip mode": a debug panel that moves the camera to a stage section ("POINT") ----
01A797  data  run_scroll_skip_mode_padding    # a 00 byte (halt) that aligns run_scroll_skip_mode: not code
01A798  func  run_scroll_skip_mode            # unused: closed (20902B scroll_skip_open 0): opens while service and 2P start are held (input_ports_active 21): last point = scroll_skip_last_points[stage], stage_start_section D (then capped), flags cleared, scroll_skip_print_panel; open: scroll_skip_draw_values, _next_point, _previous_point, _check_call, _check_exit every frame
01A826  func  scroll_skip_next_point           # unused: "NEXT:1P JUMP": 1P jump (bit 16) pressed after a release (scroll_skip_next_armed): stage_start_section + 1, at most scroll_skip_last_point; nothing while both jumps are held
01A87D  func  scroll_skip_previous_point       # unused: "BACK:2P JUMP": 2P jump (bit 24) after a release: stage_start_section - 1, not below 0
01A8D0  func  scroll_skip_check_exit           # unused: "EXIT:2P START": 2P start (bit 5) after a release: closes the mode (20902B = 0) and scroll_skip_erase_panel; nothing while both starts are held
01A918  func  scroll_skip_erase_panel          # unused: print_text_list(scroll_skip_blank_text)
01A924  func  scroll_skip_check_call           # unused: "CALL:1P START": 1P start (bit 4) after a release: move_camera_to_start_section (the camera jumps to stage_start_section), every frame while it stays held (it does not clear scroll_skip_call_armed; only opening the panel does)
01AAAE  func  index_stage_list_pair            # unused: R2 = [R2 + R5] + R3, R1 = [R1 + R5] + R3: two per-stage pointer lists indexed by stage (R5) and moved on by a section offset (R3), as load_camera_start_section (just before it) does inline with its offset tables
01AACA  func  scroll_skip_x_position_cell      # unused: R11 = text cell (34,18), after "X POSITION  =0x" (scroll_skip_draw_values' value 0)
01AAD2  func  scroll_skip_y_position_cell      # unused: R11 = text cell (34,19), after "Y POSITION  =0x" (value 1)
01AADA  func  scroll_skip_total_cell           # unused: R11 = text cell (34,17), after "SCROLL TOTAL=0x" (value 2)
01AAE2  func  scroll_skip_draw_values          # unused: one value a frame (scroll_skip_value_index 0-2): scroll_skip_value_lists[index] per-stage list, word of stage_start_section, draw_hex_4 (attribute 200) at its cell; after the third, stage_start_section in hex at (24,15) after "POINT" (attribute 600)
01AB5D  func  scroll_skip_print_panel          # unused: print_text_list(scroll_skip_panel_text)
01AB69  func  clear_bitmap_lower_half          # unused: bitmap_address 30E000 (row 70 at 8 bits per pixel, 200 bytes a row), 7000 halfwords from there cleared (rows 70-DF), bitmap layer on (video_layer_disable bit 5 cleared), bitmap_8bpp 1, window 0,0-13F,DF (and an unused mov.b #EE, R4)
param scroll_skip_x_position_cell R11 text_position      # 31F944: where scroll_skip_draw_values draws value 0
param scroll_skip_y_position_cell R11 text_position      # 31F9C4
param scroll_skip_total_cell R11 text_position           # 31F8C4
local scroll_skip_next_point 01A83C r2 held_buttons      # input_ports_active & (1P jump | 2P jump)
local scroll_skip_previous_point 01A893 r2 held_buttons  # input_ports_active & (1P jump | 2P jump)
local scroll_skip_check_exit 01A8E6 r2 held_buttons      # input_ports_active & (1P start | 2P start)
local scroll_skip_check_call 01A93A r2 held_buttons      # input_ports_active & (1P start | 2P start)
local scroll_skip_draw_values 01AB05 r0 value_list       # scroll_skip_value_lists[scroll_skip_value_index]: a per-stage offset table (stage_section_limits_x/_y, stage_section_ends)
local scroll_skip_draw_values 01AAF5 r2 point            # stage_start_section: the word index in the stage's list
local scroll_skip_draw_values 01AB23 r11 text_position   # the value's cell (scroll_skip_x_position_cell...): draw_hex_4's text_position
01ABBA  data   scroll_skip_panel_text          # print_text_list records (cell, attribute 04, text): "Scroll skip mode" (row 13), "POINT" (15), "SCROLL TOTAL=0x" (17), "X POSITION  =0x" (18), "Y POSITION  =0x" (19), "NEXT:1P JUMP", "BACK:2P JUMP", "CALL:1P START", "EXIT:2P START" (21-24)
01AC54  data   scroll_skip_blank_text          # the same nine cells in spaces: scroll_skip_erase_panel
01AD04  table  scroll_skip_value_lists u32 stage_section_list_offsets # 18DFA, 18E20, 193E0 (stage_section_limits_x, stage_section_limits_y, stage_section_ends): X POSITION, Y POSITION, SCROLL TOTAL
01AD10  table  scroll_skip_last_points u16     # [stage] 0B 0D 0D 23 16 23 23 23 23 46 46 23 0A 0A 0A 0A: the highest POINT (stage_start_section) the mode lets you pick
209026  var   scroll_skip_value_index  u8      # which of the three values scroll_skip_draw_values draws this frame (0-2)
209027  var   scroll_skip_next_armed   u8      # 1 while 1P jump is up: the next press steps the point (scroll_skip_next_point)
209028  var   scroll_skip_back_armed   u8      # 1 while 2P jump is up (scroll_skip_previous_point)
209029  var   scroll_skip_exit_armed   u8      # 1 while 2P start is up (scroll_skip_check_exit)
20902A  var   scroll_skip_call_armed   u8      # 1 while 1P start is up (scroll_skip_check_call)
20902C  var   scroll_skip_last_point   s16     # scroll_skip_last_points[stage]: stage_start_section's limit (compared signed)

# ---- background layers ----
01B08B  func  set_backdrop_color_19aa          # unused: backdrop_color = 19AA, a fourth case of load_palette_set_and_backdrop's stage switch that no stage's entry uses (they use 0, 200 and 6212)
01B0F0  func  show_winners_dont_use_drugs_screen  # unused: init_nbg0_picture_screen, then winners_dont_use_drugs_script on page 0: the US "WINNERS DON'T USE DRUGS" screen with the FBI seal
01B101  func  show_recycle_it_screen           # unused: init_nbg0_picture_screen, then recycle_it_script on page 0: the "RECYCLE IT, DON'T TRASH IT!" screen with the EPA seal
172B46  data   winners_dont_use_drugs_script   # vram script: clears page 0, then rows of tiles 1900-197C (render, bank 1): the FBI seal over "WINNERS DON'T USE DRUGS" and the Director's name (show_winners_dont_use_drugs_screen, unused)
172C25  data   recycle_it_script               # vram script: clears page 0, then tiles 1980-19F6 (render, bank 1): "RECYCLE IT, DON'T TRASH IT!" with the EPA seal and the Administrator's name (show_recycle_it_screen, unused)
01B7B9  func  init_nbg3_layer_cleared_pages    # unused: a case of init_nbg3_layer no stage uses: the four scripts that clear tile pages 0A-0D (as init_nbg2_layer), NBG3 on them (nbg3_pages 0A0B0C0D), scroll 0, set_nbg3_page_5_flags
01B83B  func  set_nbg3_pages_5_6               # unused: nbg3_pages 06060505, nbg3_scroll_x/_y 0: the end of init_nbg3_layer's stage 4 case again, without its scripts

# ---- ending ----
01C380  func  draw_ending_blast_frame_copy     # unused: the same code as draw_ending_blast_frame (01C3D4): sprite [R10], layer 6, zoom 100, palette 10B0, next frame every 6 frames

# ---- gauge ring: a ring that flies from a player to his health gauge and adds 1 health ----
01EB43  func  spawn_gauge_ring                 # unused: a ring (sprite 5E70, palette 8430, ring_animation, sound 7F) 20 above the current object (its parent; kind copied), speed_x so it reaches hud_gauge_positions_3p[player] x + 10 in 3C frames; handler gauge_ring_update
01EC15  func  gauge_ring_update                # unused: handler: step 0 gauge_ring_rise, 1 gauge_ring_fall_into_gauge (the table has two entries), then animate and queue_sprite
01EC33  func  gauge_ring_rise                  # unused: step 0: x += speed_x; direction += turn_speed: y = base_y - sine >> 8 (up to 40 higher); after hop_timer (1E) frames step 1, base_y = the gauge y (hud_gauge_positions_3p by kind), drop_height from the distance to it
01EC9D  func  gauge_ring_fall_into_gauge       # unused: step 1: x += speed_x; y = base_y + sine x drop_height (down onto the gauge's y), sprite_scale - 8 a frame; at 0 the parent's health + 1 and removed
scoped object gauge_ring 3C turn_speed u16     # 88: added to direction (24) every frame, the angle of the sine arc
scoped object gauge_ring 3E base_y u16         # the arc's base: the start screen y, then the gauge's y (hud_gauge_positions_3p +2)
scoped object gauge_ring 54 hop_timer s8       # 1E frames of rising (dec.b, bgt)
scoped object gauge_ring 55 step u8            # gauge_ring_update's table: 0 rising, 1 falling into the gauge
scoped object gauge_ring 58 drop_height s32    # -((gauge y - screen y) << 2): times the sine (4000 = 1.0) it is the drop in 16.16
local gauge_ring_rise 01EC80 r0 gauge_y        # hud_gauge_positions_3p[kind].y: the gauge's y, the new base_y
local gauge_ring_rise 01EC60 r1 y              # base_y - sine >> 8: y_int and screen_y

# ---- stage object templates spawned at a point (earlier versions of spawn_ring_rows' spawn) ----
01ECFA  func  spawn_stage_template_0           # unused: when pool 2 has a block, stage_object_templates[0] (the spinning ring) at x R5, y R6, z R7, scroll_position in 7E, project_object, find_floor, and a pool 2 shadow child (5D90, palette 9FFE, unscaled); current_object kept
01EDD4  func  spawn_stage_template             # unused: the same with template R0 (stage_object_templates + R0 x 1A)
local spawn_stage_template_0 entry r5 x        # the spawn x (x_int), kept in template_spawn_x
local spawn_stage_template_0 entry r6 y        # the spawn y
local spawn_stage_template_0 entry r7 z        # the spawn z (z_int)
local spawn_stage_template entry r5 x          # the spawn x
local spawn_stage_template entry r6 y          # the spawn y
local spawn_stage_template entry r7 z          # the spawn z
local spawn_stage_template entry r0 template_index  # the stage_object_templates record (x 1A), kept in template_spawn_index
scoped global spawn_stage_template 20F0EA template_spawn_x u16      # R5 kept across allocate_object: the new object's x_int
scoped global spawn_stage_template 20F0EC template_spawn_y u16      # R6: its y_int
scoped global spawn_stage_template 20F0EE template_spawn_z u16      # R7: its z_int
scoped global spawn_stage_template 20F0F0 template_spawn_index u16  # R0: the template (spawn_stage_template only)
01F4D4  table  stage_template_0_sprite_frame u16     # stage_object_templates[0] +2: 5E70 (spawn_stage_template_0 reads the first record field by field)
01F4D6  table  stage_template_0_sprite_palette u16   # +4: 8430
01F4D8  table  stage_template_0_hitbox_size u16      # +6: 10 (hitbox_size_x)
01F4DC  table  stage_template_0_animation_speed u8   # +A: 2
01F4DD  table  stage_template_0_animation_timer u8   # +B: 0
01F4DE  table  stage_template_0_animation_frame u8   # +C: 0
01F4DF  table  stage_template_0_kind u8              # +D: 3 (ring)
01F4E0  table  stage_template_0_animation u32 rom    # +E: ring_animation (01F56E)
01F4E4  table  stage_template_0_handler u32 rom      # +12: ring_spin
01F4E8  table  stage_template_0_offset_x u8          # +16: 4 (sprite_offset_x)
01F4E9  table  stage_template_0_offset_y u8          # +17: 1 (sprite_offset_y)
01F4EA  table  stage_template_0_hitbox_height u16    # +18: 20

# ---- shadows ----
01EEF0  func  create_child_shadow_unscaled     # unused: as create_child_shadow, but unscaled (sprite_flags 1000), palette 9FFE (the shadow colour) and R19 as the scratch register
01EF27  func  create_child_shadow_9ffe         # unused: create_child_shadow (scaled by R8) with palette 9FFE (the shadow colour) instead of 87A0
local create_child_shadow_unscaled 01EEF7 r19 owner      # the current object: its child (28) gets the shadow
local create_child_shadow_unscaled entry r11 shadow      # allocate_object_2's block: the shadow
local create_child_shadow_9ffe entry r11 shadow          # allocate_object_2's block: the shadow
local create_child_shadow_9ffe entry r8 scale            # the shadow's sprite_scale

01F06D  func  is_floor_block_off_screen        # unused: R8 = 0 when the object's floor block (5C, low half) is in nearby_floor_blocks (the blocks near the screen), FFFFFFFF when not or when there are none (before is_off_screen and in its style)
local is_floor_block_off_screen 01F075,01F080 r10 cursor       # walks nearby_floor_blocks
local is_floor_block_off_screen 01F075,01F083 r7 blocks_left   # nearby_floor_block_count, counted down (dbne)
local is_floor_block_off_screen 01F080 t2 listed_block         # the nearby entry compared with floor_block

# ---- catapults ----
080EC1  func  catapult_y_start_on_floor        # unused: catapult_y_start set up at once on the floor: rides_platform 0, flags 0, template 1A, catapult_wait_for_platform_setup
080ED4  func  catapult_x_start_on_floor        # unused: the same with template 0 (catapult_x_start's)
080EE1  func  catapult_wait_for_platform_setup # catapult_wait_for_platform once it stands on a platform (and the unused _start_on_floor two): object_pair_templates + R3: handler, animation, offsets, launch_kind; floor, anchor, launch_position, kind 5 / state 4 or the launch kind; the base part and, with a delay, the dummy part
param catapult_wait_for_platform_setup R3 template_offset  # the object_pair_templates record: 0 (x) or 1A (y), what catapult_x_start/y_start leave in state (34)
local catapult_y_start_on_floor 080EC1 r3 template_offset  # 1A: the y catapult's record
local catapult_x_start_on_floor 080EDE r3 template_offset  # 0: the x catapult's record

# ---- bouncing fireball ----
082DF2  func  bouncing_fireball_knocked_away   # unused: right after bouncing_fireball_update, probably the fireball's: speed_z -80000, direction and speed of the toucher (6A) with the direction turned 80 either way (by bit 0 of the copied speed_int), set_speed_from_direction; handler knocked_object_fly (flies off with gravity, removed with its shadow off screen)
local bouncing_fireball_knocked_away 082DFE r10 toucher        # LINKED(touched_by): its direction and speed are copied
local bouncing_fireball_knocked_away 082E0E,082E1A r0 turn     # 80, or -80 when the copied speed_int is odd: added to the direction

# Unused code 082E37-099530 (dead_work_2): names, parameters, locals, scoped fields and data.

# ---- 082E37: the handler bouncing_fireball_knocked_away (dead_work_1) installs
082E37  func  knocked_object_fly                # unused: handler set by bouncing_fireball_knocked_away (speed_z -80000, direction = what touched it (6A) +-80 by bit 0 of its speed_int, its speed, set_speed_from_direction): gravity 6000, moves by speed_x/y/z with no floor test, animate + queue_sprite; once is_off_screen frees its pool 2 child (28) and is removed. Same tail as bouncing_fireball_update next to it: probably an object knocked flying by whatever touched it

# ---- 0843BF: a lone rsr
0843BF  func  draw_opening_machine_shadow_spare_return  # unused: a lone rsr right after draw_opening_machine_shadow's tail jump (jmp queue_sprite_at_depth), before player_pacer_start

# ---- 084D8D-084E0B: signs at a fixed screen position (the screen-space twin of sign_start/spawn_sign/place_sign/sign_show)
084D8D  func  screen_sign_start                 # unused: like sign_start, for a stage list object: its x_int/y_int become the sign's screen x/y and its field 04 the kind, then place_screen_sign on itself
084D9E  func  spawn_screen_sign                 # unused: allocate_object_outside_loop, then place_screen_sign (R5 screen x, R6 screen y, R7 kind); as spawn_sign
084DAA  func  spawn_screen_sign_2               # unused: the same with allocate_object (as spawn_sign_2)
084DB4  func  place_screen_sign                 # unused: screen_x/screen_y from R5/R6; screen_sign_kinds[R7] (6-byte records): sprite, duration (speed_x_fraction), animate_sign palette list offset from 08514D (80); flags 1000, handler screen_sign_show, palette index 0, delay 4, timer 0. No project_object: the position stays a screen position
084E0B  func  screen_sign_show                  # unused: sign_show without update_screen_position: animate_sign, counts speed_x_fraction down, drawn on top (bucket 0); removed at 0
param place_screen_sign R5 screen_x             # -> screen_x (0E)
param place_screen_sign R6 screen_y             # -> screen_y (0C)
param place_screen_sign R7 kind                 # index into screen_sign_kinds (x 6)
param place_screen_sign R11 sign                # the object it sets up (allocate_object's, or the current object from screen_sign_start)
local screen_sign_start 084D99 r11 sign         # the current object (R20): place_screen_sign's sign
local spawn_screen_sign 084D9E r11 sign         # allocate_object_outside_loop's object: place_screen_sign's sign
local spawn_screen_sign_2 084DAA r11 sign       # allocate_object's object: place_screen_sign's sign
scoped object screen_sign 80 palette_list u32 rom  # as animate_sign 80: 08514D + the screen_sign_kinds record's offset (place_screen_sign)
scoped object screen_sign 84 palette_index u8   # as animate_sign 84: 0 at the start
scoped object screen_sign 85 palette_delay u8   # as animate_sign 85: 4
scoped object screen_sign 86 palette_timer u8   # as animate_sign 86: 0 at the start
084FA9  table screen_sign_kinds  screen_sign_kind  # place_screen_sign: 6-byte records (sprite, duration, animate_sign list offset from 08514D): 0 = 6378 B4 0E, 1 = 6318 12C 18; record 2 on is object_kinds (084FB5) read with the wrong stride, record 0 overlaps floor_sign_sprites[1]
struct screen_sign_kind 6 rom                # a record of screen_sign_kinds (084FA9): place_screen_sign 084DBF-084DC2 "mul.w #6, R7; add.w #84FA9, R7", then [R7+] (084DC9-084DD3); a sign_kind without its z and handler; census ROM 084FA9-084FB4 (place_screen_sign is unused)
field screen_sign_kind 00 sprite_frame u16    # "mov.h [R7+], 4[R11]": 6378, 6318
field screen_sign_kind 02 duration u16        # "mov.h [R7+], 34[R11]" (speed_x_fraction: how long the sign stays): B4, 12C
field screen_sign_kind 04 palette_list_offset u16  # "movz.hw [R7+], 80[R11]", + 8514D (sign_palette_lists): the screen_sign palette_list: E, 18

# ---- 084E25-084E67: a sign standing on the floor
084E25  func  floor_sign_start                  # unused: a stage list object: sprite floor_sign_sprites[field 04] (6350/6378, among the sign sprites 6308-63A0), palette 8450 (the sign palettes' 84xx bank), flags 1004, on its floor (find_floor, z = floor_z), handler floor_sign_update
084E67  func  floor_sign_update                 # unused: drawn (update_screen_position, queue_sprite) until check_left_screen, then removed
084FA7  table floor_sign_sprites u16            # floor_sign_start: [field 04] = sprite: 6350, 6378 (the next words are screen_sign_kinds)

# ---- 0853BA-085428: something that drops behind the players at a stage exit
0853BA  func  exit_gate_start                   # unused: sprite 6520 palette 86A0 (flags 1005) hung E0 above its floor, speed_z 0, handler exit_gate_update; clears sign_stop_flag and spawn_sign_2 kind 8 (sprite 6330, "IN" and an arrow, handler sign_show_until_gate_closed) at 942,10E0, z floor - 50. Placed after stage_03_end_pillar_show: probably a gate that closes the way once every player is in
085428  func  exit_gate_update                  # unused: removed while map_screen_active; while above its floor and the u32 at players_finished is 00FFFFFF (the three players finished, goal_unlocked still 0) it falls (gravity 6000); on landing z = floor_z, sign_stop_flag = FF (the IN sign goes) and sound 20; drawn with queue_sprite
local exit_gate_start 085421 r11 sign           # spawn_sign_2's sign (before it, find_floor's cursor handed in as it)

# ---- 085482-0856CE: what the spiked roller's ram charges (state 4, roller_ram_hit) were meant to break
085482  func  ram_target_start                  # unused: sprite 6A58 palette 91A0 flags 1045, kind (47) 21 (solid, like C), box 30 x 10 offset D0/E0, height 28, direction 300, ram_target_animation, damage script ram_target_damage_script, on its floor; handler ram_target_update
08551D  func  ram_target_update                 # unused: health 7F (players cannot break it); removed by check_left_screen_margin(40); appear_state FF once screen_x >= 50, then ram_target_draw ("test.b 80; br 085596": the branch skips ram_target_check_hits)
085556  func  ram_target_check_hits             # unused: the part of ram_target_update its br skips: on a roller_ram_hit frame (the roller's ram charge) hit_count + 1; at each damage script threshold the next animation (085E8D + offset); at the 4th hit ram_target_burst; then ram_target_draw
085596  func  ram_target_draw                   # unused: while appear_state is set the animation plays once (at_last_step: appear_state 1, frame FF = stopped); appear_state 1-7F: add_to_zone_list (solid); drawn at depth screen_y - sprite_offset_y + 10 (- 30 before it appears)
0855EC  func  ram_target_burst                  # unused: ram_target_debris_count rock_debris_fly pieces (ram_target_debris records: sprite flags, dx, dy from its screen position, speed x/y, animation offset into ram_target_debris_animations), palette 8240, zoom 100
085678  func  ram_target_part_start             # unused: variant = field 04; part list ram_target_part_list_2 (variant 0) or ram_target_part_list_3, flags D006 (multi-part sprite), offsets 2/1, on its floor; handler ram_target_part_update
0856CE  func  ram_target_part_update            # unused: on roller_ram_hit frames while chaser_object's y <= its y + 60, hit_count + 1; at 4 variant 0 becomes sprite 6A88 (palette 11B0, flags 1049, offsets 2/E7), the others are removed; removed by check_left_screen_margin(88); depth screen_y - 28
param ram_target_burst R11 piece                # the caller's value is not used: each allocate_object_outside_loop object, the last one at the end
local ram_target_check_hits 085574 r10 script_record  # damage_script + 2: the record's animation offset; + 2 again is the next record, stored back
local ram_target_check_hits 08557A r11 animation  # ram_target_damage_script + the record's offset: the new animation (48), then ram_target_burst's unused R11
local ram_target_draw 0855D6,0855E1 r1 depth    # screen_y - sprite_offset_y + 10 (- 30 while appear_state is 0): queue_sprite_at_depth's depth
local ram_target_draw 0855A1 r8 at_last_step    # animate's at_last_step: the appearing animation is over (appear_state 1, frame FF)
local ram_target_burst 0855FC r11 piece         # each allocate_object_outside_loop object: a rock_debris_fly piece
local ram_target_burst 0855F9 r12 piece_record rock_debris_piece  # past ram_target_debris_count (0855F9 "movz.hw [R12+], R8"): the first ram_target_debris record
local ram_target_burst 085606,08560F,085618,08561D,085622,085627 r12 piece_record  # moving through the record ([R12+]: flags, dx, dy, speed x, speed y, animation offset); 085627 reaches the next
local ram_target_burst 0855F9,085673 r8 pieces_left  # ram_target_debris_count (4), one piece per pass
local ram_target_part_start 08567F,08568B r10 part_list  # ram_target_part_list_2 or _3 by variant: stored in part_list (04)
local ram_target_part_update 08571E r1 margin   # 88: check_left_screen_margin's margin (then update_screen_position's unread R1)
scoped object ram_target 80 appear_state s8     # 0 until screen_x >= 50 (ram_target_update), FF while the appearing animation plays, 1 after it (then solid: add_to_zone_list for 1-7F, tested signed)
scoped object ram_target 82 variant u16         # ram_target_part: field 04 at the start: 0 = two-part list, turns into sprite 6A88 after 4 hits; else three parts, removed after 4 hits
scoped object ram_target 84 hit_count s8        # roller_ram_hit frames counted (compared signed with the damage script thresholds and with 4)
scoped object ram_target 86 damage_script u32 ram_target_damage_step  # ram_target_damage_script (movea.b to 86): the next (threshold, animation offset) record
085E53  data  ram_target_part_list_2            # part list: 2 parts of sprite 6A80 (flags 1041, palette 91B0) at (0,0) and (-20,10)
085E65  data  ram_target_part_list_3            # part list: 3 parts of 6A80 at (0,0), (-20,10), (-41,20)
085E8D  data  ram_target_damage_script          # (hits, animation offset from here) words: (1,1C) (2,28) (3,34) (4,40), 7F ends: animations 6A70/6A78, 83B0/83C0, 83A8/83C8, 83B8
struct ram_target_damage_step 4 rom      # a record of ram_target_damage_script (085E8D; ram_target.damage_script, 86): (hits, animation offset) words, 7F ends; ram_target_check_hits compares the hits byte, then takes the animation and moves 86 on by 4 (08556F-085585); census ROM 085E8D-085EA0
field ram_target_damage_step 00 hits s8   # "cmp.b 84[R20], [86[R20]]; bgt" (085565, signed): the hit count that brings the next animation (1-4; 7F never)
field ram_target_damage_step 02 animation u16  # "movz.hw [R10+], R11; add.w #85E8D, R11" (085577): its offset from ram_target_damage_script (1C, 28, 34, 40), into 48
085E9F  data  ram_target_animation              # last step 3: 6A58 6A60 6A68 6A70, played once when it appears
085ED3  table ram_target_debris_count u16       # 4 (ram_target_burst)
085ED5  table ram_target_debris rock_debris_piece  # 4 x 6 words: sprite flags 1000, dx, dy, speed x, speed y (12.4), animation offset into ram_target_debris_animations
085F05  data  ram_target_debris_animations      # five animations (+0, +E, +1A, +22 and one more): sprites 6AA8-6B10

# ---- 08C56E-08C61E: an object drawn twice, the copy between two clip entries
08C56E  func  clipped_copy_sprite_start         # unused: sprite 7058 palette 5050 flags 4 zoom 100 on its floor; with a free object and two pool 2 blocks: a take_free_object copy of its whole block (50 halfwords) in palette bank 8000 (| 8000, & 9FFF) kept in copy_object (80), and two 5000 clip entries (frame E0, zoom 140) chained as its child and grandchild; handler clipped_copy_sprite_update. As icicle_buried_part_draw (copy in another palette bank between two clip entries)
08C61E  func  clipped_copy_sprite_update        # unused: until check_left_screen (then the copy is pushed back, the clip blocks freed, removed): draw_depth = its floor's screen y; queues the second clip entry, itself, the first clip entry with the screen y of the point 60 above its floor (point_to_screen) in its low 12 flag bits, then the copy at its screen position, all at draw_depth
local clipped_copy_sprite_start 08C5CF,08C610 r8 clips_left  # 2 clip entries (allocate_object_2 blocks)
scoped object clipped_copy_sprite 80 copy_object u16 object  # the take_free_object copy (not in the list): placed at its screen position and queued by clipped_copy_sprite_update, pushed back at the end
scoped object clipped_copy_sprite 88 draw_depth u16  # (projected_y + floor_z) >> 16 - view_y every frame: the depth of all four sprites

# ---- 08DE53-08DEEB: object list helpers
08DE53  func  unlink_child_chain                # unused: from the object in R0 along its child links (28): each child = FFFF, until one is already FFFF
local unlink_child_chain 08DE53 r1 next_child   # the object's child (28), the next one to cut
08DEE5  func  clear_object_speeds               # unused: R12 = 2, R11 = the current object, into init_object_fields_tail: only speed_x/y/z are cleared
08DEEB  func  init_object_fields_tail           # init_object_fields from 34 on: speed_x/y/z = 0, then (mode not 2) z and every field up to 9C; also entered by clear_object_speeds
param init_object_fields_tail R11 object        # as init_object_fields
param init_object_fields_tail R12 mode          # as init_object_fields: 2 (clear_object_speeds) stops after the speeds
local clear_object_speeds 08DEE8 r11 object     # the current object (R20): init_object_fields_tail's object
local clear_object_speeds 08DEE5 r12 mode       # 2: init_object_fields_tail clears only the speeds

# ---- 08E457: a third explosion size
08E457  func  explode_object_medium             # unused: explosion frames F-1F (between the small one at 08E43B, 0-F, and explode_object's 1F-2B), palette 82A0, handler explosion_animate; but it falls into explode_object (no branch past its stores), which sets 1F-2B and 86D0 again

# ---- 090F48-09110A: a lone rsr and leftovers of a wall editor (debug menu texts "Pattern Add/Sub", "Number Add/Sub", "Oku"/"Temae", "Exit", "Pattern No.", "Data No." at 090C7A, after wall_kinds)
090F48  func  wall_update_spare_return          # unused: a second rsr right after wall_update's own (090F47), before check_wall_left_screen
090FDE  func  wall_editor_set_side_label        # unused: copies 12 characters into wall_editor_side_text: "Oku" (far) when the wall's depth_from_next (37) is set, else "Temae" (near)
090FF9  func  wall_editor_draw_arrow            # unused: up to 7 arrows (sprite 6388, the arrow sign; flags 1004, palette 8230) in static 80-byte slots from 209602 (wall_editor_arrow_count): x = R6, y = R7 (16.16), on the floor (find_floor), riding object_cursor's platform when it has flag 4 or 3; project_object and drawn on top (bucket 0) at once. Nothing resets the count. Probably a debugging aid of the wall editor (it sits among its routines)
091079  func  wall_editor_draw_arrow_at_z       # unused: the same with z = R8 instead of the floor
0910F1  func  wall_editor_draw_pattern_number   # unused: the wall's kind (sprite_frame field: wall_kinds offset / 14) with draw_hex_2 at column 12 row 18, right after the menu's "Pattern No." (text cell F902)
09110A  func  wall_editor_rebuild_parts         # unused: frees the wall's chain of pool 2 parts (child links, walked with R18) and builds them again with build_wall_parts (after a pattern change)
local wall_editor_set_side_label 090FDE,090FE8 r11 label_text  # wall_editor_far_label or wall_editor_near_label: the 12 characters copied
local wall_editor_draw_arrow 090FFE,091001,09100B r0 marker_number  # wall_editor_arrow_count + 1 (no arrow from 8 on), then - 1: the slot index (x 80)
local wall_editor_draw_arrow entry r6 marker_x  # R6 at the start (no caller): the arrow's x (16.16, -> 4C)
local wall_editor_draw_arrow entry r7 marker_y  # R7 at the start: the arrow's y (-> 50)
local wall_editor_draw_arrow_at_z 09107E,091081,09108B r0 marker_number  # as wall_editor_draw_arrow
local wall_editor_draw_arrow_at_z entry r6 marker_x  # R6 at the start: x (-> 4C)
local wall_editor_draw_arrow_at_z entry r7 marker_y  # R7 at the start: y (-> 50)
local wall_editor_draw_arrow_at_z entry r8 marker_z  # R8 at the start: z (-> 40)
scoped object wall_editor 37 depth_from_next u8  # as wall_update 37 (the same wall object): set shows "Oku", clear "Temae"
090CBD  data  wall_editor_far_label             # "Oku" and 9 spaces (12 characters): the menu text at 090C7A + 43 (its fifth item)
090CD2  data  wall_editor_near_label            # "Temae" and 7 spaces: the alternative text of that item
209600  var   wall_editor_arrow_count s16       # wall_editor_draw_arrow(_at_z): arrows drawn, + 1 each (none from 8, compared signed); the slots follow at 209602 (80 bytes each); nothing else uses it
20DA43  var   wall_editor_side_text u8          # 12 characters wall_editor_set_side_label copies: probably the Oku/Temae item of a RAM copy of the menu text 090C7A at 20DA00 (090CBD - 090C7A = 43); nothing else uses it

# ---- 091EBF-091EC5: the lava column turning into a bubble
091EBF  func  lava_column_land_in_place         # unused: lava_column_land with base_x offset 0 instead of 40: the bubble at the column's own x
091EC5  func  lava_column_become_bubble         # the tail of lava_column_land (and lava_column_land_in_place): base_x += x_int, base_y = y_int, z 540, flags 1004, palette D000, bubble_animation, handler bubble_near_parent_update, project_object

# ---- 094753-0947D6: rocks that fall and bounce
094753  func  spawn_falling_rocks               # unused: falling_rock_count objects from falling_rocks: x, y, z (whole), speed x/y (<< C), animation (rock_debris_animations); palette 8240, flags 1000; handler falling_rock_bounce
0947D6  func  falling_rock_bounce               # unused: until flag 400: moves, gravity 6000; on the floor speed_z = -(speed_z / 2) while that is over 20000, else flag 400 (stays); animate; drawn (queue_sprite) until check_left_screen
local spawn_falling_rocks 094758,0947D1 r16 rocks_left  # falling_rock_count (8), one object per pass
local spawn_falling_rocks 094765,09476F,094779,0947A4,0947AE,0947CC r17 rock_record  # walks falling_rocks: x, y, z, speed x, speed y, animation
local spawn_falling_rocks 094758 r17 rock_record falling_rock  # past falling_rock_count ("movz.hw [R17+], R16"): the first falling_rocks record
local falling_rock_bounce 094810 r0 bounce_speed  # speed_z >> 1 on landing: -it is the new speed_z while over 20000 (before it, find_floor's R0)
0956A8  table falling_rock_count u16            # 8 (spawn_falling_rocks)
0956AA  data  falling_rocks                     # 14-byte records: x, y, z, speed x, speed y (<< C), animation (u32: rock_debris_animations +0/+A/+14); around x F40, y 900, z 3B0
struct falling_rock E rom                 # a falling_rocks record: spawn_falling_rocks reads it with [R17+] (094765 x, 09476F y, 094779 z "mov.h [R17+]", 0947A4/0947AE "movs.hw [R17+], R0" << C, 0947CC "mov.w [R17+], 48[R11]"); census ROM 0956AA-095719 (8 records: 0F76 0885 03A8 FFF0 FFF0 09567E...)
field falling_rock 00 x u16               # into x_int (4E)
field falling_rock 02 y u16               # into y_int (52)
field falling_rock 04 z u16               # into z_int (42)
field falling_rock 06 speed_x s16         # sign-extended, << C into speed_x (34)
field falling_rock 08 speed_y s16         # sign-extended, << C into speed_y
field falling_rock 0A animation u32       # rock_debris_animations + 0 / A / 14, into the object's animation (48)

# ---- 094A48-094A7D: update_stage_05_event step 3 (Eggman's booth)
094A48  func  stage_05_event_show_message_11    # unused: like step 3's message at scene_timer A, with message box kind 11 (sprite 65E8) at y 40 for 78 frames
094A59  func  stage_05_event_show_message_12    # unused: the same with kind 12 (sprite 65F0) for 1E frames
094A68  func  stage_05_event_show_message       # update_stage_05_event step 3 at scene_timer A: spawn_message_box(A0, R6, R7, R8) and sound 70, then stage_05_event_step_3_frame
094A7D  func  stage_05_event_step_3_frame       # update_stage_05_event step 3: spawn_walls, check_hits, check_walls, check_platforms; scene_timer + 1, at A0 scene_step + 1 and scene_timer 0
param stage_05_event_show_message R6 screen_y   # spawn_message_box's screen y (50; 40 in the unused entries)
param stage_05_event_show_message R7 kind       # message_box_kinds index (10; 11 and 12 in the unused entries)
param stage_05_event_show_message R8 frames     # spawn_message_box's frames (B4; 78, 1E); spawn_walls' point_y comes back in it
param stage_05_event_show_message R9 z          # spawn_walls' R9 handed back
param stage_05_event_show_message R10 platform  # check_platforms' R10 handed back
param stage_05_event_show_message R11 box       # spawn_message_box's box (the caller's value is not used)
param stage_05_event_step_3_frame R8 point_y    # spawn_walls' R8 handed back (as event_0c_second_quake)
param stage_05_event_step_3_frame R9 z          # spawn_walls' R9 handed back
param stage_05_event_step_3_frame R10 platform  # check_platforms' R10 handed back
param stage_05_event_step_3_frame R11 wall      # handed to spawn_walls (its wall), then check_hits and check_platforms
local stage_05_event_show_message 094A77 r10 platform  # stage_05_event_step_3_frame's platform, handed back
local stage_05_event_step_3_frame 094A8F r10 platform  # check_platforms' R10, handed back
local stage_05_event_show_message_11 094A52 r6 screen_y  # 40: the message box's screen y
local stage_05_event_show_message_11 094A48 r7 kind  # 11: the message box kind
local stage_05_event_show_message_11 094A4D r8 frames  # 78: the message box's frames
local stage_05_event_show_message_12 094A63 r6 screen_y  # 40
local stage_05_event_show_message_12 094A59 r7 kind  # 12
local stage_05_event_show_message_12 094A5E r8 frames  # 1E

# ---- 0953CB-0953D5: message boxes
0953CB  func  spawn_message_box_2               # unused: spawn_message_box with allocate_object instead of allocate_object_outside_loop (as spawn_sign_2)
0953D5  func  place_message_box                 # the second half of spawn_message_box (and spawn_message_box_2): screen x/y, frames (34), message_box_kinds[R7]: sprite, palette, full width (36), height (18); flags 9000, width 0, handler message_box_show, the address word (08) from the sprite's frame record +6
param place_message_box R5 screen_x             # as spawn_message_box
param place_message_box R6 screen_y             # as spawn_message_box
param place_message_box R7 kind                 # as spawn_message_box: message_box_kinds index
param place_message_box R8 frames               # as spawn_message_box: -> speed_x_fraction (34)
param place_message_box R11 box                 # the object it sets up
local spawn_message_box_2 0953CB r11 box        # allocate_object's object: place_message_box's box

# ---- 0959EB-095D3B: map screen
0959EB  func  run_map_screen_wait_for_wipe      # unused: a step of run_map_screen its jump table no longer lists: update_screen_wipe until the four edges are done (edges_done & F = F), then map_screen_step + 1
local run_map_screen_wait_for_wipe 0959EB r5 edges_done  # update_screen_wipe's edges_done
095D3B  func  map_screen_draw_flipping_icon     # unused: map_screen_update's step 4 drawing alone: sprite 65C8 palette 6C0 at 20,10, zoom 100, layer 0, flags 10A; blink_phase cycles 0-2, timer - 1; x flipped while timer bit 1 is set, else y flipped while blink_phase bit 1 is

# ---- 0963CD-0964A1: the map's Eggman
0963CD  func  map_eggman_draw_plain             # unused: sprite 5760 (the pose before the gestures), then draw_map_eggman
096497  func  map_eggman_draw_plain_2           # unused: the same code again
0964A1  func  draw_map_eggman                   # the tail of map_eggman_update's steps: palette 680, zoom 100, layer 1, at B7,DF, flags 107, queue_sprite_in_layer

# ---- 0970D2-0983FD
0970D2  func  title_emblem_animate_spare_return # unused: an rsr inside title_emblem_animate that "cmp.h #1E0, title_timer; br 0970D3" jumps over (probably a conditional return once)
097E3E  func  init_title_cloud_block            # unused: the block in R11 gets zoom 80, layer 8, flags 0, palette D80 (title_clouds_1_drift sets its clouds' blocks the same way, zoom E0)
0983FD  data  start_ring_tally_padding        # a 00 byte ("halt") that puts start_ring_tally at an even address; not code
098A76  func  ring_tally_player_3_update_spare_return  # unused: a lone rsr after ring_tally_player_3_update's tail jump to update_ring_tally_panel, before build_ring_tally_panel

# ---- 099029-0991BB: system leftovers
099029  func  pause_on_start_3                  # unused: when start 3 is pressed (inputs_pressed bit 7): debug_pause + 1, then wait_for_frame frames (attract_timer - 1 each, so it stands still) until start 3 is pressed again, debug_pause - 1
0990D4  func  convert_from_bcd                  # unused: R0 = the 8 BCD digits of R0 as a binary number (x A + each nibble from the top)
local convert_from_bcd 0990D4,0990E9 r2 digits_left  # 8 digits
09912D  func  convert_64_to_bcd                 # unused: the 64-bit R1:R0 in BCD: divided by A twice per byte into a 12-byte buffer under SP (filled backwards, zero padded), read back as R2:R1:R0 (rvbyt, most significant first, 24 digits)
local convert_64_to_bcd entry r0 number_low     # R0 at the start (no caller): the number's low word, copied to value_low
local convert_64_to_bcd entry r1 number_high    # R1 at the start: its high word, copied to value_high
local convert_64_to_bcd 09913F,099148,099157,099160 r0 dividend  # the half being divided by A (divux); the low word at the start
local convert_64_to_bcd 09913C,099154,09917B,099184 r1 remainder  # 0 before each divux, then the remainder: each decimal digit; the high word at the start
local convert_64_to_bcd 09912D,09914E,099166 r2 value_low  # the number's low word (R0 at the start), divided by A for each digit
local convert_64_to_bcd 099130,099145,09915D r3 value_high  # its high word (R1 at the start)
local convert_64_to_bcd 099133,099172,099193,09919F r4 cursor  # SP, then each byte written backwards into the buffer
local convert_64_to_bcd 099136,099177,099198,09919C,0991A2 r5 pairs_left  # A digit pairs at most, + 2 for the zero padding
local convert_64_to_bcd 099151,09916C,099181,09918D r6 digit_pair  # two decimal digits packed (rot.b 4) into one byte
0991BB  func  reset_objects_alt                 # unused: reset_objects with reset_object_pool_2_alt instead of reset_object_pool_2: reset_object_list, the pool, run_objects

# ---- 099271-099335: the object list and pools
099271  func  allocate_object_after_current     # unused: allocate_object, but linked right after the current object (R20) instead of at the end (last_object moves to it when R20 was last); object_count + 1
local allocate_object_after_current 099271 r0 free_count  # the free stack's count: 0 = no free object
0992DD  func  delete_next_object                # unused: delete_object of the object after the current one (its next, 22)
099335  func  reset_object_pool_2_alt           # unused: reset_object_pool_2 with 1D8 blocks of 34 bytes (205000-20AFE0) instead of FF of 30
local reset_object_pool_2_alt 099335,09934A r0 block  # each block's address (205000 + 34 each), as reset_object_pool_2's block
local reset_object_pool_2_alt 09933F,09934E r1 pool_blocks_left  # 1D8 blocks (also the stack's count)
local reset_object_pool_2_alt 099344,099347 r3 stack_slot  # walks free_object_stack_2 from 20EA12

# ---- 099408-09944F: 2-bit font expansion
099408  func  expand_font_2bpp                  # unused: as expand_font with 2 bits per pixel: R1 characters of 8 words from R10 (8 pixels each), every pixel (0-3) + the colour R0 & F into a 4-bit pixel of the character at R11 (a long per row, rvbyt)
local expand_font_2bpp 09941B,099424,09942A,099430,099436 r0 font_row  # one source word: 8 pixels of 2 bits, shifted out from the top
local expand_font_2bpp 099418,09944A r1 rows_left  # the character count (R1) x 8
local expand_font_2bpp 09941B r10 source        # the 2-bit rows (a word each), R10 at the start
local expand_font_2bpp 099447 r11 dest          # the 4-bit characters, a long per row, R11 at the start
local expand_font_2bpp 09941E,09943C r2 pairs_left  # 4 passes of two pixels per word
local expand_font_2bpp 09940E r3 color_nibbles  # (R0 & F) x 11111111: the colour in every nibble, added to the row (the C folds R0 to 0)
local expand_font_2bpp 099421,099427,09942D,099433,099439 r4 pixels  # the row's 8 pixels, one per nibble
09944F  func  expand_font_2bpp_transparent      # unused: the same, but pixel 0 stays 0 (transparent): the colour is added only to the nibbles of set pixels
local expand_font_2bpp_transparent 099462,099468,099482 r0 font_row  # as expand_font_2bpp (rotated 2 bits at a time)
local expand_font_2bpp_transparent 09945F,0994AC r1 rows_left  # as expand_font_2bpp
local expand_font_2bpp_transparent 099462 r10 source  # as expand_font_2bpp
local expand_font_2bpp_transparent 0994A9 r11 dest  # as expand_font_2bpp
local expand_font_2bpp_transparent 099465,09949C r2 pairs_left  # as expand_font_2bpp
local expand_font_2bpp_transparent 099455 r3 color_nibbles  # as expand_font_2bpp
local expand_font_2bpp_transparent 099471,099474,09948B,09948E,0994A6 r4 pixels  # the row's pixels, one per nibble; + color_nibbles, masked by opaque_mask
local expand_font_2bpp_transparent 09947C,09947F,099496,099499,0994A0 r5 opaque_mask  # 1 in the nibble of each non-zero pixel (setf), x F: the nibbles the colour goes into
local expand_font_2bpp_transparent 09946E,099488 r6 pixel  # one 2-bit pixel (rot.h 2, & 3)

# ---- 09951D-099530: video set-up
09951D  func  init_video_registers_layers_off   # unused: init_video_registers, then NBG0-3 off (video_layer_disable | F) and vblank_start_hook = empty_vblank_hook (no apply_video_registers: the register shadows never reach the chip)
099530  func  empty_vblank_hook                 # unused: an rsr, the vblank_start_hook init_video_registers_layers_off installs (apply_video_registers follows it)

# dead_3: names for the unused code 099616-09B312 (build/naming/dead_work_3.txt)

# ---- text map: erase_text_list, print_formatted_text and its eraser ----
# A formatted text: a position word (text map offset, 31F000 | word), an attribute byte, then runs of characters ending
# in 0, each followed by a code byte that jumps through a table of 3-byte branches right after the run's loop (0996A7,
# 099703, 099764 to print; 0997BC, 0997FD, 099841 to erase): 0 end; 3/6/9 next run at 1/2/3 cells per character;
# C new attribute byte, same width; F/15/1B new position word and attribute byte at width 1/2/3; 12/18/1E new
# attribute byte at width 1/2/3; 21 skip s8 cells, 24 skip s16 cells (same width). Width 2 writes cell, cell + 1;
# width 3 cell, cell + 1, cell + 2 (a character drawn as 2 or 3 consecutive tiles). The eraser walks the same
# records writing R0 (0) and skipping the attribute bytes; its tables have no entry 24 (it would land inside 21's code).
099616  func  erase_text_list                        # unused: erase_text for each record until a 0 halfword (the same loop as print_text_list: "movea.b 99616[PC], [-SP]" returns to the test)
09967F  func  print_formatted_text                   # unused: R10 formatted text (position word, attribute byte, runs and codes) printed at 31F000 | the word; see the comment above
099686  func  formatted_text_position_single         # unused: code F: position word into R11's low half, then attribute byte, then a run at 1 cell per character
099689  func  formatted_text_attribute_single        # unused: codes C (in the width 1 table) and 12: attribute byte into R0's high byte, then a run at 1 cell per character
09968F  func  formatted_text_single                  # unused: a run at 1 cell per character (attribute << 8 | character) up to its 0, then the code byte through the table at 0996A7
0996C8  func  formatted_text_skip_single             # unused: code 21 at width 1: R11 += s8 x 2, then the next run at width 1
0996CB  func  formatted_text_skip_long_single        # unused: code 24 at width 1: R11 += s16 x 2, then the next run at width 1
0996DD  func  formatted_text_position_double         # unused: code 15: position word, attribute byte, a run at 2 cells per character
0996E0  func  formatted_text_attribute_double        # unused: codes C (in the width 2 table) and 18: attribute byte, a run at 2 cells per character
0996E6  func  formatted_text_double                  # unused: a run at 2 cells per character (cell, cell + 1), then the code byte through the table at 099703
099724  func  formatted_text_skip_double             # unused: code 21 at width 2: R11 += s8 x 2
099727  func  formatted_text_skip_long_double        # unused: code 24 at width 2: R11 += s16 x 2
099739  func  formatted_text_position_triple         # unused: code 1B: position word, attribute byte, a run at 3 cells per character
09973C  func  formatted_text_attribute_triple        # unused: codes C (in the width 3 table) and 1E: attribute byte, a run at 3 cells per character
099742  func  formatted_text_triple                  # unused: a run at 3 cells per character (cell, cell + 1, cell + 2), then the code byte through the table at 099764
099785  func  formatted_text_skip_triple             # unused: code 21 at width 3: R11 += s8 x 2
099788  func  formatted_text_skip_long_triple        # unused: code 24 at width 3: R11 += s16 x 2
09979B  func  erase_formatted_text                   # unused: print_formatted_text's text erased: R0 = 0 written over every cell it covers (31F000 | the position word)
0997A5  func  erase_formatted_text_position_single   # unused: code F erased: position word, the attribute byte skipped, a run at width 1
0997A8  func  erase_formatted_text_attribute_single  # unused: codes C/12 erased: skips the attribute byte, a run at width 1
0997AA  func  erase_formatted_text_single            # unused: R0 over 1 cell per character up to the run's 0, then the code byte through the table at 0997BC
0997DD  func  erase_formatted_text_skip_single       # unused: code 21 erased at width 1: R11 += s8 x 2
0997E6  func  erase_formatted_text_position_double   # unused: code 15 erased: position word, attribute skipped, a run at width 2
0997E9  func  erase_formatted_text_attribute_double  # unused: codes C/18 erased: attribute skipped, a run at width 2
0997EB  func  erase_formatted_text_double            # unused: R0 as a long (2 cells) per character, then the code byte through the table at 0997FD
09981E  func  erase_formatted_text_skip_double       # unused: code 21 erased at width 2: R11 += s8 x 2
099827  func  erase_formatted_text_position_triple   # unused: code 1B erased: position word, attribute skipped, a run at width 3
09982A  func  erase_formatted_text_attribute_triple  # unused: codes C/1E erased: attribute skipped, a run at width 3
09982C  func  erase_formatted_text_triple            # unused: R0 as a long and a halfword (3 cells) per character, then the code byte through the table at 099841
099862  func  erase_formatted_text_skip_triple       # unused: code 21 erased at width 3: R11 += s8 x 2

# ---- vram scripts: erase_vram_script ----
09999A  func  erase_vram_script                      # unused: run_vram_script's loop writing R1 (0) instead of the tiles: bytes 00-7F and commands 86-A4 write 0 to the cells they would draw (data skipped), 80/83/A7 move dest and 98 clears pages as run_vram_script does (its own routines)
0999E1  func  erase_vram_script_end                  # unused: command AA, "add.w #4, SP; rsr" returns from erase_vram_script (as vram_script_end)
0999E5  func  erase_vram_script_fill                 # unused: commands 86, 89, 8C (fill, count up, count down): count byte, word skipped; count cells of R1
0999F3  func  erase_vram_script_bytes                # unused: command 8F: count byte, high byte, count bytes skipped; count cells of R1
099A03  func  erase_vram_script_words                # unused: commands 92, 95 (95's dictionary is not written): count byte, count words skipped; count cells of R1
099A12  func  erase_vram_script_fill_down            # unused: command 9B: count byte, word skipped; count cells of R1 down a column (+40)
099A24  func  erase_vram_script_fill_up              # unused: command A1: up a column (-40)
099A36  func  erase_vram_script_fill_down_wide       # unused: command 9E: down a column of a 64-cell map (+80)
099A49  func  erase_vram_script_repeat               # unused: command A4: R1 count times to the same cell

# ---- digits ----
099A67  func  draw_hex_6                             # unused: draw_hex_digits with 6 digits of R0 & FFFFFF (the entry between draw_hex_4 and draw_hex_8)
099AAB  func  draw_bcd_4                             # unused: draw_bcd_digits with 4 digits of R0 & FFFF
099AB3  func  draw_bcd_6                             # unused: draw_bcd_digits with 6 digits of R0 & FFFFFF
099ABF  func  draw_bcd_8                             # unused: draw_bcd_digits with 8 digits
099AC2  func  draw_bcd_digits                        # draw_credit_digits (2 digits) and the unused draw_bcd_4/6/8: R2 digits of R0 leftwards from R11 + 2 x R2, each nibble + 1 as the character with attribute R1, blanks (R0 = 0) once the rest is 0; the same loop as draw_hex_digits

# ---- sprites ----
09A127  func  queue_sprite_at_depth_offset           # unused: queue_sprite_at_depth's clamp of R1 + 10 (0-FE), bucket (FF - depth + R2) & FF: R2 buckets further back
09A1B0  func  get_previous_floor_bucket              # unused: queue_sprite_at_previous_floor_2's first half: (projected_y + previous_floor_z) >> 16 - view_y + 10 clamped to 0-FE in R1, FF - it in R10; queues nothing
09A229  func  cancel_sprite_buffer_swap              # unused: sprite_list_ready = 0, so swap_sprite_buffers skips the next swap; init_sprites_without_swaps (unused) makes it the vblank_hook
09A230  func  load_sized_frame_address_at_row        # unused: sprite_scale (the address word of sized frames, flags 9xxx) = the frame record's word 3 + R0 x (word 0 >> 2, halved first unless sprite_flags bit 9, 8 bpp): the frame's graphics from row R0 down
09A257  func  load_sized_frame_address               # unused: sprite_scale = the frame record's word 3 (sprite_frames | sprite_frame, + 6): the address word of a sized frame, as spawn_message_box loads it
09A267  func  load_frame_size                        # unused: sprite_width = the frame record's word 0, sprite_height = its byte 2 (sized frames draw at this size)

# ---- sound / port D ----
09A593  func  set_port_d_bit_3_if_sound_allowed      # unused: port D bit 3 on when unused_coin_byte, game_in_progress or setting_advertise_sound is set (play_sound's own conditions, but for 20F405); init_sound clears the bit (probably a sound output line before bits 2-3 drove the moving seats)
09A5AE  func  queue_coin_sound                       # unused: port D bit 3 on (as set_port_d_bit_3_if_sound_allowed), then queue_sound 7F (update_coins' coin sound)

# ---- bitmap layer ----
09A7A5  func  set_bitmap_clip                        # unused: R0-R3 (x1, y1, x2, y2) sorted into video_bitmap_clip_left/top/right/bottom (31FF80-86), video_layer_disable bit 15 on (clear_bitmap_layer clears it)
09A7D0  func  draw_pixel                             # unused: color R2 at R0,R1 when inside bitmap_window (get_point_outcode's outcode 0): 8 bpp the byte at y x 200 + x, 4 bpp the low nibble (even x) or high nibble (odd x) at y x 100 + x / 2
09A823  func  read_pixel                             # unused: R0 = the pixel at R0,R1 (8 bpp byte, 4 bpp nibble by x parity), or -1 outside bitmap_window
09A9BB  func  draw_vertical_line_8bpp                # draw_vertical_span_8bpp with R2 = 200 (one 8 bpp row): draw_line's vertical lines (09A9A6 falls in) and draw_rect_outline's sides
09ABB1  code  draw_vertical_span_4bpp_here           # draw_vertical_span_4bpp from R1 on (after it computes the address): nibble mask by R0's parity, R5 + 1 rows R10 apart; draw_rect_outline's 4 bpp sides too. A code line, not func: a func line here makes it an entry before the split of shared code, and draw_vertical_span_4bpp (5 instructions before it) is then copied into draw_line and fill_rect_unclipped instead of staying a function
09ABF1  func  draw_dotted_line_8bpp                  # unused: draw_line's 8 bpp part (clipped the same way, step pairs at 09C4D2) writing every other pixel: horizontal +2, vertical +400, diagonal double steps, other lines skip a pixel between writes; R0 = -1 when nothing is drawn
09AD15  func  draw_rect_outline                      # unused: the frame of R0,R1-R2,R3 in color R4: clip_rect_to_window, then by its R10 (edges still inside: 1 left, 2 right, 4 top, 8 bottom) a table of 16 cases of fill_span_8bpp rows and draw_vertical_line_8bpp columns; 4 bpp only handles a lone left or right edge (and passes R10 as the row step): unfinished
09AE91  func  draw_rect_sides_8bpp                   # unused: draw_rect_outline's left and right edges: color R4 at R1 and R1 + R2, R3 + 1 rows (200 apart); R1 ends at the bottom-left pixel
09AEB0  func  draw_rect_top_bottom_8bpp              # unused: draw_rect_outline's top and bottom edges: R2 + 1 bytes of color R4 at R1 and at R1 + R3 x 200 (long stores where it can, as fill_span_8bpp)
09B187  func  fill_triangle                          # unused: the three points R0,R1 / R2,R3 / R4,R5 sorted by y, R19 = bitmap_draw_buffer (span records), R18 = bitmap_address, then fill_triangle_sorted (8 bpp, color R6)
09B1C4  func  fill_triangle_sorted                   # unused: top, middle, bottom points: one row (fill_span_8bpp) when all share y, fill_flat_top_triangle when the top two do, else steps both edges in 16.16 (div.w slopes), one span record (address, length) per row, then fill_span_list
09B27E  func  fill_flat_top_triangle                 # unused: top edge R0-R2 at row R1 down to R4 at R5 rows below: one span record per row, then fill_span_list
09B30F  func  fill_span_list_from_r8                 # unused: R6 = R8 (fill_quad keeps its color in R8), then fill_span_list
09B312  func  fill_span_list                         # unused: R1 span records from bitmap_draw_buffer (long address, halfword length; negative length: skipped) filled with color R6 (as fill_span_8bpp: odd first byte, 16-byte blocks, then 8/4/2/1)

# ---- labels ----
09B337  code  fill_span_list_aligned                 # fill_span_list: the span from an even address (after the odd first byte): blocks, then the 8/4/2/1-byte tail
09B37A  code  fill_span_list_last_byte               # fill_span_list: the span's last odd byte

# ---- data ----
09C4D2  table line_direction_steps line_direction_step               # 4 pairs (row step, column step) by draw_line's direction_index (dy < 0: +1, dx < 0: +2): (200, 1), (-200, 1), (200, -1), (-200, -1); draw_line and draw_dotted_line_8bpp read the two words of a pair
struct line_direction_step 4 rom           # a line_direction_steps record by direction_index: draw_line "movea.w 9C4D2[PC](R7), R0" (09A8C3: R7 x 4), "movs.hw [R0+], R2" (09A8C9), "movs.hw [R0], R0" (09A8CC); draw_dotted_line_8bpp the same (09AC40-09AC49); census ROM 09C4D2-09C4E1 (0200 0001, FE00 0001, 0200 FFFF, FE00 FFFF)
field line_direction_step 00 row_step s16     # bitmap bytes to the next row the line goes to: 200 down, -200 up
field line_direction_step 02 column_step s16  # bytes to the next column: 1 right, -1 left

# ---- parameters ----
param formatted_text_position_single R11 text_position     # 31xxxx: its low half replaced by the position word, then + 2 per cell
param formatted_text_attribute_single R11 text_position    # the text map cell the run starts at
param formatted_text_single R0 cell                        # attribute << 8 | character, written to each cell
param formatted_text_single R11 text_position              # + 2 per cell written
param formatted_text_attribute_double R11 text_position
param formatted_text_double R0 cell                        # attribute << 8 | character: cell and cell + 1 per character
param formatted_text_double R11 text_position
param formatted_text_attribute_triple R11 text_position
param formatted_text_triple R0 cell                        # cell, cell + 1, cell + 2 per character
param formatted_text_triple R11 text_position
param erase_formatted_text_position_single R0 cell         # 0 (erase_formatted_text): written over the cells
param erase_formatted_text_position_single R11 text_position
param erase_formatted_text_attribute_single R0 cell
param erase_formatted_text_attribute_single R10 text       # at the attribute byte, which it skips
param erase_formatted_text_attribute_single R11 text_position
param erase_formatted_text_single R0 cell                  # written to 1 cell per character
param erase_formatted_text_single R11 text_position
param erase_formatted_text_attribute_double R10 text
param erase_formatted_text_attribute_double R11 text_position
param erase_formatted_text_double R11 text_position        # + 4 per character
param erase_formatted_text_attribute_triple R10 text
param erase_formatted_text_attribute_triple R11 text_position
param erase_formatted_text_triple R11 text_position        # + 6 per character
param erase_vram_script_fill R1 cell                       # 0 (erase_vram_script): written instead of the tiles
param erase_vram_script_fill R11 dest                      # + 2 per cell
param erase_vram_script_bytes R1 cell
param erase_vram_script_bytes R11 dest
param erase_vram_script_words R1 cell
param erase_vram_script_words R11 dest
param erase_vram_script_fill_down R1 cell
param erase_vram_script_fill_down R11 dest                 # + 40 per cell
param erase_vram_script_fill_up R1 cell
param erase_vram_script_fill_up R11 dest                   # - 40 per cell
param erase_vram_script_fill_down_wide R1 cell
param erase_vram_script_fill_down_wide R11 dest            # + 80 per cell
param erase_vram_script_repeat R1 cell
param erase_vram_script_repeat R11 dest                    # unchanged: one cell written count times
param draw_bcd_digits R0 value                             # shifted right 4 per digit; once it is 0 the remaining cells get it (blank)
param draw_bcd_digits R1 attribute                         # high byte kept, low byte = nibble + 1
param draw_bcd_digits R2 digit_count                       # 2 (draw_credit_digits), 4, 6 or 8: counted down
param draw_bcd_digits R11 text_position                    # leftmost cell (it starts at the right end and comes back here)
param draw_vertical_line_8bpp R1 pixel_address             # top pixel's byte; the last one written at the end
param draw_vertical_line_8bpp R4 color                     # byte written to each row
param draw_vertical_line_8bpp R5 length                    # rows after the first
param draw_vertical_span_4bpp_here R0 start_x              # only bit 0: odd x writes the high nibble
param draw_vertical_span_4bpp_here R1 pixel_address        # the byte of the top pixel; past the last one at the end
param draw_vertical_span_4bpp_here R4 color                # low nibble, shifted up for odd x
param draw_vertical_span_4bpp_here R5 length               # rows after the first; 0 at the end
param draw_vertical_span_4bpp_here R10 row_step            # 100 or -100 (draw_vertical_span_4bpp); draw_rect_outline passes its table offset (3 or 6)
param draw_rect_sides_8bpp R1 pixel_address                # the top-left corner's byte; the bottom-left one at the end
param draw_rect_sides_8bpp R2 width                        # right - left: the right side is R1 + it
param draw_rect_sides_8bpp R3 height                       # rows after the first
param draw_rect_sides_8bpp R4 color                        # byte written
param draw_rect_top_bottom_8bpp R1 pixel_address           # the top-left corner's byte; the top row's last byte at the end
param draw_rect_top_bottom_8bpp R2 width                   # bytes after the first in each row
param draw_rect_top_bottom_8bpp R3 height                  # the bottom row is R1 + it x 200
param draw_rect_top_bottom_8bpp R4 color                   # replicated x 01010101 by draw_rect_outline for the long stores
param fill_triangle_sorted R0 top_x                        # the topmost point's x; shifted to 16.16 and stepped down the left edge
param fill_triangle_sorted R1 top_y                        # its y (the first span's row)
param fill_triangle_sorted R2 middle_x                     # the middle point's x; becomes the left edge's 16.16 slope
param fill_triangle_sorted R3 middle_y
param fill_triangle_sorted R4 bottom_x                     # the lowest point's x; becomes the right edge's 16.16 slope
param fill_triangle_sorted R5 bottom_y                     # becomes the row count of the lower part
param fill_triangle_sorted R6 color                        # byte (fill_span_list replicates it)
param fill_flat_top_triangle R0 left_x                     # the top edge's left end; 16.16 down the left edge
param fill_flat_top_triangle R1 top_y                      # the top edge's row
param fill_flat_top_triangle R2 right_x                    # the top edge's right end; 16.16 down the right edge
param fill_flat_top_triangle R4 bottom_x                   # the bottom point's x; becomes the right edge's slope
param fill_flat_top_triangle R5 height                     # rows below the top edge; + 1 spans, counted down
param fill_flat_top_triangle R6 color
param fill_span_list_from_r8 R1 span_count                 # span records to fill
param fill_span_list R1 span_count                         # span records in bitmap_draw_buffer, counted down
param fill_span_list R6 color                              # byte, replicated x 01010101

# ---- locals ----
local print_formatted_text 09967F r11 text_position       # 31F000; formatted_text_position_single puts the position word in its low half
local formatted_text_position_single 099686 r10 text      # the formatted text; past the position word
local formatted_text_position_single 099686 r11 text_position  # 31xxxx | the position word
local formatted_text_attribute_single 09968C r0 cell      # the attribute byte << 8
local formatted_text_single 0996A0 r1 code               # the code byte after the run: offset into the table at 0996A7
local formatted_text_single 099699 t1 next_char          # the next character: replaces the cell's low byte, 0 ends the run
local formatted_text_skip_single 0996D4 r10 text         # past the s8
local formatted_text_skip_single 0996D7 r11 text_position  # + s8 x 2
local formatted_text_skip_long_single 0996CB r10 text    # past the s16
local formatted_text_skip_long_single 0996CE r11 text_position  # + s16 x 2
local formatted_text_position_double 0996DD r10 text
local formatted_text_position_double 0996DD r11 text_position
local formatted_text_attribute_double 0996E3 r0 cell
local formatted_text_double 0996E6 r0 cell               # the cell with the run's first character, + 1 for the second cell
local formatted_text_double 0996FC r1 code               # offset into the table at 099703
local formatted_text_double 0996F5 t1 next_char
local formatted_text_skip_double 099730 r10 text
local formatted_text_skip_double 099733 r11 text_position
local formatted_text_skip_long_double 099727 r10 text
local formatted_text_skip_long_double 09972A r11 text_position
local formatted_text_position_triple 099739 r10 text
local formatted_text_position_triple 099739 r11 text_position
local formatted_text_attribute_triple 09973F r0 cell
local formatted_text_triple 099742 r0 cell               # the cell with the run's first character, + 1 and + 2 for the next cells
local formatted_text_triple 09975D r1 code               # offset into the table at 099764
local formatted_text_triple 099756 t1 next_char
local formatted_text_skip_triple 099791 r10 text
local formatted_text_skip_triple 099794 r11 text_position
local formatted_text_skip_long_triple 099788 r10 text
local formatted_text_skip_long_triple 09978B r11 text_position
local erase_formatted_text 0997A2 r0 cell                # 0: written over every cell
local erase_formatted_text 09979B r11 text_position      # 31F000; the position word goes in its low half
local erase_formatted_text_position_single 0997A5 r10 text
local erase_formatted_text_position_single 0997A5 r11 text_position
local erase_formatted_text_single 0997B5 r1 code         # offset into the table at 0997BC
local erase_formatted_text_single 0997AA t1 first_char   # the run's first character: 0 = nothing to erase
local erase_formatted_text_single 0997B1 t1 next_char    # each following character: 0 ends the run
local erase_formatted_text_skip_single 0997DD r10 text
local erase_formatted_text_skip_single 0997E0 r11 text_position
local erase_formatted_text_position_double 0997E6 r10 text
local erase_formatted_text_position_double 0997E6 r11 text_position
local erase_formatted_text_double entry r0 cell          # written as a long (2 cells); erase_formatted_text's R0 (only its low half is set to 0)
local erase_formatted_text_double 0997F6 r1 code         # offset into the table at 0997FD
local erase_formatted_text_double 0997EB t1 first_char
local erase_formatted_text_double 0997F2 t1 next_char
local erase_formatted_text_skip_double 09981E r10 text
local erase_formatted_text_skip_double 099821 r11 text_position
local erase_formatted_text_position_triple 099827 r10 text
local erase_formatted_text_position_triple 099827 r11 text_position
local erase_formatted_text_triple entry r0 cell          # written as a long and a halfword (3 cells)
local erase_formatted_text_triple 09983A r1 code         # offset into the table at 099841
local erase_formatted_text_triple 09982C t1 first_char
local erase_formatted_text_triple 099836 t1 next_char
local erase_formatted_text_skip_triple 099862 r10 text
local erase_formatted_text_skip_triple 099865 r11 text_position
local erase_vram_script 09999D r0 script_code             # the script byte: 00-7F a cell, else & 7F = the command's table offset
local erase_vram_script 09999A r1 cell                    # 0: written instead of every tile
local erase_vram_script 09999D r10 script                 # the vram script, past each byte and command
local erase_vram_script 0999B2 r11 dest                   # video RAM write position (command 80 sets it)
local erase_vram_script_fill 0999E5 r0 count             # the command's count byte, counted down
local erase_vram_script_bytes 0999F3 r0 count
local erase_vram_script_words 099A03 r0 count
local erase_vram_script_fill_down 099A12 r0 count
local erase_vram_script_fill_up 099A24 r0 count
local erase_vram_script_fill_down_wide 099A36 r0 count
local erase_vram_script_repeat 099A49 r0 count
local draw_bcd_4 099AAB r0 value                          # R0 & FFFF
local draw_bcd_4 entry r1 attribute                       # draw_bcd_digits' attribute (the caller's high byte)
local draw_bcd_4 entry r11 text_position
local draw_bcd_4 099AAE r2 digit_count                    # 4
local draw_bcd_6 099AB3 r0 value                          # R0 & FFFFFF
local draw_bcd_6 entry r1 attribute
local draw_bcd_6 entry r11 text_position
local draw_bcd_6 099ABA r2 digit_count                    # 6
local draw_bcd_8 entry r1 attribute
local draw_bcd_8 entry r11 text_position
local draw_bcd_8 099ABF r2 digit_count                    # 8
local queue_sprite_at_depth_offset entry r1 depth         # the sprite's y; + 10, clamped to 0-FE
local queue_sprite_at_depth_offset entry r2 bucket_offset # buckets added to FF - depth: the sprite goes that much further back
local get_previous_floor_bucket 09A1C1 r1 floor_depth     # (projected_y + previous_floor_z) >> 16 - view_y + 10, clamped to 0-FE
local load_sized_frame_address_at_row entry r0 row        # rows of the frame to skip
local load_sized_frame_address_at_row 09A239 r2 row_bytes # the record's word 0, halved for 4 bpp; >> 2 x row is added to the address word
local set_port_d_bit_3_if_sound_allowed 09A5A5 t1 port_d_bits  # io_port_d before bit 3 is set
local queue_coin_sound 09A5AE t1 port_d_bits              # io_port_d before bit 3 is set
local set_bitmap_clip entry r0 left                       # sorted with right (smaller first) -> video_bitmap_clip_left
local set_bitmap_clip entry r1 top                        # sorted with bottom -> video_bitmap_clip_top
local set_bitmap_clip entry r2 right                      # -> video_bitmap_clip_right
local set_bitmap_clip entry r3 bottom                     # -> video_bitmap_clip_bottom
local set_bitmap_clip 09A7AA t1 old_left                  # left before it swaps with right
local set_bitmap_clip 09A7B2 t1 old_top                   # top before it swaps with bottom
local draw_pixel entry r0 x                               # the pixel's column (get_point_outcode tests it against bitmap_window)
local draw_pixel 09A7F1 r0 keep_mask                      # 4 bpp: F0 (even x keeps the high nibble) or 0F (odd x)
local draw_pixel entry r1 y                               # the pixel's row; 4 bpp: becomes its byte address (y x 100 + x / 2 + bitmap_address)
local draw_pixel 09A7D7 r10 outcode                       # get_point_outcode's: not 0 = outside bitmap_window, nothing drawn
local draw_pixel 09A80C r10 outcode
local draw_pixel entry r2 color                           # byte (8 bpp) or low nibble (4 bpp)
local draw_pixel 09A7F4 r2 nibble                         # color & F, shifted up for odd x
local draw_pixel 09A7DF r3 saved_x                        # x kept for its parity
local read_pixel entry r0 x                               # the pixel's column; the pixel (or -1 outside) at the end
local read_pixel entry r1 y
local read_pixel 09A82A r10 outcode
local read_pixel 09A853 r10 outcode
local draw_dotted_line_8bpp entry r0 start_x              # first end of the line (get_window_outcode/clip_line_to_window swap and clip the ends, as in draw_line)
local draw_dotted_line_8bpp 09AC40 r0 step_record         # 9C4D2 + direction_index x 4: the two signed address steps for the line's direction
local draw_dotted_line_8bpp 09AC49 r0 major_step          # the record's second step (swapped with minor_step when |dx| < |dy|): added for every pixel, written or skipped
local draw_dotted_line_8bpp entry r1 start_y              # first end's y; the bitmap address of the dots after it
local draw_dotted_line_8bpp 09ABF4 r11 end_outcode        # get_window_outcode's first result (the end point's)
local draw_dotted_line_8bpp entry r2 end_x                # other end
local draw_dotted_line_8bpp 09AC46 r2 minor_step          # the record's first step: added when the fraction carries; on a diagonal both steps, doubled
local draw_dotted_line_8bpp 09ACF8 r2 row_step            # 400: two rows, for a vertical dotted line
local draw_dotted_line_8bpp entry r3 end_y                # other end's y
local draw_dotted_line_8bpp 09ACBE r3 span_end_copy       # movz.hw of end_x for a horizontal line
local draw_dotted_line_8bpp 09ACC1 r3 span_length         # |end_x - start_x| + 1 (the address moves to the left end when the line runs left)
local draw_dotted_line_8bpp 09ACCE r3 dots_left           # (span_length) / 2, at least 1: dots of a horizontal line, 2 bytes apart
local draw_dotted_line_8bpp entry r4 color                # byte written to each dot
local draw_dotted_line_8bpp 09AC11 r5 end_y_copy          # mov.w of end_y: sub.h turns it into dy
local draw_dotted_line_8bpp 09AC14 r5 dy                  # |end_y - start_y|; after the swap with steps_left the shorter axis, then the 0.16 slope (div.w), or the dot count's base on a diagonal or vertical line
local draw_dotted_line_8bpp 09AC9A r5 dot_count           # (dy + 1) / 2, at least 1: dots of a diagonal or vertical line
local draw_dotted_line_8bpp 09ACFF r5 dot_count
local draw_dotted_line_8bpp 09AC21 r6 end_x_copy          # mov.w of end_x: sub.h turns it into dx
local draw_dotted_line_8bpp 09AC24 r6 steps_left          # |end_x - start_x|, swapped with dy when shorter: the longer axis; + 1 = steps, counted down two per dot
local draw_dotted_line_8bpp 09AC0E r7 direction_index     # + 1 if dy < 0, + 2 if dx < 0: index of the step pairs at 09C4D2
local draw_dotted_line_8bpp 09AC64 r7 fraction            # the 0.16 accumulator; its carry adds minor_step
local draw_dotted_line_8bpp 09AC14 t2 end_y_16            # end_y (16 bits) for dy and its flags
local draw_dotted_line_8bpp 09AC14 t3 dy_16               # end_y - start_y
local draw_dotted_line_8bpp 09AC24 t2 end_x_16            # end_x (16 bits) for dx and its flags
local draw_dotted_line_8bpp 09AC24 t3 dx_16               # end_x - start_x
local draw_dotted_line_8bpp 09ACC1 t2 span_end_16         # end_x for the horizontal line's length
local draw_dotted_line_8bpp 09ACCE t4 span_dots           # (span_length) >> 1 before the at-least-1 fix
local draw_dotted_line_8bpp 09ACFF t4 column_dots         # (dy + 1) >> 1 of a vertical line
local draw_dotted_line_8bpp 09AC9A t4 diagonal_dots       # (dy + 1) >> 1 of a diagonal
local draw_dotted_line_8bpp 09AC53 t1 saved_step          # major_step kept while the steps swap (|dx| < |dy|)
local draw_dotted_line_8bpp 09AC56 t1 saved_dy            # dy kept while the lengths swap
local draw_rect_outline entry r0 left                     # clip_rect_to_window's left (sorted, clipped)
local draw_rect_outline 09AE21 r0 top_left_address        # the top-left byte kept across draw_rect_sides_8bpp / draw_rect_top_bottom_8bpp
local draw_rect_outline 09AE64 r0 top_left_address
local draw_rect_outline 09AE84 r0 top_left_address
local draw_rect_outline entry r1 top                      # clip_rect_to_window's top; becomes the top-left pixel's address
local draw_rect_outline 09AE0B r1 column_address          # the top-left pixel again, for the left side after the top row
local draw_rect_outline 09AE6A r1 column_address
local draw_rect_outline 09AD2E r10 unclipped_edges        # clip_rect_to_window's (1 left, 2 right, 4 top, 8 bottom still inside), x 3: the offset into the case tables
local draw_rect_outline entry r2 width                    # right from the caller (clipped), then right - left
local draw_rect_outline entry r3 height                   # bottom from the caller (clipped), then bottom - top
local draw_rect_outline entry r4 color                    # replicated x 01010101 (8 bpp) or x 11111111 (4 bpp)
local draw_rect_outline 09AD62 r5 left_byte_offset        # 4 bpp: left / 2, the byte of the left column in its row
local draw_rect_outline 09ADE3 r5 column_length           # height: the rows after the first of a side
local draw_rect_outline 09ADED r5 column_length
local draw_rect_outline 09AE02 r5 column_length
local draw_rect_outline 09AE11 r5 column_length
local draw_rect_outline 09AE30 r5 column_length
local draw_rect_outline 09AE6D r5 column_length
local draw_rect_outline 09AE7A r5 column_length
local draw_rect_outline 09AE3C r5 right_side_length      # right and bottom edges only: height, the right side drawn up (row step -200) from the bottom row's end
local draw_rect_outline 09ADFF r6 saved_top_left          # the top-left byte kept while fill_span_8bpp draws the top row
local draw_rect_sides_8bpp 09AE91 r5 right_address        # pixel_address + width: the right side's byte, + 200 per row
local draw_rect_sides_8bpp 09AE95 r6 row_step             # 200: one 8 bpp row
local draw_rect_top_bottom_8bpp 09AEB6 r5 bottom_address  # pixel_address + height x 200: the bottom row's byte, advanced with it
local draw_rect_top_bottom_8bpp 09AEB9 r6 bytes_left      # width (bytes after the first), less the odd first byte, + 1 = bytes to write
local draw_rect_top_bottom_8bpp 09AED4 r6 longs_left      # bytes_left / 4: the long stores, counted down
local draw_rect_top_bottom_8bpp 09AED1 r10 byte_count     # bytes_left (+ 1) before the division: its low 2 bits are the tail
local draw_rect_top_bottom_8bpp 09AEE4 r10 tail_bytes     # byte_count & 3: one halfword and/or one byte after the longs
local draw_rect_top_bottom_8bpp 09AEC7 t2 bytes_after_odd # bytes_left - 1 after the odd first byte (negative: done)
local draw_rect_top_bottom_8bpp 09AED4 t4 long_count      # bytes_left >> 2
local draw_rect_top_bottom_8bpp 09AEE4 t2 tail_count      # byte_count & 3
local fill_triangle 09B18C t1 swap_x_12                   # point 1's x while points 1 and 2 swap (2 is higher)
local fill_triangle 09B18F t1 swap_y_12
local fill_triangle 09B197 t1 swap_x_13                   # point 1's x while points 1 and 3 swap
local fill_triangle 09B19A t1 swap_y_13
local fill_triangle 09B1A2 t1 swap_x_23                   # point 2's x while points 2 and 3 swap
local fill_triangle 09B1A5 t1 swap_y_23
local fill_triangle entry r0 top_x                        # point 1's x; after the sort (by y) the topmost point's, sign-extended
local fill_triangle entry r1 top_y
local fill_triangle entry r2 middle_x                     # point 2's x; after the sort the middle point's
local fill_triangle entry r3 middle_y
local fill_triangle entry r4 bottom_x                     # point 3's x; after the sort the lowest point's
local fill_triangle entry r5 bottom_y
local fill_triangle_sorted 09B309 r4 span_color           # one-row triangle: color for fill_span_8bpp
local fill_triangle_sorted 09B215 r1 span_count           # bottom_y - top_y + 1: one span record per row, fill_span_list's count
local fill_triangle_sorted 09B27B r0 left_x               # flat top: the smaller of top_x and middle_x
local fill_triangle_sorted 09B27B r2 right_x              # flat top: the larger
local fill_triangle_sorted 09B270 r5 height               # flat top: bottom_y - top_y, fill_flat_top_triangle's rows after the first
local fill_triangle_sorted 09B1C4 r3 rows_left            # middle_y - top_y + 1: the upper part's rows, then the lower part's
local fill_triangle_sorted 09B1ED r8 bottom_x_fixed       # bottom_x << 16: the slope of the edge from the middle point is computed to it
local fill_triangle_sorted 09B1F6 r9 integer_shift        # -16: sha by it gives the integer x of a 16.16 x
local fill_triangle_sorted 09B1DA r10 span_address        # bitmap address of the current row's left end (span records' address)
local fill_triangle_sorted 09B227 r11 row_left_x          # integer x of the left edge on this row
local fill_triangle_sorted 09B1E1 r13 previous_left_x     # the left edge's x on the row before (span_address moves by the difference)
local fill_triangle_sorted 09B1FF r15 middle_on_right     # 1 when the top-middle edge is the right one (its slope is the larger): the right slope changes at the middle row
local fill_triangle_sorted 09B20D r16 right_edge_x        # 16.16 x of the right edge, + its slope per row
local fill_triangle_sorted 09B1C4 t2 saved_middle_y       # middle_y for the flat-top test
local fill_triangle_sorted 09B270 t2 saved_bottom_y       # bottom_y for the one-row test
local fill_triangle_sorted 09B2E7 t1 swap_x_12            # one row: the x being swapped while the three x's are sorted
local fill_triangle_sorted 09B2EF t1 swap_x_13
local fill_triangle_sorted 09B27B t1 swap_top_x           # flat top: top_x while it swaps with middle_x
local fill_triangle_sorted 09B207 t1 swap_slope           # a slope while the two edges swap
local fill_flat_top_triangle 09B2AC r1 span_count         # height + 1: fill_span_list's count
local fill_flat_top_triangle 09B2A3 r3 left_slope         # (bottom_x - left_x) << 16 / rows: added to left_x per row
local fill_flat_top_triangle 09B2A9 r9 integer_shift      # -16
local fill_flat_top_triangle 09B284 r10 span_address      # bitmap address of the current row's left end
local fill_flat_top_triangle 09B2BA r11 row_left_x        # integer x of the left edge on this row
local fill_flat_top_triangle 09B28E r13 previous_left_x
local fill_span_list_from_r8 09B30F r6 color              # R8's low byte
local fill_span_list_from_r8 entry r8 polygon_color       # the color fill_quad keeps in R8 (no caller sets it in the C)
local fill_span_list 09B339 r0 tail_bytes                 # the span's byte count & F: what is left after the 16-byte blocks (8, 4, then 2/1)
local fill_span_list 09B36B r0 last_bytes                 # tail_bytes & 3: a halfword and/or a byte
local fill_span_list 09B321 r11 pixel_address             # the record's address, advanced as it fills
local fill_span_list 09B324 r3 length                     # the record's length (bytes after the first; negative: skipped), + 1 = bytes
local fill_span_list 09B33C r3 blocks_left                # 16-byte blocks, counted down
local fill_span_list 09B33C t4 block_count                # bytes >> 4
local fill_span_list 09B36B t2 odd_bytes                  # tail_bytes & 3
local fill_span_list 09B333 t2 length_after_odd           # length - 1 after the odd first byte (negative: done)

# dead_work_4: 09B382-09BC7E, the unused part of the bitmap-layer drawing library (draw_line and fill_rect are the live part, used by test mode):
# a filled quadrilateral, a circle drawn as 64 lines, a midpoint circle (4 and 8 bpp, clipped) and a scanline flood fill

09B382  func  fill_quad                 # unused: fills the quadrilateral R0,R1 / R2,R3 / R4,R5 / R6,R7 (vertices in order round it, convex) with colour R8 on the 8 bpp bitmap layer: sorts the vertices by y (key y << 8 | vertex number), walks the left and right edges in 16.16 into the span list at 20B500 (bitmap address, length per row) and draws it with 09B30F; flat cases go to fill_span_8bpp (one row) or the triangle code 09B1C4/09B27E (three vertices on one row). Keeps the vertices on the stack (stack_pointer) to find the bottom vertex's neighbours
09B963  func  draw_circle_polygon       # unused: circle of radius |R2| around R0,R1 in colour R3 as 64 points (8 steps of sine_table/cosine_table x radius >> 14, mirrored into 8 octants) at 20B500 (x, y pairs), then draw_line between them; buggy: two mirrored stores use displacements 7E[R13] and 7F[R11] (-82 and 7E fit), and the line loop steps the list 2 bytes at a time (x, y and y, next x as the ends), so it would not draw a circle
09BA54  func  draw_circle               # unused: midpoint circle of radius |R2| around R0,R1 in colour R3, 8 points per step (plot_circle_point_4bpp/_8bpp by bitmap_8bpp); returns at once when the circle is wholly outside bitmap_window, and R18 (current_player in C) carries a 2-bit-per-point mask of the octants that need the window test (bit 1 draw unclipped, bit 0 test); the masks assume the points in order round the circle, but points 4, 6 and 7 are plotted in another order
09BC03  func  plot_circle_point_4bpp    # unused (draw_circle): R18 << 1 twice: top bit = plot without the window test, next bit = plot if R0,R1 is inside bitmap_window (R14-R17); at 4 bpp: (y << 8) + (x >> 1) + R12, odd x into the high nibble (R2), even into the low (R3)
09BC53  func  plot_circle_point_8bpp    # unused (draw_circle): the same at 8 bpp: byte R3 at (y << 9) + x + R12
09BC7E  func  flood_fill                # unused: scanline flood fill from R0,R1 with colour R2 up to border colour R3, inside bitmap_window (nothing when the seed is outside or already R2/R3): 09BD99 fills the seed's span, then a ring queue of 26-byte span records at 20B500-20CCC3 (written through R23, read through R24, both pushed; object_cursor/last_object in C) is worked off with 09BF99 (rows above and below); the queue overlaps string_queue and the test variables at 20C800-20CA4F

# fill_quad: the sorted vertices are top (r0,r1), second (r2,r3), third (r4,r5), bottom (r6,r7)
local fill_quad entry r0 left_x                      # vertex 0's x; after the sort the top vertex's x; then the left edge's x (16.16, + slope per row); the leftmost x of a one-row quad
local fill_quad entry r1 top_y                       # vertex 0's y (y << 8 | 0 while sorting); then the top row; in the flat-top cases the first row's bitmap address
local fill_quad entry r2 second_x                    # vertex 1's x; after the sort the second-highest vertex's; then, by case, the right edge's x (16.16) or the slope towards that vertex
local fill_quad entry r3 second_y                    # vertex 1's y (<< 8 | 1 while sorting); then its rows below the top + 1, counted down per row
local fill_quad entry r4 third_x                     # vertex 2's x; after the sort the third vertex's; then a slope or that vertex's x in 16.16 (where an edge turns towards it)
local fill_quad entry r5 third_y                     # vertex 2's y (<< 8 | 2 while sorting); then its rows below the top (+ 1)
local fill_quad entry r6 bottom_x                    # vertex 3's x; after the sort the bottom vertex's (16.16: where the edges end)
local fill_quad entry r7 bottom_y                    # vertex 3's y (<< 8 | 3 while sorting); then the rows from the top or from the third vertex to it
local fill_quad entry r8 fill_color                  # pixel value: fill_span_8bpp's color, the triangle code's R6 and 09B30F's R8
local fill_quad 09B408 r10 top_index                 # the top vertex's number (low byte of its sort key); swapped with second_index when the flat top's x are swapped
local fill_quad 09B40B r11 second_index              # the second vertex's number
local fill_quad 09B40E r12 third_index               # the third vertex's number
local fill_quad 09B411 r13 bottom_index              # the bottom vertex's number: (bottom_index - top_index) & 3 == 2 says they are opposite corners (the other two on different sides)
local fill_quad 09B512 r9 pixel_shift                # 0xF0 (-16): 16.16 x to its pixel column (the same in every case)
local fill_quad 09B57B r9 pixel_shift
local fill_quad 09B69E r9 pixel_shift
local fill_quad 09B7B7 r9 pixel_shift
local fill_quad 09B88C r9 pixel_shift
local fill_quad 09B4E6 r10 span_address              # bitmap address of the row's left end: + the left end's move + 200 per row; stored in the span list
local fill_quad 09B58D r10 span_address
local fill_quad 09B5F1 r10 span_address
local fill_quad 09B67C r10 span_address
local fill_quad 09B798 r10 span_address
local fill_quad 09B89E r10 span_address
local fill_quad 09B8EE r10 span_address
local fill_quad 09B4ED r11 previous_left             # flat-top cases: the last row's left pixel column (the span address moves by the difference)
local fill_quad 09B565 r11 previous_left
local fill_quad 09B69B r11 right_x                   # top-vertex cases: the right edge's x (16.16), starting at the top vertex
local fill_quad 09B7AB r11 right_x
local fill_quad 09B880 r11 right_x
local fill_quad 09B4F0 r12 right_column              # flat-top cases: the row's right pixel column (span length = right_column - previous_left)
local fill_quad 09B568 r12 right_column
local fill_quad 09B688 r12 prior_left                # top-vertex cases: the last row's left pixel column
local fill_quad 09B7A1 r12 prior_left
local fill_quad 09B86D r12 prior_left
local fill_quad 09B532 r3 left_column                # flat-top cases: the new row's left pixel column (left_x >> 16)
local fill_quad 09B5C4 r3 left_column
local fill_quad 09B624 r3 left_column
local fill_quad 09B6E1 r16 left_pixel                # top and bottom opposite: the new row's left pixel column
local fill_quad 09B72C r16 left_pixel
local fill_quad 09B764 r16 left_pixel
local fill_quad 09B8BF r7 left_end                   # flat bottom: the new row's left pixel column
local fill_quad 09B90F r7 left_end
local fill_quad 09B517 r1 row_count                  # rows to draw: 09B30F's R1
local fill_quad 09B5A3 r1 row_count
local fill_quad 09B603 r1 row_count
local fill_quad 09B685 r1 row_count
local fill_quad 09B7BA r1 row_count
local fill_quad 09B8A7 r1 row_count
local fill_quad 09B8F1 r1 row_count
local fill_quad 09B5A6 r15 segments_left             # 2: the edge with a bend is walked in two parts
local fill_quad 09B606 r15 segments_left
local fill_quad 09B6B6 r15 segments_left
local fill_quad 09B88F r15 segments_left
local fill_quad 09B7CC r15 bend_on_right             # top and bottom adjacent: 1 when the second and third vertices are on the right side (the right slope turns twice), 0 on the left
local fill_quad 09B7D9 r16 edge_parts_left           # top and bottom adjacent: 3 parts of the bent side (2 when the second and third vertices share a row)
local fill_quad 09B59C r16 single_edge_slope         # flat top: the slope (16.16 per row) of the side without a bend (top corner straight to the bottom)
local fill_quad 09B600 r16 single_edge_slope
local fill_quad 09B7C9 r17 right_side_slope          # top and bottom adjacent: first the slope to the bottom, after the compare the right edge's; recomputed at the right side's bends
local fill_quad 09B6BF r2 left_slope                 # top and bottom opposite, second vertex on the right: the left edge's slope (towards the third vertex)
local fill_quad 09B6BF r4 right_slope                # the right edge's slope (towards the second vertex, then the bottom)
local fill_quad 09B6B3 r5 rows_after_second          # top and bottom opposite: rows from the second vertex to the bottom
local fill_quad 09B7DF r5 next_part_rows             # top and bottom adjacent: rows of the bent side's next part
local fill_quad 09B488 r3 vertex_index               # (bottom_index & 3), + 1, + 2 more: the bottom vertex and its two neighbours; then the address of the last one's (x, y)
local fill_quad 09B48F r4 apex_x                     # the bottom vertex's x: the triangle's tip (09B27E's R4)
local fill_quad 09B492 r5 triangle_height            # the bottom vertex's y, - triangle_top_y: rows (09B27E's R5)
local fill_quad 09B49E r2 triangle_right_x           # x of the bottom vertex's next neighbour; swapped with triangle_left_x when smaller
local fill_quad 09B4C1 r2 triangle_right_x
local fill_quad 09B4AB r0 triangle_left_x            # x of the bottom vertex's previous neighbour; the flat top's left end (09B27E's R0)
local fill_quad 09B4C1 r0 triangle_left_x
local fill_quad 09B4AE r1 triangle_top_y             # that neighbour's y: the triangle's top row
local fill_quad 09B4B4 r6 triangle_color             # fill_color for 09B27E/09B1C4 (R6)
local fill_quad 09B959 r6 triangle_color
local fill_quad 09B956 r4 bottom_right_x             # three vertices on the bottom row: the larger of the third and bottom x (09B1C4's R4; the second vertex's x is left out)
local fill_quad 09B3CB t1 swap_x_01                  # the sort: x of the first and second keys swapped
local fill_quad 09B3CE t1 swap_key_01                # y << 8 | number of the first and second swapped
local fill_quad 09B3D6 t1 swap_x_02
local fill_quad 09B3D9 t1 swap_key_02
local fill_quad 09B3E1 t1 swap_x_03
local fill_quad 09B3E4 t1 swap_key_03
local fill_quad 09B3EC t1 swap_x_12
local fill_quad 09B3EF t1 swap_key_12
local fill_quad 09B3F7 t1 swap_x_13
local fill_quad 09B3FA t1 swap_key_13
local fill_quad 09B402 t1 swap_x_23
local fill_quad 09B405 t1 swap_key_23
local fill_quad 09B550 t1 swap_top_x                 # flat top with two other rows: the two top x in order (and their numbers)
local fill_quad 09B4D2 t1 swap_upper_x               # flat top and flat bottom: the top row's two x in order
local fill_quad 09B4DA t1 swap_lower_x               # ... and the bottom row's
local fill_quad 09B85D t1 swap_bottom_x              # flat bottom: the bottom row's two x in order (and their numbers)
local fill_quad 09B4C1 t1 swap_triangle_x            # three on the top row: the neighbours' x in order
local fill_quad 09B7D4 t1 swap_edge_slope            # top and bottom adjacent: the smaller slope goes left
local fill_quad 09B6BF t1 swap_slope                 # top and bottom opposite: the smaller slope goes left
local fill_quad 09B7DF t2 third_rows                 # third_y's rows before - second_y: equal = second and third on one row (the middle part is skipped)
local fill_quad 09B71B t2 rows_after_left_bend       # rows_after_second before - bottom_y: 0 = the third vertex on the second's row
local fill_quad 09B6D0 t2 rows_after_right_bend      # the same with the second vertex on the right

# draw_circle_polygon
local draw_circle_polygon entry r0 center_x          # the circle's centre x; in the line loop the low half becomes each line's start x (draw_line's R0)
local draw_circle_polygon entry r1 center_y          # centre y; then each line's start y
local draw_circle_polygon entry r2 radius            # made positive (neg.h when negative), x the sine and cosine
local draw_circle_polygon entry r3 circle_color      # copied into draw_line's color
local draw_circle_polygon 09B970 r8 angle            # 100000 + 200000 per step, rotated left 15: sine_table index 8, 18 ... 78 (the first octant's middles, 0x400 a turn); draw_line's R8 lands in it unread
local draw_circle_polygon 09B977 r9 angle_step       # 200000: 16 sine_table entries
local draw_circle_polygon 09B97B r10 sines           # sine_table (its + 0x200 bytes is cosine_table)
local draw_circle_polygon 09B982 r19 points          # 20B500: the 64 (x, y) pairs
local draw_circle_polygon 09B987 r11 point_cursor    # from 20B500 up, 4 a step: the first quadrant's points (and + 40, + 80, + C0: the other quadrants')
local draw_circle_polygon 09B98A r13 mirror_cursor   # from 20B600 down, 4 a step: the points mirrored in each quadrant
local draw_circle_polygon 09B98F r5 steps_left       # 8 points per octant
local draw_circle_polygon 09B992 r12 sine_shift      # 0xF2 (-14): sine x radius >> 14 (0x4000 = 1.0)
local draw_circle_polygon 09B995 r6 sine_entry       # the angle's sine_table entry address
local draw_circle_polygon 09B9A1 r6 sine_entry sine_and_cosine  # sines + the angle x 2 ("movea.h [R10](R6), R6"): its sine at +0, its cosine 200 bytes on
local draw_circle_polygon 09B9AD r3 cos_offset       # cosine x radius >> 14, then negated for the opposite points
local draw_circle_polygon 09B9EB r3 sin_offset       # sine x radius >> 14, then negated
local draw_circle_polygon 09B9B0 r7 coordinate       # centre + or - an offset: one x or y of the list
local draw_circle_polygon 09B9BB r7 coordinate
local draw_circle_polygon 09B9CB r7 coordinate
local draw_circle_polygon 09B9D8 r7 coordinate
local draw_circle_polygon 09B9EE r7 coordinate
local draw_circle_polygon 09B9F9 r7 coordinate
local draw_circle_polygon 09BA0A r7 coordinate
local draw_circle_polygon 09BA16 r7 coordinate
local draw_circle_polygon 09BA2B r15 x_cursor        # 20B500 + 2 per line: where the line's start x is read (its y 2 bytes further)
local draw_circle_polygon 09BA28 r16 y_cursor        # 20B502 + 2 per line: start y (end y 2 further)
local draw_circle_polygon 09BA2E r17 lines_left      # 3F lines, then the closing one from the first point

# draw_circle
local draw_circle entry r0 x                         # the centre's x (sign-extended into center_x)
local draw_circle entry r1 y                         # the centre's y
local draw_circle entry r2 radius                    # made positive; starts error and x_offset
local draw_circle entry r3 color                     # 8 bpp: plot_circle_point_8bpp's byte; 4 bpp: split into low_color and high_color
local draw_circle 09BA64 r10 center_x
local draw_circle 09BA67 r11 center_y
local draw_circle 09BA6A r12 bitmap_base             # bitmap_address: the layer's address of 0,0
local draw_circle 09BA74 r15 window_top              # bitmap_window_top (left is R14)
local draw_circle 09BA79 r16 window_right
local draw_circle 09BA7E r17 window_bottom
local draw_circle 09BA83 r13 octant_mask             # FFFF, cleared by the window tests: per point (in order round the circle) bit 1 = inside for sure, bit 0 = test it; copied into R18 every step
local draw_circle 09BA86 r0 circle_right             # center_x + radius: left of the window = nothing to draw
local draw_circle 09BAB1 r0 circle_bottom            # center_y + radius
local draw_circle 09BA90 r1 minus_radius             # -radius
local draw_circle 09BA93 r2 circle_left              # center_x - radius
local draw_circle 09BABB r2 circle_top               # center_y - radius
local draw_circle 09BA5B r5 error                    # midpoint decision: radius, - (2 y_offset + 1) per step; below 0: x_offset - 1 and + 2 x_offset
local draw_circle 09BA5E r6 x_offset                 # starts at radius, down while the error goes negative; the loop ends when y_offset passes it
local draw_circle 09BA61 r7 y_offset                 # 0, + 1 per step
local draw_circle 09BB17 r8 minus_x_offset
local draw_circle 09BB91 r8 minus_x_offset
local draw_circle 09BB1A r9 minus_y_offset
local draw_circle 09BB94 r9 minus_y_offset
local draw_circle 09BB75 r0 error_step               # y_offset + (y_offset + 1): taken from the error
local draw_circle 09BBEF r0 error_step
local draw_circle 09BB1D r0 point_x                  # center_x +- x_offset or y_offset: the point plotted
local draw_circle 09BB28 r0 point_x
local draw_circle 09BB33 r0 point_x
local draw_circle 09BB3E r0 point_x
local draw_circle 09BB49 r0 point_x
local draw_circle 09BB54 r0 point_x
local draw_circle 09BB5F r0 point_x
local draw_circle 09BB6A r0 point_x
local draw_circle 09BB97 r0 point_x
local draw_circle 09BBA2 r0 point_x
local draw_circle 09BBAD r0 point_x
local draw_circle 09BBB8 r0 point_x
local draw_circle 09BBC3 r0 point_x
local draw_circle 09BBCE r0 point_x
local draw_circle 09BBD9 r0 point_x
local draw_circle 09BBE4 r0 point_x
local draw_circle 09BB21 r1 point_y                  # center_y +- y_offset or x_offset
local draw_circle 09BB2C r1 point_y
local draw_circle 09BB37 r1 point_y
local draw_circle 09BB42 r1 point_y
local draw_circle 09BB4D r1 point_y
local draw_circle 09BB58 r1 point_y
local draw_circle 09BB63 r1 point_y
local draw_circle 09BB6E r1 point_y
local draw_circle 09BB9B r1 point_y
local draw_circle 09BBA6 r1 point_y
local draw_circle 09BBB1 r1 point_y
local draw_circle 09BBBC r1 point_y
local draw_circle 09BBC7 r1 point_y
local draw_circle 09BBD2 r1 point_y
local draw_circle 09BBDD r1 point_y
local draw_circle 09BBE8 r1 point_y
local draw_circle 09BB06 r2 high_color               # 4 bpp: color << 3 (sic: shl.b #3, not 4) for odd x pixels (the high nibble)
local draw_circle 09BB09 r3 low_color                # 4 bpp: color & F for even x pixels
local draw_circle 09BB7B t2 error_before_4bpp        # error before the step is taken off (compared with error_step)
local draw_circle 09BBF5 t2 error_before_8bpp

# plot_circle_point_4bpp / plot_circle_point_8bpp
param plot_circle_point_4bpp R0 point_x              # draw_circle's center_x +- an offset
param plot_circle_point_4bpp R1 point_y              # then the byte's address: (y << 8) + (x >> 1) + bitmap_base
param plot_circle_point_4bpp R2 high_color           # odd x: (byte & F) | high_color
param plot_circle_point_4bpp R3 low_color            # even x: (byte & F0) | low_color
param plot_circle_point_4bpp R12 bitmap_base         # bitmap_address
param plot_circle_point_4bpp R15 window_top          # bitmap_window_top (R14 = left)
param plot_circle_point_4bpp R16 window_right
param plot_circle_point_4bpp R17 window_bottom
local plot_circle_point_4bpp 09BC03 t3 shifted_mask  # R18 << 1: its carry (draw) and sign (test the window) decide; shifted again on the way out
param plot_circle_point_8bpp R0 point_x
param plot_circle_point_8bpp R1 point_y
param plot_circle_point_8bpp R12 bitmap_base
param plot_circle_point_8bpp R15 window_top
param plot_circle_point_8bpp R16 window_right
param plot_circle_point_8bpp R17 window_bottom
local plot_circle_point_8bpp entry r3 color          # draw_circle's colour byte (R3 is not passed in C: draw_circle has no caller)
local plot_circle_point_8bpp 09BC53 t3 shifted_mask

# flood_fill
local flood_fill entry r0 seed_x
local flood_fill entry r1 seed_y                     # as a byte in the records (rows 0-FF)
local flood_fill entry r2 fill_color                 # x 01010101 into fill_pattern; a pixel already this colour stops the fill
local flood_fill entry r3 border_color               # the boundary: spans end at it
local flood_fill 09BC7E r4 window_left               # bitmap_window (09BD99 scans its row up to left and right, 09BF99 stops at top and bottom)
local flood_fill 09BC89 r5 window_top
local flood_fill 09BC94 r6 window_right
local flood_fill 09BC9F r7 window_bottom
local flood_fill 09BCBC r10 seed_pixel               # the seed's pixel: fill_color or border_color = nothing to do
local flood_fill 09BCD3 r10 span_left_x              # seed_x into 09BD99, the span's left end back; then what 09BEBB/09BF99 leave
local flood_fill 09BCD6 r11 span_y                   # the span's row: seed_y, then each queued record's (flood_fill_y)
local flood_fill 09BD49 r11 span_y
local flood_fill 09BCDC r12 fill_pattern             # fill_color x 01010101 (09BD99 stores it a long at a time)
local flood_fill 09BCE3 r15 span_right_address       # 09BD99: the bitmap address of the span's right end
local flood_fill 09BCE3 r16 span_right_x             # 09BD99: the span's right end

# variables
20B500  var   bitmap_draw_buffer  u32     # work area of the unused bitmap drawing: fill_quad's and 09B187's span list (6 bytes a row: bitmap address u32, length u16, drawn by 09B312), draw_circle_polygon's 64 (x, y) points, flood_fill's ring queue of 26-byte span records up to 20CCC4 (R23 writes, R24 reads), the picture unpacker's 16-slot colour cache (init_picture_colour_cache), unpack_lz_bytes' recent_literals

# dead_work_5: 09BD58-09FEBA (unused code, helper 5)

# --- Flood fill of the bitmap layer (span fill with a queue of spans at 20B500-20CCC4) ---
# flood_fill (dead_work_4) is the entry: R0/R1 seed point, R2 fill colour, R3 border colour; it fills the
# seed's span, then takes 0x1A-byte span records from the queue (R24 = last_object reads, R23 = object_cursor
# writes, both saved and restored) until it is empty.
09BD58  func  flood_fill_push_span              # unused: flood_fill_span from R10/R14 in row R11 + R9, then queues its record (R9, row, right x/address, left x/address, the flood_fill_push_parent_* range) at R23, wrapping from 20CCC4 to 20B500
09BD99  func  flood_fill_span                   # unused: from the pixel at R10 (x) / R14 (address) scans left to R4 and right to R6 until the fill colour (R12 low byte) or the border colour (R3), fills the run with R12 (bytes, then words, 16 at a time); R10/R14 come back as its left end, R16/R15 its right end; also fill_span_at_point's 8bpp path
09BEBB  func  flood_fill_scan_seed_rows         # unused: the seed span's neighbour rows: flood_fill_scan_row above (R9 = -1) when y > window top (R5), below (R9 = 1) when y < window bottom (R7)
09BECE  func  flood_fill_scan_row               # unused: walks row y + R9 under/over the current span (flood_fill_left_x..right_x); every run not yet in the fill or border colour goes through flood_fill_push_span, flood_fill_push_parent_left_x moving past it
09BF99  func  flood_fill_scan_span_rows         # unused: the same as flood_fill_scan_seed_rows for a span taken from the queue (flood_fill_scan_row_skip_parent)
09BFAC  func  flood_fill_scan_row_skip_parent   # unused: R9 = the span's own direction (flood_fill_direction): the whole next row (flood_fill_scan_row); back toward its parent: only the parts left of flood_fill_parent_left_x and right of flood_fill_parent_right_x + 1
09C10D  func  fill_span_at_point                # unused: R0,R1 point (get_point_outcode: nothing outside bitmap_window), R2 fill colour, R3 border colour: the one horizontal run through the point. 8bpp: flood_fill_span. 4bpp: nibble scans, but R10 still holds the outcode (never set to x) and R15 is never set: that path cannot work as written

# --- Bitmap rectangles through bit strings (both MOVBSU operands are ones the V60 cannot run: invalid_operand() in the C) ---
09C2A2  func  save_bitmap_rect                  # unused: R0-R3 rectangle (sorted) -> buffer R4: width in bits, height, then each row (MOVBSU from y * row + x of bitmap_address, bit offset (x & 1) * 4 in 4bpp, rows 100 or 200 bytes)
09C324  func  draw_saved_bitmap_rect            # unused: the buffer R2 (save_bitmap_rect's: width in bits, rows, rows of (bits + 7) / 8 bytes) copied to the bitmap at R0,R1 with MOVBSU
09C389  func  get_point_outcode                 # where R0,R1 is outside bitmap_window in R10 (1 left, 2 right, 4 above, 8 below), R0/R1 sign-extended; get_window_outcode runs on into it, draw_pixel/09A823 and fill_span_at_point call it

# --- EEPROM entries that select the chip and turn interrupts back on (eeprom_lock's family) ---
09C618  func  eeprom_unlock                     # unused: command 30 (write enable): eeprom_lock's other entry, through eeprom_send_command and eeprom_deselect, interrupts on
09C632  func  eeprom_read_word_at               # unused: R4 = word address | 80 (read), eeprom_select, eeprom_read_word -> R0, interrupts on (no eeprom_deselect)
09C714  func  eeprom_read_all_and_enable_interrupts  # unused: eeprom_read_all (0x40 words to eeprom_magic), then interrupts on; load_settings calls eeprom_read_all without this
09C741  func  eeprom_write_word_at              # unused: R4 = word address | 40 (write), eeprom_select, eeprom_write_word of R0, interrupts on
09C8DF  data  eeprom_deselect_padding         # a 00 byte (halt) before eeprom_deselect (09C8E0, 4-byte aligned): alignment filler, not code

# --- Test mode leftovers ---
09D857  func  wrap_sound_test_number_down       # unused: sound_test_number > 8C (0 stepped down to FF) -> 8C: the downward twin of wrap_sound_test_number; test_sound only steps up
09E1E7  func  test_coin_assignments_spare_return    # unused: a second rsr right after the one that ends the main page's credits-to-start item (09E1B3-09E1E6), inside test_coin_assignments; nothing jumps to it
09E4C9  func  test_coin_assignments_spare_return_2  # unused: a second rsr right after the one that ends the manual page's coins-per-credit item (09E488-09E4C8), inside test_coin_assignments_manual_page

# --- Zero bytes (halt) that align the interrupt code ---
09F55F  data  irq_vblank_end_padding          # a 00 byte (halt) that aligns irq_vblank_end (09F560): not code
09F582  data  irq_timer0_padding              # two 00 bytes (halt) that align irq_timer0 (09F584): not code
09F5B5  data  int_default_padding             # three 00 bytes (halt) that align int_default (09F5B8): not code
09F5F9  data  send_sound_command_padding      # a 00 byte (halt) before send_sound_command (09F5FA, even address): not code
09F62E  func  read_service_inputs               # unused: ports F and E (MAME's SERVICE34/SERVICE12: DIPs, PCB buttons; service, test, coins, starts) as one halfword at 20F010, the last one at 20F012, pressed (old & ~new, active low) at 20F014, released at 20F016: read_controls' edge detection on 16 bits

# --- Credits display and an older single credit count ---
09FC89  func  erase_attract_credits             # unused: draw_attract_credits' undo, by cabinet_config (jump table 09FC96): erase_credit_count and erase_start_prompt at the common or per-player positions; loads them with movz.hw, without the 31F000 base draw_attract_credits ORs in, so it would write at 00Fxxx (ROM)
09FD72  func  erase_start_prompt                # unused entry of blink_start_prompt's erase: R10 = 1 (prompt 1, "Insert more coin"), port D bits 2-3 cleared, erase_text_lines of prompt_texts[1] at R11
09FE2B  func  build_fraction_glyph              # unused: R0 numerator, R1 denominator -> R4/R5 the numerator's rows raised 3 (fraction_digit_font + R0 * 8 + 3), R6/R7 the denominator's rows OR the slash (104BCA, 104BCE): a small fraction's pixel rows
09FE66  func  erase_credit_count                # unused: erase_text_here of credits_label_text ("credit  ") and of the 4 blank cells after it at R11: the credit line; only erase_attract_credits calls it
09FE7A  func  add_to_credit_pool                # unused: unless free_play: credit_pool += R0, capped at 9 (credit_pool_coins = 0 at the cap), copied to bookkeeping_coin_credits and bookkeeping_backup with update_bookkeeping_checksum; R0 = credits added (0 with free play)
09FEBA  func  take_from_credit_pool             # unused: unless free_play: credit_pool -= R0, copied to bookkeeping_coin_credits and bookkeeping_backup with update_bookkeeping_checksum

# Labels
09BDBC  func  flood_fill_span_left_pair             # unused: flood_fill_span: the next two pixels to the left ([-R14] halfword), the nearer one tested first
09BDCF  func  flood_fill_span_left_pixel            # unused: flood_fill_span: test the farther pixel of the pair (or the odd first pixel); dbne counts the pairs
09BDF3  code  flood_fill_span_left_edge             # flood_fill_span: R14 is on the fill or border pixel: the span starts one to the right
09BDF5  func  flood_fill_span_set_left_x            # unused: flood_fill_span: R10 -= R17 - R14: the run's left end x (R14 on its first pixel), then flood_fill_span_scan_right
09BDFE  func  flood_fill_span_scan_right            # unused: flood_fill_span: the right scan, from the pixel after the seed (R15 = R17 + 1) to the window's right edge R6; the seed already at it: straight to flood_fill_span_fill_run
09BE1F  func  flood_fill_span_right_pair            # unused: flood_fill_span: the next two pixels to the right ([R15+] halfword)
09BE2F  func  flood_fill_span_right_pixel           # unused: flood_fill_span: test the farther pixel of the pair (or the odd first pixel)
09BE53  code  flood_fill_span_right_edge            # flood_fill_span: the last pixel read is the edge: R15 back two, to the one before it
09BE55  code  flood_fill_span_right_end             # flood_fill_span: R15 back one, to the last pixel read (window edge reached, or the odd last pixel was the edge)
09BE57  func  flood_fill_span_set_right_x           # unused: flood_fill_span: R16 += R15 - R17: the run's right end x (R15 on its last pixel), then flood_fill_span_fill_run
09BE5D  func  flood_fill_span_fill_run              # unused: flood_fill_span: R12 written from R14 over R16 - R10 + 1 bytes (an odd first byte, 16-byte blocks, then 8, 4, 2, 1); R15 back = the last byte written (the run's right end)
09BF50  code  flood_fill_scan_row_pairs_done        # flood_fill_scan_row: no fillable pixel in the pairs: done unless an odd pixel is left
09BF55  code  flood_fill_scan_row_odd_pixel         # flood_fill_scan_row: the last (odd) pixel of the row part: fill or border colour ends the scan
09BF65  code  flood_fill_scan_row_first_of_pair     # flood_fill_scan_row: the first pixel of the pair just read is the fillable one: R14 one more back (then back_to_pixel)
09BF67  code  flood_fill_scan_row_back_to_pixel     # flood_fill_scan_row: R14 back to the fillable pixel just read (one more back for the first of the pair)
09BF69  code  flood_fill_scan_row_found             # flood_fill_scan_row: R10 += R14 - R15: the x of the fillable pixel
09BF6F  code  flood_fill_scan_row_push              # flood_fill_scan_row: flood_fill_push_span from it; go on 2 pixels past its right end while that is left of the span's right x - 1
09C013  code  flood_fill_skip_parent_left_pairs     # flood_fill_scan_row_skip_parent: the left part (left x up to the parent's left x) two pixels at a time
09C03A  code  flood_fill_skip_parent_left_odd_pixel # flood_fill_scan_row_skip_parent: the left part's odd last pixel, if any
09C051  code  flood_fill_skip_parent_left_back_to_pixel  # flood_fill_scan_row_skip_parent: R14 back to the fillable pixel just read
09C053  code  flood_fill_skip_parent_left_found     # flood_fill_scan_row_skip_parent: R10 += R14 - R15: the x of the fillable pixel
09C059  code  flood_fill_skip_parent_left_push      # flood_fill_scan_row_skip_parent: flood_fill_push_span; on 2 past its end while left of the parent's left x - 2, then the right part
09C074  func  flood_fill_skip_parent_right          # unused: flood_fill_scan_row_skip_parent's right part (from the parent's right x on), a function of its own: every way out of the left part jumps here, and it ends the scan (rsr 09C10C)
09C0D3  code  flood_fill_skip_parent_right_odd_pixel     # flood_fill_scan_row_skip_parent: the right part's odd last pixel, if any
09C0EA  code  flood_fill_skip_parent_right_back_to_pixel # flood_fill_scan_row_skip_parent: R14 back to the fillable pixel just read
09C0EC  code  flood_fill_skip_parent_right_found    # flood_fill_scan_row_skip_parent: R10 += R14 - R15: the x of the fillable pixel
09C0F2  code  flood_fill_skip_parent_right_push     # flood_fill_scan_row_skip_parent: flood_fill_push_span; on 2 past its end while left of the span's right x - 1
09C1A0  func  fill_span_at_point_left_byte          # unused: fill_span_at_point (4bpp): the byte to the left (two pixels), its high nibble (the odd, nearer pixel) tested first
09C1BB  func  fill_span_at_point_left_even_pixel    # unused: fill_span_at_point (4bpp): the low nibble (even pixel); dbne counts
09C1D2  code  fill_span_at_point_left_edge          # fill_span_at_point (4bpp): R10 + 1: the run starts right of the edge pixel
09C1D4  func  fill_span_at_point_scan_right         # unused: fill_span_at_point (4bpp): the right scan from x R16 + 1 to bitmap_window_right, a byte (two nibbles) at a time through R15 (never set); R16 already there: straight to fill_span_at_point_fill_run
09C1EE  func  fill_span_at_point_right_byte         # unused: fill_span_at_point (4bpp): the next byte to the right, its low nibble (even pixel) first
09C208  func  fill_span_at_point_right_odd_pixel    # unused: fill_span_at_point (4bpp): the high nibble (odd pixel); dbne counts
09C21E  code  fill_span_at_point_right_edge         # fill_span_at_point (4bpp): R16 - 1 (09C21C takes one more off for an even pixel); R16 had moved 2 a byte ahead of the pixel tested, so it ends on the edge pixel itself, not left of it, and the fill paints the edge pixel too
09C220  func  fill_span_at_point_fill_run           # unused: fill_span_at_point (4bpp): fills x R10..R16 from R14 with R2's low nibble: an odd first pixel (high nibble), an aligning byte, longs, a halfword, a byte, an even last pixel (low nibble)

# Work RAM
20CCDA  var   flood_fill_direction  u8                  # the span taken from the queue (record byte 0): FF it was found above its parent, 1 below; flood_fill_scan_row_skip_parent compares R9 with it
20CCDB  var   flood_fill_y  u8                          # the current span's row (record byte 1): R11 of the row scans
20CCDC  var   flood_fill_right_x  u16                   # the current span's right end x (record +2): flood_fill_scan_row's limit
20CCDE  var   flood_fill_right_address  u32             # its bitmap address (record +4)
20CCE2  var   flood_fill_left_x  u16                    # the current span's left end x (record +8): where the row scans start
20CCE4  var   flood_fill_left_address  u32              # its bitmap address (record +A); + R9 * 200 is the neighbour row
20CCE8  var   flood_fill_parent_right_x  u16            # the current span's parent range, right end (record +E): flood_fill_scan_row_skip_parent scans from 2 past it
20CCEA  var   flood_fill_parent_right_address  u32      # its address in the parent's row (record +10)
20CCEE  var   flood_fill_parent_left_x  u16             # parent range, left end (record +14): flood_fill_scan_row_skip_parent scans the left part up to it
20CCF0  var   flood_fill_parent_left_address  u32       # record +16: popped with the rest, nothing reads it
20CCF4  var   flood_fill_push_parent_right_x  u16       # the parent range flood_fill_push_span writes into new records: the current span's right x (also flood_fill_scan_row's limit)
20CCF6  var   flood_fill_push_parent_right_address  u32 # the current span's right address, written into new records
20CCFA  var   flood_fill_push_parent_left_x  u16        # the current span's left x; flood_fill_scan_row moves it 2 past each run it pushes
20CCFC  var   flood_fill_push_parent_left_address  u32  # its address, moved with it
20F010  var   service_inputs  u16                       # read_service_inputs (unused): port F << 8 | port E, active low
20F012  var   service_inputs_previous  u16              # read_service_inputs: the 20F010 value before this read
20F014  var   service_inputs_pressed  u16               # read_service_inputs: previous & ~new (active low: just pressed); 20F016: service_inputs_released
20F400  var   credit_pool  u8                           # add_to_credit_pool / take_from_credit_pool (both unused): one credit count, max 9, copied to bookkeeping_coin_credits; nothing live reads it
20F403  var   credit_pool_coins  u8                     # add_to_credit_pool clears it when credit_pool reaches 9 (as add_coin clears a block's bonus coins at the cap): probably the coins toward the next credit

# ROM tables
09FEDE  table common_start_prompt_texts  u16            # erase_attract_credits' prompt_texts for common chutes (09FEDD + 1): offsets from here to one-line texts "Push start button", "Insert more coin", "Insert coin", "Insert more coin"
09FFCC  table common_start_prompt_position  u16         # F694: the start prompt with common chutes (erase_attract_credits, just before start_prompt_positions_3p)
104B76  data  fraction_digit_font                       # build_fraction_glyph: digits 0-9, 8 bytes each (rows 3-7, 4 pixels in the low nibble), then a slash (10 20 40 80 at 104BCC)

# The flood fill queue records (0x1A bytes) as flood_fill_push_span writes them through R23 (object_cursor)
struct flood_fill_record 1A                     # one span the unused flood_fill queues (ring of them at 20B500-20CCC3, written through R23, read through R24)
field flood_fill_record 00 direction_and_y u16  # low byte the direction (R9), high byte the row (R11 + R9): popped into flood_fill_direction/flood_fill_y
field flood_fill_record 02 right_x u16  # flood_fill_span's R16
field flood_fill_record 04 right_address u32  # flood_fill_span's R15
field flood_fill_record 08 left_x u16  # flood_fill_span's R10
field flood_fill_record 0A left_address u32  # flood_fill_span's R14
field flood_fill_record 0E parent_right_x u16  # flood_fill_push_parent_right_x
field flood_fill_record 10 parent_right_address u32  # flood_fill_push_parent_right_address
field flood_fill_record 14 parent_left_x u16  # flood_fill_push_parent_left_x
field flood_fill_record 16 parent_left_address u32  # flood_fill_push_parent_left_address

# Parameters
param flood_fill_push_span R4 window_left           # bitmap_window_left, for flood_fill_span
param flood_fill_push_span R6 window_right          # bitmap_window_right, for flood_fill_span
param flood_fill_push_span R9 direction             # -1 the row above, 1 below: record byte 0; the record's row is R11 + R9
param flood_fill_push_span R10 left_x               # the first fillable x the row scan found; flood_fill_span moves it to the run's left end (record +8)
param flood_fill_push_span R11 y                    # the current span's row
param flood_fill_push_span R12 fill_pattern         # the fill colour in all 4 bytes
param flood_fill_push_span R15 right_address        # out: the run's right end address (record +4)
param flood_fill_push_span R16 right_x              # out: the run's right end x (record +2)
param flood_fill_span R4 window_left                # the left scan stops there (bitmap_window_left)
param flood_fill_span R6 window_right               # the right scan stops there (bitmap_window_right)
param flood_fill_span R10 left_x                    # in: the seed pixel's x; out: the run's left end x
param flood_fill_span R12 fill_pattern              # the fill colour in all 4 bytes: tested (low byte) and written
param flood_fill_span R15 right_address             # out: the address of the run's last pixel
param flood_fill_span R16 right_x                   # out: the run's right end x
param flood_fill_scan_seed_rows R4 window_left
param flood_fill_scan_seed_rows R5 window_top       # no row above when y is not below it
param flood_fill_scan_seed_rows R6 window_right
param flood_fill_scan_seed_rows R7 window_bottom    # no row below when y is not above it
param flood_fill_scan_seed_rows R10 scan_x          # flood_fill_scan_row's last scan position, handed back (callers pass it on unused)
param flood_fill_scan_seed_rows R11 y               # the seed span's row
param flood_fill_scan_seed_rows R12 fill_pattern
param flood_fill_scan_row R4 window_left
param flood_fill_scan_row R6 window_right
param flood_fill_scan_row R9 direction              # -1 the row above, 1 below
param flood_fill_scan_row R10 scan_x                # out: starts at flood_fill_left_x, the x the scan reached
param flood_fill_scan_row R11 y                     # the current span's row
param flood_fill_scan_row R12 fill_pattern
param flood_fill_scan_span_rows R4 window_left
param flood_fill_scan_span_rows R5 window_top
param flood_fill_scan_span_rows R6 window_right
param flood_fill_scan_span_rows R7 window_bottom
param flood_fill_scan_span_rows R10 scan_x          # the row scans' last position, handed back
param flood_fill_scan_span_rows R11 y               # flood_fill_y of the span taken from the queue
param flood_fill_scan_span_rows R12 fill_pattern
param flood_fill_scan_row_skip_parent R4 window_left
param flood_fill_scan_row_skip_parent R6 window_right
param flood_fill_scan_row_skip_parent R9 direction  # compared with flood_fill_direction
param flood_fill_scan_row_skip_parent R10 scan_x    # starts at flood_fill_left_x, the x the scan reached
param flood_fill_scan_row_skip_parent R11 y
param flood_fill_scan_row_skip_parent R12 fill_pattern
param flood_fill_skip_parent_right R3 border_color  # flood_fill_scan_row_skip_parent's, handed on
param flood_fill_skip_parent_right R4 window_left
param flood_fill_skip_parent_right R6 window_right
param flood_fill_skip_parent_right R9 direction
param flood_fill_skip_parent_right R10 scan_x   # the x the scan reached; handed back
param flood_fill_skip_parent_right R11 y
param flood_fill_skip_parent_right R12 fill_pattern
param get_point_outcode R0 point_x                  # compared signed with bitmap_window_left/right, handed back sign-extended
param get_point_outcode R1 point_y                  # compared signed with bitmap_window_top/bottom, handed back sign-extended
param get_point_outcode R10 outcode                 # out: 1 left, 2 right, 4 above, 8 below the window
param erase_start_prompt R11 text_position          # where the prompt is (erase_text_lines)
param erase_credit_count R11 text_position          # the credit line's first cell, + 2 per cell erased

# Locals
local flood_fill_span 09BDB7,09BDBC,09BDBF,09BDCC,09BE1A,09BE1F,09BE2C r0 pixels  # one pixel or a pair read while scanning, bytes swapped to test each
local flood_fill_span 09BDE2,09BE42 r0 last_pixel   # the odd pixel left after the pairs
local flood_fill_span 09BEA6 r0 tail_bytes          # bytes left after the words: 2 then 1 written
local flood_fill_span 09BE74,09BE8D,09BE9B r0 bytes_left  # the run's length, & 0xF, - 8 after two words
local flood_fill_span 09BDA2,09BDAA,09BDB5,09BDD7,09BDFE,09BE01,09BE0D,09BE18,09BE37 r1 scan_count  # pixels to the window edge, halved: the pairs the dbne counts
local flood_fill_span 09BE63,09BE6E,09BE72 r1 fill_count     # right_x - left_x (+ 1): the run's length
local flood_fill_span 09BE77,09BE89 r1 blocks_left  # 16-byte blocks of the fill
local flood_fill_span 09BDA7,09BDB3,09BE0A,09BE16 r2 pixel_count  # the scan's pixel count: bit 0 says an odd pixel is left after the pairs
local flood_fill_span entry r3 border_color         # the border colour (flood_fill's R3): no caller sets it in the C
local flood_fill_span 09BD99 r17 seed_address       # R14 on entry: the seed pixel; the ends are measured from it
local flood_fill_span 09BE06,09BE1A,09BE1F,09BE51,09BE53,09BE55 r15 right_cursor  # the right scan's cursor (seed + 1, a pair per step); right_x += right_cursor - seed_address
local flood_fill_span 09BDA2 t2 start_x             # left_x before the subtraction: compared with window_left
local flood_fill_span 09BE01 t2 right_limit         # window_right: compared with right_x
local flood_fill_span 09BE6E t2 count_after_first   # fill_count - 1 after the odd first byte: negative ends the fill
local flood_fill_span 09BE77 t4 block_count         # fill_count >> 4
local flood_fill_span 09BEA6 t2 tail_count          # bytes_left & 3
local flood_fill_scan_seed_rows 09BEC0,09BECB r9 direction  # -1 then 1 for flood_fill_scan_row
local flood_fill_scan_row 09BF11,09BF55 r0 pixel    # one pixel read: the odd first or last one
local flood_fill_scan_row 09BF32,09BF3F r0 pixels   # a pair read, bytes swapped for the second test
local flood_fill_scan_row 09BEE9,09BF09,09BF1E,09BF29,09BF2E,09BF4C,09BF72,09BF77,09BF90 r1 scan_count  # pixels from scan_x to the span's right x, then halved for the pairs
local flood_fill_scan_row 09BECE,09BF6C,09BF7E r10 scan_x  # flood_fill_left_x, then the x of each run found, then 2 past each pushed run
local flood_fill_scan_row 09BF06 r15 scan_start     # R14 when this step started: R10 += R14 - R15 gives the found pixel's x
local flood_fill_scan_row 09BF6F r15 right_address  # flood_fill_push_span's: the pushed run's right end address
local flood_fill_scan_row 09BF6F r16 right_x        # flood_fill_push_span's: the pushed run's right end x
local flood_fill_scan_row 09BF2B r2 pixel_count     # the count before halving: bit 0 = an odd pixel left
local flood_fill_scan_row entry r3 border_color     # flood_fill's R3: no caller sets it in the C
local flood_fill_scan_row 09BF1E t2 count_after_first  # scan_count - 1 after the odd first pixel: negative ends the scan
local flood_fill_scan_span_rows 09BF9E,09BFA9 r9 direction  # -1 then 1 for flood_fill_scan_row_skip_parent
local flood_fill_scan_row_skip_parent 09BFFF,09C03F,09C098,09C0D8 r0 pixel  # one pixel read: the odd first or last one
local flood_fill_scan_row_skip_parent 09C01C,09C029,09C0B5,09C0C2 r0 pixels  # a pair read, bytes swapped for the second test
local flood_fill_scan_row_skip_parent 09BFEF,09BFF7,09C00C,09C016,09C036,09C079,09C080,09C0A5,09C0AF,09C0CF,09C0F5,09C0FA,09C0FC r1 scan_count  # pixels to the part's end, halved for the pairs
local flood_fill_scan_row_skip_parent 09BFB1,09BFD4,09C056,09C069,09C074,09C07E,09C08B,09C0EF,09C101 r10 scan_x  # flood_fill_left_x, then parent right x + 2, the found pixels' x, 2 past each pushed run
local flood_fill_scan_row_skip_parent 09BFF4,09C090 r15 scan_start  # R14 when this step started
local flood_fill_scan_row_skip_parent 09C059,09C0F2 r15 right_address  # flood_fill_push_span's: the pushed run's right end address
local flood_fill_scan_row_skip_parent 09C059,09C0F2 r16 right_x  # flood_fill_push_span's: the pushed run's right end x
local flood_fill_scan_row_skip_parent 09C013,09C0AC r2 pixel_count  # the count before halving: bit 0 = an odd pixel left
local flood_fill_scan_row_skip_parent entry r3 border_color  # flood_fill's R3: no caller sets it in the C
local flood_fill_scan_row_skip_parent 09C016 t4 left_pairs  # scan_count >> 1 of the left part
local flood_fill_scan_row_skip_parent 09C00C t1 left_count_before  # scan_count before the decrement after the odd first pixel (left part)
local flood_fill_scan_row_skip_parent 09C080 t2 span_right_x  # flood_fill_right_x: nothing to scan when it is not past parent right x + 1
local flood_fill_scan_row_skip_parent 09C0FC t2 right_limit  # flood_fill_right_x - 1: compared with the pushed run's right x
local flood_fill_scan_row_skip_parent 09C0A5 t1 right_count_before  # scan_count before the decrement after the odd first pixel (right part)
local flood_fill_scan_row_skip_parent 09C0AF t4 right_pairs  # scan_count >> 1 of the right part
local fill_span_at_point entry,09C10D r0 point_x    # the point's x (get_point_outcode's, sign-extended)
local fill_span_at_point 09C19B,09C1A3,09C1BB,09C1E5,09C1F1,09C208,09C231,09C237 r0 pixel_byte  # a byte of two 4bpp pixels, then the first byte of the fill with its high nibble set
local fill_span_at_point entry,09C10D r1 point_y    # the point's y (get_point_outcode's, sign-extended)
local fill_span_at_point 09C18F,09C1B7,09C1C6,09C1D9,09C204,09C214 r1 scan_count  # pixels to the window edge (left, then right)
local fill_span_at_point 09C223,09C240,09C254,09C261,09C276,09C283 r1 fill_count  # right_x - left_x: the pixels to fill, less those written
local fill_span_at_point 09C10D,09C199,09C1A0,09C1D0,09C1D2 r10 outcode  # get_point_outcode's (0 here); the 4bpp scans then use it as the x (the code never copies point_x to R10)
local fill_span_at_point 09C138,09C14F r10 left_x   # 8bpp: point_x, flood_fill_span's left_x
local fill_span_at_point 09C129,09C167,09C16F,09C172 r1 point_pixel  # the pixel at the point (8bpp byte, 4bpp nibble): fill or border colour = nothing to do
local fill_span_at_point entry,09C24D r2 fill_color # the fill colour (in both nibbles after * 11)
local fill_span_at_point 09C25A r2 fill_word        # (fill_color & F) * 11111111: 8 pixels a word
local fill_span_at_point entry r3 border_color      # the border colour
local fill_span_at_point 09C145,09C181 r4 window_left  # bitmap_window_left
local fill_span_at_point 09C26B,09C272 r4 words_left  # fill_count >> 3 words to write
local fill_span_at_point 09C1A9,09C1F7 r4 nibble    # the pixel tested: the high or low nibble of pixel_byte
local fill_span_at_point 09C14A r6 window_right     # bitmap_window_right
local fill_span_at_point 09C13E r12 fill_pattern    # the fill colour in all 4 bytes (8bpp)
local fill_span_at_point entry,09C1E5,09C1EC,09C1F1 r15 right_cursor  # the right scan's byte cursor: never set from the point's address
local fill_span_at_point 09C226,09C23D,09C251,09C26F,09C280,09C288 r15 fill_cursor  # R14 (the left end's byte), advanced by the fill
local fill_span_at_point 09C189,09C1DE,09C1E8,09C1EE,09C21C,09C21E r16 right_x  # the run's right end x
local fill_span_at_point 09C18F t2 start_x          # R10 before the subtraction: compared with window_left
local fill_span_at_point 09C1B7 t1 left_count_before  # scan_count before the decrement (left)
local fill_span_at_point 09C1D9 t2 right_limit      # bitmap_window_right
local fill_span_at_point 09C204 t1 right_count_before  # scan_count before the decrement (right)
local fill_span_at_point 09C240 t2 count_after_first  # fill_count - 1 after the odd first pixel: negative ends
local fill_span_at_point 09C276 t2 bytes_after_words  # fill_count & 7
local fill_span_at_point 09C283 t2 bytes_after_half  # fill_count & 3
local save_bitmap_rect entry,09C2A7 r0 left         # x1, the smaller x after the swap
local save_bitmap_rect entry,09C2AF r1 top          # y1, the smaller y after the swap
local save_bitmap_rect entry,09C2A7 r2 right        # x2, the larger x after the swap; the same variable then holds right - left + 1 and the bits per row (* 4 with byte adds in 4bpp, * 8), stored first in the buffer
local save_bitmap_rect entry,09C2AF r3 bottom       # y2, the larger y after the swap; then bottom - top + 1, stored second and counted down per row
local save_bitmap_rect 09C2A7 t1 swap_x             # the old left during the swap
local save_bitmap_rect 09C2AF t1 swap_y             # the old top during the swap
local draw_saved_bitmap_rect 09C365,09C37E r4 rows_left  # the buffer's row count, counted down
local eeprom_read_word_at 09C636,09C639 r2 lines  # eeprom_select's EEPROM lines byte (io_port_d), through eeprom_read_word
local eeprom_read_word_at entry,09C632 r4 command       # the word address on entry, | 80: the read command (one variable)
local eeprom_write_word_at 09C745,09C748 r2 lines  # eeprom_select's EEPROM lines byte (io_port_d), through eeprom_write_word
local eeprom_write_word_at entry,09C741 r4 command      # the word address on entry, | 40: the write command (one variable)
local read_service_inputs 09F638 r0 inputs          # port F << 8 | port E, active low: stored in service_inputs
local read_service_inputs 09F644 r1 previous        # service_inputs before this read
local read_service_inputs 09F62E t1 port_f_bits     # io_port_f
local read_service_inputs 09F638 t1 port_e_bits     # io_port_e
local erase_attract_credits 09FC9E,09FCA6,09FCB4,09FCBC,09FCC9,09FCD1,09FCDE,09FCE6 r11 text_position  # the credit and prompt positions (movz.hw: no 31F000 base)
local erase_attract_credits 09FCAB,09FCC1,09FCD6,09FCEB r13 prompt_texts  # common_start_prompt_texts or start_prompt_texts
local erase_start_prompt 09FD75 t1 port_d_bits      # io_port_d read before clearing bits 2-3
local add_to_credit_pool entry r0 credits_to_add    # credits to add
local add_to_credit_pool 09FE88 r0 excess           # credit_pool + credits_to_add - 9: taken off when not negative
local add_to_credit_pool 09FE80,09FE93 r5 credits_added  # credits_to_add, less the excess over 9: added and handed back in R0
local add_to_credit_pool 09FE88 t1 new_total        # credit_pool + credits_to_add
local take_from_credit_pool entry r0 credits_to_take  # credits taken from credit_pool

# What the functions nothing calls hand back (result lines): no caller says it, so without these the C would drop
# the work that makes it
result get_floor_z_at_point R9 floor_z               # the found block's height (+10) << 16, plus the rise of its slope at the point
result get_floor_z_at_point R10 block                # the block holding the point, or no_floor_block
result find_floor_block_at_point R10 block           # the block holding the point, or no_floor_block
result get_floor_block_side_or_corner R0 outcode     # 1 past x max, 2 before x min, 4 before y min, 8 past y max
result get_floor_block_side_or_corner R3 side_code   # floor_block_side_codes[outcode]
result index_stage_list_pair R1 first_entry          # [R1 + stage] + section offset
result index_stage_list_pair R2 second_entry         # [R2 + stage] + section offset
result is_floor_block_off_screen R8 off_screen       # 0 when the object's floor block is among nearby_floor_blocks, FFFFFFFF when not
result convert_from_bcd R0 value                     # the 8 BCD digits handed in, as a binary number
result convert_64_to_bcd R0 bcd_low                  # the 24 BCD digits, the lowest 8
result convert_64_to_bcd R1 bcd_middle               # the middle 8
result convert_64_to_bcd R2 bcd_high                 # the highest 8
result get_previous_floor_bucket R1 floor_depth      # (projected_y + previous_floor_z) >> 16 - view_y + 10, clamped to 0-FE
result get_previous_floor_bucket R10 bucket          # FF - floor_depth: the sprite bucket queue_sprite_at_previous_floor would use
result read_pixel R0 pixel                           # the pixel at the point (8 bpp byte, 4 bpp nibble), or -1 outside bitmap_window
result build_fraction_glyph R4 numerator_top_rows    # the numerator digit's rows (fraction_digit_font), raised 3
result build_fraction_glyph R5 numerator_bottom_rows
result build_fraction_glyph R6 denominator_top_rows  # the denominator digit's rows, with the slash ORed in
result build_fraction_glyph R7 denominator_bottom_rows
result add_to_credit_pool R0 credits_added           # the credits added (0 with free play)

# ======== unused code: parameters, results and locals the open functions got (from build/naming/dead2_1..2.txt) ========
# dead2_work_1: parameters and locals of the unused code still named after their registers (helper 1)

# ---- build_fraction_glyph
param build_fraction_glyph R0 numerator             # the numerator digit (0-9): x 8 into fraction_digit_font (+ 3, raised 3 rows); then the address its rows are read from
param build_fraction_glyph R1 denominator           # the denominator digit (0-9): x 8 into fraction_digit_font
local build_fraction_glyph 09FE3A r4 numerator_top_rows      # the numerator's rows 3-6 (from fraction_digit_font + 3): handed back in R4
local build_fraction_glyph 09FE3D r5 numerator_bottom_rows   # its rows 7-A: handed back in R5
local build_fraction_glyph 09FE4A r6 denominator_top_rows    # the denominator's rows 0-3 ORed with the slash's (104BCA): handed back in R6
local build_fraction_glyph 09FE51 r7 denominator_bottom_rows # its rows 4-7 ORed with the slash's (104BCE): handed back in R7

# ---- call_run_map_screen
param call_run_map_screen R11 object                # passed on as run_map_screen's object (allocate_object_outside_loop's in step 2); a leftover

# ---- catapults
param catapult_wait_for_platform_setup R11 object   # handed to find_floor (its cursor), then allocate_object's object: the base part, then the dummy part; the incoming value is a leftover
param catapult_x_start_on_floor R11 object          # passed on as catapult_wait_for_platform_setup's object (a leftover)
param catapult_y_start_on_floor R11 object          # passed on as catapult_wait_for_platform_setup's object (a leftover)

# ---- clipped_copy_sprite
param clipped_copy_sprite_start R11 copy            # handed to find_floor (its cursor), then take_free_object's object: the copy (palette bank 8000) kept in copy_object; the incoming value is a leftover

# ---- credits
local add_to_credit_pool 09FEA5 r0 crc              # update_bookkeeping_checksum's R0 (compute_bookkeeping_crc's crc): only its high half stays in the result
local add_to_credit_pool 09FEB2 r0 result           # movz.bh of credits_added into crc's low half: the R0 handed back

# ---- BCD conversions
local convert_from_bcd 0990D7,0990E6 r3 number      # 0, x A + each nibble from the top: the binary number
local convert_from_bcd 0990ED r0 value              # number, handed back in R0 (the result's name)
local convert_64_to_bcd 0991A6 r2 bcd_high          # rvbyt of the buffer's first long: the highest 8 digits, handed back in R2
local convert_64_to_bcd 0991A9 r1 bcd_middle        # the middle 8 digits, handed back in R1
local convert_64_to_bcd 0991AC r0 bcd_low           # the lowest 8 digits, handed back in R0

# ---- digits
param draw_bcd_4 R0 value                           # the BCD number: & FFFF, 4 digits (draw_bcd_digits' value)
param draw_bcd_6 R0 value                           # the BCD number: & FFFFFF, 6 digits
param draw_bcd_8 R0 value                           # the BCD number: 8 digits
param draw_hex_6 R0 value                           # as draw_hex_4: & FFFFFF, 6 digits (draw_hex_digits' value)
param draw_hex_6 R1 attribute                       # as draw_hex_4
param draw_hex_6 R11 text_position                  # as draw_hex_4

# ---- bitmap layer
param draw_circle_polygon R4 color                  # draw_line's color: only its low byte is set (circle_color), the upper bytes are the caller's
param fill_triangle R6 color                        # pixel value: fill_triangle_sorted's color
param flood_fill_push_span R3 border_color          # flood_fill's R3: flood_fill_span stops at it
param flood_fill_scan_seed_rows R3 border_color     # flood_fill's R3: flood_fill_scan_row stops at it
param flood_fill_scan_span_rows R3 border_color     # flood_fill's R3: flood_fill_scan_row_skip_parent stops at it

# ---- EEPROM
param eeprom_read_all_and_enable_interrupts R0 data # passed on as eeprom_read_all's data (only the high half of the first word read keeps it)
param eeprom_write_word_at R0 data                  # the word to write (eeprom_write_word's data)
result eeprom_read_word_at R0 data                  # eeprom_read_word's word read at the address R4 (interrupts_on does not touch R0)

# ---- formatted text eraser (R0 cell, R10 text, R11 text_position as in erase_formatted_text_single)
param erase_formatted_text R0 cell                  # its low half set to 0 (mov.h): written over every cell
param erase_formatted_text R10 text                 # the formatted text: position word, attribute byte, runs and codes
param erase_formatted_text_position_double R0 cell
param erase_formatted_text_position_double R11 text_position  # its low half replaced by the position word
param erase_formatted_text_position_triple R0 cell
param erase_formatted_text_position_triple R11 text_position  # its low half replaced by the position word
param erase_formatted_text_attribute_double R0 cell
param erase_formatted_text_attribute_triple R0 cell
param erase_formatted_text_skip_single R0 cell
param erase_formatted_text_skip_single R11 text_position  # + s8 x 2
param erase_formatted_text_skip_double R0 cell
param erase_formatted_text_skip_double R11 text_position  # + s8 x 2
param erase_formatted_text_skip_triple R0 cell
param erase_formatted_text_skip_triple R11 text_position  # + s8 x 2

# ---- text and vram scripts
param erase_vram_script R11 dest                    # video RAM write position (command 80 sets it), as run_vram_script's

# ---- exit gate
param exit_gate_start R11 sign                      # handed to find_floor (its cursor), then spawn_sign_2's sign (the IN sign); the incoming value is a leftover

# ---- 2-bit font expansion (as expand_font: R0 color, R1 count, R10 source, R11 dest)
param expand_font_2bpp R0 color                     # colour of the pixels: & F into every nibble (color_nibbles)
param expand_font_2bpp R1 count                     # characters to expand; x 8: the rows, counted down
param expand_font_2bpp R11 dest                     # the 4-bit characters, a long per row
param expand_font_2bpp_transparent R0 color         # as expand_font_2bpp
param expand_font_2bpp_transparent R1 count         # as expand_font_2bpp
param expand_font_2bpp_transparent R4 pixels        # shifted up a nibble per pixel: what the caller leaves is shifted out by each row's 8 pixels
param expand_font_2bpp_transparent R5 opaque_mask   # the same, a 1 per non-zero pixel, then x F
param expand_font_2bpp_transparent R11 dest         # as expand_font_2bpp

# ---- falling rocks and floor signs

# ---- floor search
local find_floor_block_at_point 00C8F6 r2 block_offset  # the nearby entry: the block's offset from floor_blocks + 2 (as get_floor_z_at_point's)

# dead2_work_2: parameters and locals of the unused code (and the functions it calls) still named after a register

# ---- formatted text (print_formatted_text and its runs): R0 the cell, R10 the text, R11 the text map position ----
param print_formatted_text R10 text                        # the formatted text: position word, attribute byte, runs and codes (formatted_text_position_single's text)
param formatted_text_position_double R11 text_position     # its low half replaced by the position word, then + 2 per cell
param formatted_text_position_triple R11 text_position     # its low half replaced by the position word
param formatted_text_skip_single R0 cell                   # formatted_text_single's cell (attribute << 8 | character), handed back to it
param formatted_text_skip_single R11 text_position         # + s8 x 2
param formatted_text_skip_long_single R0 cell              # as formatted_text_skip_single
param formatted_text_skip_long_single R11 text_position    # + s16 x 2
param formatted_text_skip_double R0 cell                   # formatted_text_double's cell, handed back to it
param formatted_text_skip_double R11 text_position         # + s8 x 2
param formatted_text_skip_long_double R0 cell
param formatted_text_skip_long_double R11 text_position    # + s16 x 2
param formatted_text_skip_triple R0 cell                   # formatted_text_triple's cell, handed back to it
param formatted_text_skip_triple R11 text_position         # + s8 x 2
param formatted_text_skip_long_triple R0 cell
param formatted_text_skip_long_triple R11 text_position    # + s16 x 2

# ---- floor blocks ----
param get_floor_block_side_or_corner R10 block             # in: the block's offset (a floor_block value), + floor_blocks + 2: the block's box (the name of its 00CADF local line; get_floor_block_side calls it floor_block)
param get_floor_block_side_or_corner R11 x                 # the point tested against the box's x min (+0) and x max (+4), as get_floor_block_side's x
param get_floor_block_side_or_corner R12 y                 # the point tested against the box's y min (+8) and y max (+C)
local get_floor_block_side_or_corner 00CAD7,00CAEA,00CAF5,00CAFE,00CB09 r0 outcode  # 0, then | 2 before x min, 1 past x max, 4 before y min, 8 past y max: handed back (result R0)
local get_floor_block_side_or_corner 00CB0C r3 side_code    # floor_block_side_codes[outcode]: handed back (result R3)
local get_floor_z_at_point 00C8AB r11 slope_record floor_slope         # floor_slopes + slope * 8: flag bit 1 (along x), the slope factor at +2
local get_floor_z_at_point 00C8BC,00C8C4 r3 slope_distance # the point's x - the block's x min (bit 1 set) or y - its y min: >> 16, times the factor, is the slope's rise
local get_floor_z_at_point 00C89B,00C8D5 r9 floor_z        # the block's height (+10) << 16, plus the slope's rise: handed back (result R9)
local get_previous_floor_bucket 09A1CD,09A1DA r1 floor_depth  # the clamped depth: FE when not below FE, 0 when negative (the same floor_depth as 09A1C1, handed back in R1)
param get_previous_floor_bucket R0 top_bucket              # in: a leftover, only its upper half survives (into the bucket); FF goes into its low half
local get_previous_floor_bucket 09A1DD r0 top_bucket        # FF in the low half: bucket = top_bucket - floor_depth (mov.h: the caller's upper half kept)
local get_previous_floor_bucket 09A1E5 r10 bucket           # FF - floor_depth: handed back (result R10)
local is_floor_block_off_screen 01F06D,01F089 r8 off_screen  # 0, FFFFFFFF when the object's floor block is not in nearby_floor_blocks: handed back (result R8)

# ---- bitmap layer ----
local read_pixel 09A844,09A84C,09A84F,09A866 r0 pixel  # the byte at the point (8 bpp), its nibble by x parity (4 bpp), or -1 outside bitmap_window: handed back (result R0) (09A923, the -1, is code draw_pixel, draw_line and fill_rect share)
local read_pixel 09A832 r2 saved_x                         # 4 bpp: x kept for its parity (odd x: the high nibble), as draw_pixel's saved_x

# ---- stage lists ----
param index_stage_list_pair R3 section_offset             # added to both entries read: as load_camera_start_section's section_offset, the offset into the section lists
param index_stage_list_pair R5 stage_offset               # added (unscaled) to both list addresses: the stage's entry in each per-stage list
local index_stage_list_pair 01AABD r1 first_entry         # [first list + stage_offset] + section_offset: handed back (result R1)
local index_stage_list_pair 01AAB4 r2 second_entry        # [second list + stage_offset] + section_offset (add.h: only the low half of the address moves): handed back (result R2)

# ---- video ----
param init_nbg3_layer_cleared_pages R11 dest              # run_vram_script's destination through the four clear_tile_page_0a-0d scripts (they set it themselves), as init_nbg3_layer's dest
param run_map_screen_wait_for_wipe R5 edges_done          # update_screen_wipe's edges_done: the map screen step moves on when its low 4 bits are F (the name of its 0959EB local line)

# ---- players ----
param player_tumbling_knock_with_toucher_speed R11 effect # handed on to player_tumbling_set_launch_speed and player_tumbling_launch: spawn_tumble_effect's effect

# ---- spawners: R11 is the object allocate_object(_outside_loop) hands back; the incoming value is a leftover ----
param spawn_gauge_ring R11 ring                           # allocate_object's object (handler gauge_ring_update, ring_animation); in: a leftover kept when none is free
param spawn_debug_sprite_mover R11 object                 # allocate_object_outside_loop's object (handler debug_sprite_mover_update); in: a leftover kept when none is free
param spawn_falling_rocks R11 object                      # allocate_object_outside_loop's object, one per falling_rocks record (handler falling_rock_bounce): the last one at the end
param spawn_message_box_2 R5 screen_x                     # place_message_box's screen_x (as spawn_message_box)
param spawn_message_box_2 R6 screen_y                     # place_message_box's screen_y
param spawn_message_box_2 R7 kind                         # place_message_box's kind: index into message_box_kinds
param spawn_message_box_2 R8 frames                       # place_message_box's frames (speed_x_fraction)
param spawn_message_box_2 R11 box                         # allocate_object's object, set up by place_message_box (the name of its 0953CB local line)
param spawn_screen_sign R5 screen_x                       # place_screen_sign's screen_x
param spawn_screen_sign R6 screen_y                       # place_screen_sign's screen_y
param spawn_screen_sign R7 kind                           # place_screen_sign's kind: index into screen_sign_kinds
param spawn_screen_sign R11 sign                          # allocate_object_outside_loop's object, set up by place_screen_sign (the name of its 084D9E local line)
param spawn_screen_sign_2 R5 screen_x                     # as spawn_screen_sign
param spawn_screen_sign_2 R6 screen_y
param spawn_screen_sign_2 R7 kind
param spawn_screen_sign_2 R11 sign                        # allocate_object's object, set up by place_screen_sign
param spawn_stage_template R11 object                     # allocate_object's object filled from the template, find_floor's cursor in between, then its pool 2 shadow (allocate_object_2): the shadow at the end
param spawn_stage_template_0 R11 object                   # as spawn_stage_template (template 0 read field by field)

# ---- object handlers ----
param wall_editor_rebuild_parts R11 part                  # build_wall_parts' part: each new pool 2 part, the last one at the end

# ---- stage 05 event messages ----
param stage_05_event_show_message_11 R11 box              # handed to stage_05_event_show_message as spawn_message_box's box (its value is not used)
param stage_05_event_show_message_12 R11 box              # the same

# ---- trampolines (a lone br set_camera_velocity: no C of their own, every register passed on) ----

local flood_fill 09BCCB r23 queue_write flood_fill_record   # R23 (object_cursor in C): where the next span record goes
local flood_fill 09BCD0,09BD44 r24 queue_read flood_fill_record  # R24 (last_object in C): the next record to take; back to the start past 20CCC4
local flood_fill_push_span entry r23 queue_write flood_fill_record  # R23: where the record goes (moved on 1A, back to 20B500 past 20CCC4)
# ======== parameters and results handed on from call to call (param lines, second round: build/naming/params2_1..3.txt) ========
param project_object R0 screen_y  # out only: update_screen_position's R0, the new screen_y ((projected_y + z) >> 16 - view_y + sprite_offset_y), handed back
param project_object R1 depth  # out only: update_screen_position's R1: sprite_offset_y in the low half, projected_y's high half above it; callers hand it to queue_sprite / queue_sprite_at_base (their R1 depth, whose low half they replace with screen_y) or keep_player_on_screen
param find_floor R0 x  # the object's x tested against each nearby block's x min/max (with R1 y); out: the found block's floor type index (byte 12), or the slope factor on a slope; the caller's R0 when no block is near
param queue_sound R1 write_index  # out only: sound_queue_write + 1 & 1F, stored back (the next free slot of sound_queue)
param queue_sound R2 write_index_address  # out only: &sound_queue_write (20ED81), read with [R2+] and written back with [-R2]
param check_walls R1 hitbox_offset  # low half: each wall's hitbox_offset_x, then hitbox_offset_y, added to its x_int / y_int for the box start; the last tested wall's is handed back
param check_platforms R1 middle  # compute_bridge_sag's R1 handed back (low half): plank_count >> 1
param check_platforms R2 plank  # compute_bridge_sag's R2 handed back: the last load entry's plank number
param check_platforms R3 span  # compute_bridge_sag's R3 handed back: planks left in the last span (0)
param check_platforms R4 sag  # compute_bridge_sag's R4 handed back (low half): the last sag written + sag_step
param check_platforms R5 sag_step  # compute_bridge_sag's R5 handed back (low half)
param check_platforms R9 passes_left  # compute_bridge_sag's R9 handed back: the sort's pass counter (0)
param update_scroll R2 position  # scroll_position, compared with scroll_end (>= runs stage 3's end); out: what the scroll routines leave (set_camera_velocity's scroll_y), camera_velocity_y in stage 3's end
param update_scroll R4 speed_value  # set_camera_velocity's R4 handed back: the speed's low 16 bits (the velocity factor); update_scroll's own in stage 3's end
param update_scroll R5 next_scroll_position  # passed through the scroll routines (advance_scroll's / set_camera_velocity's R5)
param update_scroll R8 second_player_object  # modes 7/8: player_2_object's object, handed back; otherwise the caller's
param update_scroll R9 third_player_object  # modes 7/8: player_3_object's object, handed back; otherwise the caller's
param update_scroll R11 dest  # run_vram_script's video RAM write address for stage 3's end scripts; otherwise the scroll routines' R11 (update_nbg3_scroll's quadrant_cursor), spawn_stage_03_end_pillar's object at the end step
param player_state_hurt R1 depth  # player chain R1 (see player_animate): handed in/out of project_object, update_screen_position, keep_player_on_screen, queue_sprite_at_base, animate_with_flags, find_floor, play_sound and passed on to the next state
param player_state_hurt R11 cursor  # player chain R11 (see player_animate): find_floor's / check_player_contacts' / push_player_out_of_wall's R11, passed on to the next state
param player_state_tripped R11 cursor  # player chain R11: only handed to check_player_contacts (its R11)
param player_state_stuck R11 cursor  # player chain R11: find_floor's cursor; player_stuck_begin's object slot (kept when nothing is allocated)
param player_state_stage_b_sequence R1 depth  # player chain R1: update_screen_position's R1 handed to queue_sprite; passed on to player_move
param player_state_stage_b_sequence R11 cursor  # player chain R11: only passed on to player_move
param player_state_over_edge R0 jump_offset  # what player_dispatch_state leaves in R0: state x 3, the jump table offset; only its upper bytes survive the substate load (switch on substate)
param player_state_over_edge R11 cursor  # player chain R11: get_ledge_side's / find_floor's / check_player_contacts' R11; the near-edge hang animation table for a while
param player_state_entering_stage R1 depth  # player chain R1: play_sound's R1; passed on to player_move
param player_state_entering_stage R11 cursor  # player chain R11: only passed on to player_move
param player_state_carried_off R1 depth  # player chain R1: find_floor's / animate's / project_object's R1; animation_frame when it hands over to player_state_hurt
param player_state_carried_off R11 cursor  # player chain R11: find_floor's cursor; the animation step (animation + 2 + frame x 4) when it hands over to player_state_hurt
param player_state_stunned R1 depth  # player chain R1: update_screen_position's R1 into queue_sprite_at_base; animate's / find_floor's; passed on to the next state
param player_state_stunned R11 cursor  # player chain R11: check_player_contacts' / find_floor's R11, passed on to the next state
param player_state_knocked_off R1 depth  # player chain R1: play_sound's / find_floor's R1, passed on to the next state
param player_state_knocked_off R11 cursor  # player chain R11: find_floor's cursor, passed on to the next state
param player_state_floor_8_sequence R11 cursor  # player chain R11: find_floor's / player_move_with_scroll's R11; spawn_player_effect's and spawn_struggle_marker's object slot, then spawn_sign_2's sign
param compute_camera_ground_speed R3 octant  # angle_of_vector's R3 for the map direction (camera_velocity >> 8), handed back; the caller's when the velocity did not change
param set_camera_velocity R2 scroll_y  # out only: update_background_scroll's R2 (update_nbg3_scroll's scroll_y or block_pages; scroll_position past a fast-scroll start); update_camera_with_shake's (view_y) once the scroll ended
param set_camera_velocity R4 speed_value  # out: the speed's low 16 bits (camera_velocity_x/_y = section_direction_x/_y x it), through update_background_scroll (select_nbg3_pages' byte mask on stages with NBG3 pages)
param set_camera_velocity R5 next_scroll_position  # advance_scroll's R5 (move_camera_along_path's result) passed through update_background_scroll; select_nbg3_pages' R5 comes back on stages with NBG3 pages
param set_camera_velocity R10 range  # update_background_scroll's R10: its cursor in stage_fast_scroll_ranges; the caller's once the scroll ended
param set_camera_velocity R11 quadrant_cursor  # update_background_scroll's R11 (update_nbg3_scroll's / select_nbg3_pages' quadrant_cursor); the caller's otherwise
param compute_camera_slope_speed R3 octant  # angle_of_vector's R3 for camera_scroll_direction (velocity >> 5), handed back; the caller's when nothing changed
param drop_rings R3 shadow  # each new ring's child (28: its shadow), which gets the ring's flip bits too and is handed on to the next round; before that only handed to scatter_rings_random as its random_value (next_random's result when released_rings_random, else the caller's leftover)
param drop_rings R11 allocated  # scatter_rings_random's R11 (the last block it allocated), handed in and back ring after ring
param centipede_move R11 block  # only handed to spawn_centipede_sand_puff (spawn_object_with_pieces' R11 block, kept when nothing is spawned); centipede_chase passes move_object_projected's R11
param attract_ranking R4 rows  # step 0: start_ranking_display's R4 handed back; the caller's on the other steps
param attract_ranking R10 source  # step 0: start_ranking_display's R10 handed back; the caller's on the other steps
param attract_demo_1 R1 pressed  # run_frame's R1 (wait_for_frame / read_controls: the bits just pressed), only passed on to run_game_mode
param attract_demo_1 R2 released  # run_frame's R2 (read_controls' released bits), only passed on to run_game_mode
param attract_demo_1 R3 input_cursor  # run_frame's R3 (read_controls' input_cursor, or credits_needed), only passed on to run_game_mode
param attract_demo_1 R4 scale  # run_frame's R4 (the last frame's draw_queued_strings scale), in and out of run_game_mode
param attract_demo_1 R5 cursor  # run_frame's R5 (draw_queued_strings' string cursor), only passed on to run_game_mode
param attract_demo_1 R8 count  # run_frame's R8 (read_controls' count, 0), in and out of run_game_mode
param attract_demo_1 R9 height  # run_frame's R9 (draw_queued_strings' height), in and out of run_game_mode
param attract_demo_1 R10 trackball_port  # run_frame's R10 (read_controls' trackball_port), in and out of run_game_mode
param attract_demo_1 R11 trackball_cursor  # run_frame's R11 (read_controls' trackball_cursor, or draw_attract_credits' text position), only passed on to run_game_mode
param spawn_sign R1 depth  # place_sign's R1 (init_sign_from_kind's: project_object's R1) handed back when a sign is made
param polar_to_xy R1 cos_part  # out only: cosine(angle) x length >> 6 (dy is its negation)
param wait_for_frame R1 pressed  # read_controls' R1 handed back: inputs_pressed (the bits just pressed)
param metal_wall_update R3 octant  # metal_wall_wait passes face_nearest_player's R3 (octant); only handed to scatter_rings_random (random_value, replaced) and delete_if_off_screen
param start_stage_view R2 direction_y  # init_stage_view's R2 handed back: the stage's first section_direction_y; the caller's for stage 11
param start_stage_view R3 direction_x  # init_stage_view's R3 handed back: the first section_direction_x
param start_stage_view R4 count  # init_stage_view's R4 handed back: set_stage_layer_mixing's count (0)
param set_facing R0 facing  # returned: facing_angle + 20 >> 6 & F (the 16-way facing) in the low half, the caller's high half above it
param released_ring_move R11 cursor  # handed in/out of move_object_projected (find_floor's cursor), then to spawn_ring_glint (its sparkle slot)
param player_animate R1 depth  # the player chain's R1: after player_move, keep_player_on_screen's R1 (sprite_offset_y in the low half, what update_screen_position leaves and queue_sprite takes as depth); player_update hands player_hud_update's R1 (digit_attribute); passed on to the states
param player_animate R11 cursor  # the player chain's R11: after player_move, find_floor's cursor (or check_player_floor_edge's); player_update hands check_scene_states' R11; passed on to the states
param move_camera_to_start_section R2 y  # update_camera_view's R2: view_y (camera_y_pixels - view_offset_y)
param vram_script_set_address R0 script_code  # run_vram_script's command byte; only its upper bytes survive (the cell address word replaces the low half)
param vram_script_set_address R11 dest  # out only: 300000 + the script's halfword x 2, the next video RAM write address
param update_stage_00_exit_scene R4 speed_value  # only handed in/out of update_background_scroll (its R4, set_camera_velocity's speed_value)
param stage_05_event_step_3_frame R2 part_size  # the caller's R2 (stage_05_event_show_message's), handed to spawn_walls (its R2) and through check_hits / check_platforms; not handed back
param stage_05_event_step_3_frame R4 sag  # check_hits' then check_platforms' R4 (compute_bridge_sag's sag, low half), handed back
param scroll_speeding_up R2 other_list  # advance_scroll_unless_ended's R2 (advance_scroll's other_list), or set_camera_velocity's on the frame the ramp ends
param scroll_speeding_up R4 speed_value  # advance_scroll_unless_ended's / set_camera_velocity's R4 (the speed's low 16 bits)
param scroll_speeding_up R5 next_scroll_position  # advance_scroll_unless_ended's / set_camera_velocity's R5
param scroll_speeding_up R10 range  # advance_scroll_unless_ended's / set_camera_velocity's R10 (update_background_scroll's range)
param scroll_speeding_up R11 quadrant_cursor  # advance_scroll_unless_ended's / set_camera_velocity's R11 (update_nbg3_scroll's quadrant_cursor)
param run_map_screen R4 bottom_speed  # step 0: start_window_wipe's R4 (window_wipe_bottom_speed); the caller's on the other steps
param player_steer R0 wanted_speed  # follow_player_path's target speed (path_speed << 16), through apply_speed_scale; the trackball's speed^2 x 1.5 without a path; the caller's leftover only goes into follow_player_path
param load_tile_set R1 count  # run_vram_script's R1 handed back (the last command's count: 0)
param event_0c_start R2 block_offset  # spawn_door_rollers' R2 (find_floor's block_offset), handed back
param event_0c_start R3 slope_height  # spawn_door_rollers' R3 (find_floor's slope_height), handed back
param compute_bridge_sag R3 span  # out: the planks between two loads, counted down by the fill (0); unchanged when only the end loads are on the bridge
param compute_bridge_sag R11 sag_curve  # out only: bridge_sag_curve; past the cleared sag table (clear_dest) when only the end loads are on the bridge
param animate_palette_stage_0d_0e R1 copy_offset  # cycle_palette's R1 (queue_vblank_copy's copy_offset, or the frame offset x 20 while waiting)
param animate_palette_stage_0d_0e R2 copy_entry  # cycle_palette's R2 (queue_vblank_copy's copy_entry), handed back
param update_nbg3_scroll R4 speed_value  # update_background_scroll's R4 (set_camera_velocity's speed_value) handed back; select_nbg3_pages' R4 (its byte mask, 0) on stages 0, 3, 5, 6, 7, A
param update_nbg3_scroll R5 next_scroll_position  # update_background_scroll's R5 handed back; select_nbg3_pages' R5 (its last shift) on stages 0, 3, 5, 6, 7, A
param tornado_throw_whirlwinds R2 free_count  # only handed to spawn_whirlwind (spawn_object_with_pieces' R2: its high half survives the free-stack load)
param test_inputs R8 count  # run_test_mode's R8 (wait_for_frame's count), only handed to move_input_test_cursors (build_sprite_list's R8 there)
param test_inputs R9 blocks_per_row  # fill_rect's R9 / move_input_test_cursors' R9 handed back
param test_coin_assignments R4 color  # run_test_mode's R4 (the test screens' fill colour) handed back; draw_coin_credit_table's R4 (0) after the manual page is drawn, eeprom_save_and_lock's command when it saves on exit
param start_climb_at_spawn R0 climbed_height  # only its high half survives: the low half becomes the wall's climb height (floor_types record + E)
param spawn_whirlwind R2 free_count  # spawn_object_with_pieces' R2: only its high half survives the free_object_stack load
param side_of_box R1 hitbox_offset_y  # out (low half): the other's hitbox_offset_y, used for the y box start
param run_sega_logo R4 color  # step 0: clear_bitmap_layer's R4 (the fill colour, 0) handed back; the caller's otherwise
param run_sega_logo R8 row_pairs_left  # step 0: clear_bitmap_layer's R8 (fill_rect_unclipped's row_pairs_left) handed back
param run_sega_logo R9 blocks_per_row  # step 0: clear_bitmap_layer's R9 handed back
param run_sega_logo R10 lower_row_cursor  # step 0: init_sprites' R10, then clear_bitmap_layer's R10 handed back; the caller's (run_title_screen's text) otherwise
param run_sega_logo R11 logo_block  # allocate_object_2's block every frame: the logo sprite (screen_sprite_block, freed again); step 0 clear_sprite_buckets' R11 before it; the incoming value is a leftover kept when no block is free
param push_crate_release_all_rings R11 allocated  # push_crate_release_rings' R11 (drop_rings' allocated), handed in and back
param player_animate_jumping R11 sign  # only handed to spawn_sign_2 (stage 5 before the cage bar is hit): allocate_object's slot
param join_character_chooser_update R0 text_x  # only its high half survives: the low half becomes screen_x (the text x)
param init_system R4 polynomial  # init_game_settings' R4 (load_ranking's: compute_crc16's CRC polynomial), handed back; unused
param init_system R5 words_left  # check_moving_seat_dip's R5 (eeprom_save_and_lock's words_left, or load_text_font's R5), handed back
param flame_machine_arm_take_disc R2 free_count  # spawn_object_with_pieces' R2: only its high half survives the free_object_stack load
param flame_machine_arm_take_disc R11 block  # spawn_object_with_pieces' R11: the last block it allocated
param event_0c_first_quake R2 write_index_address  # play_sound's R2 at 3C (queue_sound's &sound_queue_write); the caller's otherwise
param check_scene_states R11 object  # spawn_player_ride's object (event 1C, nothing riding yet); otherwise the caller's R11 (player_hud_update's text_position) handed back
param build_ending_scene R2 write_index_address  # step 0: queue_sound's R2 (&sound_queue_write); unchanged in the other steps
param build_ending_scene R4 color  # step 0: clear_bitmap_layer's R4 (set_stage_layer_mixing's 0, then the replicated fill colour); unchanged in the other steps
param build_ending_scene R8 row_pairs_left  # step 0: clear_bitmap_layer's R8 (fill_rect_unclipped's row_pairs_left); unchanged in the other steps
param animate_palette_stage_0c R1 copy_offset  # cycle_palette's R1 (queue_vblank_copy's copy_offset, or the frame offset x 20 while waiting)
param animate_palette_stage_0c R2 copy_entry  # cycle_palette's R2 (queue_vblank_copy's copy_entry), handed back
param animate_palette_stage_04 R1 copy_offset  # cycle_palette's R1 (queue_vblank_copy's copy_offset, or the frame offset x 20 while waiting)
param animate_palette_stage_04 R2 copy_entry  # cycle_palette's R2 (queue_vblank_copy's copy_entry), handed back

param play_sound R1 write_index  # out: queue_sound's R1 (sound_queue_write after the + 1, & 1F); in attract with the id masked off, the id's bit number (& 1F); unchanged when advertise sound is off (queue_sound R1's name)
param play_sound R2 write_index_address  # out: queue_sound's R2 (sound_queue_write's address); in attract with the id masked off, the id's word index (>> 5) into attract_sound_mask; unchanged when advertise sound is off
param add_to_touchable_list R0 old_head  # out: the list head before the push (stored as the object's hit_list_next); in: only its high half is used (the work RAM base for LINKED)
param unproject_object R0 screen_y  # out only: update_screen_position_no_offset's R0
param unproject_object R2 block_offset  # find_floor's R2 (the floor block's offset in nearby_floor_blocks); scaled x (projected_x << 8) is what goes in
param unproject_object R11 cursor  # find_floor's cursor (nearby_floor_blocks, then the slope record), handed in and back
param move_object_projected R2 block_offset  # unproject_object's (find_floor's R2), out only
param move_object_projected R11 cursor  # unproject_object's / find_floor's cursor, handed in and back
param keep_player_on_screen R1 depth  # low half = sprite_offset_y (sprite_offset_x before it): the caller's project_object R1; high half kept; the carry path leaves the y step (cos x slope speed >> 8); push_player_from_screen_edge's R1 on its exits
param advance_scroll R4 speed_value  # out only: set_camera_velocity's R4 (the u16 speed it multiplied by; select_nbg3_pages' byte_mask on NBG3 parallax stages)
param advance_scroll R10 range  # out: set_camera_velocity's R10, update_background_scroll's pointer into stage_fast_scroll_ranges; the incoming value (update_scroll's end_script) is only handed on
param advance_scroll R11 quadrant_cursor  # set_camera_velocity's R11: update_nbg3_scroll's / select_nbg3_pages' pointer into the 1BCBC table on NBG3 parallax stages, else unchanged
param update_stage_tile_layers R2 copy_entry  # handed to animate_stage_palette (cycle_palette's queue_vblank_copy entry comes back); 0 (rows_left) after the block map read, or reload_changed_tile_set's R2
param update_stage_tile_layers R4 palette_count  # handed to animate_stage_palette (cycle_palette's count); 20 (set_shift) after the block map read, or reload_changed_tile_set's R4
param update_stage_tile_layers R10 block_cursor  # into stage_block_maps (tile_block_row + tile_block_column), past the 2 x 2 blocks read; animate_stage_palette's source goes in; reload_changed_tile_set's script when it reloads
param square_root R1 steps_left  # the 16 rounds, counted down: 0 when it returns
param face_movement_direction R0 facing  # set_facing's result: the 16-way facing (heading + 20 >> 6 & F); only the incoming high half survives
param check_zones R1 box_edge  # low half: the zone's hitbox offsets, then its bottom (z - hitbox_height) and the hitter's, last compared; the caller's value when zone_list is empty
param spawn_object_groups_2 R1 list_offset  # stage_record_spawns[stage] the first time (group_spawn_cursor_2 = 093xxx + it); a member's alternate animation (record +10) when spawning; otherwise the caller's value back (spawn_objects_by_handler R1's name)
param check_player_contacts R1 health_limit  # ring kinds (3, 2A): low byte = rings per health point (4, 8 at setting 3, halved with 2+ players in play), then the max health it caps at (max_health_by_setting); the solid/hurt paths leave side_of_box's / aim_at_screen_centre's R1; else the caller's R1 (the player states' offset_y) back
param check_player_contacts R11 cursor  # the player states' R11 (find_floor's cursor) handed back; kinds 6/7 leave the touched launcher, kind 1A the climbed object (parent of the touched one); no caller reads it
param add_to_zone_list R0 old_head  # out: the list head before the push (stored as the object's hit_list_next); in: only its high half is used
param player_state_teetering R11 cursor  # player_move's R11 (find_floor's cursor), handed to check_player_contacts; substate 0 puts teetering_animations in it
param player_state_stage_exit R1 depth  # player_animate's R1 (keep_player_on_screen's sprite y offset), handed to player_move; update_screen_position's R1 later
param player_state_stage_exit R11 cursor  # player_animate's R11 (find_floor's cursor), handed to player_move
param player_state_scene_thrown R1 depth  # player_animate's R1, handed to player_state_hurt / find_floor; the hurt animation offset is loaded into it
param player_state_scene_thrown R11 cursor  # player_animate's R11 (find_floor's cursor), handed to find_floor and player_state_hurt
param player_state_game_over R0 jump_offset  # the dispatcher's state x 3 (branch table offset); the player number on the first frame; handed to enter_ranking_initials, which keeps only its high half
param player_state_climbing_object R11 cursor  # player_animate's R11, handed to player_object_climb_push (spawn_climb_hand's hand comes back there)
param player_state_caught R11 cursor  # player_animate's R11 (find_floor's cursor), handed to find_floor; spawn_player_effect's effect lands in it
param player_state_trapped R1 depth  # player_animate's R1 (keep_player_on_screen's sprite y offset), handed to check_player_contacts / player_move
param player_state_trapped R11 cursor  # player_animate's R11 (find_floor's cursor), handed to check_player_contacts / player_move
param player_state_pulled R11 cursor  # player_animate's R11 (find_floor's cursor), handed to find_floor and player_move_with_scroll
param player_state_in_zone R1 depth  # player_animate's R1 (keep_player_on_screen's sprite y offset), handed to check_player_contacts / player_move
param player_state_in_zone R11 cursor  # player_animate's R11 (find_floor's cursor), handed to spawn_sign_2 / check_player_contacts / player_move
param player_state_climbing R1 depth  # player_animate's R1 (keep_player_on_screen's sprite y offset), handed to check_player_contacts / player_move
param player_state_climbing R11 cursor  # player_animate's R11 (find_floor's cursor), handed to check_player_contacts / player_move
param update_background_scroll R1 fast_scroll_start  # out only: the stage_fast_scroll_ranges start word read last (the range past section_start, or the FFFF end)
param update_background_scroll R2 scroll_y  # update_nbg3_scroll's R2 (its NBG3 y, or select_nbg3_pages' block_pages); scroll_position when the stage has a fast scroll range past the section start
param update_background_scroll R4 speed_value  # set_camera_velocity's u16 speed goes in; update_nbg3_scroll's R4 comes back (select_nbg3_pages' byte_mask on NBG3 parallax stages)
param update_background_scroll R5 next_scroll_position  # advance_scroll's (via set_camera_velocity) goes in; update_nbg3_scroll's R5 comes back (select_nbg3_pages' shift on NBG3 parallax stages)
param update_background_scroll R11 quadrant_cursor  # update_nbg3_scroll's R11, handed in and back
param push_crate_move R11 cursor  # handed to move_object_projected (find_floor's cursor comes back), then to push_crate_release_rings
param push_crate_move R16 random_value  # handed to push_crate_release_rings (drop_rings' random_value)
param push_crate_move R17 ring  # handed to push_crate_release_rings (drop_rings' ring)
param attract_title R4 color  # run_title_screen's R4 (clear_bitmap_layer's color on run_sega_logo's first step); otherwise the caller's value back
param attract_title R8 row_pairs_left  # run_title_screen's R8 (clear_bitmap_layer's row_pairs_left on run_sega_logo's first step); otherwise the caller's value back
param attract_title R9 blocks_per_row  # run_title_screen's R9 (clear_bitmap_layer's blocks_per_row on run_sega_logo's first step); otherwise the caller's value back
param attract_title R10 text  # out only: print_text's text (past the record at 000644), handed to run_title_screen; its R10 comes back (the background vram script when it builds the scene)
param update_screen_wipe R1 clip_right  # out only: the right edge written to video_bitmap_clip_right
param update_screen_wipe R2 clip_top  # out only: the top edge written to video_bitmap_clip_top
param update_screen_wipe R3 clip_bottom  # out only: the bottom edge written to video_bitmap_clip_bottom
param spawn_centipede_sand_puff R2 free_count  # spawn_object_with_pieces' R2
param spawn_centipede_sand_puff R11 block  # spawn_object_with_pieces' R11 (the last block it allocated)
param draw_credit_count R1 credits  # draw_credit_count_now's R1, handed back when it redraws
param draw_credit_count R10 text  # draw_credit_count_now's text (past it) when it redraws; else the coin block compared (p1_coins or p2_coins)
param spawn_debris R0 spawned  # spawn_objects' R0: 1 when made, 0 when the pool is short (then nothing happens); the caller's value only when count <= 0
param spawn_debris R11 block  # spawn_objects' R11: each block it allocates
param draw_line R8 unclipped_y  # clip_line_to_window's R8, handed back
param clear_bitmap_layer R5 inner_count  # fill_rect_unclipped's R5, handed back
param clear_bitmap_layer R8 row_pairs_left  # fill_rect_unclipped's R8, handed back
param clear_bitmap_layer R10 lower_row_cursor  # fill_rect_unclipped's R10, handed back
param spawn_eggman_booth R1 piece_count  # spawn_object_with_pieces' R1 (spawn_count - 1)
param spawn_eggman_booth R2 free_count  # spawn_object_with_pieces' R2, handed back
param spawn_eggman_booth R11 block  # spawn_object_with_pieces' R11 (the last block it allocated), handed back
param player_move_with_scroll R1 depth  # out only: queue_sprite's depth key (or queue_sprite_at_depth's screen_y - 20 in sequence step A of state 25); keep_player_on_screen's R1 on the exits before drawing
param player_move_with_scroll R11 cursor  # push_player_out_of_wall's and find_floor's R11 (find_floor's cursor), handed in and back
param place_sign R1 depth  # out only: init_sign_from_kind's R1 (project_object's R1 for the sign)
param int_default R0 source  # run_vblank_copies' R0 (via int_vector_02)
param int_default R2 dest  # run_vblank_copies' R2 (via int_vector_02)
param int_default R3 copies_left  # run_vblank_copies' R3: 0 at the end
param int_default R4 copy_entry  # run_vblank_copies' R4: past the last vblank_copies entry
param update_sinking_floor R1 sink_offset  # sink_depth << 8 added to floor_z for z (floor type A with a floor block); the caller's value otherwise
param select_nbg3_pages R4 byte_mask  # out only: the byte mask of the page packing loop, shifted out to 0 when it returns
param select_nbg3_pages R5 shift  # out only: the packing loop's shift, - 8 per byte: -20 when it returns
param scroll_slowing_down R2 other_list  # scroll_at_scroll_speed's R2 (advance_scroll's other_list), handed back
param scroll_slowing_down R4 speed_value  # scroll_at_scroll_speed's R4 (advance_scroll's speed_value), handed back
param scroll_slowing_down R5 next_scroll_position  # scroll_at_scroll_speed's R5 (advance_scroll's), handed back
param scroll_slowing_down R10 range  # update_scroll's end_script, handed on; advance_scroll's range pointer comes back
param scroll_slowing_down R11 quadrant_cursor  # scroll_at_scroll_speed's R11 (advance_scroll's quadrant_cursor), handed back
param push_crate_release_rings R3 seed  # handed to drop_rings (scatter_rings_random's random_value when released_rings_random is 0)
param push_crate_release_rings R11 allocated  # drop_rings' R11 (scatter_rings_random's last allocated block), handed in and back
param player_animate_skidding R1 depth  # player_animate's R1, handed back to player_animate (state 4); play_sound's / animate_with_flags' R1 land in it
param player_animate_skidding R11 cursor  # player_animate's R11 (find_floor's cursor), handed to player_animate / player_animate_skidding_slow_down
param fill_rect_unclipped R10 lower_row_cursor  # out only: 8 bpp, the second row's write cursor of the 16-byte block fill; 4 bpp, the halfwords left in a row (0), or 100 (the row step) for a single column
param compute_scroll_drift R9 drift_byte  # stored in unused_drift_byte (callers pass 0)
param clip_rect_to_window R5 window_min  # out only (low half): bitmap_window_left, then bitmap_window_top: the last window edge compared
param aim_at_screen_centre R1 cos_part  # out only: polar_to_xy's R1 (cosine x 200 >> 6, the negated dy)
param update_stage_05_event R8 count  # update_player_summary's / check_stage_clear's count in steps 0-1; the message frames (B4) in step 3, the rocks left in the geyser step
param test_memory R9 blocks_per_row  # clear_bitmap_layer's when it redraws the screen (run_test_mode's R9); memory_test_result_texts while the results are printed
param test_game_assignments R4 color  # draw_difficulty_setting / draw_vitality_setting color (fill_rect's), eeprom_save_and_lock's command when it saves; handed back
param start_ring_tally R2 stage_rings_3  # out only (low half): player 3's stage rings | 100 when it gets the bonus, >> 8 when its panel is made
param start_ring_tally R3 player_3_out_flags  # out only: player 3's object flags & 10000400 (0: a panel was made for it)
param start_ring_tally R4 ring_total  # out only: stage_ring_counts[stage] (the percentage's divisor); player 1's bonus flag (its rings word >> 8) when its panel is made
param start_ring_tally R8 p2_object  # out only: player 2's object (player_2_object)
param start_ring_tally R9 p3_object  # out only: player 3's object (player_3_object)
param spawn_mine_splash R2 free_count  # spawn_object_with_pieces' R2
param spawn_mine_splash R11 block  # spawn_object_with_pieces' R11 (the last block it allocated)
param run_title_screen R4 color  # run_sega_logo's R4 (clear_bitmap_layer's color on its first step); otherwise the caller's value back
param run_title_screen R8 row_pairs_left  # run_sega_logo's R8 (clear_bitmap_layer's on its first step); otherwise the caller's value back
param run_title_screen R9 blocks_per_row  # run_sega_logo's R9 (clear_bitmap_layer's on its first step); otherwise the caller's value back
param run_title_screen R10 text  # attract_title's print_text text, handed to run_sega_logo; background_script (the background vram script) when the scene is built
param run_title_screen R11 text_position  # attract_title's print_text text_position, only handed to run_sega_logo (allocate_object_2 replaces it there)
param run_frame R1 pressed  # wait_for_frame's R1 (read_controls' pressed bits), handed to the system mode handlers and draw_attract_credits
param run_frame R2 released  # wait_for_frame's R2, handed on
param run_frame R3 input_cursor  # wait_for_frame's R3; its high half goes with credits_per_start (credits_needed)
param run_frame R8 count  # wait_for_frame's R8 (0), handed to the mode handlers; build_sprite_list's scale_x replaces it for draw_queued_strings
param run_frame R10 trackball_port  # wait_for_frame's R10, handed to the mode handlers; build_sprite_list's sprite_flags replaces it for draw_queued_strings
param run_frame R11 trackball_cursor  # wait_for_frame's R11, handed to the mode handlers
param run_frame R4 scale  # draw_queued_strings' R4 (the last string's scale) when it returns; boot hands it back next frame
param run_frame R5 cursor  # draw_queued_strings' R5 (past the last string's characters) when it returns; the incoming low half goes to the mode handlers
param run_frame R9 height  # draw_queued_strings' R9 when it returns
param move_input_test_cursors R8 count  # test_inputs' R8 (wait_for_frame's count); build_sprite_list's scale_x replaces it for draw_queued_strings
param move_input_test_cursors R10 text  # test_inputs' coin_text; input_test_trackball_*_good_text for print_text_list; build_sprite_list's sprite_flags for draw_queued_strings
param move_input_test_cursors R4 cursors_left  # out only: the three trackball cursors, counted down: 0
param move_input_test_cursors R9 height  # draw_queued_strings' R9 when it returns
param init_stage_view R2 direction_y  # out only: the first section's y direction (section_direction_y)
param init_stage_view R3 direction_x  # out only: the first section's x direction (section_direction_x)
param init_stage_view R4 count  # out only: set_stage_layer_mixing's count (0)
param event_0f_show_messages R5 screen_x  # spawn_message_box's R5: each record's screen x (16-bit load: only the incoming high half survives)
param enter_ranking_initials R0 jump_offset  # player_state_game_over's R0 (the dispatcher's state x 3): only its high half survives into the blink timer
param check_player_step R11 cursor  # player_move's R11 (find_floor's cursor) handed back; the player's x (get_floor_block_side's) on the bounce path
param animate_stage_palette R1 copy_offset  # out only: the stage function's R1 (cycle_palette's: queue_vblank_copy's copy_offset, or the frame offset while it waits)
param animate_stage_palette R2 copy_entry  # the stage function's R2 (cycle_palette's: queue_vblank_copy's copy_entry when it copied), handed in and back
param animate_palette_stage_0f R1 copy_offset  # out only: the second cycle_palette's R1
param animate_palette_stage_0f R2 copy_entry  # cycle_palette's R2 (queue_vblank_copy's copy_entry when it copied), handed in and back
param animate_palette_stage_0a R1 copy_offset  # out only: the second cycle_palette's R1
param animate_palette_stage_0a R2 copy_entry  # cycle_palette's R2 (queue_vblank_copy's copy_entry when it copied), handed in and back
param stage_05_event_show_message_12 R2 part_size  # as stage_05_event_show_message_11: play_sound's R2, handed on to stage_05_event_show_message (spawn_walls' R2)

# params2_3: parameters and results of the functions in params2_work_3.txt (helper 3)

param update_screen_position R0 screen_y     # out: (projected_y + z) >> 16 - view_y + sprite_offset_y, also stored in screen_y (callers mostly ignore it)
param update_screen_position R1 depth        # out: low half = sprite_offset_y, high half the caller's; callers hand it to queue_sprite / queue_sprite_at_base (their R1 depth, whose low half they overwrite with screen_y) or keep_player_on_screen

param player_move R1 depth                   # the player code's leftover R1, handed through every call and back to player_animate; on the move path the last one is keep_player_on_screen's (project_object's sprite_offset_y half), which the animation states give update_screen_position and queue_sprite as depth
param player_move R11 cursor                 # the player code's leftover R11 (player_animate's name): check_player_contacts', find_floor's cursor, check_player_floor_edge's; the states that take it allocate their effect objects in it (allocate_object, spawn_player_effect)

param delete_if_off_screen R2 block_offset   # the caller's R2 (unproject_object / move_object_projected: find_floor's block offset), handed back; when the object is deleted, delete_object_and_children's (the callers return then)
param delete_if_off_screen R3 slope_height   # the caller's R3 (unproject_object / move_object_projected: find_floor's slope part of floor_z), handed back; delete_object_and_children's when deleted

param delete_object_and_children R2 block_offset  # the caller's R2 (find_floor's block offset in most handlers) when there is no child; else the last child's delete_object R2 (previous) or free_object_2's free stack (20EA10); the callers return after it
param delete_object_and_children R3 slope_height  # the caller's R3 (find_floor's slope part), handed back; changed only on the trap path (int_default) when free_object_stack_2 goes past 0x100

param check_hits R1 box_edge                     # check_hit's R1 of the last pair tested, handed back
param check_hits R2 distance                 # check_hit's R2 of the last pair tested (low half), handed back
param check_hits R5 radius                   # check_hit's R5 of the last pair tested (low half), handed back
param check_hits R8 centre_offset            # check_hit's R8 of the last pair tested (low half), handed back

param spawn_walls R2 part_size               # build_wall_parts' R2 handed back (the part size, or allocate_object_2's free stack once parts were made); delete_object_outside_loop's previous when pool 2 is too short; else the caller's

param spawn_objects_by_type R1 player_object # count_active_players' R1 (the last player object checked, player 1's); the caller's on the stage's first call
param spawn_objects_by_type R2 player_index  # count_active_players' R2 (2, 1, 0 counted down: FFFFFFFF at the end); the caller's on the stage's first call
param spawn_objects_by_type R11 object       # spawn_object_with_pieces' R11 (the last block it allocated), then unproject_object's R11 (find_floor's cursor) once it spawned; the caller's when nothing is due

param spawn_ring_rows R2 free_stack          # find_floor's block offset for each ring, then allocate_object_2's R2 for its shadow: 20EA10 (free_object_stack_2) once a ring spawned; else the caller's

param spawn_object_groups R2 free_stack      # spawn_waterfall's R2 (allocate_object_2's free stack, 20EA10); otherwise the caller's

param player_state_tumbling R1 depth         # the player code's leftover R1 (player_move's name), handed to check_player_contacts, push_player_out_of_wall, find_floor and the animate calls
param player_state_tumbling R11 cursor       # the player code's leftover R11 (player_move's name): handed to player_tumbling_launch / player_tumbling_set_launch_speed, which allocate the tumble effect in it

param player_state_stage_scene R1 depth      # the player code's leftover R1, handed back to player_move or to play_sound / animate / update_screen_position / queue_sprite
param player_state_stage_scene R11 cursor    # the player code's leftover R11, handed back to player_move

param player_state_sinking R11 cursor        # the player code's leftover R11: spawn_player_effect allocates the sinking effect in it (handler sinking_effect_update)

param player_state_intro_turn R1 depth       # the player code's leftover R1, handed to player_move
param player_state_intro_turn R11 cursor     # the player code's leftover R11, handed to player_move

param player_state_dropping R11 cursor       # the player code's leftover R11: allocate_object's slip effect (handler slip_effect_update), then spawn_slip_piece's / spawn_slip_pieces' piece

param player_state_choosing_character R11 cursor  # the player code's leftover R11, handed to start_character_chooser

param player_state_blown_away R11 cursor     # the player code's leftover R11: allocate_object's blown-away effect (handler blown_away_effect_update)

param player_state_stuck_on_object R1 depth  # the player code's leftover R1: check_player_contacts', handed to the next state, then update_screen_position / find_floor / keep_player_on_screen / queue_sprite
param player_state_stuck_on_object R11 cursor  # the player code's leftover R11: check_player_contacts', handed to the next state or to player_stuck_on_object_begin (its sign)

param player_state_held R1 depth             # the player code's leftover R1, handed to the state it switches to
param player_state_held R11 cursor           # the player code's leftover R11, handed to the state it switches to

param cycle_palette R1 copy_offset           # out: queue_vblank_copy's R1 after a copy; on the waiting frames the frame's offset into the source (frame x 20)
param cycle_palette R2 copy_entry            # out: queue_vblank_copy's R2 (past the vblank_copies entry it wrote) after a copy; else the caller's

param project_point R1 projected_y           # out: point_to_screen's R1: (projected y + z) >> 16 - view_y, the point's screen y (32 bits; R8 gets its low half); what callers pass is not read

param attract_demo_2 R1 pressed              # run_frame's R1 (wait_for_frame / read_controls: the bits just pressed), only passed on to run_game_mode (as attract_demo_1)
param attract_demo_2 R2 released             # run_frame's R2 (read_controls' released bits), only passed on to run_game_mode
param attract_demo_2 R3 input_cursor         # run_frame's R3 (read_controls' input_cursor), only passed on to run_game_mode
param attract_demo_2 R5 cursor               # run_frame's R5 (draw_queued_strings' string cursor, low half), only passed on to run_game_mode
param attract_demo_2 R11 trackball_cursor    # run_frame's R11 (read_controls' trackball_cursor), only passed on to run_game_mode
param attract_demo_2 R4 scale                # run_frame's R4 (the last frame's draw_queued_strings scale), in and out of run_game_mode
param attract_demo_2 R8 count                # run_frame's R8 (read_controls' count, 0), in and out of run_game_mode
param attract_demo_2 R9 height               # run_frame's R9 (draw_queued_strings' height), in and out of run_game_mode
param attract_demo_2 R10 trackball_port      # run_frame's R10 (read_controls' trackball_port), in and out of run_game_mode

param face_nearest_player R0 direction       # angle_of_vector's R0 for each active player (dx in, the angle out); the last one + direction_to_player_offsets[nearest_player] is stored in direction; the caller's value only when no player is in play

param centipede_hop R0 height                # fall_to_floor's R0 (floor_z - z while still above the floor, 0 once on it) when 86 = 0; else the caller's
param centipede_hop R2 z                     # fall_to_floor's R2 (the z it loaded while falling) when 86 = 0; else the caller's

param draw_map_stage_markers R11 badge_offset map_marker_layout@map_marker_layouts+54  # result: stage_map_markers[stage] x C, the current marker's offset in map_number_badge_sprites (set while it blinks); else the caller's

param update_stage_06_background R3 random_value  # next_random's R3 (step 6's two rolls, & F); the caller's value is not read
param update_stage_06_background R9 words_left    # next_random's R9 (0 after a roll); else the caller's, handed back
param update_stage_06_background R10 source       # step 6: cycle_palette_set_0/_1's R10 (the frame queued, or handed back) and next_random's R10 in between; the vram script past its end after steps 1, 3 and 5

param stage_05_event_show_message R2 part_size  # play_sound's R2, handed to stage_05_event_step_3_frame as spawn_walls' R2
param stage_05_event_show_message R4 sag        # stage_05_event_step_3_frame's R4 handed back (low half): check_platforms' (compute_bridge_sag's sag)

param scroll_at_scroll_speed R2 other_list   # advance_scroll_unless_ended's R2 handed back (advance_scroll's other_list)
param scroll_at_scroll_speed R4 speed_value  # advance_scroll_unless_ended's R4 handed back (set_camera_velocity's: the speed's low half, or what update_background_scroll leaves)
param scroll_at_scroll_speed R5 next_scroll_position  # advance_scroll_unless_ended's R5 handed back (advance_scroll's)
param scroll_at_scroll_speed R10 range       # advance_scroll_unless_ended's R10 handed back (update_background_scroll's fast scroll range cursor)
param scroll_at_scroll_speed R11 quadrant_cursor  # advance_scroll_unless_ended's R11 handed back (update_nbg3_scroll's / select_nbg3_pages')

param move_scattered_ring R11 cursor         # find_floor's R11 (callers pass the player or 0, overwritten), then handed to spawn_ring_glint as the register it allocates the glint in

param advance_scroll_unless_ended R2 other_list  # advance_scroll's R2 (cursor into the section lists); the caller's when scroll_ended < 0
param advance_scroll_unless_ended R4 speed_value  # advance_scroll's R4 (set_camera_velocity's: the speed's low half, or what update_background_scroll leaves); the caller's when scroll_ended < 0
param advance_scroll_unless_ended R5 next_scroll_position  # advance_scroll's R5 (move_camera_along_path's); the caller's when scroll_ended < 0
param advance_scroll_unless_ended R10 range  # advance_scroll's R10 (update_background_scroll's range cursor); the caller's when scroll_ended < 0
param advance_scroll_unless_ended R11 quadrant_cursor  # advance_scroll's R11 (update_nbg3_scroll's, select_nbg3_pages' quadrant cursor); the caller's when scroll_ended < 0

param start_stage R4 bounds_offset           # out: find_nearby_floor_blocks' R4 (offset of the last block's bounds record)
param start_stage R5 stage_offset            # start_stage_view's R5 (stage x 2), handed to move_camera_to_start_section and find_nearby_floor_blocks; the caller's value is not read
param start_stage R8 corners_left            # apply_stage_layer_blending's count, then find_nearby_floor_blocks' corner counter (out)
param start_stage R9 palette_record          # out: load_stage_palette's R9 (stage_palettes + stage x 8, + 4)
param start_stage R10 source                 # load_stage_map_pointers' bounds list, find_nearby_floor_blocks' R10, then load_stage_palette's source (out)
param start_stage R11 object                 # in: handed to start_player_objects_alt (its object) and find_nearby_floor_blocks; out: load_stage_palette's R11 (60B800)

param run_test_mode R1 pressed               # wait_for_frame's R1 (read_controls': the bits just pressed); only handed to test_crt, which overwrites it
param run_test_mode R8 count                 # wait_for_frame's R8 (read_controls' counter); only handed to test_inputs
param run_test_mode R4 color                 # the screen routine's R4 handed back: fill_rect's colour (start_test_mode, test_crt, test_inputs' panel_color), test_memory's results_left, test_backup_clear's polynomial
param run_test_mode R9 blocks_per_row        # the screen routine's R9 handed back (fill_rect's leftover, as start_test_mode and test_crt)

param player_animate_skidding_slow_down R11 cursor  # the player code's leftover R11, handed to spawn_skid_dust as the register it allocates the dust in

param init_sign_from_kind R1 depth           # project_object's R1 handed back (low half sprite_offset_y; update_screen_position R1)

param count_free_characters R0 characters_left  # 3 counted down: 0 at the end
param count_free_characters R2 character     # the character checked (2, 1, 0): 0 at the end
param count_free_characters R3 taken_bit     # character_taken_bits[character]: character 0's at the end

param check_hit R1 box_edge                      # the far edge of a box (x_int + offset or +- size) a coordinate is compared with, the slope correction in the round test; last the hitter's z_int - hitbox_height (low half)
param check_hit R2 distance                  # round test: dx, then dx^2 + dy^2 compared with R5; box tests: the hitter's box start; stored in both objects' touch_distance on a hit
param check_hit R5 radius                    # round test: the two hitbox_size_x summed, then squared (compared with R2); box/box: the other's hitbox offset (x, then y)

param update_quicksand_object R1 sink_offset # result: 4EC4 x (68 - touch_distance), added to floor_z (and z) while the object is drawn into the quicksand (flags bit 31); else the caller's

param update_ending R10 platform             # out: check_platforms' R10 (the last platform tested)
param update_ending R11 object               # handed through build_ending_scene, find_nearby_floor_blocks and the spawners (their new objects), then check_hits and check_platforms

param test_crt R1 start_y                    # draw_line's R1 (each line's first y) in step 3; clear_text_layer's count before it; the caller's value is not read

param start_ranking_display R11 display      # allocate_object_or_start_list's object: the ranking display (handler build_ranking_display); the caller's value only when none is free

param start_character_chooser R11 block      # show_nbg0_picture's R11, then spawn_object_with_pieces' R11 (the last piece it allocated)

param spawn_door_rollers R2 block_offset     # find_floor's R2 for the last roller (its floor block); else the caller's

param run_stage_05_switch R3 speed           # handed to update_background_scroll (its R3) in step 2; the caller's value
param run_stage_05_switch R2 scroll_y        # out after step 2: update_background_scroll's R2 (update_nbg3_scroll's scroll y); the camera's x pixels go in
param run_stage_05_switch R4 speed_value     # update_background_scroll's R4 handed back after step 2; else the caller's

param reload_changed_tile_set R1 shifted_tile_set  # the caller's (update_stage_tile_layers' shifted_tile_set) handed back; load_tile_set's R1 (run_vram_script's count, 0) when a set was loaded
param reload_changed_tile_set R2 slots_left  # the 4 loaded slots, counted down to the one holding the wanted set: 0 when none did (load_tile_set ran)
param reload_changed_tile_set R5 slot_tile_set  # the tile set byte of the loaded slot that matched (the wanted one); load_tile_set's R5 (stage x 2) when none did and it loaded the set
param reload_changed_tile_set R8 loaded_shift  # 0, -8, -10, -18: the shift of the slot compared last in loaded_tile_sets

param player_object_climb_push R11 cursor    # the player code's leftover R11 (player_state_climbing_object's), handed to spawn_climb_hand as the register it allocates the hand in

param metal_wall_rise R3 octant              # metal_wall_wait's face_nearest_player result, handed to metal_wall_update

param int_vector_02 R0 source                # run_vblank_copies' R0 handed back (the last copy's source)
param int_vector_02 R2 dest                  # run_vblank_copies' R2 handed back (the last copy's destination)
param int_vector_02 R3 copies_left           # run_vblank_copies' R3 (vblank_copy_count, counted down: 0)
param int_vector_02 R4 copy_entry            # run_vblank_copies' R4 (past the last vblank_copies entry)

param event_0c_second_quake R5 second_sprite # handed to spawn_walls (its R5), the caller's value
param event_0c_second_quake R2 part_size     # spawn_walls' R2 handed back, or play_sound's after it at the end of the step

param draw_attract_credits R1 credits        # draw_credit_count's R1 (draw_credit_count_now's credits); blink_demo_prompt's prompt frame in the demo-prompt setup
param draw_attract_credits R2 blink_bit      # blink_start_prompt's R2 (the frame_counter bit of the blink); draw_credit_count's coins_per_credit in the demo-prompt setup

param check_player_floor_edge R1 depth       # the player's fall draw depth (fall_draw_depth before its - 10/20) when it jumps off an edge; polar_to_xy's R1 when it starts over the edge; else the caller's
param check_player_floor_edge R11 block      # get_ledge_side's R11 (the previous floor block's record) when it jumps off an edge; else the caller's

param animate_palette_stage_10 R1 copy_offset   # cycle_palette's R1 (the second cycle's)
param animate_palette_stage_10 R2 copy_entry    # cycle_palette's R2 (vblank_copies entry), else the caller's
param animate_palette_stage_10 R4 palette_count # 6, then 1
param animate_palette_stage_10 R9 cycle         # 20CA00, then 20CA05
param animate_palette_stage_10 R10 source       # 108990, then 108A90
param animate_palette_stage_10 R11 dest         # 608B40, then 608940

param animate_palette_stage_09 R1 copy_offset   # cycle_palette's R1
param animate_palette_stage_09 R2 copy_entry    # cycle_palette's R2 (vblank_copies entry), else the caller's

param stage_05_event_show_message_11 R2 part_size  # handed to stage_05_event_show_message (play_sound's, then spawn_walls' R2)
013429  func  spawn_eggman_teleport                        # unused: no caller, no table: spawn_count 2 (8[R25]), spawn_object_with_pieces, handler eggman_teleport_init (01343C), rsr: the only spawner of eggman_teleport_init ("never spawned")
013AAD  func  spawn_bubble_column                          # unused: no caller, no table: spawn_count 1, spawn_object_with_pieces, palette 1060, 92 = R14, handler bubble_column_animate, play_sound 24 (what bubble_column_init does), rsr

# ---- unused code the data of the code chips hid (after tables; the static analysis reached none of it) ----
# 099AE7-099F2C (it was the piece data_099AE7 of assets/code_data/080000/): a packed-picture unpacker for the 8 bpp
# bitmap layer, an LZ unpacker, a copy of the bitmap layer set-up, and two other ways into init_sprites. The
# unpackers read their bit stream with EXTBFZ / SCH0BSU from the address in R12 at the bit position in R18 (the
# register the C calls current_player); a number is a gamma code: n 1 bits, a 0, then n + 1 bits v, worth
# v + 2^(n + 1) - 1 (1, 2, 3 ...)
099AE7  func  draw_packed_picture              # unused: unpacks the picture at R10 into the 8 bpp bitmap layer: init_bitmap_layer(1), the four tile layers off (video_layer_disable |= F), init_picture_unpacker, init_picture_colour_cache, read_picture_size, unpack_picture_pixels, then rotate_bitmap_for_monitor_flip; no packed picture is known in the ROMs
099B08  func  init_picture_unpacker            # unused: colour cache at 20B500 (bitmap_draw_buffer, R15), newest slot 0 (R16), bit position 0 (R18), and the done map at 400100 (R17, sprite RAM): 1C00 halfwords cleared, a bit per byte of the bitmap's 200 x E0 bytes
099B29  func  init_picture_colour_cache        # unused: 16 slots of 3 bytes at R15 (next slot, previous slot, colour): slot n links n + 1 and n - 1, colour 0; slot F's next = 0 and slot 0's previous = F close the ring
099B54  func  read_picture_size                # unused: two 16-bit numbers from the bit stream: the width (R13) and the height (R14) of the picture in pixels
099B6B  func  unpack_picture_pixels            # unused: walks the bitmap from its top left (bitmap_address, rows 200 bytes apart, width x height pixels) by runs: a gamma code n, then n - 1 pixels that keep the colour already there if the done map has their bit (that colour becomes the current one), else get the current colour, then a pixel that takes read_picture_colour's colour (bit set in the done map); a 1 bit after it starts a chain going down from it: 2-bit steps through picture_chain_step_jumps (1 left, 2 straight, 3 right, 0 then 0 ends the chain or 10 two left, 11 two right), each painting the pixel one row down in the colour and setting its done bit; ends after the last row. The chain also keeps x and y in R1 and R2 and never reads them
099BD9  code  unpack_picture_new_pixel         # unpack_picture_pixels: the pixel that ends a run, with read_picture_colour's colour (a run of 1 comes straight here)
099C58  code  unpack_picture_chain_down        # unpack_picture_pixels: the chain's next pixel, one row (200 bytes) down after the step's x move: painted, done bit set, then the next 2-bit step
099C6A  data  picture_chain_step_jumps         # unpack_picture_pixels (099C1C "mov.w 99C6A[PC](R0), R0; jmp [R0]"): 4 code addresses by the 2-bit step: 099C24 (another bit: 0 ends the chain, 1 then reads the x move of 2), 099C4E (x - 1), 099C58 (straight down), 099C54 (x + 1)
099C7A  func  read_picture_colour              # unused: a colour for unpack_picture_pixels (R7) through the 16-slot cache at R15: bit 1 and 4 bits name a slot, whose colour it takes; the slot is unlinked and put back after the newest (R16), and becomes the newest; bit 0 and 8 bits are a new colour, stored in the slot after the newest (the least recently used one), which becomes the newest
099CF9  func  rotate_bitmap_for_monitor_flip   # unused: when setting_monitor_flip is set, turns the 320 x 224 picture of the 8 bpp bitmap round (180 degrees): each of the top 70 rows swaps its 320 bytes with the mirrored bottom row's, in reverse order (RVBYT on each word), so the picture is the right way up on a flipped monitor
099D5B  func  init_bitmap_layer                # unused: clear_bitmap_layer's set-up without its fill: set_bitmap_scroll_2(0, 0, R0 != 0), get_bitmap_address_2(0, 0), video_layer_disable bits 5/10/15 off, bitmap clip 0,0-13F (19F wide),DF, then set_bitmap_window_2(0, 0, 1FF, DF)
099DAE  func  get_bitmap_address_2             # unused: a copy of get_bitmap_address (300000 + y x row + x, by bitmap_8bpp, into bitmap_address); init_bitmap_layer's
099DD3  func  set_bitmap_scroll_2              # unused: a copy of set_bitmap_scroll (video_bitmap_scroll_x/y, bitmap_8bpp and video_control bit 11 from R2 bit 0); init_bitmap_layer's
099DF9  func  set_bitmap_window_2              # unused: a copy of set_bitmap_window (R0-R3 sorted into bitmap_window_left/top/right/bottom); init_bitmap_layer's
099E1E  func  set_bitmap_clip_2                # unused: a copy of set_bitmap_clip (R0-R3 sorted into video_bitmap_clip_left/top/right/bottom, video_layer_disable bit 15 on); nothing calls either
099E49  func  unpack_lz_bytes                  # unused: unpacks the bit stream at R10 into bytes from R11: a gamma code is the byte count, then items: 0 and 8 bits a literal byte (also kept in recent_literals, 16 of them in a ring), 10 and 4 bits a byte from recent_literals, 11 and two gamma codes a copy of earlier output (distance back, length); ends when the count runs out (a copy can overshoot it)
099F11  func  init_sprites_without_swaps       # unused: init_sprites with vblank_hook = cancel_sprite_buffer_swap in place of swap_sprite_buffers (br into init_sprites at 099F35): the VBLANK only clears sprite_list_ready, the sprite buffers never swap
099F1B  func  init_sprites_swapping_every_frame  # unused: init_sprites with vblank_hook = swap_sprite_buffers_now (a swap at every VBLANK, ready or not) and frames_to_wait = frames_per_wait = 1 (mov.h #101), then br into init_sprites at 099F3B
099F35  func  init_sprites_from_frame_wait     # init_sprites from its frames_to_wait = frames_per_wait = 0 (mov.h #0) on, then init_sprite_hardware; init_sprites_without_swaps' way in (br 099F35)
099F3B  func  init_sprite_hardware             # the rest of init_sprites: sprite_flip, 500006-50000C, the sprite list header, back buffer, end marks, sprite_frames, clear_sprite_buckets; init_sprites_swapping_every_frame's way in (br 099F3B)
09A1FB  func  swap_sprite_buffers_now          # swap_sprite_buffers past its sprite_list_ready test and first sprite_buffer_flip += 2 (it falls in here): flip += 2, back buffer low word and entry 1 jump from sprite_buffer_swaps[flip & 6], reads sprite_status; as the hook init_sprites_swapping_every_frame stores (+= 2 a VBLANK) it steps through all four records of sprite_buffer_swaps, (4000, A201) and (0020, A5FF) among them, which the game's += 4 skips
08F998  func  reset_character_select           # unused: after ranking_stage_sprites' 10 words (08F984-08F997): characters_taken, players_in_join_chooser, free_character_count and character_select_timer (208090-208094) = 0 with two overlapping 32-bit stores and a 16-bit one (game mode 0x1B clears 208090-208093 with one 32-bit store, 00109E); start_character_chooser follows
09754C  func  init_title_puff_sprite           # unused: after title_smoke_puff_frames' FFFF: the object at R11 gets screen_y 7A, sprite_layer 7, sprite_scale 100, sprite_flags 0, sprite_palette EB0: title_smoke_puff_animate's set-up (it uses screen_y 70) for an object title_flying_craft_move allocates (R11); title_clouds_1_drift follows
09C5C0  func  compute_crc16_strided            # unused: after eeprom_defaults' 40 bytes: compute_crc16's loop (polynomial 1021, 8 more shifts at the end, >> 16 into R0) over R3 bytes from R4, R6 + 1 bytes apart, with the first three bytes where compute_crc16 has its seed DEBDEB: R0 = the bytes, as one number, mod 11021 (compute_crc16 gives that of DE BD EB and its bytes; checked in Python); R6 is put back; eeprom_lock follows
08E1A8  func  copy_screen_position_from_cursor # the current object's screen_x and screen_y = object_cursor's (R23); only draw_eel_piece_parts (unused) calls it

param draw_packed_picture R10 picture           # the packed picture: two 16-bit sizes, then the bit stream (R12 = it)
local draw_packed_picture 099AE7 r12 stream     # the bit stream the unpacker reads (R18 counts the bits read)
local draw_packed_picture 099AEA r0 is_8bpp     # 1: init_bitmap_layer's 8 bits per pixel
param init_picture_unpacker R15 colour_cache    # 20B500 (bitmap_draw_buffer): 16 slots of 3 bytes for init_picture_colour_cache
param init_picture_unpacker R16 newest_slot     # 0: the slot of the colour used last
param init_picture_unpacker R17 done_bits       # 400100: a bit per byte of the bitmap, set once its pixel has its colour
local init_picture_colour_cache 099B29,099B37,099B3A,099B3D r11 entry_cursor  # walks the slots' bytes
local init_picture_colour_cache 099B30,099B41 r0 next_slot      # 1, 2 ... 10 (slot F's is put back to 0)
local init_picture_colour_cache 099B33,099B43 r2 previous_slot  # FF, 0, 1 ... (slot 0's is put back to F)
local init_picture_colour_cache 099B2C,099B45 r1 slots_left     # 10
param read_picture_size R12 stream             # the bit stream (R18 its bit position)
param read_picture_size R13 width              # the first 16 bits: pixels a row (R14, the height, is a shared register in C: r14)
param unpack_picture_pixels R12 stream          # the bit stream (R18 its bit position)
param unpack_picture_pixels R13 width           # pixels a row (read_picture_size); the height is in R14 (r14)
param unpack_picture_pixels R16 newest_slot     # read_picture_colour's
param unpack_picture_pixels R17 done_bits       # a bit per byte of the bitmap (insbfr / extbfz at the pixel's offset)
local unpack_picture_pixels 099B6B,099BAA,099BBB,099BD9,099BEA r5 x   # FFFF at the start, + 1 a pixel, 0 on the next row
local unpack_picture_pixels 099B6E,099BB3,099BE2 r6 y                 # rows done: the picture ends at the height (r14)
local unpack_picture_pixels 099B71,099BCB r7 colour                   # the current colour: read_picture_colour's, or the colour of a done pixel the run crosses
local unpack_picture_pixels 099B79 r9 row_bytes                       # 200: the bitmap's row
local unpack_picture_pixels 099B81 r10 row_skip                       # 200 - width: from the end of the picture's row to the start of the next
local unpack_picture_pixels 099B84,099BAC,099BBE,099BDB,099BED r11 offset  # the pixel's offset from bitmap_address (and its bit in the done map)
local unpack_picture_pixels 099B74 r19 bitmap                         # bitmap_address (get_bitmap_address_2's 300000)
local unpack_picture_pixels 099B87,099B8D r1 code_bits                # the gamma code's 1 bits (sch0bsu), + 1: the bits its value has
local unpack_picture_pixels 099B92 r0 code_value                      # the value bits of the run's gamma code
local unpack_picture_pixels 099BA6 t1 run_length                      # code_value + 2^code_bits - 1: the run's pixels, the last of which gets a new colour
local unpack_picture_pixels 099BA6,099BD5 r8 pixels_left              # the run's pixels before the new one
local unpack_picture_pixels 099BFD,099C24,099C31 r0 bit               # one bit of the stream: a chain follows (099BFD), the chain goes on (099C24), the x move's side (099C31)
local unpack_picture_pixels 099C10,099C41,099C49,099C50,099C56,099C5A r3 chain_offset  # the chain pixel's offset from bitmap_address
local unpack_picture_pixels 099BF7 t3 done_address                    # where insbfr reads and writes the 32 bits that hold the pixel's done bit
local unpack_picture_pixels 099C61 t3 chain_done_address              # the same for the chain's pixel
local unpack_picture_pixels 099C1C t0 chain_step                      # the 2-bit step that indexes picture_chain_step_jumps
param read_picture_colour R12 stream            # the bit stream (R18 its bit position)
param read_picture_colour R16 newest_slot       # the slot of the colour used last; the slot this colour is in on the way out
param read_picture_colour R7 colour             # out: the colour (its low byte; a new colour, 8 bits, replaces the whole register)
local read_picture_colour 099C7A r0 is_cached   # the first bit: 1 a slot number follows, 0 a new colour
local read_picture_colour 099C87 r0 slot        # 4 bits: the slot whose colour it takes
local read_picture_colour 099C95 r1 slot_offset # slot x 3
local read_picture_colour 099C99 r4 previous_slot  # the slot before it, linked to the one after it to take it out
local read_picture_colour 099CA2 r3 next_slot   # the slot after it
local read_picture_colour 099CB3 r2 newest_offset  # newest_slot x 3
local read_picture_colour 099CB7 r3 oldest_slot # the slot after the newest (the least recently used): it goes after the picked slot
local read_picture_colour 099CD1,099CEB r16 picked_slot  # the slot the colour is in: the newest from now on
local read_picture_colour 099CD8,099CDE r7 picked_colour # the slot's colour, or the 8 bits of a new one
local rotate_bitmap_for_monitor_flip 099CFF,099D21,099D2D,099D39,099D45,099D4C r10 top_cursor  # 300000 on: a row's bytes from the left
local rotate_bitmap_for_monitor_flip 099D06,099D1B,099D27,099D33,099D3F,099D51 r11 bottom_cursor  # 31BF40 back: the mirrored row's bytes from the right (the end of row DF's 320)
local rotate_bitmap_for_monitor_flip 099D0D,099D56 r3 row_pairs_left  # 70: half of the 224 rows
local rotate_bitmap_for_monitor_flip 099D11 r2 blocks_per_row         # 14: 320 bytes in 16-byte blocks
local rotate_bitmap_for_monitor_flip 099D15,099D48 r5 blocks_left
local rotate_bitmap_for_monitor_flip 099D1B,099D27,099D33,099D3F r1 bottom_word  # 4 bytes of the bottom row, written byte-reversed at the top
param init_bitmap_layer R0 is_8bpp              # not 0: set_bitmap_scroll_2's R2 = 1 (8 bits per pixel), as clear_bitmap_layer
local init_bitmap_layer 099D60 r0 scroll_x
local init_bitmap_layer 099D63 r1 scroll_y
local init_bitmap_layer 099D69 r0 x
local init_bitmap_layer 099D6C r1 y
local init_bitmap_layer 099D72 r1 video_base    # video_control's address (31FF00): + 0x84 is video_bitmap_clip_right/bottom
local init_bitmap_layer 099D86,099D91 r0 clip_right_bottom  # DF013F (right 19F with video_control bit 15)
local init_bitmap_layer 099D9B r0 left
local init_bitmap_layer 099D9E r1 window_top
local init_bitmap_layer 099DA1 r2 right
local init_bitmap_layer 099DA6 r3 bottom
param get_bitmap_address_2 R0 x                 # as get_bitmap_address
param get_bitmap_address_2 R1 y
local get_bitmap_address_2 099DB4 r2 x_shift    # bitmap_8bpp - 1: x >> 1 at 4 bpp, x at 8 bpp; + 9 is the y shift
param set_bitmap_scroll_2 R0 scroll_x           # as set_bitmap_scroll
param set_bitmap_scroll_2 R1 scroll_y
param set_bitmap_scroll_2 R2 is_8bpp
param set_bitmap_window_2 R0 left               # as set_bitmap_window
param set_bitmap_window_2 R1 top
param set_bitmap_window_2 R2 right
param set_bitmap_window_2 R3 bottom
local set_bitmap_window_2 099DFE t1 old_left    # left kept while left and right swap
local set_bitmap_window_2 099E06 t1 old_top     # top kept while top and bottom swap
param set_bitmap_clip_2 R0 left                 # as set_bitmap_clip
param set_bitmap_clip_2 R1 top
param set_bitmap_clip_2 R2 right
param set_bitmap_clip_2 R3 bottom
local set_bitmap_clip_2 099E23 t1 old_left
local set_bitmap_clip_2 099E2B t1 old_top
param unpack_lz_bytes R10 packed                # the bit stream (R12 = it; R18 its bit position from 0)
param unpack_lz_bytes R11 dest                  # where the bytes go, past the last one at the end
scoped global unpack_lz_bytes 20B500 recent_literals u8  # the last 16 literal bytes, in a ring (literal_slot & F); an item 10 and 4 bits copies one of them
local unpack_lz_bytes 099E49 r12 stream
local unpack_lz_bytes 099E4F,099EEC,099EF5 r15 literal_slot   # where in recent_literals the next literal goes (& F)
local unpack_lz_bytes 099E6C,099EDA,099EF7,099F0C r17 bytes_left  # the first gamma code: bytes to unpack, less each item's
local unpack_lz_bytes 099E52,099E58,099E8B,099E91,099EB0,099EB6 r1 code_bits  # a gamma code's 1 bits (sch0bsu), + 1: the bits its value has
local unpack_lz_bytes 099E5D,099E96,099EBB r0 code_value       # the value bits of a gamma code
local unpack_lz_bytes 099E71,099E7E r0 bit                     # an item's first bits: 0 a literal, 10 a recent literal, 11 a copy
local unpack_lz_bytes 099EE0 r0 literal
local unpack_lz_bytes 099EAD,099ED2 r10 copy_source  # dest less the first gamma code: earlier output
local unpack_lz_bytes 099ECF,099ED6 r3 copy_left
local unpack_lz_bytes 099EDA t2 bytes_before_copy   # bytes_left before the copy's length comes off it (the loop goes on while it was more)
param init_sprites_from_frame_wait R0 end_mark  # init_sprite_hardware's FFFF, handed back
local init_sprites 099F2D r10 control_init     # init_sprites_from_frame_wait's R10 (past sprite_control_init), handed back
local init_sprites_from_frame_wait 099F35 r10 control_init  # init_sprite_hardware's R10, handed back
local init_sprites_without_swaps 099F19 r0 end_mark         # init_sprites_from_frame_wait's FFFF (nothing reads it)
local init_sprites_swapping_every_frame 099F2B r0 end_mark  # init_sprite_hardware's FFFF (nothing reads it)
param init_sprites_from_frame_wait R10 source   # init_sprite_hardware's sprite_control_init cursor, handed back
param init_sprite_hardware R0 end_mark          # FFFF, the end mark written at the starts of the four sprite list buffers (low half)
param init_sprite_hardware R10 source           # walks sprite_control_init (4 bytes to 500006-50000C): 9A6B6 at the end
param init_title_puff_sprite R11 puff object    # the object to set up (title_flying_craft_move's allocate_object hands its puff back in R11)
param compute_crc16_strided R0 crc              # in: only bits the first shifts push out of the register; out: the CRC in the low half
result compute_crc16_strided R0 crc             # the 32-bit division register >> 16 (shl.w #F0)
param compute_crc16_strided R3 count            # bytes to read, three or more (the first three seed the division register); 0 at the end
param compute_crc16_strided R4 data             # the first byte; left at the last byte read
param compute_crc16_strided R6 byte_gap         # bytes skipped between two read (0: every byte, 1: every other one); + 1 while it runs, put back at the end
local compute_crc16_strided 09C5C2,09C5CB,09C5D7,09C5E7,09C5ED,09C5F2,09C600,09C605 r0 shift_register  # the first three bytes, then a byte at a time in the low byte, shifted with polynomial; >> 16 is the CRC
local compute_crc16_strided 09C5EA,09C5F5,09C5FD,09C608 r1 bits_left  # 8 bits per byte (and 8 more at the end)
local compute_crc16_strided 09C5DD r2 crc_polynomial  # 0x10210000
param spawn_eggman_teleport R2 free_count       # spawn_object_with_pieces' R2
param spawn_eggman_teleport R3 teleport         # spawn_object_with_pieces' object: handler eggman_teleport_init; it is written without a test of R0, so when nothing was spawned the caller's R3 gets the handler
param spawn_eggman_teleport R11 block           # spawn_object_with_pieces' R11 (the last block it allocated)
param spawn_bubble_column R0 spawned            # spawn_object_with_pieces' result: low byte 0 when nothing was spawned; in: leftover
param spawn_bubble_column R2 free_count         # spawn_object_with_pieces' R2, then play_sound's
param spawn_bubble_column R3 column             # spawn_object_with_pieces' object: palette 1060, 92 = R14 (bubble_column_animate's column: mine_bubble_frames + 92 x 13), handler bubble_column_animate
param spawn_bubble_column R11 block             # spawn_object_with_pieces' R11
local spawn_bubble_column 013ACF r0 sound_id    # 24
param spawn_eel_den R0 spawned                  # spawn_object_with_pieces' result: low byte 0 when nothing was spawned (then R20 = R23 and nothing else)
param spawn_eel_den R2 free_count               # spawn_object_with_pieces' R2
param spawn_eel_den R3 den                      # spawn_object_with_pieces' object: R20 = it, then eel_den_init sets it up
param spawn_eel_den R11 block                   # spawn_object_with_pieces' R11
local draw_eel_piece_parts 014E04 r1 floor_depth  # projected_y + floor_z - view_y + 1 of the piece (R23): the depth both parts are queued at (kept in R14)
local draw_eel_piece_parts 014DC6 t2 part_2_timer  # part 2's field 24 (direction in C) before dec.h: at 0 back to 2 and its next frame (every 3 frames); its byte 2D is the step
local draw_eel_piece_parts 014E13 t2 part_3_timer  # the same for part 3

# ======== the program data cut where each thing starts (data lines from build/naming/formats_*_labels.txt) ========
# -- palettes --
# Labels proposed by the palettes family (FORMATS_GUIDE.md): where the pieces of the palette data start.
104D92  data  text_palettes                     # default_palette_list entry 0 (kind 0, copy_palettes_with_pen_0): 16 palettes of pens 1-15 (480 bytes) to 609000, colours 800-8FF: the text layer's palette base (mixer_defaults text 002F); palettes 0-6 hold text colours (white, greys, a blue ramp), 7-F look like noise (bit 15 set at random), probably never shown
104F72  data  tile_bank_0_palette_repeats       # default_palette_list entry 1 and tile_bank_0_palette_list (kind 4, repeat_palette_block's source): 46 u16 counts that fill the 128 tile palettes at 608000
104FCE  data  tile_bank_0_palettes              # the same entries' second pointer: the 46 palettes those counts repeat (16 colours each); tile_palettes.json finds the stage tiles drawn with them
10558E  data  sprite_palettes                   # default_palette_list entry 2 (kind 2, copy_palette_block): 4048 colours (253 palettes) to 609800, colours C00-1BCF: the sprite groups' palette base (mixer_defaults 003x); sprite_palettes.json finds most sprite frames drawn with them (the players' 130/140/150 among them)
10752E  data  bitmap_palettes                   # default_palette_list entry 3 (kind 2): 11 palettes to 609200, colours 900-9AF: the bitmap layer's (mixer base 800 + video_bitmap palette 31FF8C = 0010 from video_register_defaults): ramps of 32 reds, greens, blues and greys at pixel values 10, 30, 50, 70, the bars test_crt draws from crt_test_color_bars (10/30/50/70)
10768E  data  backdrop_palette                  # default_palette_list entry 4 (kind 2): 16 colours to 609400; the first is backdrop_color (load_palette_set_and_backdrop writes it again by stage)
1076CA  data  tile_bank_1_palette_repeats       # tile_bank_1_palette_list (kind 4, repeat_palette_block's source): 67 u16 counts that fill the 128 tile palettes at 608000
107750  data  tile_bank_1_palettes              # tile_bank_1_palette_list's second pointer: the 67 palettes those counts repeat (16 colours each); tile_palettes.json finds the tiles of the bank 1 stages and screens drawn with them
108D6C  data  stages_00_to_03_sprite_palettes   # stage_palettes[0-3] (load_stage_palette to 60B800, colour 1C00 on): 784 colours (49 palettes); sprite_palettes.json finds stage objects drawn with them
10938C  data  stage_04_sprite_palettes          # stage_palettes[4]: 592 colours (37 palettes)
10982C  data  stages_05_to_08_sprite_palettes   # stage_palettes[5-8]: 624 colours (39 palettes)
109D0C  data  stage_09_sprite_palettes          # stage_palettes[9]: 480 colours (30 palettes)
10A0CC  data  stage_0a_sprite_palettes          # stage_palettes[0A]: 640 colours (40 palettes)
10A5CC  data  stages_10_12_sprite_palettes      # stage_palettes[10] and [12]: 912 colours (57 palettes); also the source of the 4 bytes after the 19 records (a 20th record, stage 13, whose count would be the first colours of stages_00_to_03_sprite_palettes)
10ACEC  data  stages_0b_to_0f_sprite_palettes   # stage_palettes[0B-0F]: 560 colours (35 palettes)
10B14C  data  stage_11_sprite_palettes          # stage_palettes[11]: 704 colours (44 palettes)
113E3D  data  sonic_palette_list                # player_palette_lists[stage][0] for every stage but 8: last index 1, sprite palettes 8130 8340, then 8130 again (player_reset puts word +2 in sprite_palette)
113E45  data  ray_palette_list                  # player_palette_lists[stage][1] for every stage but 8: 8140 8720 (8140)
113E4D  data  mighty_palette_list               # player_palette_lists[stage][2] for every stage but 8: 8150 8730 (8150)
113E55  data  sonic_stage_08_palette_list       # player_palette_lists[8][0]: C130 C340 (C130): the same palettes in another priority group
113E5D  data  ray_stage_08_palette_list         # player_palette_lists[8][1]: C140 C720 (C140)
113E65  data  mighty_stage_08_palette_list      # player_palette_lists[8][2]: C150 C730 (C150); after it three more lists of the same shape for group A (A130, A140, A150) that nothing points at, then 16 bytes nothing reads
# -- animations --
1128BA  data  sonic_near_edge_teeter_animation   # teetering_animations[0*2+1] = +0C: first frame 1658, then the 13-frame teeter animation (animation = +2); the table and this record were one piece
1128EE  data  sonic_far_edge_teeter_animation    # teetering_animations[0*2+0] = +40: first frame 16C8, then frames 16D0-1730; was inside sonic_near_edge_fall_animation
11290C  data  sonic_far_edge_fall_animation      # state 6 (5843): animation += 1C from the far teeter animation (1128F0 + 1C): last step 3, (frame, dx, dy) 1598-15B0 (animate_and_move)
11291E  data  mighty_near_edge_teeter_animation  # teetering_animations[2*2+1] = +70: first frame 3248, then frames 3250-32B0; its + 1C is mighty_near_edge_fall_animation
112952  data  mighty_far_edge_teeter_animation   # teetering_animations[2*2+0] = +A4: first frame 32B8, then frames 32C0-3320
112970  data  mighty_far_edge_fall_animation     # 112954 + 1C (state 6): last step 3, (frame, dx, dy) 31C0-31D8
112982  data  ray_near_edge_teeter_animation     # teetering_animations[1*2+1] = +D4: first frame 4E00, then frames 4E08-4E68; its + 1C is ray_near_edge_fall_animation
1129B6  data  ray_far_edge_teeter_animation      # teetering_animations[1*2+0] = +108: first frame 4E70, then frames 4E78-4ED8
1129D4  data  ray_far_edge_fall_animation        # 1129B8 + 1C (state 6): last step 3, (frame, dx, dy) 4D40-4D58
113C46  data  sonic_stuck_struggle_level_animations  # the three two-frame animations sonic_stuck_struggle_animations points at (113C46, 113C4C, 113C52): 1A68-1A80
113C6A  data  ray_stuck_struggle_level_animations    # the same for Ray (ray_stuck_struggle_animations): 51A8-51C0
113C8E  data  mighty_stuck_struggle_level_animations # the same for Mighty (mighty_stuck_struggle_animations): 35F0-3608
114AD0  data  sonic_trap_struggle_level_animations   # the four two-frame animations sonic_trap_struggle_animations points at (114AD0-114AE2): B4C0-B4E0; they lay inside mighty_trap_struggle_animations
114AE8  data  ray_trap_struggle_level_animations     # the four of ray_trap_struggle_animations (114AE8-114AFA): B558-B578
114B00  data  mighty_trap_struggle_level_animations  # the four of mighty_trap_struggle_animations (114B00-114B12): B440-B460
# -- scripts --
# Proposed data lines for symbols.txt (formats, family scripts). Pieces of formats_work_scripts.txt cut
# where the scripts and tables they hold start.
#
# The tile set scripts: load_tile_set runs, for stages 3-10, every script of the set's list:
# stage_tile_sets[stage] (01A695) -> a per-stage table of u32 list pointers (140000-14090F) -> lists of u32
# script pointers ending in FFFFFFFF (140910-141A0F); both +100000 (get_tile_set_list_bank,
# get_tile_set_script_bank). Sets 00-13 are the same lists in every table. A script is named after the
# first stage (and set) that loads it, _N its place in that list when the list has several; the 24-byte
# scripts that only clear a page (shared by many sets) after the page.

# ---- inside the pieces of formats_work_scripts.txt ----
145A4E  data  stage_03_tile_set_42_script_1             # vram script, tile set 42 of stages 3, 7 (list 140AA4[1] of 3): page 11
145B2B  data  stage_03_tile_set_43_script               # vram script, tile set 43 of stages 3, 7 (list 140AB4): page 00
145E52  data  stage_03_tile_set_44_script               # vram script, tile set 44 of stages 3, 7 (list 140ABC): page 02
146169  data  stage_03_tile_set_45_script               # vram script, tile set 45 of stages 3, 7 (list 140AC4): page 00
146456  data  stage_03_tile_set_46_script               # vram script, tile set 46 of stages 3, 7 (list 140ACC): page 03
1464CF  data  stage_03_tile_set_47_script               # vram script, tile set 47 of stages 3, 7 (list 140AD4): page 01
1467A0  data  stage_03_tile_set_48_script               # vram script, tile set 48 of stages 3, 7 (list 140ADC): page 03
1469B5  data  stage_03_tile_set_49_script_0             # vram script, tile set 49 of stages 3, 7 (list 140AE4[0] of 2): page 01
146A1A  data  stage_03_tile_set_4a_script_0             # vram script, tile set 4A of stages 3, 7 (list 140AF0[0] of 2): page 03
146C1E  data  stage_03_tile_set_4b_script               # vram script, tile set 4B of stages 3, 7 (list 140AFC): page 01
146CE1  data  stage_03_tile_set_4c_script               # vram script, tile set 4C of stages 3, 7 (list 140B04): page 02
146DD9  data  stage_03_tile_set_42_script_0             # vram script, tile set 42 of stages 3, 7 (list 140AA4[0] of 3): page 0C
147041  data  stage_05_tile_set_14_script               # vram script, tile set 14 of stages 5, 6 (list 140B14): page 02
1472C9  data  stage_05_tile_set_15_script               # vram script, tile set 15 of stages 5, 6 (list 140B1C): page 00
14739D  data  stage_05_tile_set_16_script               # vram script, tile set 16 of stages 5, 6 (list 140B24): page 03
147442  data  stage_05_tile_set_17_script               # vram script, tile set 17 of stages 5, 6 (list 140B2C): page 01
1474F6  data  stage_04_tile_set_56_script_0             # vram script, tile set 56 of stage 4 (list 140B34[0] of 2): page 02
147550  data  stage_04_tile_set_56_script_1             # vram script, tile set 56 of stage 4 (list 140B34[1] of 2): page 0C
1475E5  data  stage_04_tile_set_57_script               # vram script, tile set 57 of stage 4 (list 140B40): page 02
147652  data  stage_04_tile_set_58_script_0             # vram script, tile set 58 of stage 4 (list 140B48[0] of 2): page 00
1477C4  data  stage_04_tile_set_58_script_1             # vram script, tile set 58 of stage 4 (list 140B48[1] of 2): page 0A
1477FA  data  stage_04_tile_set_59_script_0             # vram script, tile set 59 of stage 4 (list 140B54[0] of 2): page 01
147854  data  stage_04_tile_set_59_script_1             # vram script, tile set 59 of stage 4 (list 140B54[1] of 2): page 0B
1478E9  data  stage_04_tile_set_5a_script               # vram script, tile set 5A of stage 4 (list 140B60): page 01
147956  data  stage_04_tile_set_5b_script_0             # vram script, tile set 5B of stage 4 (list 140B68[0] of 2): page 03
147AC8  data  stage_04_tile_set_5b_script_1             # vram script, tile set 5B of stage 4 (list 140B68[1] of 2): page 0D
147AFE  data  stage_04_tile_set_5c_script_0             # vram script, tile set 5C of stage 4 (list 140B74[0] of 2): page 01
147C92  data  stage_04_tile_set_5d_script_0             # vram script, tile set 5D of stage 4 (list 140B80[0] of 3): page 03
147EFC  data  stage_04_tile_set_5d_script_1             # vram script, tile set 5D of stage 4 (list 140B80[1] of 3): page 0D
14806F  data  stage_04_tile_set_5e_script               # vram script, tile set 5E of stage 4 (list 140B90): page 02
1481E4  data  stage_04_tile_set_5f_script_0             # vram script, tile set 5F of stage 4 (list 140B98[0] of 2): page 00
14843F  data  stage_04_tile_set_5f_script_1             # vram script, tile set 5F of stage 4 (list 140B98[1] of 2): page 0A
14859F  data  stage_04_tile_set_60_script_0             # vram script, tile set 60 of stage 4 (list 140BA4[0] of 2): page 02
14863C  data  stage_04_tile_set_60_script_1             # vram script, tile set 60 of stage 4 (list 140BA4[1] of 2): page 0C
14872C  data  stage_04_tile_set_61_script               # vram script, tile set 61 of stage 4 (list 140BB0): page 01
1488A1  data  stage_04_tile_set_62_script_0             # vram script, tile set 62 of stage 4 (list 140BB8[0] of 3): page 03
148AFC  data  stage_04_tile_set_62_script_1             # vram script, tile set 62 of stage 4 (list 140BB8[1] of 3): page 0D
148C5C  data  stage_04_tile_set_63_script_0             # vram script, tile set 63 of stage 4 (list 140BC8[0] of 2): page 01
148CF9  data  stage_04_tile_set_63_script_1             # vram script, tile set 63 of stage 4 (list 140BC8[1] of 2): page 0B
148DE9  data  stage_04_tile_set_64_script_0             # vram script, tile set 64 of stage 4 (list 140BD4[0] of 2): page 03
149009  data  stage_04_tile_set_64_script_1             # vram script, tile set 64 of stage 4 (list 140BD4[1] of 2): page 0D
149047  data  stage_04_tile_set_5d_script_2             # vram script, tile set 5D of stage 4 (list 140B80[2] of 3): page 12
1490FD  data  stage_04_tile_set_14_script               # vram script, tile set 14 of stage 4 (list 140BE0): page 02
14938C  data  stage_04_tile_set_15_script               # vram script, tile set 15 of stage 4 (list 140BE8): page 00
1494C2  data  stage_04_tile_set_16_script               # vram script, tile set 16 of stage 4 (list 140BF0): page 03
149545  data  stage_04_tile_set_17_script               # vram script, tile set 17 of stage 4 (list 140BF8): page 01
1495A2  data  stage_04_tile_set_18_script               # vram script, tile set 18 of stage 4 (list 140C00): page 02
1495FB  data  stage_04_tile_set_19_script               # vram script, tile set 19 of stage 4 (list 140C08): page 00
14966A  data  stage_04_tile_set_1a_script               # vram script, tile set 1A of stage 4 (list 140C10): page 02
1496C4  data  stage_04_tile_set_1b_script               # vram script, tile set 1B of stage 4 (list 140C18): page 01
149732  data  stage_04_tile_set_1c_script               # vram script, tile set 1C of stage 4 (list 140C20): page 03
14980D  data  stage_04_tile_set_1d_script               # vram script, tile set 1D of stage 4 (list 140C28): page 01
14985F  data  stage_04_tile_set_1e_script               # vram script, tile set 1E of stage 4 (list 140C30): page 02
14988D  data  stage_04_tile_set_1f_script               # vram script, tile set 1F of stage 4 (list 140C38): page 00
14993F  data  stage_04_tile_set_20_script               # vram script, tile set 20 of stage 4 (list 140C40): page 01
149962  data  stage_04_tile_set_21_script               # vram script, tile set 21 of stage 4 (list 140C48): page 03
149A14  data  stage_04_tile_set_22_script               # vram script, tile set 22 of stage 4 (list 140C50): page 01
149A8D  data  stage_04_tile_set_23_script               # vram script, tile set 23 of stage 4 (list 140C58): page 03
149B9F  data  stage_04_tile_set_24_script               # vram script, tile set 24 of stage 4 (list 140C60): page 02
149DD6  data  stage_04_tile_set_25_script_0             # vram script, tile set 25 of stage 4 (list 140C68[0] of 2): page 00
14A046  data  stage_04_tile_set_26_script_0             # vram script, tile set 26 of stage 4 (list 140C74[0] of 2): page 02
14A2B1  data  stage_04_tile_set_27_script_0             # vram script, tile set 27 of stage 4 (list 140C80[0] of 2): page 00
14A342  data  stage_04_tile_set_28_script_0             # vram script, tile set 28 of stage 4 (list 140C8C[0] of 2): page 02
14A391  data  stage_04_tile_set_29_script               # vram script, tile set 29 of stage 4 (list 140C98): page 00
14A3EB  data  stage_04_tile_set_2a_script               # vram script, tile set 2A of stage 4 (list 140CA0): page 01
14A560  data  stage_04_tile_set_2b_script_0             # vram script, tile set 2B of stage 4 (list 140CA8[0] of 2): page 03
14A629  data  stage_04_tile_set_2e_script               # vram script, tile set 2E of stage 4 (list 140CC0): page 00
14A69C  data  stage_04_tile_set_2f_script               # vram script, tile set 2F of stage 4 (list 140CC8): page 02
14A790  data  stage_04_tile_set_30_script               # vram script, tile set 30 of stage 4 (list 140CD0): page 03
14A7EA  data  stage_04_tile_set_31_script               # vram script, tile set 31 of stage 4 (list 140CD8): page 01
14A8EC  data  stage_04_tile_set_32_script               # vram script, tile set 32 of stage 4 (list 140CE0): page 00
14A95A  data  stage_04_tile_set_33_script               # vram script, tile set 33 of stage 4 (list 140CE8): page 02
14A9CC  data  stage_04_tile_set_34_script               # vram script, tile set 34 of stage 4 (list 140CF0): page 03
14AA55  data  stage_04_tile_set_35_script               # vram script, tile set 35 of stage 4 (list 140CF8): page 01
14AB66  data  stage_04_tile_set_36_script               # vram script, tile set 36 of stage 4 (list 140D00): page 00
14ABC5  data  stage_04_tile_set_37_script               # vram script, tile set 37 of stage 4 (list 140D08): page 02
14AD00  data  stage_04_tile_set_38_script               # vram script, tile set 38 of stage 4 (list 140D10): page 03
14AE78  data  stage_04_tile_set_39_script               # vram script, tile set 39 of stage 4 (list 140D18): page 01
14B22F  data  stage_04_tile_set_3b_script               # vram script, tile set 3B of stage 4 (list 140D24): page 03
14B2A2  data  stage_04_tile_set_3c_script               # vram script, tile set 3C of stage 4 (list 140D2C): page 01
14B3D2  data  stage_04_tile_set_3d_script               # vram script, tile set 3D of stage 4 (list 140D34): page 02
14B487  data  stage_04_tile_set_3e_script               # vram script, tile set 3E of stage 4 (list 140D3C): page 00
14B5AE  data  stage_04_tile_set_40_script               # vram script, tile set 40 of stage 4 (list 140D4C): page 00
14B5FD  data  stage_04_tile_set_41_script               # vram script, tile set 41 of stage 4 (list 140D54): page 02
14B68E  data  stage_09_tile_set_14_script_0             # vram script, tile set 14 of stage 9 (list 140D5C[0] of 3): page 02
14B6E9  data  stage_09_tile_set_14_script_1             # vram script, tile set 14 of stage 9 (list 140D5C[1] of 3): page 11
14B8CE  data  stage_09_tile_set_14_script_2             # vram script, tile set 14 of stage 9 (list 140D5C[2] of 3): page 0C
14B8E1  data  stage_09_tile_set_15_script_0             # vram script, tile set 15 of stage 9 (list 140D6C[0] of 3): page 00
14BAC6  data  stage_09_tile_set_15_script_1             # vram script, tile set 15 of stage 9 (list 140D6C[1] of 3): page 0F
14BD02  data  stage_09_tile_set_15_script_2             # vram script, tile set 15 of stage 9 (list 140D6C[2] of 3): page 0A
14BE20  data  stage_09_tile_set_16_script_0             # vram script, tile set 16 of stage 9 (list 140D7C[0] of 3): page 02
14BE6A  data  stage_09_tile_set_16_script_1             # vram script, tile set 16 of stage 9 (list 140D7C[1] of 3): page 11
14BEE7  data  stage_09_tile_set_16_script_2             # vram script, tile set 16 of stage 9 (list 140D7C[2] of 3): page 0C
14BFDE  data  stage_09_tile_set_17_script_1             # vram script, tile set 17 of stage 9 (list 140D8C[1] of 3): page 12
14C025  data  stage_09_tile_set_18_script_0             # vram script, tile set 18 of stage 9 (list 140D9C[0] of 3): page 01
14C062  data  stage_09_tile_set_18_script_1             # vram script, tile set 18 of stage 9 (list 140D9C[1] of 3): page 10
14C1F3  data  stage_09_tile_set_19_script_0             # vram script, tile set 19 of stage 9 (list 140DAC[0] of 3): page 03
14C341  data  stage_09_tile_set_19_script_1             # vram script, tile set 19 of stage 9 (list 140DAC[1] of 3): page 12
14C557  data  stage_09_tile_set_19_script_2             # vram script, tile set 19 of stage 9 (list 140DAC[2] of 3): page 0D
14C5F5  data  stage_09_tile_set_1a_script_0             # vram script, tile set 1A of stage 9 (list 140DBC[0] of 3): page 01
14C653  data  stage_09_tile_set_1a_script_1             # vram script, tile set 1A of stage 9 (list 140DBC[1] of 3): page 10
14C6DC  data  stage_09_tile_set_1a_script_2             # vram script, tile set 1A of stage 9 (list 140DBC[2] of 3): page 0B
14C7CF  data  stage_09_tile_set_1b_script_0             # vram script, tile set 1B of stage 9 (list 140DCC[0] of 3): page 02
14C805  data  stage_09_tile_set_1b_script_1             # vram script, tile set 1B of stage 9 (list 140DCC[1] of 3): page 11
14C918  data  stage_09_tile_set_1c_script_0             # vram script, tile set 1C of stage 9 (list 140DDC[0] of 3): page 00
14CAC0  data  stage_09_tile_set_1c_script_1             # vram script, tile set 1C of stage 9 (list 140DDC[1] of 3): page 0F
14CCF0  data  stage_09_tile_set_1c_script_2             # vram script, tile set 1C of stage 9 (list 140DDC[2] of 3): page 0A
14CD8E  data  stage_09_tile_set_1d_script_0             # vram script, tile set 1D of stage 9 (list 140DEC[0] of 3): page 02
14CDF0  data  stage_09_tile_set_1d_script_1             # vram script, tile set 1D of stage 9 (list 140DEC[1] of 3): page 11
14CED3  data  stage_09_tile_set_1d_script_2             # vram script, tile set 1D of stage 9 (list 140DEC[2] of 3): page 0C
14CFA3  data  stage_09_tile_set_1e_script_0             # vram script, tile set 1E of stage 9 (list 140DFC[0] of 3): page 01
14CFD9  data  stage_09_tile_set_1e_script_1             # vram script, tile set 1E of stage 9 (list 140DFC[1] of 3): page 10
14D0F6  data  stage_09_tile_set_1f_script_0             # vram script, tile set 1F of stage 9 (list 140E0C[0] of 3): page 03
14D285  data  stage_09_tile_set_1f_script_1             # vram script, tile set 1F of stage 9 (list 140E0C[1] of 3): page 12
14D4D0  data  stage_09_tile_set_1f_script_2             # vram script, tile set 1F of stage 9 (list 140E0C[2] of 3): page 0D
14D56B  data  stage_09_tile_set_20_script_0             # vram script, tile set 20 of stage 9 (list 140E1C[0] of 3): page 01
14D5C9  data  stage_09_tile_set_20_script_1             # vram script, tile set 20 of stage 9 (list 140E1C[1] of 3): page 10
14D63E  data  stage_09_tile_set_20_script_2             # vram script, tile set 20 of stage 9 (list 140E1C[2] of 3): page 0B
14D6F0  data  stage_09_tile_set_21_script_0             # vram script, tile set 21 of stage 9 (list 140E2C[0] of 3): page 02
14D726  data  stage_09_tile_set_21_script_1             # vram script, tile set 21 of stage 9 (list 140E2C[1] of 3): page 11
14D843  data  stage_09_tile_set_22_script_0             # vram script, tile set 22 of stage 9 (list 140E3C[0] of 3): page 00
14D9D2  data  stage_09_tile_set_22_script_1             # vram script, tile set 22 of stage 9 (list 140E3C[1] of 3): page 0F
14DC1D  data  stage_09_tile_set_22_script_2             # vram script, tile set 22 of stage 9 (list 140E3C[2] of 3): page 0A
14DCB8  data  stage_09_tile_set_23_script_0             # vram script, tile set 23 of stage 9 (list 140E4C[0] of 3): page 02
14DD16  data  stage_09_tile_set_23_script_2             # vram script, tile set 23 of stage 9 (list 140E4C[2] of 3): page 0C
14DDC8  data  stage_09_tile_set_23_script_1             # vram script, tile set 23 of stage 9 (list 140E4C[1] of 3): page 11
14DE3D  data  stage_09_tile_set_24_script_0             # vram script, tile set 24 of stage 9 (list 140E5C[0] of 3): page 02
14DE73  data  stage_09_tile_set_24_script_1             # vram script, tile set 24 of stage 9 (list 140E5C[1] of 3): page 11
14DFF5  data  stage_09_tile_set_25_script_0             # vram script, tile set 25 of stage 9 (list 140E6C[0] of 3): page 00
14E0A9  data  stage_09_tile_set_25_script_1             # vram script, tile set 25 of stage 9 (list 140E6C[1] of 3): page 0F
14E334  data  stage_09_tile_set_25_script_2             # vram script, tile set 25 of stage 9 (list 140E6C[2] of 3): page 0A
14E3ED  data  stage_09_tile_set_26_script_1             # vram script, tile set 26 of stage 9 (list 140E7C[1] of 3): page 10
14E4FC  data  stage_09_tile_set_27_script_1             # vram script, tile set 27 of stage 9 (list 140E8C[1] of 3): page 12
14E7CB  data  stage_09_tile_set_28_script_1             # vram script, tile set 28 of stage 9 (list 140E9C[1] of 3): page 10
14EAE2  data  stage_09_tile_set_29_script_1             # vram script, tile set 29 of stage 9 (list 140EAC[1] of 3): page 11
14EBF1  data  stage_09_tile_set_2a_script_1             # vram script, tile set 2A of stage 9 (list 140EBC[1] of 3): page 0F
14EEC0  data  stage_09_tile_set_2b_script_1             # vram script, tile set 2B of stage 9 (list 140ECC[1] of 3): page 11
14F1D7  data  stage_09_tile_set_2c_script_0             # vram script, tile set 2C of stage 9 (list 140EDC[0] of 3): page 03
14F1E7  data  stage_09_tile_set_2c_script_1             # vram script, tile set 2C of stage 9 (list 140EDC[1] of 3): page 12
14F43B  data  stage_09_tile_set_2d_script_0             # vram script, tile set 2D of stage 9 (list 140EEC[0] of 3): page 01
14F652  data  stage_09_tile_set_2d_script_1             # vram script, tile set 2D of stage 9 (list 140EEC[1] of 3): page 10
14F96A  data  stage_09_tile_set_2d_script_2             # vram script, tile set 2D of stage 9 (list 140EEC[2] of 3): page 0B
14F9F0  data  stage_09_tile_set_2e_script_0             # vram script, tile set 2E of stage 9 (list 140EFC[0] of 3): page 03
14FA0B  data  stage_09_tile_set_2e_script_1             # vram script, tile set 2E of stage 9 (list 140EFC[1] of 3): page 12
14FBDD  data  stage_09_tile_set_2e_script_2             # vram script, tile set 2E of stage 9 (list 140EFC[2] of 3): page 0D
14FC5D  data  stage_09_tile_set_2f_script_1             # vram script, tile set 2F of stage 9 (list 140F0C[1] of 3): page 11
14FC7F  data  stage_09_tile_set_30_script_0             # vram script, tile set 30 of stage 9 (list 140F1C[0] of 3): page 00
14FD3D  data  stage_09_tile_set_30_script_1             # vram script, tile set 30 of stage 9 (list 140F1C[1] of 3): page 0F
14FEF5  data  stage_09_tile_set_31_script_0             # vram script, tile set 31 of stage 9 (list 140F2C[0] of 3): page 02
15013A  data  stage_09_tile_set_31_script_1             # vram script, tile set 31 of stage 9 (list 140F2C[1] of 3): page 11
1503B5  data  stage_09_tile_set_31_script_2             # vram script, tile set 31 of stage 9 (list 140F2C[2] of 3): page 0C
150477  data  stage_09_tile_set_32_script_0             # vram script, tile set 32 of stage 9 (list 140F3C[0] of 3): page 00
150572  data  stage_09_tile_set_32_script_1             # vram script, tile set 32 of stage 9 (list 140F3C[1] of 3): page 0F
15065A  data  stage_09_tile_set_32_script_2             # vram script, tile set 32 of stage 9 (list 140F3C[2] of 3): page 0A
150745  data  stage_09_tile_set_33_script_0             # vram script, tile set 33 of stage 9 (list 140F4C[0] of 3): page 03
150882  data  stage_09_tile_set_33_script_1             # vram script, tile set 33 of stage 9 (list 140F4C[1] of 3): page 12
150B6F  data  stage_09_tile_set_34_script_0             # vram script, tile set 34 of stage 9 (list 140F5C[0] of 3): page 01
150E06  data  stage_09_tile_set_34_script_1             # vram script, tile set 34 of stage 9 (list 140F5C[1] of 3): page 10
151033  data  stage_09_tile_set_34_script_2             # vram script, tile set 34 of stage 9 (list 140F5C[2] of 3): page 0B
151187  data  stage_09_tile_set_35_script_0             # vram script, tile set 35 of stage 9 (list 140F6C[0] of 3): page 03
1511B4  data  stage_09_tile_set_35_script_1             # vram script, tile set 35 of stage 9 (list 140F6C[1] of 3): page 12
1511D8  data  stage_09_tile_set_35_script_2             # vram script, tile set 35 of stage 9 (list 140F6C[2] of 3): page 0D
151266  data  stage_09_tile_set_36_script_0             # vram script, tile set 36 of stage 9 (list 140F7C[0] of 3): page 02
151287  data  stage_09_tile_set_36_script_1             # vram script, tile set 36 of stage 9 (list 140F7C[1] of 3): page 11
1513AE  data  stage_09_tile_set_37_script_0             # vram script, tile set 37 of stage 9 (list 140F8C[0] of 3): page 00
15154B  data  stage_09_tile_set_37_script_1             # vram script, tile set 37 of stage 9 (list 140F8C[1] of 3): page 0F
15187A  data  stage_09_tile_set_37_script_2             # vram script, tile set 37 of stage 9 (list 140F8C[2] of 3): page 0A
1518AE  data  stage_09_tile_set_38_script_0             # vram script, tile set 38 of stage 9 (list 140F9C[0] of 4): page 02
151B34  data  stage_09_tile_set_38_script_1             # vram script, tile set 38 of stage 9 (list 140F9C[1] of 4): page 11
151D7D  data  stage_09_tile_set_38_script_2             # vram script, tile set 38 of stage 9 (list 140F9C[2] of 4): page 0C
151E7A  data  stage_09_tile_set_38_script_3             # vram script, tile set 38 of stage 9 (list 140F9C[3] of 4): page 07
151EB4  data  stage_09_tile_set_39_script_0             # vram script, tile set 39 of stage 9 (list 140FB0[0] of 3): page 00
151FC4  data  stage_09_tile_set_39_script_1             # vram script, tile set 39 of stage 9 (list 140FB0[1] of 3): page 0F
1520A6  data  stage_09_tile_set_39_script_2             # vram script, tile set 39 of stage 9 (list 140FB0[2] of 3): page 0A
152190  data  stage_09_tile_set_3a_script_0             # vram script, tile set 3A of stage 9 (list 140FC0[0] of 3): page 01
152372  data  stage_09_tile_set_3a_script_1             # vram script, tile set 3A of stage 9 (list 140FC0[1] of 3): page 10
152509  data  stage_09_tile_set_3a_script_2             # vram script, tile set 3A of stage 9 (list 140FC0[2] of 3): page 0B
15254D  data  stage_09_tile_set_3b_script_0             # vram script, tile set 3B of stage 9 (list 140FD0[0] of 3): page 03
15273A  data  stage_09_tile_set_3b_script_1             # vram script, tile set 3B of stage 9 (list 140FD0[1] of 3): page 12
1528B2  data  stage_09_tile_set_3b_script_2             # vram script, tile set 3B of stage 9 (list 140FD0[2] of 3): page 0D
1529B0  data  stage_09_tile_set_3c_script_0             # vram script, tile set 3C of stage 9 (list 140FE0[0] of 3): page 01
1529FD  data  stage_09_tile_set_3c_script_1             # vram script, tile set 3C of stage 9 (list 140FE0[1] of 3): page 10
152A41  data  stage_09_tile_set_3c_script_2             # vram script, tile set 3C of stage 9 (list 140FE0[2] of 3): page 0B
152AD3  data  stage_09_tile_set_3d_script_0             # vram script, tile set 3D of stage 9 (list 140FF0[0] of 3): page 02
152B68  data  stage_09_tile_set_3d_script_1             # vram script, tile set 3D of stage 9 (list 140FF0[1] of 3): page 11
152BD0  data  stage_09_tile_set_3e_script_0             # vram script, tile set 3E of stage 9 (list 141000[0] of 3): page 00
152E4E  data  stage_09_tile_set_3e_script_1             # vram script, tile set 3E of stage 9 (list 141000[1] of 3): page 0F
1530A5  data  stage_09_tile_set_3e_script_2             # vram script, tile set 3E of stage 9 (list 141000[2] of 3): page 0A
15317E  data  stage_09_tile_set_3f_script_0             # vram script, tile set 3F of stage 9 (list 141010[0] of 4): page 02
1531CE  data  stage_09_tile_set_3f_script_1             # vram script, tile set 3F of stage 9 (list 141010[1] of 4): page 11
153233  data  stage_09_tile_set_3f_script_2             # vram script, tile set 3F of stage 9 (list 141010[2] of 4): page 0C
1532D8  data  stage_09_tile_set_40_script_0             # vram script, tile set 40 of stage 9 (list 141024[0] of 3): page 01
153341  data  stage_09_tile_set_40_script_1             # vram script, tile set 40 of stage 9 (list 141024[1] of 3): page 10
15337A  data  stage_09_tile_set_40_script_2             # vram script, tile set 40 of stage 9 (list 141024[2] of 3): page 0B
153386  data  stage_09_tile_set_41_script_0             # vram script, tile set 41 of stage 9 (list 141034[0] of 3): page 03
1534FE  data  stage_09_tile_set_41_script_1             # vram script, tile set 41 of stage 9 (list 141034[1] of 3): page 12
15364F  data  stage_09_tile_set_41_script_2             # vram script, tile set 41 of stage 9 (list 141034[2] of 3): page 0D
15373A  data  stage_09_tile_set_42_script_0             # vram script, tile set 42 of stage 9 (list 141044[0] of 3): page 01
15378A  data  stage_09_tile_set_42_script_1             # vram script, tile set 42 of stage 9 (list 141044[1] of 3): page 10
1537EF  data  stage_09_tile_set_42_script_2             # vram script, tile set 42 of stage 9 (list 141044[2] of 3): page 0B
153894  data  stage_09_tile_set_43_script_0             # vram script, tile set 43 of stage 9 (list 141054[0] of 3): page 02
1538FD  data  stage_09_tile_set_43_script_1             # vram script, tile set 43 of stage 9 (list 141054[1] of 3): page 11
153936  data  stage_09_tile_set_43_script_2             # vram script, tile set 43 of stage 9 (list 141054[2] of 3): page 0C
153942  data  stage_09_tile_set_44_script_0             # vram script, tile set 44 of stage 9 (list 141064[0] of 3): page 00
153ABA  data  stage_09_tile_set_44_script_1             # vram script, tile set 44 of stage 9 (list 141064[1] of 3): page 0F
153C0B  data  stage_09_tile_set_44_script_2             # vram script, tile set 44 of stage 9 (list 141064[2] of 3): page 0A
153CF6  data  stage_09_tile_set_45_script_0             # vram script, tile set 45 of stage 9 (list 141074[0] of 3): page 02
153D46  data  stage_09_tile_set_45_script_1             # vram script, tile set 45 of stage 9 (list 141074[1] of 3): page 11
153DAB  data  stage_09_tile_set_45_script_2             # vram script, tile set 45 of stage 9 (list 141074[2] of 3): page 0C
153E50  data  stage_09_tile_set_46_script_0             # vram script, tile set 46 of stage 9 (list 141084[0] of 3): page 00
153F7F  data  stage_09_tile_set_46_script_1             # vram script, tile set 46 of stage 9 (list 141084[1] of 3): page 0F
1540AE  data  stage_09_tile_set_46_script_2             # vram script, tile set 46 of stage 9 (list 141084[2] of 3): page 0A
1541C3  data  stage_09_tile_set_47_script_0             # vram script, tile set 47 of stage 9 (list 141094[0] of 3): page 03
154245  data  stage_09_tile_set_47_script_1             # vram script, tile set 47 of stage 9 (list 141094[1] of 3): page 12
1542F1  data  stage_09_tile_set_48_script_0             # vram script, tile set 48 of stage 9 (list 1410A4[0] of 3): page 01
154402  data  stage_09_tile_set_48_script_1             # vram script, tile set 48 of stage 9 (list 1410A4[1] of 3): page 10
1544E7  data  stage_09_tile_set_48_script_2             # vram script, tile set 48 of stage 9 (list 1410A4[2] of 3): page 0B
1545E2  data  stage_0a_tile_set_14_script               # vram script, tile set 14 of stage A (list 1410B4): page 00
1546FD  data  stage_0a_tile_set_15_script               # vram script, tile set 15 of stage A (list 1410BC): page 02
154786  data  stage_0a_tile_set_16_script               # vram script, tile set 16 of stage A (list 1410C4): page 03
1548A1  data  stage_0a_tile_set_17_script               # vram script, tile set 17 of stage A (list 1410CC): page 01
15492A  data  stage_0a_tile_set_18_script               # vram script, tile set 18 of stage A (list 1410D4): page 03
1549E3  data  stage_0a_tile_set_19_script_0             # vram script, tile set 19 of stage A (list 1410DC[0] of 3): page 02
154B79  data  stage_0a_tile_set_19_script_1             # vram script, tile set 19 of stage A (list 1410DC[1] of 3): page 11
154C71  data  stage_0a_tile_set_1a_script_1             # vram script, tile set 1A of stage A (list 1410EC[1] of 2): page 12
154DCD  data  stage_0a_tile_set_1b_script               # vram script, tile set 1B of stage A (list 1410F8): page 00
154EB9  data  stage_0a_tile_set_1c_script_0             # vram script, tile set 1C of stage A (list 141100[0] of 2): page 01
155043  data  stage_0a_tile_set_1c_script_1             # vram script, tile set 1C of stage A (list 141100[1] of 2): page 10
15521F  data  stage_0a_tile_set_1d_script_1             # vram script, tile set 1D of stage A (list 14110C[1] of 2): page 0F
15535B  data  stage_0a_tile_set_1e_script_1             # vram script, tile set 1E of stage A (list 141118[1] of 3): page 11
155397  data  stage_0a_tile_set_1f_script_0             # vram script, tile set 1F of stage A (list 141128[0] of 3): page 03
1554E8  data  stage_0a_tile_set_1f_script_1             # vram script, tile set 1F of stage A (list 141128[1] of 3): page 12
155621  data  stage_0a_tile_set_20_script_0             # vram script, tile set 20 of stage A (list 141138[0] of 3): page 02
155718  data  stage_0a_tile_set_20_script_1             # vram script, tile set 20 of stage A (list 141138[1] of 3): page 11
1558F4  data  stage_0a_tile_set_21_script_0             # vram script, tile set 21 of stage A (list 141148[0] of 2): page 00
155996  data  stage_0a_tile_set_21_script_1             # vram script, tile set 21 of stage A (list 141148[1] of 2): page 0F
155B5F  data  stage_0a_tile_set_22_script_0             # vram script, tile set 22 of stage A (list 141154[0] of 3): page 01
155C6E  data  stage_0a_tile_set_22_script_1             # vram script, tile set 22 of stage A (list 141154[1] of 3): page 10
155CBB  data  stage_0a_tile_set_23_script_0             # vram script, tile set 23 of stage A (list 141164[0] of 2): page 00
155CF2  data  stage_0a_tile_set_24_script_0             # vram script, tile set 24 of stage A (list 141170[0] of 2): page 02
155DCF  data  stage_0a_tile_set_24_script_1             # vram script, tile set 24 of stage A (list 141170[1] of 2): page 11
155E26  data  stage_0a_tile_set_25_script_0             # vram script, tile set 25 of stage A (list 14117C[0] of 3): page 03
155E5D  data  stage_0a_tile_set_26_script_1             # vram script, tile set 26 of stage A (list 14118C[1] of 3): page 10
1560B5  data  stage_0a_tile_set_27_script_1             # vram script, tile set 27 of stage A (list 14119C[1] of 3): page 12
156236  data  stage_0a_tile_set_28_script_1             # vram script, tile set 28 of stage A (list 1411AC[1] of 2): page 0F
1563B7  data  stage_0a_tile_set_29_script_1             # vram script, tile set 29 of stage A (list 1411B8[1] of 2): page 11
156538  data  stage_0a_tile_set_2a_script               # vram script, tile set 2A of stage A (list 1411C4): page 0F
156832  data  stage_0a_tile_set_2b_script               # vram script, tile set 2B of stage A (list 1411CC): page 11
156B2C  data  stage_0a_tile_set_2d_script               # vram script, tile set 2D of stage A (list 1411D8): page 10
156E26  data  stage_0a_tile_set_2e_script_0             # vram script, tile set 2E of stage A (list 1411E0[0] of 2): page 01
156E92  data  stage_0a_tile_set_2e_script_1             # vram script, tile set 2E of stage A (list 1411E0[1] of 2): page 10
15705F  data  stage_0a_tile_set_2f_script               # vram script, tile set 2F of stage A (list 1411EC): page 12
1570EC  data  stage_0a_tile_set_30_script_0             # vram script, tile set 30 of stage A (list 1411F4[0] of 2): pages 0A 00
157246  data  stage_0a_tile_set_30_script_1             # vram script, tile set 30 of stage A (list 1411F4[1] of 2): page 0F
1572A0  data  stage_0a_tile_set_31_script               # vram script, tile set 31 of stage A (list 141200): page 11
1572FD  data  stage_0a_tile_set_32_script_0             # vram script, tile set 32 of stage A (list 141208[0] of 2): page 02
157357  data  stage_0a_tile_set_33_script_0             # vram script, tile set 33 of stage A (list 141214[0] of 2): page 03
15754D  data  stage_0a_tile_set_34_script_0             # vram script, tile set 34 of stage A (list 141220[0] of 2): page 02
15760E  data  stage_0a_tile_set_35_script_0             # vram script, tile set 35 of stage A (list 14122C[0] of 2): page 01
157668  data  stage_0a_tile_set_36_script_0             # vram script, tile set 36 of stage A (list 141238[0] of 2): page 00
157807  data  stage_0a_tile_set_37_script_0             # vram script, tile set 37 of stage A (list 141244[0] of 2): page 01
1578B4  data  stage_0a_tile_set_38_script_0             # vram script, tile set 38 of stage A (list 141250[0] of 2): page 03
157984  data  stage_0a_tile_set_38_script_1             # vram script, tile set 38 of stage A (list 141250[1] of 2): page 12
1579D8  data  stage_0a_tile_set_39_script_0             # vram script, tile set 39 of stage A (list 14125C[0] of 2): page 02
157A63  data  stage_0a_tile_set_39_script_1             # vram script, tile set 39 of stage A (list 14125C[1] of 2): page 11
157B03  data  stage_0a_tile_set_3a_script_0             # vram script, tile set 3A of stage A (list 141268[0] of 2): pages 0D 03
157B7C  data  stage_0a_tile_set_3a_script_1             # vram script, tile set 3A of stage A (list 141268[1] of 2): page 12
157BEE  data  stage_0a_tile_set_3b_script_0             # vram script, tile set 3B of stage A (list 141274[0] of 2): page 01
157E70  data  stage_0a_tile_set_3b_script_1             # vram script, tile set 3B of stage A (list 141274[1] of 2): page 10
157F10  data  stage_0a_tile_set_3c_script_0             # vram script, tile set 3C of stage A (list 141280[0] of 2): page 00
158149  data  stage_0a_tile_set_3c_script_1             # vram script, tile set 3C of stage A (list 141280[1] of 2): page 0F
15822D  data  stage_0a_tile_set_3d_script_0             # vram script, tile set 3D of stage A (list 14128C[0] of 2): page 01
158363  data  stage_0a_tile_set_3d_script_1             # vram script, tile set 3D of stage A (list 14128C[1] of 2): page 10
15845A  data  stage_0a_tile_set_3e_script_0             # vram script, tile set 3E of stage A (list 141298[0] of 2): page 00
15850F  data  stage_0a_tile_set_3e_script_1             # vram script, tile set 3E of stage A (list 141298[1] of 2): page 0F
158563  data  stage_0a_tile_set_3f_script               # vram script, tile set 3F of stage A (list 1412A4): page 03
15866E  data  stage_0a_tile_set_40_script_0             # vram script, tile set 40 of stage A (list 1412AC[0] of 2): page 02
1587B7  data  stage_0a_tile_set_41_script_0             # vram script, tile set 41 of stage A (list 1412B8[0] of 2): page 03
1587D8  data  stage_0a_tile_set_41_script_1             # vram script, tile set 41 of stage A (list 1412B8[1] of 2): page 12
15880A  data  stage_0a_tile_set_42_script_0             # vram script, tile set 42 of stage A (list 1412C4[0] of 2): page 02
15899D  data  stage_0a_tile_set_42_script_1             # vram script, tile set 42 of stage A (list 1412C4[1] of 2): page 11
1589FB  data  stage_0a_tile_set_43_script_0             # vram script, tile set 43 of stage A (list 1412D0[0] of 3): page 03
158B8A  data  stage_0a_tile_set_1a_script_0             # vram script, tile set 1A of stage A (list 1410EC[0] of 2): page 03
158BAB  data  stage_0a_tile_set_1d_script_0             # vram script, tile set 1D of stage A (list 14110C[0] of 2): page 00
158BCC  data  stage_0a_tile_set_44_script               # vram script, tile set 44 of stage A (list 1412E0): page 01
158C08  data  stage_0a_tile_set_45_script               # vram script, tile set 45 of stage A (list 1412E8): page 02
158C44  data  stage_0a_tile_set_19_script_2             # vram script, tile set 19 of stage A (list 1410DC[2] of 3): page 0C
158D1D  data  stage_0a_tile_set_1f_script_2             # vram script, tile set 1F of stage A (list 141128[2] of 3): page 0D
158DC4  data  stage_0a_tile_set_22_script_2             # vram script, tile set 22 of stage A (list 141154[2] of 3): page 0B
15997A  data  stage_0b_tile_set_14_script               # vram script, tile set 14 of stages B, C, D, E, F (list 1412F0): page 03
159AB6  data  stage_0b_tile_set_15_script               # vram script, tile set 15 of stages B, C, D, E, F (list 1412F8): page 01
159D6D  data  stage_0b_tile_set_16_script               # vram script, tile set 16 of stages B, C, D, E, F (list 141300): page 00
159EBB  data  stage_0b_tile_set_17_script               # vram script, tile set 17 of stages B, C, D, E, F (list 141308): page 02
15A18B  data  stage_0b_tile_set_18_script               # vram script, tile set 18 of stages B, C, D, E, F (list 141310): page 00
15A2D4  data  stage_0b_tile_set_19_script_0             # vram script, tile set 19 of stages B, C, D, E, F (list 141318[0] of 2): page 01
15A58F  data  stage_0b_tile_set_19_script_1             # vram script, tile set 19 of stages B, C, D, E, F (list 141318[1] of 2): page 10
15A5B7  data  stage_0b_tile_set_1a_script_0             # vram script, tile set 1A of stages B, C, D, E, F (list 141324[0] of 2): page 03
15A7A3  data  stage_0b_tile_set_1a_script_1             # vram script, tile set 1A of stages B, C, D, E, F (list 141324[1] of 2): page 12
15A951  data  stage_0b_tile_set_1b_script_0             # vram script, tile set 1B of stages B, C, D, E, F (list 141330[0] of 2): page 00
15A95D  data  stage_0b_tile_set_1b_script_1             # vram script, tile set 1B of stages B, C, D, E, F (list 141330[1] of 2): page 0F
15A9BA  data  stage_0b_tile_set_1c_script_0             # vram script, tile set 1C of stages B, C, D, E, F (list 14133C[0] of 2): page 02
15AB0E  data  stage_0b_tile_set_1c_script_1             # vram script, tile set 1C of stages B, C, D, E, F (list 14133C[1] of 2): page 11
15ABCD  data  stage_0b_tile_set_1d_script_0             # vram script, tile set 1D of stages B, C, D, E, F (list 141348[0] of 2): page 01
15AC10  data  stage_0b_tile_set_1d_script_1             # vram script, tile set 1D of stages B, C, D, E, F (list 141348[1] of 2): page 10
15AD02  data  stage_0b_tile_set_1e_script_0             # vram script, tile set 1E of stages B, C, D, E, F (list 141354[0] of 2): page 03
15AF32  data  stage_0b_tile_set_1e_script_1             # vram script, tile set 1E of stages B, C, D, E, F (list 141354[1] of 2): page 12
15B01C  data  stage_0b_tile_set_1f_script_0             # vram script, tile set 1F of stages B, C, D, E, F (list 141360[0] of 2): page 00
15B288  data  stage_0b_tile_set_1f_script_1             # vram script, tile set 1F of stages B, C, D, E, F (list 141360[1] of 2): page 0F
15B3D6  data  stage_0b_tile_set_20_script_0             # vram script, tile set 20 of stages B, C, D, E, F (list 14136C[0] of 2): page 02
15B47B  data  stage_0b_tile_set_20_script_1             # vram script, tile set 20 of stages B, C, D, E, F (list 14136C[1] of 2): page 11
15B62B  data  stage_0b_tile_set_21_script_0             # vram script, tile set 21 of stages B, C, D, E, F (list 141378[0] of 2): page 01
15B740  data  stage_0b_tile_set_21_script_1             # vram script, tile set 21 of stages B, C, D, E, F (list 141378[1] of 2): page 10
15B905  data  stage_0b_tile_set_22_script_0             # vram script, tile set 22 of stages B, C, D, E, F (list 141384[0] of 2): page 00
15BA59  data  stage_0b_tile_set_22_script_1             # vram script, tile set 22 of stages B, C, D, E, F (list 141384[1] of 2): page 0F
15BB18  data  stage_0b_tile_set_23_script_0             # vram script, tile set 23 of stages B, C, D, E, F (list 141390[0] of 2): page 03
15BB5B  data  stage_0b_tile_set_23_script_1             # vram script, tile set 23 of stages B, C, D, E, F (list 141390[1] of 2): page 12
15BC4D  data  stage_0b_tile_set_24_script_0             # vram script, tile set 24 of stages B, C, D, E, F (list 14139C[0] of 2): page 01
15BE7D  data  stage_0b_tile_set_24_script_1             # vram script, tile set 24 of stages B, C, D, E, F (list 14139C[1] of 2): page 10
15BF67  data  stage_0b_tile_set_25_script_0             # vram script, tile set 25 of stages B, C, D, E, F (list 1413A8[0] of 2): page 02
15C1D3  data  stage_0b_tile_set_25_script_1             # vram script, tile set 25 of stages B, C, D, E, F (list 1413A8[1] of 2): page 11
15C321  data  stage_0b_tile_set_26_script_0             # vram script, tile set 26 of stages B, C, D, E, F (list 1413B4[0] of 2): page 00
15C3C6  data  stage_0b_tile_set_26_script_1             # vram script, tile set 26 of stages B, C, D, E, F (list 1413B4[1] of 2): page 0F
15C576  data  stage_0b_tile_set_27_script_0             # vram script, tile set 27 of stages B, C, D, E, F (list 1413C0[0] of 2): page 03
15C68B  data  stage_0b_tile_set_27_script_1             # vram script, tile set 27 of stages B, C, D, E, F (list 1413C0[1] of 2): page 12
15C850  data  stage_0b_tile_set_28_script_0             # vram script, tile set 28 of stages B, C, D, E, F (list 1413CC[0] of 2): page 01
15CB37  data  stage_0b_tile_set_28_script_1             # vram script, tile set 28 of stages B, C, D, E, F (list 1413CC[1] of 2): page 10
15CBC3  data  stage_0b_tile_set_29_script_0             # vram script, tile set 29 of stages B, C, D, E, F (list 1413D8[0] of 2): page 00
15CC6F  data  stage_0b_tile_set_2a_script_0             # vram script, tile set 2A of stages B, C, D, E, F (list 1413E4[0] of 2): page 02
15CCAC  data  stage_0b_tile_set_2b_script_0             # vram script, tile set 2B of stages B, C, D, E, F (list 1413F0[0] of 2): page 03
15CDAE  data  stage_0b_tile_set_2c_script_0             # vram script, tile set 2C of stages B, C, D, E, F (list 1413FC[0] of 2): page 01
15CDEB  data  stage_0b_tile_set_2d_script_0             # vram script, tile set 2D of stages B, C, D, E, F (list 141408[0] of 2): page 02
15D05A  data  stage_0b_tile_set_2d_script_1             # vram script, tile set 2D of stages B, C, D, E, F (list 141408[1] of 2): page 11
15D08B  data  stage_0b_tile_set_2e_script_0             # vram script, tile set 2E of stages B, C, D, E, F (list 141414[0] of 2): page 00
15D10D  data  stage_0b_tile_set_2f_script               # vram script, tile set 2F of stages B, C, D, E, F (list 141420): page 00
15D31B  data  stage_0b_tile_set_30_script               # vram script, tile set 30 of stages B, C, D, E, F (list 141428): page 02
15D4D4  data  stage_0b_tile_set_31_script               # vram script, tile set 31 of stages B, C, D, E, F (list 141430): page 01
15D5C9  data  stage_0b_tile_set_32_script               # vram script, tile set 32 of stages B, C, D, E, F (list 141438): page 03
15D9C6  data  stage_0b_tile_set_33_script               # vram script, tile set 33 of stages B, C, D, E, F (list 141440): page 01
15DC8F  data  stage_0b_tile_set_34_script               # vram script, tile set 34 of stages B, C, D, E, F (list 141448): page 02
15DE3B  data  stage_0b_tile_set_35_script               # vram script, tile set 35 of stages B, C, D, E, F (list 141450): page 00
15E1A6  data  stage_0b_tile_set_36_script               # vram script, tile set 36 of stages B, C, D, E, F (list 141458): page 02
15E358  data  stage_0b_tile_set_37_script               # vram script, tile set 37 of stages B, C, D, E, F (list 141460): page 01
15E4B9  data  stage_0b_tile_set_38_script               # vram script, tile set 38 of stages B, C, D, E, F (list 141468): page 03
15E8CE  data  stage_0b_tile_set_39_script               # vram script, tile set 39 of stages B, C, D, E, F (list 141470): page 01
15EAE4  data  stage_0b_tile_set_3a_script               # vram script, tile set 3A of stages B, C, D, E, F (list 141478): page 00
15ED1C  data  stage_0b_tile_set_3b_script               # vram script, tile set 3B of stages B, C, D, E, F (list 141480): page 02
15EECA  data  stage_0b_tile_set_3c_script               # vram script, tile set 3C of stages B, C, D, E, F (list 141488): page 00
15F0DE  data  stage_0b_tile_set_3d_script               # vram script, tile set 3D of stages B, C, D, E, F (list 141490): page 02
15F13D  data  stage_0b_tile_set_3e_script               # vram script, tile set 3E of stages B, C, D, E, F (list 141498): page 00
15F336  data  stage_0b_tile_set_3f_script               # vram script, tile set 3F of stages B, C, D, E, F (list 1414A0): page 02
15F461  data  stage_0b_tile_set_40_script               # vram script, tile set 40 of stages B, C, D, E, F (list 1414A8): page 01
15F4CA  data  stage_0b_tile_set_41_script               # vram script, tile set 41 of stages B, C, D, E, F (list 1414B0): page 03
15F6AC  data  stage_0b_tile_set_42_script               # vram script, tile set 42 of stages B, C, D, E, F (list 1414B8): page 01
15F81B  data  stage_0b_tile_set_43_script               # vram script, tile set 43 of stages B, C, D, E, F (list 1414C0): page 03
15F835  data  stage_0b_tile_set_44_script               # vram script, tile set 44 of stages B, C, D, E, F (list 1414C8): page 00
15F919  data  stage_0b_tile_set_45_script               # vram script, tile set 45 of stages B, C, D, E, F (list 1414D0): page 02
15FB36  data  stage_0b_tile_set_46_script               # vram script, tile set 46 of stages B, C, D, E, F (list 1414D8): page 00
15FB97  data  stage_0b_tile_set_47_script               # vram script, tile set 47 of stages B, C, D, E, F (list 1414E0): page 01
15FBB5  data  stage_0b_tile_set_48_script               # vram script, tile set 48 of stages B, C, D, E, F (list 1414E8): page 03
15FD6B  data  stage_0b_tile_set_49_script               # vram script, tile set 49 of stages B, C, D, E, F (list 1414F0): page 01
15FF9A  data  stage_0b_tile_set_4a_script               # vram script, tile set 4A of stages B, C, D, E, F (list 1414F8): page 03
15FFF7  data  stage_0b_tile_set_4b_script               # vram script, tile set 4B of stages B, C, D, E, F (list 141500): page 02
160015  data  stage_0b_tile_set_4c_script               # vram script, tile set 4C of stages B, C, D, E, F (list 141508): page 00
1601CB  data  stage_0b_tile_set_4d_script               # vram script, tile set 4D of stages B, C, D, E, F (list 141510): page 02
1603FA  data  stage_0b_tile_set_4e_script               # vram script, tile set 4E of stages B, C, D, E, F (list 141518): page 00
160457  data  stage_0b_tile_set_4f_script               # vram script, tile set 4F of stages B, C, D, E, F (list 141520): page 00
1606AC  data  stage_0b_tile_set_50_script               # vram script, tile set 50 of stages B, C, D, E, F (list 141528): page 02
1609CB  data  stage_0b_tile_set_51_script               # vram script, tile set 51 of stages B, C, D, E, F (list 141530): page 00
160CA9  data  stage_0b_tile_set_52_script               # vram script, tile set 52 of stages B, C, D, E, F (list 141538): page 02
160DC2  data  stage_0b_tile_set_53_script               # vram script, tile set 53 of stages B, C, D, E, F (list 141540): page 02
160E21  data  stage_0b_tile_set_54_script               # vram script, tile set 54 of stages B, C, D, E, F (list 141548): page 00
16111C  data  stage_0b_tile_set_55_script               # vram script, tile set 55 of stages B, C, D, E, F (list 141550): page 02
1612C7  data  stage_0b_tile_set_56_script               # vram script, tile set 56 of stages B, C, D, E, F (list 141558): page 01
161330  data  stage_0b_tile_set_57_script               # vram script, tile set 57 of stages B, C, D, E, F (list 141560): page 03
1615D3  data  stage_0b_tile_set_58_script               # vram script, tile set 58 of stages B, C, D, E, F (list 141568): page 01
161823  data  stage_0b_tile_set_59_script               # vram script, tile set 59 of stages B, C, D, E, F (list 141570): page 03
161849  data  stage_0b_tile_set_5a_script               # vram script, tile set 5A of stages B, C, D, E, F (list 141578): page 00
161977  data  stage_0b_tile_set_5b_script               # vram script, tile set 5B of stages B, C, D, E, F (list 141580): page 02
161CEB  data  stage_0b_tile_set_5c_script               # vram script, tile set 5C of stages B, C, D, E, F (list 141588): page 00
161DA2  data  stage_0b_tile_set_5d_script               # vram script, tile set 5D of stages B, C, D, E, F (list 141590): page 01
161DC0  data  stage_0b_tile_set_5e_script               # vram script, tile set 5E of stages B, C, D, E, F (list 141598): page 03
161FD5  data  stage_0b_tile_set_5f_script               # vram script, tile set 5F of stages B, C, D, E, F (list 1415A0): page 01
16233E  data  stage_0b_tile_set_60_script               # vram script, tile set 60 of stages B, C, D, E, F (list 1415A8): page 03
1623E0  data  stage_0b_tile_set_61_script               # vram script, tile set 61 of stages B, C, D, E, F (list 1415B0): page 02
1623FE  data  stage_0b_tile_set_62_script               # vram script, tile set 62 of stages B, C, D, E, F (list 1415B8): page 00
162613  data  stage_0b_tile_set_63_script               # vram script, tile set 63 of stages B, C, D, E, F (list 1415C0): page 02
16297C  data  stage_0b_tile_set_64_script               # vram script, tile set 64 of stages B, C, D, E, F (list 1415C8): page 00
162A1E  data  stage_0b_tile_set_65_script               # vram script, tile set 65 of stages B, C, D, E, F (list 1415D0): page 00
162C73  data  stage_0b_tile_set_66_script               # vram script, tile set 66 of stages B, C, D, E, F (list 1415D8): page 02
162F92  data  stage_0b_tile_set_67_script               # vram script, tile set 67 of stages B, C, D, E, F (list 1415E0): page 00
1632B1  data  stage_0b_tile_set_68_script               # vram script, tile set 68 of stages B, C, D, E, F (list 1415E8): page 02
163436  data  stage_0b_tile_set_69_script_0             # vram script, tile set 69 of stages B, C, D, E, F (list 1415F0[0] of 2): page 00
1634D4  data  stage_0b_tile_set_69_script_1             # vram script, tile set 69 of stages B, C, D, E, F (list 1415F0[1] of 2): page 0F
16360D  data  stage_10_tile_set_14_script               # vram script, tile set 14 of stage 10 (list 1415FC): page 0F
1636BF  data  stage_10_tile_set_16_script               # vram script, tile set 16 of stage 10 (list 141608): page 0F
1637D1  data  stage_10_tile_set_17_script               # vram script, tile set 17 of stage 10 (list 141610): page 11
1638C9  data  stage_10_tile_set_18_script               # vram script, tile set 18 of stage 10 (list 141618): page 0F
1639ED  data  stage_10_tile_set_19_script_1             # vram script, tile set 19 of stage 10 (list 141620[1] of 2): page 02
163A8E  data  stage_10_tile_set_19_script_0             # vram script, tile set 19 of stage 10 (list 141620[0] of 2): page 11
163CEC  data  stage_10_tile_set_1a_script_1             # vram script, tile set 1A of stage 10 (list 14162C[1] of 2): page 00
163F9A  data  stage_10_tile_set_1a_script_0             # vram script, tile set 1A of stage 10 (list 14162C[0] of 2): page 0F
164299  data  stage_10_tile_set_1b_script_1             # vram script, tile set 1B of stage 10 (list 141638[1] of 2): page 02
164312  data  stage_10_tile_set_1b_script_0             # vram script, tile set 1B of stage 10 (list 141638[0] of 2): page 11
164491  data  stage_10_tile_set_1c_script_1             # vram script, tile set 1C of stage 10 (list 141644[1] of 2): page 02
164532  data  stage_10_tile_set_1c_script_0             # vram script, tile set 1C of stage 10 (list 141644[0] of 2): page 11
16463A  data  stage_10_tile_set_1d_script_1             # vram script, tile set 1D of stage 10 (list 141650[1] of 2): page 01
1646DB  data  stage_10_tile_set_1d_script_0             # vram script, tile set 1D of stage 10 (list 141650[0] of 2): page 10
1647E3  data  stage_10_tile_set_1e_script_1             # vram script, tile set 1E of stage 10 (list 14165C[1] of 2): page 03
164A91  data  stage_10_tile_set_1e_script_0             # vram script, tile set 1E of stage 10 (list 14165C[0] of 2): page 12
164D90  data  stage_10_tile_set_1f_script_1             # vram script, tile set 1F of stage 10 (list 141668[1] of 2): page 01
164E09  data  stage_10_tile_set_1f_script_0             # vram script, tile set 1F of stage 10 (list 141668[0] of 2): page 10
164F88  data  stage_10_tile_set_20_script_1             # vram script, tile set 20 of stage 10 (list 141674[1] of 2): page 03
165236  data  stage_10_tile_set_20_script_0             # vram script, tile set 20 of stage 10 (list 141674[0] of 2): page 12
165557  data  stage_10_tile_set_21_script_1             # vram script, tile set 21 of stage 10 (list 141680[1] of 2): page 01
1655F8  data  stage_10_tile_set_21_script_0             # vram script, tile set 21 of stage 10 (list 141680[0] of 2): page 10
16573C  data  stage_10_tile_set_22_script_1             # vram script, tile set 22 of stage 10 (list 14168C[1] of 2): page 00
1658E9  data  stage_10_tile_set_22_script_0             # vram script, tile set 22 of stage 10 (list 14168C[0] of 2): page 0F
165AF5  data  stage_10_tile_set_23_script_1             # vram script, tile set 23 of stage 10 (list 141698[1] of 2): page 02
165B96  data  stage_10_tile_set_23_script_0             # vram script, tile set 23 of stage 10 (list 141698[0] of 2): page 11
165DBE  data  stage_10_tile_set_24_script_0             # vram script, tile set 24 of stage 10 (list 1416A4[0] of 2): page 0F
166048  data  stage_10_tile_set_25_script_0             # vram script, tile set 25 of stage 10 (list 1416B0[0] of 2): page 11
166310  data  stage_10_tile_set_26_script_0             # vram script, tile set 26 of stage 10 (list 1416BC[0] of 2): page 0F
16644D  data  stage_10_tile_set_26_script_1             # vram script, tile set 26 of stage 10 (list 1416BC[1] of 2): page 0A
1667B0  data  stage_10_tile_set_27_script_0             # vram script, tile set 27 of stage 10 (list 1416C8[0] of 2): page 0C
166A2C  data  stage_10_tile_set_28_script_0             # vram script, tile set 28 of stage 10 (list 1416D4[0] of 2): page 0B
166CA8  data  stage_10_tile_set_29_script_0             # vram script, tile set 29 of stage 10 (list 1416E0[0] of 3): page 10
166D96  data  stage_10_tile_set_29_script_1             # vram script, tile set 29 of stage 10 (list 1416E0[1] of 3): page 0B
166DE6  data  stage_10_tile_set_2a_script_0             # vram script, tile set 2A of stage 10 (list 1416F0[0] of 3): page 12
166F24  data  stage_10_tile_set_2a_script_1             # vram script, tile set 2A of stage 10 (list 1416F0[1] of 3): page 0D
167287  data  stage_10_tile_set_2b_script_0             # vram script, tile set 2B of stage 10 (list 141700[0] of 2): page 11
167375  data  stage_10_tile_set_2b_script_1             # vram script, tile set 2B of stage 10 (list 141700[1] of 2): page 0C
1673C5  data  stage_10_tile_set_2c_script_0             # vram script, tile set 2C of stage 10 (list 14170C[0] of 2): page 0F
167503  data  stage_10_tile_set_2c_script_1             # vram script, tile set 2C of stage 10 (list 14170C[1] of 2): page 0A
167784  data  stage_10_tile_set_2d_script_0             # vram script, tile set 2D of stage 10 (list 141718[0] of 2): page 0C
1679DC  data  stage_10_tile_set_2e_script_0             # vram script, tile set 2E of stage 10 (list 141724[0] of 2): page 0F
167BFF  data  stage_10_tile_set_2e_script_1             # vram script, tile set 2E of stage 10 (list 141724[1] of 2): page 0A
167D94  data  stage_10_tile_set_2f_script_0             # vram script, tile set 2F of stage 10 (list 141730[0] of 2): page 11
167DEF  data  stage_10_tile_set_2f_script_1             # vram script, tile set 2F of stage 10 (list 141730[1] of 2): page 0C
16816F  data  stage_10_tile_set_30_script_0             # vram script, tile set 30 of stage 10 (list 14173C[0] of 2): page 0A
16821C  data  stage_10_tile_set_31_script_0             # vram script, tile set 31 of stage 10 (list 141748[0] of 2): page 10
168242  data  stage_10_tile_set_32_script_0             # vram script, tile set 32 of stage 10 (list 141754[0] of 2): page 12
16847E  data  stage_10_tile_set_32_script_1             # vram script, tile set 32 of stage 10 (list 141754[1] of 2): page 0D
168613  data  stage_10_tile_set_33_script_0             # vram script, tile set 33 of stage 10 (list 141760[0] of 2): page 10
16866E  data  stage_10_tile_set_33_script_1             # vram script, tile set 33 of stage 10 (list 141760[1] of 2): page 0B
1689F6  data  stage_10_tile_set_34_script_0             # vram script, tile set 34 of stage 10 (list 14176C[0] of 2): page 0D
168B47  data  stage_10_tile_set_35_script_0             # vram script, tile set 35 of stage 10 (list 141778[0] of 2): page 0C
168D3A  data  stage_10_tile_set_36_script_0             # vram script, tile set 36 of stage 10 (list 141784[0] of 2): page 0F
168E84  data  stage_10_tile_set_36_script_1             # vram script, tile set 36 of stage 10 (list 141784[1] of 2): page 0A
168FDC  data  stage_10_tile_set_37_script_0             # vram script, tile set 37 of stage 10 (list 141790[0] of 2): page 11
169002  data  stage_08_tile_set_14_script_1             # vram script, tile set 14 of stage 8 (list 14179C[1] of 2): page 0F
169119  data  stage_08_tile_set_15_script_0             # vram script, tile set 15 of stage 8 (list 1417A8[0] of 2): page 02
169172  data  stage_08_tile_set_15_script_1             # vram script, tile set 15 of stage 8 (list 1417A8[1] of 2): page 11
1691F4  data  stage_08_tile_set_16_script_0             # vram script, tile set 16 of stage 8 (list 1417B4[0] of 2): page 00
169213  data  stage_08_tile_set_17_script_0             # vram script, tile set 17 of stage 8 (list 1417C0[0] of 2): page 10
16933C  data  stage_08_tile_set_18_script_0             # vram script, tile set 18 of stage 8 (list 1417CC[0] of 2): page 03
1693AA  data  stage_08_tile_set_18_script_1             # vram script, tile set 18 of stage 8 (list 1417CC[1] of 2): page 12
169613  data  stage_08_tile_set_19_script_0             # vram script, tile set 19 of stage 8 (list 1417D8[0] of 2): page 01
1697B9  data  stage_08_tile_set_19_script_1             # vram script, tile set 19 of stage 8 (list 1417D8[1] of 2): page 10
1699C6  data  stage_08_tile_set_1a_script_0             # vram script, tile set 1A of stage 8 (list 1417E4[0] of 2): page 03
169B31  data  stage_08_tile_set_1a_script_1             # vram script, tile set 1A of stage 8 (list 1417E4[1] of 2): page 12
169BA7  data  stage_08_tile_set_1b_script_0             # vram script, tile set 1B of stage 8 (list 1417F0[0] of 2): page 01
169BC6  data  stage_08_tile_set_1c_script_0             # vram script, tile set 1C of stage 8 (list 1417FC[0] of 2): page 11
169BE0  data  stage_08_tile_set_1d_script_0             # vram script, tile set 1D of stage 8 (list 141808[0] of 2): page 0F
169D4A  data  stage_08_tile_set_1e_script_0             # vram script, tile set 1E of stage 8 (list 141814[0] of 2): page 02
169DC8  data  stage_08_tile_set_1e_script_1             # vram script, tile set 1E of stage 8 (list 141814[1] of 2): page 11
16A0AF  data  stage_08_tile_set_1f_script_0             # vram script, tile set 1F of stage 8 (list 141820[0] of 2): page 00
16A255  data  stage_08_tile_set_1f_script_1             # vram script, tile set 1F of stage 8 (list 141820[1] of 2): page 0F
16A462  data  stage_08_tile_set_20_script_0             # vram script, tile set 20 of stage 8 (list 14182C[0] of 2): page 02
16A5CD  data  stage_08_tile_set_20_script_1             # vram script, tile set 20 of stage 8 (list 14182C[1] of 2): page 11
16A643  data  stage_08_tile_set_21_script_0             # vram script, tile set 21 of stage 8 (list 141838[0] of 2): page 12
16A65D  data  stage_08_tile_set_22_script_0             # vram script, tile set 22 of stage 8 (list 141844[0] of 2): page 10
16A7C7  data  stage_08_tile_set_23_script_0             # vram script, tile set 23 of stage 8 (list 141850[0] of 2): page 03
16A845  data  stage_08_tile_set_23_script_1             # vram script, tile set 23 of stage 8 (list 141850[1] of 2): page 12
16AB2C  data  stage_08_tile_set_24_script_0             # vram script, tile set 24 of stage 8 (list 14185C[0] of 2): page 02
16ABAA  data  stage_08_tile_set_24_script_1             # vram script, tile set 24 of stage 8 (list 14185C[1] of 2): page 11
16AE85  data  stage_08_tile_set_25_script_0             # vram script, tile set 25 of stage 8 (list 141868[0] of 2): page 00
16AFDC  data  stage_08_tile_set_25_script_1             # vram script, tile set 25 of stage 8 (list 141868[1] of 2): page 0F
16B1F5  data  stage_08_tile_set_26_script_0             # vram script, tile set 26 of stage 8 (list 141874[0] of 2): page 02
16B334  data  stage_08_tile_set_26_script_1             # vram script, tile set 26 of stage 8 (list 141874[1] of 2): page 11
16B4B3  data  stage_08_tile_set_27_script_0             # vram script, tile set 27 of stage 8 (list 141880[0] of 2): page 00
16B568  data  stage_08_tile_set_27_script_1             # vram script, tile set 27 of stage 8 (list 141880[1] of 2): page 0F
16B6FB  data  stage_08_tile_set_28_script_0             # vram script, tile set 28 of stage 8 (list 14188C[0] of 2): page 02
16B7B0  data  stage_08_tile_set_28_script_1             # vram script, tile set 28 of stage 8 (list 14188C[1] of 2): page 11
16B943  data  stage_08_tile_set_29_script_0             # vram script, tile set 29 of stage 8 (list 141898[0] of 2): page 10
16BA6D  data  stage_08_tile_set_2a_script_0             # vram script, tile set 2A of stage 8 (list 1418A4[0] of 2): page 03
16BAAB  data  stage_08_tile_set_2a_script_1             # vram script, tile set 2A of stage 8 (list 1418A4[1] of 2): page 12
16BEAA  data  stage_08_tile_set_2b_script_0             # vram script, tile set 2B of stage 8 (list 1418B0[0] of 2): page 01
16BEFE  data  stage_08_tile_set_2b_script_1             # vram script, tile set 2B of stage 8 (list 1418B0[1] of 2): page 10
16C31D  data  stage_08_tile_set_2d_script_0             # vram script, tile set 2D of stage 8 (list 1418C0[0] of 2): page 10
16C719  data  stage_08_tile_set_2e_script_0             # vram script, tile set 2E of stage 8 (list 1418CC[0] of 2): page 12
16CB15  data  stage_08_tile_set_2f_script_0             # vram script, tile set 2F of stage 8 (list 1418D8[0] of 2): page 10
16CE31  data  stage_08_tile_set_30_script_0             # vram script, tile set 30 of stage 8 (list 1418E4[0] of 2): page 12
16CFCF  data  stage_08_tile_set_31_script_0             # vram script, tile set 31 of stage 8 (list 1418F0[0] of 2): page 11
16D059  data  stage_08_tile_set_32_script_0             # vram script, tile set 32 of stage 8 (list 1418FC[0] of 2): page 00
16D093  data  stage_08_tile_set_32_script_1             # vram script, tile set 32 of stage 8 (list 1418FC[1] of 2): page 0F
16D402  data  stage_08_tile_set_33_script_0             # vram script, tile set 33 of stage 8 (list 141908[0] of 2): page 02
16D614  data  stage_08_tile_set_33_script_1             # vram script, tile set 33 of stage 8 (list 141908[1] of 2): page 11
16D800  data  stage_08_tile_set_34_script_0             # vram script, tile set 34 of stage 8 (list 141914[0] of 2): page 00
16D84E  data  stage_08_tile_set_35_script_0             # vram script, tile set 35 of stage 8 (list 141920[0] of 2): page 10
16D8D8  data  stage_08_tile_set_36_script_0             # vram script, tile set 36 of stage 8 (list 14192C[0] of 2): page 03
16D915  data  stage_08_tile_set_36_script_1             # vram script, tile set 36 of stage 8 (list 14192C[1] of 2): page 12
16DC84  data  stage_08_tile_set_37_script_0             # vram script, tile set 37 of stage 8 (list 141938[0] of 2): page 01
16DE96  data  stage_08_tile_set_37_script_1             # vram script, tile set 37 of stage 8 (list 141938[1] of 2): page 10
16E082  data  stage_08_tile_set_38_script_0             # vram script, tile set 38 of stage 8 (list 141944[0] of 2): page 03
16E0D0  data  stage_08_tile_set_39_script_0             # vram script, tile set 39 of stage 8 (list 141950[0] of 2): page 0F
16E217  data  stage_08_tile_set_3a_script_0             # vram script, tile set 3A of stage 8 (list 14195C[0] of 2): page 02
16E46A  data  stage_08_tile_set_3a_script_1             # vram script, tile set 3A of stage 8 (list 14195C[1] of 2): page 11
16E62F  data  stage_08_tile_set_3b_script_0             # vram script, tile set 3B of stage 8 (list 141968[0] of 2): page 12
16E88E  data  stage_08_tile_set_3c_script_0             # vram script, tile set 3C of stage 8 (list 141974[0] of 2): page 01
16E99C  data  stage_08_tile_set_3c_script_1             # vram script, tile set 3C of stage 8 (list 141974[1] of 2): page 10
16ECD9  data  stage_08_tile_set_3d_script_0             # vram script, tile set 3D of stage 8 (list 141980[0] of 2): page 03
16EE6E  data  stage_08_tile_set_3d_script_1             # vram script, tile set 3D of stage 8 (list 141980[1] of 2): page 12
16F115  data  stage_08_tile_set_3e_script_0             # vram script, tile set 3E of stage 8 (list 14198C[0] of 2): page 01
16F2AA  data  stage_08_tile_set_3e_script_1             # vram script, tile set 3E of stage 8 (list 14198C[1] of 2): page 10
16F400  data  stage_08_tile_set_3f_script_0             # vram script, tile set 3F of stage 8 (list 141998[0] of 2): page 11
16F4B8  data  stage_08_tile_set_40_script_0             # vram script, tile set 40 of stage 8 (list 1419A4[0] of 2): page 00
16F54D  data  stage_08_tile_set_40_script_1             # vram script, tile set 40 of stage 8 (list 1419A4[1] of 2): page 0F
16F70B  data  stage_08_tile_set_41_script_0             # vram script, tile set 41 of stage 8 (list 1419B0[0] of 2): page 02
16F951  data  stage_08_tile_set_41_script_1             # vram script, tile set 41 of stage 8 (list 1419B0[1] of 2): page 11
16F9A0  data  stage_08_tile_set_42_script_0             # vram script, tile set 42 of stage 8 (list 1419BC[0] of 2): page 10
16FA54  data  stage_08_tile_set_43_script_0             # vram script, tile set 43 of stage 8 (list 1419C8[0] of 2): page 03
16FAE9  data  stage_08_tile_set_43_script_1             # vram script, tile set 43 of stage 8 (list 1419C8[1] of 2): page 12
16FC86  data  stage_08_tile_set_44_script_0             # vram script, tile set 44 of stage 8 (list 1419D4[0] of 2): page 01
16FECC  data  stage_08_tile_set_44_script_1             # vram script, tile set 44 of stage 8 (list 1419D4[1] of 2): page 10
16FF1B  data  stage_08_tile_set_45_script_0             # vram script, tile set 45 of stage 8 (list 1419E0[0] of 2): page 11
16FFCF  data  stage_08_tile_set_46_script_0             # vram script, tile set 46 of stage 8 (list 1419EC[0] of 2): page 00
170064  data  stage_08_tile_set_46_script_1             # vram script, tile set 46 of stage 8 (list 1419EC[1] of 2): page 0F
170201  data  stage_08_tile_set_47_script_0             # vram script, tile set 47 of stage 8 (list 1419F8[0] of 2): page 10
17030F  data  stage_08_tile_set_48_script_0             # vram script, tile set 48 of stage 8 (list 141A04[0] of 2): page 03
1703A4  data  stage_08_tile_set_48_script_1             # vram script, tile set 48 of stage 8 (list 141A04[1] of 2): page 12
1705DA  data  blank_tile_set_list_offsets               # u16 per tile set: get_tile_set_list adds blank_tile_set_lists (170602) for stages 0-2, 11, 12 (stage_tile_sets[0-2, 11, 12] = 1705DA)
170662  data  clear_tile_page_01_script                 # vram script: clears page 1 and fills its 200 cells with 0; blank_tile_set_lists' list at 17060A holds it (as 070662, no bank added)
17067A  data  clear_tile_page_02_script                 # as clear_tile_page_01_script, page 2 (blank_tile_set_lists' list at 170612)
1706AA  data  unused_clear_tile_page_03_script          # vram script: a second copy of clear_tile_page_03_script (same 24 bytes); nothing points at it
172055  data  unused_tiles_0d00_page_05_script          # vram script: clears page 5, then fills all of it with tiles 0D00-0D77 (default_nbg3_script does the same with 1D80-1DFF); nothing points at it
1720E5  data  unused_tiles_0d00_page_06_script          # vram script: unused_tiles_0d00_page_05_script on page 6 (only its two page bytes differ); nothing points at it
17235E  data  unused_tiles_0700_page_00_script          # vram script: clears page 0, then tiles 0700-0720 in rows 9-13 (33 cells; map_eggman_head_script before it puts 06C0-06DC in rows 10-13); nothing points at it
172AA5  data  unused_tiles_1700_page_05_script          # vram script: clears page 5, then tiles 1E40-1E63 in rows 2-6 and 1700-1749 down to row 13 (155 cells); nothing points at it

# ---- inside sprite_frame_records (130000, formats_work_misc.txt): the tile set scripts before 145A46 ----
# (the misc family's piece; the same format fits them: formats_scripts.json names them)
141A10  data  tile_set_clear_page_00_script             # vram script: clears page 0 and fills its 200 cells with 0 (24 bytes); in the lists of 15 tile sets, first list 140910 (stage 3 set 00)
141A28  data  tile_set_clear_page_01_script             # vram script: clears page 1 and fills its 200 cells with 0 (24 bytes); in the lists of 21 tile sets, first list 140918 (stage 3 set 01)
141A40  data  tile_set_clear_page_02_script             # vram script: clears page 2 and fills its 200 cells with 0 (24 bytes); in the lists of 20 tile sets, first list 140920 (stage 3 set 02)
141A58  data  tile_set_clear_page_03_script             # vram script: clears page 3 and fills its 200 cells with 0 (24 bytes); in the lists of 17 tile sets, first list 140928 (stage 3 set 03)
141A70  data  unused_tile_set_clear_page_03_script      # vram script: the same 24 bytes as tile_set_clear_page_03_script (clears page 3); no list points at it
141A88  data  tile_set_clear_page_0a_script             # vram script: clears page A and fills its 200 cells with 0 (24 bytes); in the lists of 15 tile sets, first list 140934 (stage 3 set 05)
141AA0  data  tile_set_clear_page_0b_script             # vram script: clears page B and fills its 200 cells with 0 (24 bytes); in the lists of 17 tile sets, first list 14093C (stage 3 set 06)
141AB8  data  tile_set_clear_page_0c_script             # vram script: clears page C and fills its 200 cells with 0 (24 bytes); in the lists of 22 tile sets, first list 140944 (stage 3 set 07)
141AD0  data  tile_set_clear_page_0d_script             # vram script: clears page D and fills its 200 cells with 0 (24 bytes); in the lists of 18 tile sets, first list 14094C (stage 3 set 08)
141AE8  data  tile_set_clear_page_0f_script             # vram script: clears page F and fills its 200 cells with 0 (24 bytes); in the lists of 6 tile sets, first list 1417B4[1] of 2 (stage 8 set 16)
141B00  data  tile_set_clear_page_10_script             # vram script: clears page 10 and fills its 200 cells with 0 (24 bytes); in the lists of 5 tile sets, first list 1417F0[1] of 2 (stage 8 set 1B)
141B18  data  tile_set_clear_page_11_script             # vram script: clears page 11 and fills its 200 cells with 0 (24 bytes); in the lists of 8 tile sets, first list 141208[1] of 2 (stage A set 32)
141B30  data  tile_set_clear_page_12_script             # vram script: clears page 12 and fills its 200 cells with 0 (24 bytes); in the lists of 8 tile sets, first list 140A14[1] of 2 (stage 3 set 31)
141B48  data  tile_set_clear_page_05_script             # vram script: clears page 5 and fills its 200 cells with 0 (24 bytes); in the lists of 1 tile set, first list 14173C[1] of 2 (stage 10 set 30)
141B60  data  unused_tile_set_clear_page_06_script      # vram script: clears page 6 and fills its 200 cells with 0, as the tile_set_clear_page scripts; no list points at it
141B78  data  tile_set_clear_page_07_script             # vram script: clears page 7 and fills its 200 cells with 0 (24 bytes); in the lists of 1 tile set, first list 141010[3] of 4 (stage 9 set 3F)
141B90  data  unused_tile_set_clear_page_08_script      # vram script: the same for page 8; no list points at it
141BA8  data  stage_04_tile_set_25_script_1             # vram script, tile set 25 of stage 4 (list 140C68[1] of 2): page 0A
141C90  data  stage_04_tile_set_26_script_1             # vram script, tile set 26 of stage 4 (list 140C74[1] of 2): page 0C
141D76  data  stage_04_tile_set_2b_script_1             # vram script, tile set 2B of stage 4 (list 140CA8[1] of 2): page 0D
141DBF  data  stage_03_tile_set_17_script               # vram script, tile set 17 of stages 3, 7 (list 14095C): page 01
142072  data  stage_03_tile_set_19_script               # vram script, tile set 19 of stages 3, 7 (list 140968): page 01
14216A  data  stage_03_tile_set_1a_script               # vram script, tile set 1A of stages 3, 7 (list 140970): page 00
1421E3  data  stage_03_tile_set_1b_script               # vram script, tile set 1B of stages 3, 7 (list 140978): page 02
1424B4  data  stage_03_tile_set_1c_script               # vram script, tile set 1C of stages 3, 7 (list 140980): page 00
1426C9  data  stage_03_tile_set_1d_script_0             # vram script, tile set 1D of stages 3, 7 (list 140988[0] of 2): page 02
1429A1  data  stage_03_tile_set_1d_script_1             # vram script, tile set 1D of stages 3, 7 (list 140988[1] of 2): page 0C
142AA4  data  stage_03_tile_set_1e_script_0             # vram script, tile set 1E of stages 3, 7 (list 140994[0] of 2): page 00
142D00  data  stage_03_tile_set_1e_script_1             # vram script, tile set 1E of stages 3, 7 (list 140994[1] of 2): page 0A
142F4D  data  stage_03_tile_set_1f_script               # vram script, tile set 1F of stages 3, 7 (list 1409A0): page 02
143028  data  stage_03_tile_set_20_script_0             # vram script, tile set 20 of stages 3, 7 (list 1409A8[0] of 2): page 02
14308D  data  stage_03_tile_set_21_script_0             # vram script, tile set 21 of stages 3, 7 (list 1409B4[0] of 2): page 00
143291  data  stage_03_tile_set_22_script               # vram script, tile set 22 of stages 3, 7 (list 1409C0): page 02
143354  data  stage_03_tile_set_29_script               # vram script, tile set 29 of stages 3, 7 (list 1409CC): page 03
14356B  data  stage_03_tile_set_2a_script               # vram script, tile set 2A of stages 3, 7 (list 1409D4): page 01
14369A  data  stage_03_tile_set_2b_script_0             # vram script, tile set 2B of stages 3, 7 (list 1409DC[0] of 2): page 00
143714  data  stage_03_tile_set_2c_script               # vram script, tile set 2C of stages 3, 7 (list 1409E8): page 02
143983  data  stage_03_tile_set_2d_script_0             # vram script, tile set 2D of stages 3, 7 (list 1409F0[0] of 2): page 00
143A3A  data  stage_03_tile_set_2e_script               # vram script, tile set 2E of stages 3, 7 (list 1409FC): page 01
143B2F  data  stage_03_tile_set_2f_script               # vram script, tile set 2F of stages 3, 7 (list 140A04): page 03
143BD3  data  stage_03_tile_set_30_script               # vram script, tile set 30 of stages 3, 7 (list 140A0C): page 01
143E42  data  stage_03_tile_set_31_script_0             # vram script, tile set 31 of stages 3, 7 (list 140A14[0] of 2): page 03
143F39  data  stage_03_tile_set_32_script               # vram script, tile set 32 of stages 3, 7 (list 140A20): page 00
144039  data  stage_03_tile_set_33_script               # vram script, tile set 33 of stages 3, 7 (list 140A28): page 02
1441D4  data  stage_03_tile_set_34_script               # vram script, tile set 34 of stages 3, 7 (list 140A30): page 00
14436B  data  stage_03_tile_set_35_script               # vram script, tile set 35 of stages 3, 7 (list 140A38): page 02
1444EA  data  stage_03_tile_set_36_script               # vram script, tile set 36 of stages 3, 7 (list 140A40): page 00
1446A5  data  stage_03_tile_set_37_script               # vram script, tile set 37 of stages 3, 7 (list 140A48): page 02
14480F  data  stage_03_tile_set_38_script               # vram script, tile set 38 of stages 3, 7 (list 140A50): page 00
1448BE  data  stage_03_tile_set_39_script               # vram script, tile set 39 of stages 3, 7 (list 140A58): page 03
144973  data  stage_03_tile_set_3a_script               # vram script, tile set 3A of stages 3, 7 (list 140A60): page 01
144A54  data  stage_03_tile_set_3b_script               # vram script, tile set 3B of stages 3, 7 (list 140A68): page 03
144B65  data  stage_03_tile_set_3c_script               # vram script, tile set 3C of stages 3, 7 (list 140A70): page 01
144D6E  data  stage_03_tile_set_3d_script_0             # vram script, tile set 3D of stages 3, 7 (list 140A78[0] of 2): page 03
144DE3  data  stage_03_tile_set_3d_script_1             # vram script, tile set 3D of stages 3, 7 (list 140A78[1] of 2): page 12
145030  data  stage_03_tile_set_3e_script               # vram script, tile set 3E of stages 3, 7 (list 140A84): page 01
1451F5  data  stage_03_tile_set_3f_script               # vram script, tile set 3F of stages 3, 7 (list 140A8C): page 03
1453BA  data  stage_03_tile_set_40_script               # vram script, tile set 40 of stages 3, 7 (list 140A94): page 01
14557F  data  stage_03_tile_set_41_script               # vram script, tile set 41 of stages 3, 7 (list 140A9C): page 00
145727  data  stage_03_tile_set_42_script_2             # vram script, tile set 42 of stages 3, 7 (list 140AA4[2] of 3): page 02
# -- stages --
# Proposed data labels for the stage family (FORMATS_GUIDE.md): they cut the pointer tables off the lists
# they point at, one list a stage (stages that share a list share its label: the stage numbers in hex).
# The player paths and the goal records stay one piece each (one list of short records).
1001B4  data  stage_00_type_spawns               # stage_spawns_by_type[0]: spawn_objects_by_type's 14-byte records, FFFF ends
1001B8  data  stage_01_type_spawns               # stage_spawns_by_type[1]: spawn_objects_by_type's 14-byte records, FFFF ends
1001BC  data  stage_02_type_spawns               # stage_spawns_by_type[2]: spawn_objects_by_type's 14-byte records, FFFF ends
1001C4  data  stage_03_type_spawns               # stage_spawns_by_type[3]: spawn_objects_by_type's 14-byte records, FFFF ends
10043E  data  stage_04_type_spawns               # stage_spawns_by_type[4]: spawn_objects_by_type's 14-byte records, FFFF ends
1004C0  data  stage_05_type_spawns               # stage_spawns_by_type[5]: spawn_objects_by_type's 14-byte records, FFFF ends
1004C4  data  stage_06_type_spawns               # stage_spawns_by_type[6]: spawn_objects_by_type's 14-byte records, FFFF ends
1004C8  data  stage_07_type_spawns               # stage_spawns_by_type[7]: spawn_objects_by_type's 14-byte records, FFFF ends
1004E8  data  stage_08_type_spawns               # stage_spawns_by_type[8]: spawn_objects_by_type's 14-byte records, FFFF ends
1005E8  data  stage_09_type_spawns               # stage_spawns_by_type[9]: spawn_objects_by_type's 14-byte records, FFFF ends
100B2C  data  stage_0a_type_spawns               # stage_spawns_by_type[A]: spawn_objects_by_type's 14-byte records, FFFF ends
101038  data  stage_0b_type_spawns               # stage_spawns_by_type[B]: spawn_objects_by_type's 14-byte records, FFFF ends
101082  data  stage_0c_type_spawns               # stage_spawns_by_type[C]: spawn_objects_by_type's 14-byte records, FFFF ends
10122A  data  stage_0d_type_spawns               # stage_spawns_by_type[D]: spawn_objects_by_type's 14-byte records, FFFF ends
10122E  data  stage_0e_type_spawns               # stage_spawns_by_type[E]: spawn_objects_by_type's 14-byte records, FFFF ends
1012F6  data  stage_0f_type_spawns               # stage_spawns_by_type[F]: spawn_objects_by_type's 14-byte records, FFFF ends
1012FA  data  stage_10_type_spawns               # stage_spawns_by_type[10]: spawn_objects_by_type's 14-byte records, FFFF ends
10162A  data  stage_11_type_spawns               # stage_spawns_by_type[11]: spawn_objects_by_type's 14-byte records, FFFF ends
10162E  data  stage_12_type_spawns               # stage_spawns_by_type[12]: spawn_objects_by_type's 14-byte records, FFFF ends
10B6F2  data  stage_00_floor_blocks              # 10B6CC + stage_floor_blocks[0] (load_stage_map_pointers): count, then 22-byte floor blocks
10B720  data  stage_01_02_03_07_floor_blocks     # 10B6CC + stage_floor_blocks[1, 2, 3, 7] (load_stage_map_pointers): count, then 22-byte floor blocks
10BC08  data  stage_04_floor_blocks              # 10B6CC + stage_floor_blocks[4] (load_stage_map_pointers): count, then 22-byte floor blocks
10C18A  data  stage_05_06_floor_blocks           # 10B6CC + stage_floor_blocks[5, 6] (load_stage_map_pointers): count, then 22-byte floor blocks
10C1E4  data  stage_08_floor_blocks              # 10B6CC + stage_floor_blocks[8] (load_stage_map_pointers): count, then 22-byte floor blocks
10C6B6  data  stage_09_11_12_floor_blocks        # 10B6CC + stage_floor_blocks[9, 11, 12] (load_stage_map_pointers): count, then 22-byte floor blocks
10CB30  data  stage_0a_floor_blocks              # 10B6CC + stage_floor_blocks[A] (load_stage_map_pointers): count, then 22-byte floor blocks
10D3CA  data  stage_0b_floor_blocks              # 10B6CC + stage_floor_blocks[B] (load_stage_map_pointers): count, then 22-byte floor blocks
10D6A2  data  stage_0c_0e_floor_blocks           # 10B6CC + stage_floor_blocks[C, E] (load_stage_map_pointers): count, then 22-byte floor blocks
10D846  data  stage_0d_0f_floor_blocks           # 10B6CC + stage_floor_blocks[D, F] (load_stage_map_pointers): count, then 22-byte floor blocks
10D93A  data  stage_10_floor_blocks              # 10B6CC + stage_floor_blocks[10] (load_stage_map_pointers): count, then 22-byte floor blocks
10E2C2  data  stage_00_floor_block_bounds        # 10E29C + stage_floor_block_bounds[0] (floor_block_bounds): count, then 16-byte outlines (find_nearby_floor_blocks)
10E2D4  data  stage_01_02_03_07_floor_block_bounds # 10E29C + stage_floor_block_bounds[1, 2, 3, 7] (floor_block_bounds): count, then 16-byte outlines (find_nearby_floor_blocks)
10E666  data  stage_04_floor_block_bounds        # 10E29C + stage_floor_block_bounds[4] (floor_block_bounds): count, then 16-byte outlines (find_nearby_floor_blocks)
10E7C8  data  stage_05_06_floor_block_bounds     # 10E29C + stage_floor_block_bounds[5, 6] (floor_block_bounds): count, then 16-byte outlines (find_nearby_floor_blocks)
10E80A  data  stage_08_floor_block_bounds        # 10E29C + stage_floor_block_bounds[8] (floor_block_bounds): count, then 16-byte outlines (find_nearby_floor_blocks)
10EA1C  data  stage_09_floor_block_bounds        # 10E29C + stage_floor_block_bounds[9] (floor_block_bounds): count, then 16-byte outlines (find_nearby_floor_blocks)
10ECDE  data  stage_0a_floor_block_bounds        # 10E29C + stage_floor_block_bounds[A] (floor_block_bounds): count, then 16-byte outlines (find_nearby_floor_blocks)
10F020  data  stage_0b_floor_block_bounds        # 10E29C + stage_floor_block_bounds[B] (floor_block_bounds): count, then 16-byte outlines (find_nearby_floor_blocks)
10F232  data  stage_0c_0e_floor_block_bounds     # 10E29C + stage_floor_block_bounds[C, E] (floor_block_bounds): count, then 16-byte outlines (find_nearby_floor_blocks)
10F364  data  stage_0d_0f_floor_block_bounds     # 10E29C + stage_floor_block_bounds[D, F] (floor_block_bounds): count, then 16-byte outlines (find_nearby_floor_blocks)
10F386  data  stage_10_floor_block_bounds        # 10E29C + stage_floor_block_bounds[10] (floor_block_bounds): count, then 16-byte outlines (find_nearby_floor_blocks)
10F69A  data  stage_11_12_floor_block_bounds     # 10E29C + stage_floor_block_bounds[11, 12] (floor_block_bounds): count, then 16-byte outlines (find_nearby_floor_blocks)
1141C0  data  stage_goal_records                 # 11419A + stage_goals[stage] (load_stage_goal): 12-byte goal records (the lowest offset, 26; 11 records to 114244)
114BAA  data  stage_00_restart_points            # 114B84 + stage_restart_points[0] - 6 (place_player_at_restart_point reads the start there): start, then 8-byte points
114C08  data  stage_01_restart_points            # 114B84 + stage_restart_points[1] - 6 (place_player_at_restart_point reads the start there): start, then 8-byte points
114D1E  data  stage_02_restart_points            # 114B84 + stage_restart_points[2] - 6 (place_player_at_restart_point reads the start there): start, then 8-byte points
114DDC  data  stage_03_07_restart_points         # 114B84 + stage_restart_points[3, 7] - 6 (place_player_at_restart_point reads the start there): start, then 8-byte points
114E92  data  stage_04_restart_points            # 114B84 + stage_restart_points[4] - 6 (place_player_at_restart_point reads the start there): start, then 8-byte points
114FD0  data  stage_05_06_restart_points         # 114B84 + stage_restart_points[5, 6] - 6 (place_player_at_restart_point reads the start there): start, then 8-byte points
114FE6  data  stage_08_restart_points            # 114B84 + stage_restart_points[8] - 6 (place_player_at_restart_point reads the start there): start, then 8-byte points
1150E4  data  stage_09_restart_points            # 114B84 + stage_restart_points[9] - 6 (place_player_at_restart_point reads the start there): start, then 8-byte points
115252  data  stage_0a_restart_points            # 114B84 + stage_restart_points[A] - 6 (place_player_at_restart_point reads the start there): start, then 8-byte points
1152F8  data  stage_0b_restart_points            # 114B84 + stage_restart_points[B] - 6 (place_player_at_restart_point reads the start there): start, then 8-byte points
11540E  data  stage_0c_0e_restart_points         # 114B84 + stage_restart_points[C, E] - 6 (place_player_at_restart_point reads the start there): start, then 8-byte points
11545C  data  stage_0d_0f_restart_points         # 114B84 + stage_restart_points[D, F] - 6 (place_player_at_restart_point reads the start there): start, then 8-byte points
11547A  data  stage_10_restart_points            # 114B84 + stage_restart_points[10] - 6 (place_player_at_restart_point reads the start there): start, then 8-byte points
11551A  data  stage_11_12_restart_points         # 114B84 + stage_restart_points[11, 12] - 6 (place_player_at_restart_point reads the start there): start, then 8-byte points
1155D4  data  player_paths                       # 115528 + player_path_offsets[2] (the lowest non-zero offset, follow_player_path): 8-byte path records, a word with bit 15 set ends a path; to 115A32
# -- text, fonts, demos and the rest --
# Proposed data lines for symbols.txt (formats, family misc). Pieces of formats_work_misc.txt cut where
# the tables, texts and recordings they hold start. Coin setting numbers are decimal, as the test mode
# shows them (COIN/CREDIT SETTING #1-#26; coin_setting_text_1 already follows it).

# ---- after the text font and the warning text ----
103876  data  unused_103876                        # text_font + 1740: past the 9A 4-bit characters (load_text_font) and the 80 1-bit ones (ASCII 00-7F, expand_font takes 00-5A): 300 bytes no code address reaches, up to ending_eggman_flight_path
104D0E  data  unused_warning_border_script         # after warning_screen_text's list (its 0000): a vram script on the text map (page 7C = 31F000) drawing two boxes of characters 15-1A (corners, edges) around the warning; attract_warning only prints the list, nothing runs it

# ---- the bounce records: 4 records, then the two animations they point at (offsets 18 and 2C) ----
113A00  data  sonic_bounce_200_animation           # sonic_bounce_records + 18 (records 0-1: directions 200, 300): last step 8, frames 1870-18B0
113A14  data  sonic_bounce_000_animation           # sonic_bounce_records + 2C (records 2-3: directions 000, 100): last step 8, frames 1828-1868
113A40  data  ray_bounce_200_animation             # ray_bounce_records + 18: frames 5018-5058
113A54  data  ray_bounce_000_animation             # ray_bounce_records + 2C: frames 4FD0-5010
113A80  data  mighty_bounce_200_animation          # mighty_bounce_records + 18: frames 3460-34A0
113A94  data  mighty_bounce_000_animation          # mighty_bounce_records + 2C: frames 3418-3458

# ---- the HUD prompts after hud_prompt_positions_2p (same shape as hud_wait_text; no code address found) ----
113FC1  data  hud_insert_coins_text                # print_text_lines text: attribute 0, "Insert Coins"
113FCF  data  hud_push_start_text                  # print_text_lines text: attribute 0, "Push Start  "

# ---- test mode: the lists the text pointer tables point at ----
115F36  data  cabinet_type_2p_text                 # cabinet_type_texts[0]: "2P" at row 17 col 25
115F3E  data  cabinet_type_3p_text                 # cabinet_type_texts[1]: "3P"
1161BF  data  credits_to_start_1_text              # credits_to_start_texts[0]: "1 CREDIT " at row 5 col 24
1161CE  data  credits_to_start_2_text              # credits_to_start_texts[1]: "2 CREDITS"
11644A  data  coins_per_credit_1_text              # coins_per_credit_texts[0] and [1]: "1 COIN " at row 5 col 25
116457  data  coins_per_credit_2_text              # coins_per_credit_texts[2]: "2 COINS"
116464  data  coins_per_credit_3_text              # [3]: "3 COINS"
116471  data  coins_per_credit_4_text              # [4]: "4 COINS"
11647E  data  coins_per_credit_5_text              # [5]: "5 COINS"
11648B  data  coins_per_credit_6_text              # [6]: "6 COINS"
116498  data  coins_per_credit_7_text              # [7]: "7 COINS"
1164A5  data  coins_per_credit_8_text              # [8]: "8 COINS"
1164B2  data  coins_per_credit_9_text              # [9]: "9 COINS"
1164E7  data  bonus_adder_none_text                # bonus_adder_texts[0] and [1]: "            NO BONUS ADDER" at row 9 col 24
116507  data  bonus_adder_2_text                   # bonus_adder_texts[2]: " 2 COINS GIVE 1 EXTRA COIN"
116527  data  bonus_adder_3_text                   # [3]: " 3 COINS GIVE 1 EXTRA COIN"
116547  data  bonus_adder_4_text                   # [4]
116567  data  bonus_adder_5_text                   # [5]
116587  data  bonus_adder_6_text                   # [6]
1165A7  data  bonus_adder_7_text                   # [7]
1165C7  data  bonus_adder_8_text                   # [8]
1165E7  data  bonus_adder_9_text                   # [9]: " 9 COINS GIVE 1 EXTRA COIN"; after its list 43 bytes no code address reaches (53 00, 40 spaces, 00)
1167EE  data  coin_setting_text_2                  # coin_setting_texts[1]: "1 COIN  2 CREDITS" (row 17), then the chute 1 line (row 11)
116819  data  coin_setting_text_3                  # coin_setting_texts[2]
116844  data  coin_setting_text_4                  # [3]
11686F  data  coin_setting_text_5                  # [4]
11689A  data  coin_setting_text_6                  # [5]: its chute 1 line is coin_setting_text_6_individual
1168AF  data  coin_setting_text_6_individual       # coin_setting_texts_individual[1] (individual_coin_setting_numbers[1] = 6): "1 COIN  2 CREDITS" at row 11
1168C6  data  coin_setting_text_7                  # coin_setting_texts[6]
1168F2  data  coin_setting_text_8                  # [7]
116907  data  coin_setting_text_8_individual       # coin_setting_texts_individual[2]
11691E  data  coin_setting_text_9                  # coin_setting_texts[8]
116933  data  coin_setting_text_9_individual       # coin_setting_texts_individual[3]
11694A  data  coin_setting_text_10                 # coin_setting_texts[9]
11695F  data  coin_setting_text_10_individual      # coin_setting_texts_individual[4]
116976  data  coin_setting_text_11                 # coin_setting_texts[10]
11698B  data  coin_setting_text_11_individual      # coin_setting_texts_individual[5]
1169A2  data  coin_setting_text_12                 # coin_setting_texts[11]: "2 COINS 1 CREDIT"
1169B6  data  coin_setting_text_12_individual      # coin_setting_texts_individual[6]
1169CC  data  coin_setting_text_13                 # coin_setting_texts[12]
1169F6  data  coin_setting_text_14                 # [13]
116A21  data  coin_setting_text_15                 # [14]: two chute 2 lines (rows 17-18), then the chute 1 lines
116A4A  data  coin_setting_text_15_individual      # coin_setting_texts_individual[7]: rows 11-12
116A75  data  coin_setting_text_16                 # coin_setting_texts[15]
116AB5  data  coin_setting_text_17                 # [16]: "3 COINS 1 CREDIT"
116AC9  data  coin_setting_text_17_individual      # coin_setting_texts_individual[8]
116ADF  data  coin_setting_text_18                 # coin_setting_texts[17]
116AF3  data  coin_setting_text_18_individual      # coin_setting_texts_individual[9]
116B09  data  coin_setting_text_19                 # coin_setting_texts[18]: four chute 2 lines (rows 17-20)
116B5C  data  coin_setting_text_19_individual      # coin_setting_texts_individual[10]: rows 11-14
116BB1  data  coin_setting_text_20                 # coin_setting_texts[19]
116C1B  data  coin_setting_text_21                 # [20]: "1 COIN 2 CREDITS" (one space, sic)
116C2F  data  coin_setting_text_21_individual      # coin_setting_texts_individual[11]
116C5A  data  coin_setting_text_22                 # coin_setting_texts[21]
116C98  data  coin_setting_text_22_individual      # coin_setting_texts_individual[12]
116CD8  data  coin_setting_text_23                 # coin_setting_texts[22]: "1 COIN  3 CREDIT" (sic)
116D2C  data  coin_setting_text_24                 # [23]: five chute 2 lines
116D94  data  coin_setting_text_24_individual      # coin_setting_texts_individual[13]
116DFE  data  coin_setting_text_25                 # coin_setting_texts[24]
116E7D  data  coin_setting_text_26                 # [25]: "   FREE PLAY" (rows 17 and 11)
116E8D  data  coin_setting_text_26_individual      # coin_setting_texts_individual[14]: "   FREE PLAY" at row 11
117119  data  input_test_start_3_on_text           # input_test_on_texts_3p[0] (bit 07): "ON " at row 15 col 12 (PLAYER 3 START)
117122  data  input_test_attack_3_on_text          # [1] (bit 12): row 16
11712B  data  input_test_start_1_on_text           # [2] (bit 04): row 5
117134  data  input_test_attack_1_on_text          # [3] (bit 10): row 6
11713D  data  input_test_start_2_on_text           # [4] (bit 05): row 10
117146  data  input_test_attack_2_on_text          # [5] (bit 18): row 11
11714F  data  input_test_service_on_text           # [6] and [7] (bits 00, 0C): row 22 col 16 (SERVICE BUTTON)
117158  data  input_test_test_button_on_text       # [8] and [9] (bits 01, 0D): row 24 col 16 (TEST BUTTON)
117161  data  input_test_start_3_off_text          # input_test_off_texts_3p[0]: "OFF" at row 15 col 12
11716A  data  input_test_attack_3_off_text         # [1]: row 16
117173  data  input_test_start_1_off_text          # [2]: row 5
11717C  data  input_test_attack_1_off_text         # [3]: row 6
117185  data  input_test_start_2_off_text          # [4]: row 10
11718E  data  input_test_attack_2_off_text         # [5]: row 11
117197  data  input_test_service_off_text          # [6] and [7]: row 22 col 16
1171A0  data  input_test_test_button_off_text      # [8] and [9]: row 24 col 16
11781E  data  eeprom_write_count_text              # after eeprom_caution_text's list, no code address found: "A WRITTEN NUMBER OF EEPROM IS " at row 2 col 1 (attribute 1C)
117843  data  control_ball_text                    # no code address found: "  CONTROL BALL" at row 3 col 9
117857  data  control_ball_small_text              # small_large_texts[0]: "SMALL" at row 3 col 25 (beside "  CONTROL BALL")
117862  data  control_ball_large_text              # small_large_texts[1]: "LARGE"

# ---- after the attract demos ----
119876  data  unused_fill_119876                   # attract_demo_2_inputs + 1000 (both recordings are 1000 bytes): CD 00, then FF up to 130000; no code address reaches it

# ---- sprite_frame_records: 6138 records (13BFD0), FF, then the tile set tables (the scripts family labels the scripts from 141A10) ----
13BFD0  data  unused_fill_13BFD0                   # FF up to 140000 after the 6138th frame record (assets.json count 6138)
140000  data  stage_04_tile_sets                   # stage_tile_sets[4]: per tile set a u32 pointer (+100000, get_tile_set_list_bank) to its script list in tile_set_script_lists
140194  data  stage_05_tile_sets                   # stage_tile_sets[5] and [6]
1401F4  data  stage_03_tile_sets                   # stage_tile_sets[3] and [7]
140328  data  stage_09_tile_sets                   # stage_tile_sets[9]
14044C  data  stage_0a_tile_sets                   # stage_tile_sets[A]
140564  data  stage_0b_tile_sets                   # stage_tile_sets[B]-[F]
14070C  data  stage_10_tile_sets                   # stage_tile_sets[10]
1407EC  data  stage_08_tile_sets                   # stage_tile_sets[8]
140910  data  tile_set_script_lists                # the lists the stage_*_tile_sets point at: u32 script pointers (+100000, get_tile_set_script_bank), FFFFFFFF ends each; up to the scripts (141A10)


# ======== the data in the code chips cut where each thing starts (data lines from build/naming/code_data_*_labels.txt) ========
00007E  data  boot_e00000_values                           # boot: 8 byte pairs it copies to E00000-E0000F (unmapped on this board, MAME and s32ref ignore them)
00008E  data  boot_irq_controller_values                   # boot: the 12 bytes it copies next (same source register R10) to D00000-D0000B: vectors 00-04, D00005 0, mask 0, acknowledge 0, both timer reloads FFFF
00060C  data  attract_mode_names                           # after mixer_defaults' 0x28 words (init_mixer stops at 00060C): four print_text_here texts, attribute 0, "Ranking Mode", " Title Mode ", " Demo 1 Mode", " Demo 2 Mode" (system modes 2-5); no code address reaches them
000644  data  title_copyright_text                         # attract_title (000291 print_text): row 27 col 30, attribute 0, characters DA ED DF E1 DB CB D3 D3 CC (the expanded font's "@SEGA1992": probably the (c) SEGA 1992 line)
000651  data  run_game_mode_padding                        # a 00 byte (halt) after title_copyright_text, aligning run_game_mode (000652): not code
002439  data  unused_002439                                # after player_objects_alt's six handlers: 56 bytes that read as a 1-bit picture 32 pixels wide, 14 rows (a round hole, symmetric); no code address reaches it
0024B8  data  stage_08_events                              # 002492 + stage_events[8]: (u16 at, u8 game_mode, u8 0) records, FFFF ends (run_stage_events)
0024C6  data  stage_04_events                              # 002492 + stage_events[4]: (u16 at, u8 game_mode, u8 0) records, FFFF ends (run_stage_events)
0024D0  data  stage_05_events                              # 002492 + stage_events[5]: (u16 at, u8 game_mode, u8 0) records, FFFF ends (run_stage_events)
0024D6  data  stage_06_events                              # 002492 + stage_events[6]: (u16 at, u8 game_mode, u8 0) records, FFFF ends (run_stage_events)
0024DC  data  stage_03_events                              # 002492 + stage_events[3]: (u16 at, u8 game_mode, u8 0) records, FFFF ends (run_stage_events)
0024EE  data  stage_09_events                              # 002492 + stage_events[9]: (u16 at, u8 game_mode, u8 0) records, FFFF ends (run_stage_events)
0024FC  data  stage_0a_events                              # 002492 + stage_events[A]: (u16 at, u8 game_mode, u8 0) records, FFFF ends (run_stage_events)
002502  data  stage_0b_events                              # 002492 + stage_events[B]: (u16 at, u8 game_mode, u8 0) records, FFFF ends (run_stage_events)
002508  data  stage_0c_events                              # 002492 + stage_events[C]: (u16 at, u8 game_mode, u8 0) records, FFFF ends (run_stage_events)
00250E  data  stage_10_events                              # 002492 + stage_events[10]: (u16 at, u8 game_mode, u8 0) records, FFFF ends (run_stage_events)
002518  data  stage_11_events                              # 002492 + stage_events[11]: (u16 at, u8 game_mode, u8 0) records, FFFF ends (run_stage_events)
00251E  data  stage_shared_events                          # 002492 + stage_events[0 1 2 7 D E F 12]: (u16 at, u8 game_mode, u8 0) records, FFFF ends (run_stage_events)
002546  data  stage_01_link_point                          # 002520 + stage_link_points[1] (the only stage with its own): from_block 2BC, to_block 28 (continue_to_next_stage_slot)
00254A  data  unused_game_mode_text                        # print_text record: row 24 col 14, attribute 0, " Game Mode  "; no code address reaches it
00255A  data  stage_select_mode_text                       # game_mode_stage_select: print_text_here at 31FA1C (row 20 col 14), attribute 0, "Stage Select Mode" (erase_text_here when a stage is picked)
00256D  data  stage_number_text                            # game_mode_stage_select: print_text_here at 31FAA4 (row 21 col 18), "Stage No."; erased at 31FAA4 and 31FAB0 (over the number)
002578  data  unused_fence_set_mode_text                   # print_text_here text, attribute 0, "Fence Set Mode"; no code address reaches it
002588  data  unused_status_texts                          # four print_text records no code address reaches: "Stage Change" and "Stg St Wait" (row 24 col 14), "GAME OVER" and "RING BINUS" (row 14 col 14)
0025C2  data  continue_text                                # game_mode_continue: print_text record row 14 col 14, attribute 0, "CONTINUE ?" (erase_text_here reads it from 0025C4, its attribute)
0025D0  data  game_over_string                             # game_mode_game_over: queue_string_record (x 58, y 68, palette 8440, scale 200), "GAME OVER"
0025E2  data  unused_to_be_continued_string                # a queue_string_record record (x 28, y 48), "TO BE CONTINUED"; no code address reaches it
0025FA  data  you_failed_string                            # game_mode_you_failed: queue_string_record (x 50, y 68), "YOU FAILED"
00260D  data  unused_00260D                                # 19 u16 offsets from 260D to (u16, u8, u8) lists ended by FFFF, the shape of stage_events: stage 0 -> 2633 (00C0, 10, 2B), the others -> 2637 (FFFF); no code address reaches it
0026BA  data  stage_05_layer_blending                      # 002694 + stage_layer_blending[5]: count, then 6-byte records (apply_stage_layer_blending)
0026C7  data  stage_09_10_layer_blending                   # 002694 + stage_layer_blending[9 10]: count, then 6-byte records (apply_stage_layer_blending)
0026CE  data  stage_shared_layer_blending                  # 002694 + stage_layer_blending[0 1 2 3 4 6 7 8 A B C D E F 11 12]: count, then 6-byte records (apply_stage_layer_blending)
0026F9  data  cleared_banner_animation                     # game_mode_stage_clear: the CLEARED banner's animation (it follows cleared_banner_x/y, the name banner record of the stages without one): last step 5, frames 8A80-8AA8
002707  data  stage_08_name_banner                         # 0026CF + stage_name_banners[8]: screen x, screen y, then the banner's animation (game_mode_stage_clear)
002723  data  stage_03_name_banner                         # 0026CF + stage_name_banners[3]: screen x, screen y, then the banner's animation (game_mode_stage_clear)
002739  data  stage_04_name_banner                         # 0026CF + stage_name_banners[4]: screen x, screen y, then the banner's animation (game_mode_stage_clear)
002755  data  stage_09_name_banner                         # 0026CF + stage_name_banners[9]: screen x, screen y, then the banner's animation (game_mode_stage_clear)
002765  data  stage_0a_name_banner                         # 0026CF + stage_name_banners[A]: screen x, screen y, then the banner's animation (game_mode_stage_clear)
00277F  data  stage_10_name_banner                         # 0026CF + stage_name_banners[10]: screen x, screen y, then the banner's animation (game_mode_stage_clear)
00279D  data  banner_letter_animation                      # stage_clear_banner_hold: the banner's letters (banner_letter_spin): last step 0E, frames 90E0-9150
0027BD  data  banner_sparkle_animation                     # banner_letter_spin's sparkle (banner_sparkle_animate): last step 10, frames 9158-91D8
0027E1  data  unused_player_select_text                    # print_text_here text, attribute 0, " PLayer Select "; no code address reaches it
009BF7  data  player_effect_animation                      # spawn_player_effect (009A4A, states 25, 30, 33): last step 5, frames 7770-7798
009CB3  data  object_climb_animation_sonic                 # player_object_climb_begin (kind 0): last step 5, frames 82D0-82F8
009CC1  data  object_climb_animation_ray_2                 # player_object_climb_move: Ray's other hand (climb_hand back to positive): frames 8260-8288
009CCF  data  object_climb_animation_ray                   # player_object_climb_begin (kind 1) and player_object_climb_move (climb_hand negative): frames 8290-82B8
009CDD  data  object_climb_animation_mighty                # player_object_climb_begin (kind 2): frames 8228-8250
009CEB  data  player_state_held_still_padding              # a 00 byte (halt) after object_climb_animation_mighty, aligning player_state_held_still (009CEC): not code
00CD1C  data  unused_hud_name_offsets                      # three u16 offsets from CD1C to the character names (6 SONIC, D RAY, 13 MIGHTY); no code address reaches the table
00CD22  data  hud_sonic_name_text                          # print_text_here text, attribute 0C, "SONIC" (reached only through unused_hud_name_offsets)
00CD29  data  hud_ray_name_text                            # attribute 0A, " RAY" (reached only through unused_hud_name_offsets)
00CD2F  data  hud_mighty_name_text                         # attribute 00, "MIGHTY": player_state_out_of_game erases the name with it (the longest)
00CD37  data  hud_gauge_ring_palettes                      # player_hud_update (hud_gauge_anim, animate_hud_palette): the ring flash, last 9, palettes 88E0-8950
00CD4D  data  hud_gauge_hit_palettes                       # player_hud_init / player_hud_update (hud_gauge_anim): the hit flash, last 2: 88E0 88F0 8900
00CD55  data  hud_gauge_low_health_palettes                # player_hud_update (hud_gauge_anim): low health, last 5
00CD63  data  hud_portrait_hurt_palettes_sonic             # player_hud_init / player_hud_update (hud_portrait_anim), character 0: last 2, 8850 8860 8870
00CD6B  data  hud_portrait_hurt_palettes_ray               # the same for character 1: 88B0 88C0 88D0
00CD73  data  hud_portrait_hurt_palettes_mighty            # the same for character 2: 8880 8890 88A0
00CD7B  data  hud_portrait_low_health_palettes_sonic       # player_hud_update (hud_portrait_anim) at low health, character 0: last 5
00CD89  data  hud_portrait_low_health_palettes_ray         # the same for character 1
00CD97  data  hud_portrait_low_health_palettes_mighty      # the same for character 2
00CDA5  data  hud_no_continue_text                         # player_hud_update (joining closed): print_text_lines, attribute 0, "   No   " / "Continue"
00E459  data  climb_zone_effect_animation                  # spawn_climb_zone_effect: last step 3, frames BF90-BFA8
00E463  data  unused_00E463                                # an animation of frames like the one before it (last step 3, BFB0-BFC8); no code address reaches it
00E46D  data  intro_marker_animation                       # spawn_intro_marker: last step 0B, frames A0C0-A0F0
00E487  data  check_player_contacts_padding                # a 00 byte (halt) after intro_marker_animation, aligning check_player_contacts (00E488): not code
00F4AA  data  unused_00F4AA                                # after spike_panel_head_sprites' six frames: six 8-byte records 0054 002D EAFF 003D (hit box sizes and offsets, probably, like the 6-byte boxes); no code address reaches them
0138C5  data  fence_post_init_padding                      # a 00 byte (halt) after sand_arch_spray_frames, aligning fence_post_init (0138C6): not code
014907  data  unused_014907                                # 8 sprite frames 7F30-7F68 right after turtle_init's code (probably the turtle's: its sprites run 7EA8-7F90); no code address reaches them
017C82  data  stage_10_block_map                           # 017C5C + stage_block_maps[10]: a byte (tile set) per block, rows of stage_block_map_widths[stage]
017D82  data  stage_0f_block_map                           # 017C5C + stage_block_maps[F]: a byte (tile set) per block, rows of stage_block_map_widths[stage]
017DC2  data  stage_0e_block_map                           # 017C5C + stage_block_maps[E]: a byte (tile set) per block, rows of stage_block_map_widths[stage]
017E02  data  stage_0d_block_map                           # 017C5C + stage_block_maps[D]: a byte (tile set) per block, rows of stage_block_map_widths[stage]
017E42  data  stage_0c_block_map                           # 017C5C + stage_block_maps[C]: a byte (tile set) per block, rows of stage_block_map_widths[stage]
017E82  data  stage_0b_block_map                           # 017C5C + stage_block_maps[B]: a byte (tile set) per block, rows of stage_block_map_widths[stage]
017F82  data  stage_0a_block_map                           # 017C5C + stage_block_maps[A]: a byte (tile set) per block, rows of stage_block_map_widths[stage]
018082  data  stage_09_block_map                           # 017C5C + stage_block_maps[9]: a byte (tile set) per block, rows of stage_block_map_widths[stage]
018182  data  stage_08_block_map                           # 017C5C + stage_block_maps[8]: a byte (tile set) per block, rows of stage_block_map_widths[stage]
018382  data  stage_03_07_block_map                        # 017C5C + stage_block_maps[3 7]: a byte (tile set) per block, rows of stage_block_map_widths[stage]
018582  data  stage_04_block_map                           # 017C5C + stage_block_maps[4]: a byte (tile set) per block, rows of stage_block_map_widths[stage]
018682  data  stage_00_block_map                           # 017C5C + stage_block_maps[0]: a byte (tile set) per block, rows of stage_block_map_widths[stage]
018782  data  stage_05_06_block_map                        # 017C5C + stage_block_maps[5 6]: a byte (tile set) per block, rows of stage_block_map_widths[stage] (stage 6 reads it 6 wide)
0187C2  data  stage_01_02_11_12_block_map                  # 017C5C + stage_block_maps[1 2 11 12]: a byte (tile set) per block, rows of stage_block_map_widths[stage]
018896  data  stage_10_section_directions_x                # 01884A + stage_section_directions_x[10]: s16 per section: the x of the scroll direction (0x4000 = 1.0)
0188CC  data  stage_0b_section_directions_x                # 01884A + stage_section_directions_x[B]: s16 per section: the x of the scroll direction (0x4000 = 1.0)
018904  data  stage_0c_0e_section_directions_x             # 01884A + stage_section_directions_x[C E]: s16 per section: the x of the scroll direction (0x4000 = 1.0)
01891E  data  stage_0d_0f_section_directions_x             # 01884A + stage_section_directions_x[D F]: s16 per section: the x of the scroll direction (0x4000 = 1.0)
01892C  data  stage_0a_section_directions_x                # 01884A + stage_section_directions_x[A]: s16 per section: the x of the scroll direction (0x4000 = 1.0)
018994  data  stage_09_section_directions_x                # 01884A + stage_section_directions_x[9]: s16 per section: the x of the scroll direction (0x4000 = 1.0)
0189FE  data  stage_08_section_directions_x                # 01884A + stage_section_directions_x[8]: s16 per section: the x of the scroll direction (0x4000 = 1.0)
018A38  data  stage_05_06_section_directions_x             # 01884A + stage_section_directions_x[5 6]: s16 per section: the x of the scroll direction (0x4000 = 1.0)
018A3A  data  stage_03_07_section_directions_x             # 01884A + stage_section_directions_x[3 7]: s16 per section: the x of the scroll direction (0x4000 = 1.0)
018A72  data  stage_00_section_directions_x                # 01884A + stage_section_directions_x[0]: s16 per section: the x of the scroll direction (0x4000 = 1.0)
018A8E  data  stage_01_02_11_12_section_directions_x       # 01884A + stage_section_directions_x[1 2 11 12]: s16 per section: the x of the scroll direction (0x4000 = 1.0)
018B1C  data  stage_04_section_directions_x                # 01884A + stage_section_directions_x[4]: s16 per section: the x of the scroll direction (0x4000 = 1.0)
018B48  data  stage_10_section_directions_y                # 018870 + stage_section_directions_y[10]: s16 per section: the y of the scroll direction
018B7E  data  stage_0b_section_directions_y                # 018870 + stage_section_directions_y[B]: s16 per section: the y of the scroll direction
018BB6  data  stage_0c_0e_section_directions_y             # 018870 + stage_section_directions_y[C E]: s16 per section: the y of the scroll direction
018BD0  data  stage_0d_0f_section_directions_y             # 018870 + stage_section_directions_y[D F]: s16 per section: the y of the scroll direction
018BDE  data  stage_0a_section_directions_y                # 018870 + stage_section_directions_y[A]: s16 per section: the y of the scroll direction
018C46  data  stage_09_section_directions_y                # 018870 + stage_section_directions_y[9]: s16 per section: the y of the scroll direction
018CB0  data  stage_08_section_directions_y                # 018870 + stage_section_directions_y[8]: s16 per section: the y of the scroll direction
018CEA  data  stage_05_06_section_directions_y             # 018870 + stage_section_directions_y[5 6]: s16 per section: the y of the scroll direction
018CEC  data  stage_03_07_section_directions_y             # 018870 + stage_section_directions_y[3 7]: s16 per section: the y of the scroll direction
018D24  data  stage_00_section_directions_y                # 018870 + stage_section_directions_y[0]: s16 per section: the y of the scroll direction
018D40  data  stage_01_02_11_12_section_directions_y       # 018870 + stage_section_directions_y[1 2 11 12]: s16 per section: the y of the scroll direction
018DCE  data  stage_04_section_directions_y                # 018870 + stage_section_directions_y[4]: s16 per section: the y of the scroll direction
018E46  data  stage_10_section_limits_x                    # 018DFA + stage_section_limits_x[10]: u16 per section from the second word: camera x limits
018E7E  data  stage_0b_section_limits_x                    # 018DFA + stage_section_limits_x[B]: u16 per section from the second word: camera x limits
018EB8  data  stage_0c_0e_section_limits_x                 # 018DFA + stage_section_limits_x[C E]: u16 per section from the second word: camera x limits
018ED6  data  stage_0d_0f_section_limits_x                 # 018DFA + stage_section_limits_x[D F]: u16 per section from the second word: camera x limits
018EE6  data  stage_0a_section_limits_x                    # 018DFA + stage_section_limits_x[A]: u16 per section from the second word: camera x limits
018F50  data  stage_09_section_limits_x                    # 018DFA + stage_section_limits_x[9]: u16 per section from the second word: camera x limits
018FBC  data  stage_08_section_limits_x                    # 018DFA + stage_section_limits_x[8]: u16 per section from the second word: camera x limits
018FF8  data  stage_05_06_section_limits_x                 # 018DFA + stage_section_limits_x[5 6]: u16 per section from the second word: camera x limits
018FFC  data  stage_03_07_section_limits_x                 # 018DFA + stage_section_limits_x[3 7]: u16 per section from the second word: camera x limits
019034  data  stage_00_section_limits_x                    # 018DFA + stage_section_limits_x[0]: u16 per section from the second word: camera x limits
019052  data  stage_01_02_11_12_section_limits_x           # 018DFA + stage_section_limits_x[1 2 11 12]: u16 per section from the second word: camera x limits
0190E6  data  stage_04_section_limits_x                    # 018DFA + stage_section_limits_x[4]: u16 per section from the second word: camera x limits
019114  data  stage_10_section_limits_y                    # 018E20 + stage_section_limits_y[10]: u16 per section from the second word: camera y limits
01914C  data  stage_0b_section_limits_y                    # 018E20 + stage_section_limits_y[B]: u16 per section from the second word: camera y limits
019186  data  stage_0c_0e_section_limits_y                 # 018E20 + stage_section_limits_y[C E]: u16 per section from the second word: camera y limits
0191A2  data  stage_0d_0f_section_limits_y                 # 018E20 + stage_section_limits_y[D F]: u16 per section from the second word: camera y limits
0191B2  data  stage_0a_section_limits_y                    # 018E20 + stage_section_limits_y[A]: u16 per section from the second word: camera y limits
01921C  data  stage_09_section_limits_y                    # 018E20 + stage_section_limits_y[9]: u16 per section from the second word: camera y limits
019288  data  stage_08_section_limits_y                    # 018E20 + stage_section_limits_y[8]: u16 per section from the second word: camera y limits
0192C4  data  stage_05_06_section_limits_y                 # 018E20 + stage_section_limits_y[5 6]: u16 per section from the second word: camera y limits
0192C8  data  stage_03_07_section_limits_y                 # 018E20 + stage_section_limits_y[3 7]: u16 per section from the second word: camera y limits
019300  data  stage_00_section_limits_y                    # 018E20 + stage_section_limits_y[0]: u16 per section from the second word: camera y limits
01931E  data  stage_01_02_11_12_section_limits_y           # 018E20 + stage_section_limits_y[1 2 11 12]: u16 per section from the second word: camera y limits
0193B2  data  stage_04_section_limits_y                    # 018E20 + stage_section_limits_y[4]: u16 per section from the second word: camera y limits
019406  data  stage_10_section_ends                        # 0193E0 + stage_section_ends[10]: u16 scroll positions where the sections end (from the second word), FFFF ends
019440  data  stage_0b_section_ends                        # 0193E0 + stage_section_ends[B]: u16 scroll positions where the sections end (from the second word), FFFF ends
01947C  data  stage_0c_0e_section_ends                     # 0193E0 + stage_section_ends[C E]: u16 scroll positions where the sections end (from the second word), FFFF ends
01949A  data  stage_0d_0f_section_ends                     # 0193E0 + stage_section_ends[D F]: u16 scroll positions where the sections end (from the second word), FFFF ends
0194AC  data  stage_0a_section_ends                        # 0193E0 + stage_section_ends[A]: u16 scroll positions where the sections end (from the second word), FFFF ends
019518  data  stage_09_section_ends                        # 0193E0 + stage_section_ends[9]: u16 scroll positions where the sections end (from the second word), FFFF ends
019586  data  stage_08_section_ends                        # 0193E0 + stage_section_ends[8]: u16 scroll positions where the sections end (from the second word), FFFF ends
0195C4  data  stage_05_06_section_ends                     # 0193E0 + stage_section_ends[5 6]: u16 scroll positions where the sections end (from the second word), FFFF ends
0195CA  data  stage_03_07_section_ends                     # 0193E0 + stage_section_ends[3 7]: u16 scroll positions where the sections end (from the second word), FFFF ends
019606  data  stage_00_section_ends                        # 0193E0 + stage_section_ends[0]: u16 scroll positions where the sections end (from the second word), FFFF ends
019626  data  stage_01_11_section_ends                     # 0193E0 + stage_section_ends[1 11]: u16 scroll positions where the sections end (from the second word), FFFF ends
019678  data  stage_02_12_section_ends                     # 0193E0 + stage_section_ends[2 12]: u16 scroll positions where the sections end (from the second word), FFFF ends
0196BC  data  stage_04_section_ends                        # 0193E0 + stage_section_ends[4]: u16 scroll positions where the sections end (from the second word), FFFF ends
01973A  data  stage_00_11_initial_tile_sets                # 019712 + stage_initial_tile_sets[0 11]: four u32 tile set numbers (init_stage_view)
01974A  data  stage_shared_initial_tile_sets               # 019712 + stage_initial_tile_sets[1 2 3 4 5 6 7 8 9 A B C D E F 10 12 13]: four u32 tile set numbers (init_stage_view)
019818  data  stage_10_scroll_modes                        # 0197F2 + stage_scroll_modes[10]: (scroll mode, duration) pairs, one per section
019908  data  stage_0f_scroll_modes                        # 0197F2 + stage_scroll_modes[F]: (scroll mode, duration) pairs, one per section
019944  data  stage_0e_scroll_modes                        # 0197F2 + stage_scroll_modes[E]: (scroll mode, duration) pairs, one per section
019980  data  stage_0d_scroll_modes                        # 0197F2 + stage_scroll_modes[D]: (scroll mode, duration) pairs, one per section
0199BC  data  stage_0c_scroll_modes                        # 0197F2 + stage_scroll_modes[C]: (scroll mode, duration) pairs, one per section
0199F8  data  stage_0b_scroll_modes                        # 0197F2 + stage_scroll_modes[B]: (scroll mode, duration) pairs, one per section
019A70  data  stage_0a_scroll_modes                        # 0197F2 + stage_scroll_modes[A]: (scroll mode, duration) pairs, one per section
019BD8  data  stage_09_scroll_modes                        # 0197F2 + stage_scroll_modes[9]: (scroll mode, duration) pairs, one per section
019CF0  data  stage_08_scroll_modes                        # 0197F2 + stage_scroll_modes[8]: (scroll mode, duration) pairs, one per section
019E58  data  stage_05_06_scroll_modes                     # 0197F2 + stage_scroll_modes[5 6]: (scroll mode, duration) pairs, one per section
019E80  data  stage_shared_scroll_modes                    # 0197F2 + stage_scroll_modes[0 1 2 3 7 11 12]: (scroll mode, duration) pairs, one per section
019F20  data  stage_04_scroll_modes                        # 0197F2 + stage_scroll_modes[4]: (scroll mode, duration) pairs, one per section
01A150  data  stage_00_01_02_03_fast_scroll_ranges         # 01A128 + stage_fast_scroll_ranges[0 1 2 3]: u16 scroll positions, FFFF ends (update_background_scroll)
01A152  data  stage_04_fast_scroll_ranges                  # 01A128 + stage_fast_scroll_ranges[4]: u16 scroll positions, FFFF ends (update_background_scroll)
01A15C  data  stage_05_06_07_fast_scroll_ranges            # 01A128 + stage_fast_scroll_ranges[5 6 7]: u16 scroll positions, FFFF ends (update_background_scroll)
01A15E  data  stage_08_09_fast_scroll_ranges               # 01A128 + stage_fast_scroll_ranges[8 9]: u16 scroll positions, FFFF ends (update_background_scroll)
01A160  data  stage_0a_to_13_fast_scroll_ranges            # 01A128 + stage_fast_scroll_ranges[A B C D E F 10 11 12 13]: u16 scroll positions, FFFF ends (update_background_scroll)
01A188  data  stage_10_section_speeds                      # 01A162 + stage_section_speeds[10]: scroll_speed, then a byte per section
01A200  data  stage_0f_section_speeds                      # 01A162 + stage_section_speeds[F]: scroll_speed, then a byte per section
01A20F  data  stage_0e_section_speeds                      # 01A162 + stage_section_speeds[E]: scroll_speed, then a byte per section
01A21E  data  stage_0d_section_speeds                      # 01A162 + stage_section_speeds[D]: scroll_speed, then a byte per section
01A22D  data  stage_0c_section_speeds                      # 01A162 + stage_section_speeds[C]: scroll_speed, then a byte per section
01A23C  data  stage_0b_section_speeds                      # 01A162 + stage_section_speeds[B]: scroll_speed, then a byte per section
01A25A  data  stage_0a_section_speeds                      # 01A162 + stage_section_speeds[A]: scroll_speed, then a byte per section
01A2FA  data  stage_09_section_speeds                      # 01A162 + stage_section_speeds[9]: scroll_speed, then a byte per section
01A34A  data  stage_08_section_speeds                      # 01A162 + stage_section_speeds[8]: scroll_speed, then a byte per section
01A3F4  data  stage_05_06_section_speeds                   # 01A162 + stage_section_speeds[5 6]: scroll_speed, then a byte per section
01A407  data  stage_00_section_speeds                      # 01A162 + stage_section_speeds[0]: scroll_speed, then a byte per section
01A440  data  stage_01_02_11_12_section_speeds             # 01A162 + stage_section_speeds[1 2 11 12]: scroll_speed, then a byte per section
01A4C5  data  stage_03_07_section_speeds                   # 01A162 + stage_section_speeds[3 7]: scroll_speed, then a byte per section
01A515  data  stage_04_section_speeds                      # 01A162 + stage_section_speeds[4]: scroll_speed, then a byte per section
01A58B  data  unused_01A58B                                # after stage_section_follow_speeds' offsets: 50 bytes 03, the shape of a follow speed list; no stage points at it
01A5BD  data  stage_08_section_follow_speeds               # 01A565 + stage_section_follow_speeds[8]: follow_base_speed, then a byte per section
01A5DB  data  stage_shared_section_follow_speeds           # 01A565 + stage_section_follow_speeds[0 1 2 3 5 6 7 9 A B C D E F 10 11 12]: follow_base_speed, then a byte per section
01A617  data  stage_04_section_follow_speeds               # 01A565 + stage_section_follow_speeds[4]: follow_base_speed, then a byte per section
01A653  data  unused_01A653                                # after stage 4's follow speeds: 0002, then (0080, 3) (0180, 1) (0280, 4) (0380, 2) and 03FF; no code address reaches it
01A673  data  stage_00_01_02_section_unused                # stage_section_unused[0-2] (the entries after them are these bytes and stage_tile_sets', nothing reads them): a word per section that advance_scroll steps 20E488 over; nothing reads the values
01BB3D  data  stage_0a_nbg3_page_map                       # stage_nbg3_block_maps[A] (select_nbg3_pages): a byte (page) per block, rows of stage_nbg3_widths[stage] (9)
01BBD2  data  stage_04_nbg3_page_map                       # stage_nbg3_block_maps[4] (select_nbg3_pages): a byte (page) per block, rows of stage_nbg3_widths[stage] (6)
01BBE2  data  stage_0b_to_0f_nbg3_page_map                 # stage_nbg3_block_maps[B C D E F] (select_nbg3_pages): a byte (page) per block, rows of stage_nbg3_widths[stage] (6)
01BC64  data  stage_shared_nbg3_page_map                   # stage_nbg3_block_maps[0 1 2 3 5 6 7 8 9] (select_nbg3_pages): a byte (page) per block, rows of stage_nbg3_widths[stage] (6 or 9)
01BCBC  data  nbg3_quadrant_grid                           # select_nbg3_pages: 3 x 3 bytes; [row & 1][column & 1] is the top left of the 2 x 2 quadrant numbers it gathers
01BCC5  data  build_ending_scene_padding                   # a 00 byte (halt) after nbg3_quadrant_grid, aligning build_ending_scene (01BCC6): not code
01F22C  data  stage_shared_ring_rows                       # 01F206 + stage_template_spawns[0 C D E F 11 12]: 12-byte ring rows, FFFF ends (spawn_ring_rows)
01F22E  data  stage_04_ring_rows                           # 01F206 + stage_template_spawns[4]: 12-byte ring rows, FFFF ends (spawn_ring_rows)
01F2E4  data  stage_08_ring_rows                           # 01F206 + stage_template_spawns[8]: 12-byte ring rows, FFFF ends (spawn_ring_rows)
01F32E  data  stage_03_ring_rows                           # 01F206 + stage_template_spawns[3]: 12-byte ring rows, FFFF ends (spawn_ring_rows)
01F36C  data  stage_09_ring_rows                           # 01F206 + stage_template_spawns[9]: 12-byte ring rows, FFFF ends (spawn_ring_rows)
01F3CE  data  stage_01_02_0a_ring_rows                     # 01F206 + stage_template_spawns[1 2 A]: 12-byte ring rows, FFFF ends (spawn_ring_rows)
01F40C  data  stage_10_ring_rows                           # 01F206 + stage_template_spawns[10]: 12-byte ring rows, FFFF ends (spawn_ring_rows)
01F46E  data  stage_05_06_07_ring_rows                     # 01F206 + stage_template_spawns[5 6 7]: 12-byte ring rows, FFFF ends (spawn_ring_rows)
01F470  data  stage_0b_ring_rows                           # 01F206 + stage_template_spawns[B]: 12-byte ring rows, FFFF ends (spawn_ring_rows)
01F5F0  data  unused_01F5F0                                # the word 00CD after ring_sparkle_animation; no code address reaches it
01F5F2  data  low_code_chip_padding                        # FF up to the end of the chip (01FFFF)
083329  data  blank_fireball_animation     # fireball_types' animation offsets are from here (fireball_emitter_update: + 083329); type offset 0 plays it: last step 4, five frame words 0000 (nothing drawn); offset 1A reaches boulder_animation's second animation (6EC8-6EE8)
08514D  data  sign_palette_lists           # palette cycles for animate_sign: init_sign_from_kind and place_screen_sign add the kind's offset to 08514D (0: 0460-0490, E: 84A0-84D0, 18: 04E0-0500); object_kinds' 34 records end here
08DABE  data  river_raft_unused_animation  # after river_raft_spawners' last list (8000 loop, FFFF): an animation (last step 2: frames 8DF0 8DF8 8E00) no code reads; river_raft_spawner_update gives every raft the fixed sprite 8DF0
08DADA  data  river_raft_unused_animation_2  # after river_raft_foam_animation (last step 2, 8E08-8E18): another animation (last step 3: frames 8DD0-8DE8) no code reads, then a word 0004 (the last index of river_raft_fall_palettes, which the code indexes directly)
08EFF8  data  ranking_rank_texts           # enter_ranking_initials: queue_string_at(x + 20, y - 20, palette 8AD0, scale 100, 08EFF8 + rank x A): 10-byte strings "Rank TOP!", "Rank  2nd" ... "Rank 15th" (after ranking_entry_buttons' three bytes)
08F08E  data  countdown_second_texts       # enter_ranking_initials (time left / 3C x 3) and join_character_chooser_update (field 82 / 3C x 3): 3-byte strings " 0" ... "20" for queue_string_at; then two 00 bytes before ranking_entry_blocks
08F858  data  ranking_row_numbers          # build_ranking_display: 08F858 + row x 2, two ranking letter codes a row (ranking_letter_sprites): "15" "14" ... "10", " 9" ... " 1" (2B is the blank), the lowest row first
090C7A  data  wall_editor_menu             # after wall_kinds' 26 records (090A72 + 26 x 20): print_text_list records of the wall editor's menu ("Pattern Add ", "Pattern Sub ", "Number Add  ", "Number Sub  ", then the cell FC02 and attribute of the fifth item, whose text is wall_editor_far_label = 090C7A + 43)
096C6F  data  map_marker_blink_sprites     # blink_current_map_marker: 096C6F + marker x 10 (mul 2, shl 3): layer, screen x, y, sprite, palette words copied to its object (sprites 9F98-9FC8, palette 0670); map_marker_layouts' 14 records (2 x 7, draw_map_stage_markers) end here
096E02  data  title_sonic_loop_frames      # the title from title_sonic_step 14 on: frame byte at 096E02 + step - 14 (sprite 5300 + (n - 1) x 8), FF loops back to step 14: 15 16 17 18 19 1A 19 18 17 16 15 14 FF (stage_map_sounds_3, 19 bytes, before it)
097538  data  title_smoke_puff_frames      # title_smoke_puff_animate: sprite_frame = 097538 + frame_index x 2 every 3 frames, FFFF removes the puff (BB68-BBA0); unreached code follows at 09754C

# ======== local variables, fourth round (after variables.py cut what calls leave in part of a register; build/naming/locals3_1..4.txt) ========
local spawn_objects_by_type 08DCE5 r3 slope_height  # unproject_object's slope height (find_floor's) after placing the new object: handed back in R3 (p_first)
local spawn_object_with_pieces 08DD75 r2 free_stack  # add_object_pieces' R2: allocate_object_2's free_object_stack_2 address (the free-block check above rules out add_object_pieces' failing path); handed back as free_count
local add_pieces_to_current_object 08DD7A r2 free_stack  # add_object_pieces' R2: the free_object_stack_2 address once the pieces are allocated (the pool-2 count less the pieces when they could not be); handed back as free_count
local add_object_pieces 08DD9D r2 free_stack  # allocate_object_2's free_stack: the address of free_object_stack_2, handed back as free_count once the pieces are chained
local move_object_projected 08DFD7 r2 block_offset  # unproject_object's block_offset (find_floor's: the floor block's offset in the nearby list): handed back as block_offset
local delete_if_off_screen 08E0B2,08E0C9 r0 deleted  # delete_object_and_children's deleted (1; int_default's R0 on the pool-2 overflow path): handed back as deleted, like the 0 of the on-screen path
local delete_object_and_children 08E0FF r3 copies_left  # int_default's copies_left (run_vblank_copies' R3) on the pool-2 overflow path (jmp int_default): handed back in R3 (slope_height)
local reset_objects 0991B6 r0 block  # reset_object_pool_2's block: 0x205000 + 0xFF * 0x30, past the last pool-2 block it stacked; handed back as block
local reset_objects 0991B6 r3 stack_slot  # reset_object_pool_2's stack_slot: past the last entry it wrote in free_object_stack_2; handed back as stack_slot
local exit_door_show 0845C2 r1 depth  # update_screen_position's depth (R1), which queue_sprite turns into the screen_y depth and hands back; not read here
local stage_05_floor_patch_show 084610 r1 depth  # update_screen_position's depth (R1), which queue_sprite turns into the screen_y depth and hands back; not read here
local spiked_press_update 0848A8,0848BD,0848E2 r11 sign  # spawn_sign_2's sign: the sign object just placed (frame counts C8 and 168), handed to the next spawn_sign_2; spawn_message_box gets it (the caller's leftover on frame 14) as the register it allocates the box into; spawn_message_box's box lands in it unread
local player_state_teetering 003AE0,003B1A r1 depth  # project_object's depth, through update_sinking_floor (sink_depth << 8 on a sinking floor) into queue_sprite_at_base; the same R1 as the draw_depth path's depth
local start_stage 000921 r11 palette_dest  # load_stage_palette's dest: the palette RAM address past the stage palette it queued; handed back in R11 (p_object); 000921 is both webs (r11, r11_2: the call is on both paths)
local continue_to_next_stage_slot 001BE2 r10 bounds_list  # load_stage_map_pointers' bounds_list for the next stage (floor_block_bounds): handed back in R10 (p_link_record)
local start_player_objects_alt 001CFB r3 player_number  # reset_objects_from_list's player_number (1 or 2 for the last player object it set up; reset_objects' stack slot when none): returned
local start_player_objects 001CFD r10 handler_cursor  # reset_objects_from_list's handler_cursor: past the last handler it read from object_handler_list; returned
local run_frame 00020F,entry r5 cursor  # draw_queued_strings' cursor: handed back as cursor (the same R5 the next frame starts with)
local init_system 000474 r4 polynomial  # init_game_settings' R4 (load_ranking's: compute_crc16's 10210000): handed back as polynomial
local init_game_settings 000577 r4 polynomial  # load_ranking's R4 (compute_crc16's 10210000, the CRC polynomial): returned
local wait_for_frames 098EC6 r8 count  # wait_for_frame's count (read_controls' R8): returned
local wait_for_frame 098EF7 r1 pressed  # read_controls' pressed: handed back as pressed
local wait_for_frame 098EF7 r10 trackball_port  # read_controls' trackball_port: handed back as trackball_port
local wait_for_frame 098EF7 r11 trackball_cursor  # read_controls' trackball_cursor: handed back as trackball_cursor
local wait_for_frame 098EF7 r2 released  # read_controls' released: handed back as released
local wait_for_frame 098EF7 r3 input_cursor  # read_controls' input_cursor: handed back as input_cursor
local wait_for_frame 098EF7 r8 count  # read_controls' count: handed back as count
local int_default 0FF5BB r3 copies_left  # int_vector_02's copies_left (run_vblank_copies'): handed back as copies_left
local int_default 0FF5BB r4 copy_entry  # int_vector_02's copy_entry (run_vblank_copies'): handed back as copy_entry
local int_vector_02 0FF5BF r3 copies_left  # run_vblank_copies' copies_left: handed back as copies_left
local int_vector_02 0FF5BF r4 copy_entry  # run_vblank_copies' copy_entry: handed back as copy_entry
local read_pixel 09A923,09A844 r0 pixel  # FFFFFFFF, returned when get_point_outcode puts the point outside the bitmap window: read_pixel's result, as the pixel read inside it
local fill_rect_unclipped 09AF32 r5 span_length  # fill_span_4bpp's length register as it ends the span of a one-row 4bpp rectangle: handed back as inner_count
local get_window_outcode 09C386 r10 outcode  # get_point_outcode's outcode for the end point: handed back as outcode
local clip_line_to_window 09C3CD,09C3BE r8 unclipped_y  # the edge clippers' unclipped_y (clip_line_at_x/y's start y before the clip): handed back as unclipped_y, the same R8 as the entry one
local clip_line_to_left_edge 09C3FC r8 unclipped_y  # clip_line_at_x's unclipped_y: the start y before the clip, handed back
local clip_line_to_right_edge 09C403 r8 unclipped_y  # clip_line_at_x's unclipped_y: the start y before the clip, handed back
local clip_line_to_top_edge 09C435 r8 unclipped_y  # clip_line_at_y's unclipped_y: the start y before the clip, handed back
local clip_line_to_bottom_edge 09C43C r8 unclipped_y  # clip_line_at_y's unclipped_y: the start y before the clip, handed back
local build_sprite_list 09A400 r4 copy_entry  # what int_default (the halt trap jumped to when a part list has 0x11 or more parts) leaves in R4: run_vblank_copies' copy_entry; handed back in R4 (height)
local spawn_sign entry,084BB8 r1 depth  # place_sign's R1 (init_sign_from_kind's: project_object's depth for the new sign), handed back as the depth parameter; one variable with it
local place_sign 084BE5 r1 depth  # init_sign_from_kind's R1 (project_object's depth for the sign), handed back through *p_depth
local spawn_walls 090DC0 r1 free_count_after  # delete_object_outside_loop's free_count (the free stack's count once the new wall is pushed back) when pool 2 is too short for its parts; handed back in R1 (spawns_offset)
local spawn_walls 090DC0 r2 previous  # delete_object_outside_loop's previous: the object before the deleted wall in the list; handed back in R2 (part_size)
local build_wall_parts 0911B1,0911E2 r2 free_stack  # allocate_object_2's R2 (the address of free_object_stack_2) for each part; handed back through *p_part_size once parts were made
local spiked_roller_crash_into_machine 080AC2 r0 jump_offset  # substate * 3 (movea.h [R0](R0)): the offset into the 3-byte branch table at 080ACB
local geyser_front_rise 08C8ED,08C8F8,08C936,08C93D r1 step  # the move per frame: base_speed or the catch-up speed (camera_ground_speed_int * catch_up_factor >> 8) stored in chaser_speed, then slope_step (negated when attribute_record's byte 0 is set) added to fixed_projected_x; one variable (play_sound's in/out R1 joins them)
local player_state_tumbling 00694B r0 facing  # the player's facing (block byte 1): >> 2 picks the 4-byte record (sprite flags, animation offset) in knocked_out_animations; push_player_out_of_wall's angle and find_floor's x land in it unread
local player_state_tumbling 0065AB,0065B2 r0 knock_direction  # the hitter's direction (100 on floor type D): 0 knocks along -y, 200 along +y, 100 along +x, anything else along -x
local build_ending_scene 01BD05 r11 dest  # load_stage_palette_set's palette RAM address, then run_vram_script's video RAM write position through the six scripts of step 0; handed back in R11 (scene_actor)
local build_ending_scene entry,01BCED r4 color  # set_stage_layer_mixing's count (always 0) as clear_bitmap_layer's colour (low byte cleared: the layer is filled with 0), whose replicated fill colour comes back; handed back (the color parameter)
local update_stage_tile_layers 015D1E r1 copy_offset  # animate_stage_palette's R1 (cycle_palette's copy offset, or the frame offset while it waits): handed back in R1 when scroll_ended < 0
local update_stage_tile_layers 015DCA r2 slots_left  # reload_changed_tile_set's slots_left (0 when it loaded the set): handed back in R2 on the reload path
local update_stage_tile_layers 015DCA r5 slot_tile_set  # reload_changed_tile_set's slot_tile_set (the matched slot's tile set, or load_tile_set's stage x 2): handed back in R5 on the reload path
local update_stage_tile_layers 015DCA r8 loaded_shift  # reload_changed_tile_set's loaded_shift (the shift of the slot compared last): handed back in R8 on the reload path
local reload_changed_tile_set entry,015E2B r10 script  # load_tile_set's script (the FFFFFFFF end marker of the set's list) when it loaded a set: handed back through *p_script; one variable with the parameter
local reload_changed_tile_set 015E2B r5 stage_offset  # load_tile_set's stage_offset (stage x 2, get_tile_set_list's switch index): handed back in R5 when no slot held the set
local update_scroll entry,016264 r2 position  # what advance_scroll / set_camera_velocity leave in R2 (set_camera_velocity's scroll_y), handed back through *p_position as in the other scroll modes; one variable with position
local update_scroll entry,015FB2 r3 speed  # scroll mode 3: scroll_slowing_down's speed (scroll_speed after the decrement), handed back through *p_speed: one variable with speed
local update_scroll entry,016264 r4 speed_value  # advance_scroll's / set_camera_velocity's speed_value (the speed's low 16 bits), handed back through *p_speed_value: one variable with speed_value
local update_scroll entry,016264 r5 next_scroll_position  # advance_scroll's next_scroll_position (modes 7 and 8), handed back: one variable with next_scroll_position
local scroll_speeding_up entry,016A06 r2 other_list  # set_camera_velocity's scroll_y on the frame the ramp ends, handed back through *p_other_list as advance_scroll_unless_ended's is: one variable with other_list
local scroll_speeding_up entry,016A06 r4 speed_value  # set_camera_velocity's speed_value on the frame the ramp ends, handed back: one variable with speed_value
local advance_scroll_unless_ended entry,016A80 r2 other_list  # advance_scroll's R2 (set_camera_velocity's scroll_y), handed back; the caller's when scroll_ended < 0: one variable with other_list
local advance_scroll_unless_ended entry,016A80 r4 speed_value  # advance_scroll's speed_value, handed back; the caller's when scroll_ended < 0
local advance_scroll_unless_ended entry,016A80 r5 next_scroll_position  # advance_scroll's next_scroll_position, handed back; the caller's when scroll_ended < 0
local advance_scroll 016A9F,016B30 r2 scroll_y  # set_camera_velocity's scroll_y, handed back through *p_other_list
local advance_scroll 016A9F,016B30 r4 speed_value  # set_camera_velocity's speed_value (the speed's low 16 bits), handed back through *p_speed_value
local set_camera_velocity entry,016B6D r10 range  # update_background_scroll's range (its cursor in stage_fast_scroll_ranges), handed back through *p_range: one variable with range
local set_camera_velocity 016B63,016B6D r2 scroll_y  # update_camera_with_shake's result, then update_background_scroll's scroll_y: handed back through *p_scroll_y
local run_stage_05_switch 016D0E r1 jump_offset  # stage_05_switch_step * 3 (movea.h [R1](R1)): the offset into the 3-byte branch table at 016D16; then handed to update_fade as delta (only its high half survives); update_fade's and run_vram_script's R1 land in it unread
local update_stage_00_exit_scene 0173F8,0173FF r10 range  # update_background_scroll's range (its cursor in stage_fast_scroll_ranges), handed back through *p_script on the scrolling steps
local update_camera_with_shake 017660 r2 y  # update_camera_view's view_y (returned when there is no shake), else the shaken 16.16 camera y given to update_camera_view_y, whose view_y goes through set_nbg0_scroll_from_camera (camera_x_pixels in the low half) and is returned
local move_camera_to_start_section 01A95F r2 y  # update_camera_view's view_y, handed back through *p_y
local update_nbg3_scroll 01B99D r4 byte_mask  # select_nbg3_pages' byte_mask (0 when it returns), handed back in R4 (speed_value) on the parallax stages
local update_nbg3_scroll 01B99D r5 shift  # select_nbg3_pages' packing shift (-20 when it returns), handed back in R5 (next_scroll_position) on the parallax stages
local draw_attract_credits 0FFBC3,0FFC71,0FFBF7 r10 text  # draw_credit_count's text (and blink_start_prompt's after it), handed back through *p_message_text on cabinets 0/1/4 without the two-credit text and on cabinets 2/3: one variable with text
local draw_credit_count 0FFDBA r10 text  # draw_credit_count_now's text (past "credit" or "Free play"), handed back through *p_text when it redraws
local draw_credit_count_now entry,0FFDD9,0FFDF0,0FFE03 r1 credits  # the block's credits (+1) into the low byte, then their units digit (% 10), then the two BCD digits under print_text_here's attribute for draw_credit_digits, handed back: one variable with credits
local load_default_palettes 09A4B5 r11 dest  # load_palette_list's palette RAM write position past default_palette_list, returned
local check_hits entry r3 dy  # check_hit's R3 for the last pair tested (round test: dy, then dy * dy; box tests: the hitter's hitbox offset), handed back (low half)
local check_hits entry r4 edge_or_centre_y  # check_hit's R4 for the last pair tested (the other's box edge, or the hitter's centre y in the round test), handed back (low half)
local check_hit 001F87,001FAF,002051 r3 dy  # round test: the hitter's centre x (x_int + s8 byte 12), then the other's centre y - the hitter's (dy, as the labels check_hit_add/sub_slope_from_dx call R3), slope-corrected, then dy * dy added into distance; box tests: the hitter's hitbox_offset_x / _y, added to its x_int / y_int for its box start
local check_hit 001E64,001FAC r4 edge_or_centre_y  # box/box: the other's box start (x_int + hitbox_offset_x), then its end (+ hitbox_size_x), the same in y, each compared with the hitter's box; round test: the hitter's centre y (y_int + s8 byte 13), which dy is taken from
local check_platforms 0021A1,0021E5 r8 load_count  # compute_bridge_sag's count (its dbr counter) after the second bridge, handed back as the first bridge's call hands it back in load_count: one variable with load_count
local metal_wall_update 00F90B,00F947 r1 last_health_or_debris  # metal_wall.last_health, compared with health (last_health - health > 1: the wall hops); then (4 - the new last_health, halfword 92) x 2, the debris count for spawn_debris. fall_to_floor's in/out R1 (over_pit, unread) joins the two
local flame_machine_update 00FF5A r0 bucket  # place_sprite_at_cursor_floor's bucket (FF, behind everything) for the last piece, set at the machine's floor: queue_sprite_in_bucket's bucket (the other callers' C calls it bucket too); spawn_object_with_pieces' spawned flag lands in it later, unread
local flame_machine_cannon_update 010420 r0 fire_bit_or_spawned  # spawn_arg_1 + 2: the flame_machine_flags bit that fires this cannon; then spawn_object_with_pieces' spawned flag (tested as a byte: 0 = no flame). The call clears only the low byte on failure, so the two share the variable
local lava_flow_advance 087717,08775D r0 x_step_or_sound  # chaser_speed << 12: added to x and stored in speed_x (the frame's step) once wait_timer runs out; later 10, the sound play_sound gets every 40 frames. project_object's screen_y and add_to_touchable_list's list head pass through it unread
local lava_rock_update 091B23 r11 block_or_slot_entry  # allocate_object_2's block (lava_rock_parts copied in, stored in part_list), the blocks spawn_lava_column hands back and is handed again (the wide rock's second column), animate_all_parts' part; in state 2 also group_slots + group_slot x 2 (slot_entry in state 1): negative = the slot was triggered
local handle_rod_init 01145C,011465 r0 count_or_spawned  # handle_count (into spawn_count), then x 12: how far the pivot is raised (z_int -= it); then spawn_objects' spawned flag (low byte 0: no handles, return); its high half stays under handle_index
local ice_launcher_update 011D5B r0 duration_index_or_spawned  # ice_launcher.step_duration_index: indexes ice_launcher_frame_durations for the new step_timer; then, on step C, spawn_object_with_pieces' spawned flag (the ice cube). unproject_object's screen_y passes through it to add_to_touchable_list
local icicle_update 088C13 r8 off_screen_or_clip_y  # state 7: is_off_screen_margin(28, A0)'s result (not 0: the icicle and its two parts are freed); then point_to_screen's screen y of the floor point (projected_y + floor_z), written to the first clip part's word 04 (sprite_frame): the clip line. point_to_screen writes only the low half, so the two share the variable
local tornado_move 0892A5,0892BA,0892F3 r1 threshold  # FFB0, or 190 for directions below 100 or above 200: the screen_threshold while holding back (as hold_threshold in icicle_dropper_move); or the path record's coordinate: the next record loads once y or x (by direction) passes it (as record_coordinate in biting_head_chase). find_floor's in/out R1 at tornado_move_step joins the two
local sinkhole_open 089B72,089BE2 r1 margin_or_scale  # 68: check_left_screen_margin's margin; after animate the new sprite_scale again (as scale before it), which scales unscaled_hitbox_size_x into hitbox_size_x (>> 8). play_sound's write_index, update_screen_position's depth and queue_sprite's result pass through it unread
local run_map_screen entry,0959E3 r4 bottom_speed  # start_window_wipe's result (window_wipe_bottom_speed), returned on step 0 as the other steps return the caller's: one variable with bottom_speed
local play_sound entry,09A5C0 r1 write_index  # queue_sound's write_index (sound_queue_write after the + 1, & 1F), handed back: one variable with the parameter write_index
local play_sound entry,09A5C0 r2 write_index_address  # queue_sound's write_index_address (sound_queue_write's address), handed back: one variable with the parameter write_index_address
local player_hud_update 00A969 r11 prompt_position        # draw_player_start_prompt's result (the start prompt's text position), handed back as text_position
local push_player_from_screen_edge 00AF9B r1 push_cos      # polar_to_xy's cos_part (cosine x 0x200 >> 6: the push's y, negated), only returned
local update_ledge_pullback 00B361 r1 cos_speed            # set_speed_from_trackball's result (set_speed_from_direction's cos_speed, speed_y negated), only returned
local check_player_floor_edge 00B4AE r1 edge_speed_cos     # polar_to_xy's cos_part of the edge speed (direction, 0x100), handed back as depth on the state 7 path
local compute_eeprom_checksum 09C899 r4 polynomial         # what compute_eeprom_crc (fallen into) leaves in R4: the CRC polynomial 0x10210000, handed back as polynomial
local update_bookkeeping_checksum 0FFAA4 r0 crc            # compute_bookkeeping_crc's crc: stored to bookkeeping_checksum and returned
local test_memory 09CB7C,09CC24 r11 print_position         # the text position clear_text_layer leaves and print_text_list hands back, before and after the tests (symbols.txt's "entry r11 print_position" no longer matches)
local test_inputs 09D323 r4 first_panel_color              # 2: the colour of the first panel fill_rect paints (panel_color then holds 0xE and 6 for the next ones)
local test_inputs 09D54E r4 cursors_left                   # move_input_test_cursors' loop count (0 when it ends), handed back as panel_color
local test_sound 09D79A r11 print_position                 # the text position clear_text_layer leaves and print_text_list hands back (text_position is the number's place; symbols.txt's "entry" line no longer matches)
local test_crt 09D8AC r11 print_position                   # the text position clear_text_layer leaves and print_text_list hands back (for crt_test_1_text, then crt_test_2_text)
local test_coin_assignments 09E351 r11 manual_print_position  # on the coin manual page: the text position clear_text_layer leaves and print_text_list hands back (print_position is the main page's, another web)
local test_coin_assignments 09E3E2,09E4C5,09E516,09E577,09E5D8 r4 coins_left  # draw_coin_credit_table's loop count (0 when it ends), returned where the other paths return color
local test_bookkeeping 09E722 r11 print_position           # the text position clear_text_layer leaves and print_text_list hands back (text_position is the numbers' place; symbols.txt's "entry" line no longer matches)
local test_backup_clear 09EAD6 r4 crc_polynomial           # reset_ranking's result: compute_crc16's polynomial 0x10210000 through copy_ranking_records, returned like the polynomial parameter
local test_outputs 09EB40 r11 setup_print_position         # on the first frame: the text position clear_text_layer leaves and print_text_list hands back (print_position is the later frames', another web)
local exit_test_mode 09EC3D r11 print_position             # the text position clear_text_layer leaves and print_text_list hands back (symbols.txt's "entry" line no longer matches)
local load_text_font 0993A2 r4 pixels                      # expand_font's last row of coloured pixels, handed back as pixels
local load_text_font 0993A2 r5 color_index                 # expand_font's colour index (1 & 0xF), handed back as color_index
local clear_text_layer 0993B0 r0 value                     # clear_blocks' fill value (0), handed back as value (test_inputs reads it as value too)

# ======== the last places without a name (build/naming/last_names.txt) ========
104BCA  table  fraction_slash_top_rows     u32  # build_fraction_glyph: or.w 54[R2], R6 (09FE4A, R2 = fraction_digit_font 104B76) ORs it over the denominator digit's rows 0-3 (one byte a row): 00 00 10 20, rows 2-3 of the fraction's slash (10 20 40 80 from 104BCC, in the high nibble, beside the digit's four low pixels)
104BCE  table  fraction_slash_bottom_rows  u32  # build_fraction_glyph: or.w 58[R2], R7 (09FE51) over rows 4-7: 40 80 00 00, rows 4-5 of the slash. The two words are the slash glyph's 8 rows, 4 bytes after fraction_digit_font's ten 8-row digits
20F016  var    service_inputs_released     u16  # read_service_inputs (unused): ~previous & new (09F650, stored at 09F65C): the bits of port F << 8 | port E that just went from 0 (pressed, active low) to 1, as service_inputs_pressed (20F014) holds the ones just pressed. check_zones also takes its address as the anchor of hitter_list (movea.b 7016[R25], R17 at 0018B6: 20F016 + 68, hit_list_next, = 20F07E), so the C there reads "hitter = service_inputs_released_ADDRESS" (note N8)
09FFF6  data  high_code_chip_padding       # FF after reset's jmp 0 (09FFF0-09FFF5) up to the end of the chip (09FFFF): erased EPROM, as low_code_chip_padding; 10 bytes, too short for coverage.py's fill rule (16)
083108  data  unused_083108                # 17 bytes after roller_back_zone_update's jmp add_to_zone_list and before flame_animation, at the start of the tables that follow the stage 4 objects and the chaser zones: the words 0002 0006 05AC FDE0 01C0 00A0 0120 0040 and a byte FF. No code or pointer reaches them and no table format of the stretch (animations, fireball lists and types, door pieces, zone records) has this shape: meaning unknown
085347  data  unused_chaser_path           # 22 bytes after spawns_with_next_handler's 4 records (count 4 x 14 bytes ends here), no code reads them: a word 0002, a position 073F,0B94 (the stage C fire front starts at 0740,0B94: fire_front_paths[0]), one chaser record (position 0, screen threshold 140, catch-up factor 20, speed 10, wait 0: the same words as spiked_roller_kinds kind 4's second record) and the FFFF FFFF FFFF end of the chase scripts: probably a leftover chaser path
08535D  data  spawn_stage_03_end_pillar_padding  # a 00 byte (halt) after unused_chaser_path, aligning spawn_stage_03_end_pillar (08535E, even, a new stretch): not code
09C569  data  unused_09C569                # 23 bytes between wait_attract_timer's rsr and eeprom_defaults: 00 02 00 00, then 00 02 00 00 F8 00 00 54 F3 01 and 00 02 00 00 F8 00 00 54 F4 (two groups that differ only in that byte; read as 10-byte records the second is cut short by eeprom_defaults' "A23S"). No reference, no pointer; meaning unknown
scoped object flame_jet 88 growth_rate_high u16  # the high half of flame_jet_init's mov.w #200, 86[R3] (010EE0) on the flame it makes (growth_rate is the low half): always 0, nothing reads it. flame_jet_init's C showed it as var_88 (the flame's family is seen there through its handler flame_jet_pilot)
scoped object static_triangle 81 frame_index_high u8  # the high byte of static_triangle_animate's mov.h #0, 80[R20] (015302) that resets frame_index (the C shows the halfword as two byte stores); nothing else reads or writes it

# ======== parts of fields and variables with a meaning of their own (build/naming/parts_1..2.txt) ========
scoped object draw_map_trail_dot 32 base_x s16               # (also parts_work_2: flags +2 16) as next_map_trail_segment 32: the segment's start x; the dot's screen_x = offset_x >> 16 + it
scoped object draw_map_trail_dot 34 base_y s16               # as next_map_trail_segment 34 (today the whole field speed_x_fraction): the dot's screen y = offset_y >> 16 + it
scoped object draw_map_trail_dot 36 step_x s32               # (also parts_work_2: speed_x_int +0 32) as next_map_trail_segment 36: taken off offset_x every frame
scoped object draw_map_trail_dot 3A step_y s32               # as next_map_trail_segment 3A: taken off offset_y every frame (AS32(speed_y_int))
scoped object draw_map_trail_dot 3E offset_x s32             # as next_map_trail_segment 3E: 16.16, - step_x a frame while the trail runs (AS32(speed_z_int))
scoped object draw_map_trail_dot 42 offset_y s32             # (also parts_work_2: z_int +0 32) as next_map_trail_segment 42 (cleared there as z_int and the two animation bytes): - step_y a frame
scoped object draw_map_trail_dot 4A trail_done s8            # as map_screen 4A: the dot moves and the segment timer runs only while it is not negative (AT8(animation, 2))
scoped object draw_map_trail_dot 4B segment_frames u16       # (also parts_work_2: animation +3 16) as start_map_trail 4B: reloads segment_timer when the segment ends
scoped object draw_map_trail_dot 51 next_trail_point u32 rom # as next_map_trail_segment 51: handed to next_map_trail_segment when segment_timer runs out (AT32(y, 1))
scoped object draw_map_trail_dot 55 segment_timer u16        # as start_map_trail 55: counted down (dec.h 55, AT16(floor_z, 1)); its low byte is tested (parts_work_2's floor_z +1 8, stays a cast), at 0 reloaded from segment_frames (parts_2.txt has the same family lines)
scoped object draw_map_stage_markers 4A trail_done s8        # as map_screen 4A: a cleared stage's badge blinks (the 57 counter) only once the trail is done (AT8(animation, 2) < 0)
scoped object draw_map_stage_markers 57 marker_blink_count u16  # (also parts_work_2: floor_z_int +1 16) as map_screen 57: + 1 for each pass over the current stage's badge; the badge shows while its bit 3 is set (it reads 56-57 whole there: floor_z_int & 800)
scoped object character_chooser 95 left_turns u8   # the high byte of left_count (+ 8 a frame while the trackball goes left): character_chooser_wait reads it as the whole turns and clears it every frame (the low byte keeps the fraction); right_turns at 93 is parts_work_2's
field object 24 direction_low u8    # the low byte of direction where it is not a direction: electric_pole_update steps its sparks' frame (0-6, every other frame by direction_high) and flame_machine_update its capsule's the same way; player_hud_update keeps the health the gauge shows (player hud_gauge_health); character_select_countdown_update sets the PLAYER SELECT heading's palette delay (the u16 direction) to 8 with a byte store
local climb_zone_effect_update 00E2D5 r10 climbing_player player  # the effect's parent is the player block (spawn_climb_zone_effect: parent = current_player), not an object: byte 0 is the player's state (A climbing), 13E its splash_zone and 14 its object, which the C read as OBJECT draw_next, a raw byte and anchor_x
field object 30 flags_low u16       # flags bits 0-15 (the contact and drawing bits) as one halfword: rimmed_sinkhole_start and spawn_sink_swirl clear them with mov.h, collapsing_bridge_plank_update copies bit 0 (box hitbox) from the plank before with or.h (and.h / or.h of bits 0-15 elsewhere print on it too). It also names the scene objects' halfword stores and reads of 30 that their families cannot (title_land_slide's mov.h #1, 30; ending_horizon_show's cmp.h #1, 30, now right instead of a cast of started), and takes the place of ending_big_explosion's split 'started = 1; step = 0' (one mov.h #1, 30): flags_low = 1. The first byte alone is parts_2.txt's flags_low_byte
field object 31 step u8             # timer objects (started in 30, a frame count in 32; ending 31 step): init_ending_scene_object clears it in a new ending object; start_ring_tally writes the panel's player number 1-3 here, which nothing reads
scoped object geyser_front 98 projected_x_fraction u16  # the low word of fixed_projected_x (16.16; projected_x_whole at 9A): geyser_front_init clears it (mov.h #0, 98) when it sets the whole part from projected_x. A scoped overlay (decompile.py 23:07): without it the clear prints as the generic var_98
field object 1C handler_low u16     # the low word of handler in blocks that run no handler: fence_post_update steps the fence arc (the post's child) every other frame with it (+ 1 & 1); the input test's cursor block (test_inputs, move_input_test_cursors: three 16-byte sprite entries, see the report) keeps the second cursor's screen y here (its x at 1E: parts_2.txt's handler_high)
field object 10 shadow_blink u8    # child shadows (pool 2 blocks): create_child_shadow, create_child_shadow_9ffe, create_child_shadow_unscaled and spawn_ring_rows clear it; draw_child_shadow and bouncing_fireball_update invert it every frame while the parent rides (flags 10) and draw the shadow only when it is 0
field object 12 hitbox_centre_x s8    # round hitboxes (flags bit 1 clear): x offset of the centre (check_hit 001F78; fireball_emitter_update from fireball_types, drop_icicle_row 0); the wall parts (build_wall_parts, wall_update) keep their screen x step to the next part here; the y offset at 13 is parts_2.txt's hitbox_centre_y
scoped object map_eggman_hands 5A frame_step_pad u8   # nothing reads it: map_eggman_update clears frame_step with a halfword store (mov.h #0, 59 on the new hands object), which clears this byte too
field object 62 cycle_count u16    # timer objects' second counter (ending 62, animate_ending 62): ending_eggman_fly_away clears it and counts the circle wipe's frames with it (bit 0 read as previous_floor_block bit 16), init_ending_runner_across clears it on object_cursor
field object 2C hud_anim_repeats s8   # HUD records (gauge, portrait): runs of the palette animation left (player hud_gauge_anim_repeats / hud_portrait_anim_repeats); player_hud_update sets 1 or 3 and counts it down when animate_hud_palette wraps
scoped object ranking_display 82 rows_remove_pad u8   # nothing reads it: ranking_display_hold sets rows_remove with a halfword store (mov.h #1, 81), which clears this byte
field object 94 spawn_arg_2_low u8   # spawn_arg_2's low byte as a byte of its own: the eel anchor's piece_count (eel_piece_explode takes one off its parent's: eel_anchor 94), spawn_object_groups_2's planks (0, FF on the last), biting_head_init clears it (nothing reads the head's)
field object 6E member_state u8    # group members' state (lava_rock, crumbling_floor, ice_platform_piece, rope_bridge_plank... 6E state): spawn_object_groups and spawn_object_groups_2 clear it (the member number goes to 6F: parts_2.txt's member_number)
scoped object wall_update 38 edge_flags u8   # check_wall_left_screen's bits (it tests them on speed_y): 0/1 the wall's start seen / gone, 2/3 its far end (the screen offset kept in speed_z); spawn_walls clears them
scoped object spiked_roller 72 event_offset_long u32  # event_offset (72) as the long spiked_roller_chase steps it with: add.w #8, 72 (4 of its 8 uses); its high half is roller_kind (74), which the add never reaches (the offset stays small). With the scoped overlays of decompile.py (23:07) the existing u16 event_offset becomes its overlay
scoped object spiked_roller 74 roller_kind u16  # the stage list's kind of the roller (the spawn record's sprite_frame, 0-5: spiked_roller_kinds[it], spiked_roller_events[it]): spiked_roller_start keeps it, spiked_roller_chase keeps the roller on the floor except for kinds 5 and 2. Needed with event_offset_long (it is a part of it); before, these two accesses printed as the generic platform
scoped object title 32 timer_low u8  # the low byte of timer: title_smoke_puff_animate counts it with inc.b 32 and compares the word (cmp.h; original code), as parts_2.txt's animate_ending_debris 32 timer_low (a scoped overlay of timer)
field object 83 var_83 u8   # the object type's own byte at 83, var_82's high byte (as parts_2.txt's var_91; it names 82 as a byte var_82_low): spawn_door_rollers clears the door roller's off_screen (door_roller 83), door_roller_update tests it after its state calls (where the C no longer sees its own object); rising_saw_init clears the saw blade's state (saw_blade 83)
field object 85 var_85 u8   # the object type's own byte at 85, var_84's high byte (parts_2.txt: var_84_low): init_sign_from_kind sets a new sign's palette delay there (animate_sign 85 palette_delay: 4 frames), as the list at 80, the index at 84 and the timer at 86 (with the pointer lines at the end, init_sign_from_kind names it animate_sign.palette_delay and nothing uses this name)
field player 9A animation_speed_int s16    # the whole part of animation_speed (16.16): walk_cycle adds it (player_animate_walking, player_animate_run_from_table), player_steer compares it with 20, player_move with speed_int, player_state_in_zone uses it as a depth
field player 130 climb_base_z_fraction u16    # the low half of climb_base_z: start_climb and start_climb_at_spawn clear it (the whole part at 132 is parts_work_2's)
field player E4 continue_countdown u16    # continue_frames and continue_seconds as one halfword (seconds x 100 + frames): player_state_continue sets the continue up when it is 0900 (cmp.h #900: seconds 9, frames 0); the states clear both with mov.h #0. A read, but the halfword means something of its own (the countdown, seconds and frames); continue_frames becomes its overlay, continue_seconds a part, and the two halfword clears (mov.h #0, E4) print on it as one store instead of two
field player CA escape_progress_int s16    # the whole part of escape_progress in player_state_trapped (16.16: + speed x 4, - 2000 a frame): the struggle animation (trap_struggle_animations[it], at most 4); at 4 the player breaks free
field player 80 previous_z_int s16    # the whole part of previous_z: player_place tests it for 800 (no floor) before putting the player back
field player BA scene_zoom_fraction u16    # the low half of scene_zoom (16.16 in the opening scene): player_state_opening_scene clears it when it sets the whole part (+ 6000 a frame, the whole part goes to sprite_scale)
field player 34 screen_x_fraction u16    # the low half of screen_x (16.16; screen_x_int at 36): cleared to put the player on a whole pixel (player_place, start_climb, the climbing, dying and stage scene states)
field player 32 step_accumulator_int s16    # the whole part of step_accumulator: player_state_climbing compares it with +-4 (one step), player_object_climb_push adds speed_int and clears it at 400 (a step)
20F201  var   bitmap_8bpp_pad          u8    # nothing reads it: clear_bitmap_lower_half sets bitmap_8bpp with mov.h #1, which clears this byte
209065  var   camera_shake_frame_pad   u8    # nothing reads it: init_stage_view and update_camera_with_shake clear camera_shake_frame with mov.h #0, which clears this byte
20E450  var   scroll_ramp_speed_pad    u8    # nothing reads it: load_camera_start_section sets scroll_ramp_speed with mov.h #1 (the others with mov.b), which clears this byte
20F40B  var   coin_counter_2_off       u8    # frames coin counter 2 stays off after a pulse (pulse_coin_counter: 6 with coin_counter_2_on, counted down once that ran out); apply_settings clears 20F40A-20F40D as one long, which then splits into its four variables
207FE2  var   first_object_link        u16   # the low half of first_object_address: the first object's 16-bit address (the high half stays 0020); run_objects and allocate_object_or_start_list store only it
207FE6  var   last_object_link         u16   # the low half of last_object_address; run_objects and link_object store only it
20F061  var   game_mode_word           u16   # game_mode and the byte after it (the low byte of trackball_1_x) as one halfword: check_stage_select_buttons sets game mode 8 with mov.h #8 (original code), which also clears that byte (read_controls rewrites it every frame)
209085  var   sega_logo_timer_word     u16   # sega_logo_timer and the byte after it (the low byte of sega_logo_scale): run_sega_logo clears the timer with mov.h #0 between fade steps (original code); the scale is set before it is used
20908C  var   stage_05_switch_step_word u16  # stage_05_switch_step and the byte after it (the low byte of fade_delta): init_stage_view clears both with mov.h #0 (original code)
20F29A  var   nbg3_scroll_x_long       u32   # nbg3_scroll_x and the halfword after it (20F29C, NBG3's y scroll fraction: it goes to 31FF2C) as one long: update_stage_06_background step 2 writes nbg1's whole x part sign-extended to it (mov.w, original code), so the fraction gets 0 or FFFF
scoped global spawn_object_groups_2 20F0EC group_floor_block u32  # as spawn_object_groups 20F0EC: every member's floor_block (5C)
scoped global spawn_object_groups_2 20F0F0 group_bridge_number u8  # as spawn_object_groups 20F0F0 (only spawn_object_groups_2 uses it): 0 or 1, every member's 95
scoped global spawn_object_groups_2 20F0F2 group_direction u16  # as spawn_object_groups 20F0F2: every member's 24
scoped global spawn_object_groups_2 20F0F4 group_leader u16  # as spawn_object_groups 20F0F4: every member's parent (26)
scoped global spawn_object_groups_2 20F0F6 group_previous_member u16  # FFFF, then each member once made: the next member's 9A (the plank before it)
scoped global spawn_object_groups 20F0F6 group_silent u8  # 0, FF for type 6 records (spawn_object_groups_mark_silent); every member's 91 (crumbling_floor 91 silent_row: no sound D when it falls)
20F048  var   power_on_time            u32   # the low 32 bits of the 48-bit power-on frame count (power_on_frames and the low half of power_on_frames_high): load_bookkeeping restores it from bookkeeping_power_on_time, count_frames_and_check_test_switch saves it there (the top 16 bits at 20F04C go to bookkeeping_power_on_time_high: parts_2.txt's power_on_frames_top; power_on_time / power_on_time_high would follow the bookkeeping names)
209002  var   screen_sprite_block_address u32  # screen_sprite_block as a long: draw_map_trail_dot keeps the whole address of its pool 2 block here (mov.w) and frees it from the low half
20F108  var   sprite_back_buffer_offset u16  # the low half of sprite_back_buffer: the buffer's offset in sprite RAM (0020 or 4000; the high half stays 0040): swap_sprite_buffers_now rewrites only it, wait_for_frame copies it
20F10C  var   sprite_list_start_offset  u16  # the low half of sprite_list_start: wait_for_frame copies sprite_back_buffer_offset into it every frame
scoped global player_move 20F0EA saved_sprite_offset_y u8  # sprite_offset_y kept while project_object runs with 12 (as player_animate 20F0EA)
scoped global player_state_falling_into_pit 20F0EA saved_sprite_offset_y u8  # the same
scoped global player_state_launched 20F0EA saved_sprite_offset_y u8  # the same
scoped global player_climbing_pull_up 20F0EA pull_up_done u8  # animate_around_anchor's at_last_step for the climb over the top (004D9B): when set the player stands on the wall's top (state 0)
scoped global player_climbing_side_step 20F0EE step_projected_y s32  # the projected_y after a sideways step along the wall (projected_y +- half the step): taken (with the new screen_x at 20F0EA) only when the step stays on the wall and on screen
2090F0  var   window_wipe_left_edge_fraction u16  # the low half of window_wipe_left_edge (16.16): start_window_wipe, closing, puts window_wipe_left there and then shifts the edge left 16 (it becomes the whole part)
2090F8  var   window_wipe_top_edge_fraction  u16  # the same for window_wipe_top_edge (right and bottom: parts_work_2)
20E5CC  var   propped_board_object     u16 object.propped_board  # propped_board_start; board_prop_update and stuck_effect_update (007A1E, a player-side object riding it) follow its speed_z and shake: AS16(var_80) through it is its shake_dx [3]
scoped object burning_boulder_flame 26 boulder u16 object.burning_boulder  # the flame's parent, its boulder (burning_boulder_start chains the four flames to it): the flames draw at its draw_depth (burning_boulder 9A) + 1 [25]
local check_player_contacts 00E877 r11 catapult object.catapult  # touched_by for kinds 6/7 (the catapults): the player is launched (state F) when its state (catapult 34) is 1 [37]
scoped object collapsing_bridge_plank 9A previous_plank u16 object.collapsing_bridge_plank  # the plank before it: state 2 copies its angle (80, u32) (+ bridge_swing_angle), x, z, flags bit 0 and swing_count (8A) [48] [51]
param init_sign_from_kind R11 sign object.animate_sign  # the object it sets up, a sign that animate_sign animates (palette_list 80, palette_index 84, palette_delay 85, palette_timer 86); project_object runs on it, then it is handed back unchanged [48] [50]
20E638  var   lead_burning_boulder     u16 object.burning_boulder  # the boulder with a speed script (burning_boulder_start); the follower copies its speed and state 3 (burning_boulder 96 state) [52]
local icicle_shadow_follow 088DE5,088E7A r12 icicle object.icicle_update  # the shadow's parent, its icicle: rides_platform (96), state (82), timer (80), draw_depth (88) [52]
field object 00 credit_part_slot u32     # ending_credits_*_scroll point current_object at one of the credit block's part slots (the block + the record's offset: 38, 40-58; add.h [R10+], R20) and store the new part there (mov.w R11, [R20]); no object keeps a long at 0 otherwise. draw_next becomes its overlay, sprite_flags a part; climb_zone_effect_update's byte read at 0 then casts credit_part_slot instead of draw_next (parts_1.txt's local there makes it a player's state)
field object 13 hitbox_centre_y s8       # hitbox_size_y's high byte (parts_1.txt names the low one hitbox_centre_x): round hitboxes' y centre offset (check_hit 001F9D: y + it), the wall parts' y step (build_wall_parts, wall_update), drop_icicle_row F0, the vine bar and the fireball emitter's records; the choosers keep their turn direction here (character_chooser 13 turn_direction; start_character_chooser, join_character_chooser_update)
field object 1E handler_high u16         # handler's high half, in blocks that run no handler (parts_1.txt: 1C handler_low): the input test's cursor block (three 16-byte sprite records, 10 apart: test_inputs, move_input_test_cursors) keeps the second cursor's screen_x here
field object 25 direction_high u8        # direction's high byte, a byte of their own for the pieces that are never turned: the sparks of electric_pole_update and flame_machine_update count every other frame in it (inc.b, & 1: the frame in 24 steps on 0); player_hud_update restarts the HUD records' palette frame in it (animate_hud 25 frame)
field object 27 hud_anim_timer s8        # parent's high byte: the HUD records' palette animation timer (animate_hud 27 anim_timer): player_hud_update clears it when it starts an animation
field object 28 hud_anim u32             # child and projected_x as one long: the HUD records' palette animation (animate_hud 28 anim; player hud_*_anim): player_hud_update stores the portrait's (hud_portrait_*_palettes_*) and reads the gauge's (+2: its first palette); child becomes the overlay, projected_x a part
field object 2D piece_frame s8           # projected_y's second byte: pool 2 pieces (never projected) keep their animation step here (the mine's bubbles and splash, the eel's parts, the turtle, the lid trap's lid, the flame jets, the rock wall's sparkles and glints, the sand arch's spray, the breakable panel's glow, the select screen title, the ranking portraits and letters): stepped and compared signed (cmp.b + bgt) by their parent's code; the HUD records keep their reset flag here (player hud_*_reset, +2D). The halfword reads at 2D (lid_trap_update, turtle_wait, turtle_swim; 2E is 0 there) then print (uint16_t)piece_frame, a cast that drops 2E
field object 30 flags_low_byte u8        # flags' first byte: the scene objects' started flag (scoped title/ending/ring_tally_*/timed_spawner 30 started: their spawners clear it, run_title_screen, timed_spawner_run, run_timed_spawns_2/3, start_ring_tally, init_ending_scene_object; init_ending_runner_across sets 1), and flag bits 0-7 where the code works on them a byte at a time (collapsing_bridge_plank_update clears bit 0, remember_platform bit 4): a name like 'started' would print there too. As an exact generic name it also replaces map_screen_update's (uint8_t)map_screen.timer (test.b 30)
field object 32 timer s16                # flags' high word, the scene objects' frame counter (scoped ending/title/timed_spawner 32 timer): init_ending_scene_object and init_ending_runner_across clear it, wrap_credits_scroll_timer puts it back to 0 at 2 (signed compare)
field object 34 speed_x_low_byte u8      # speed_x's first byte, a byte of its own for some objects: the propped board sets its shake_strength (propped_board 34, s16) with a byte store (mov.b #3C, 34: the high byte stays 0); check_player_contacts reads the touched catapult's state there (catapult 34)
field object 67 tally_gets_bonus u8      # hitbox_height's high byte: start_ring_tally writes a ring tally panel's bonus flag here (the update_ring_tally_panel / build_ring_tally_panel 67 gets_bonus)
field object 6A tally_character u8       # touched_by's low byte: start_ring_tally writes the player object's kind here (ring_tally_player 6A character)
field object 6F member_number u8         # speed's second byte (parts_1.txt: 6E member_state): spawn_object_groups(_2) write group_member_number here (lava_rock/ice_platform_piece 6F member, rope/collapsing_bridge_plank 6F plank_index); check_platforms reads it as the plank's slot in its bridge's load list
field object 82 var_82_low u8            # var_82's low byte: the icicles' state (icicle_update 82) through other pointers: drop_icicle_row sets it, icicle_shadow_follow reads its icicle's
field object 84 var_84_low u8            # var_84's low byte: init_sign_from_kind (the sign's) and river_raft_spawner_update (the raft's) clear it
field object 88 var_88_low u8            # var_88's low byte: fire_vent_burst_animate sets its parent's (fire_vent 88 burst_state) to FF
field object 91 var_91 u8                # the object type's own byte at 91 (var_90's high byte, as parts_1.txt's var_83): spawn_object_groups puts its silent flag there (FF for type 6 records, kept in group_previous_member's byte): crumbling_floor 91 silent_row
field object 92 spawn_arg_1_low u8       # spawn_arg_1's low byte, a parameter of its own for many objects: spawn_object_groups(_2) put group_takes_slot here (lava_rock/rope_bridge_plank 92 takes_slot), icicle_update sets the ice platform piece's to FF (ice_platform_piece 92 icicle_landed), player_state_climbing_object reads the climbed object's, enter_ranking_initials resets the cursor's fraction (80)
field object 93 spawn_arg_1_high u8      # spawn_arg_1's high byte: handle_rod 93 drops_players (handle_rod_handle_update reads the rod's), the join chooser's right turns (+ 8 a frame in the word), the ranking entry's initial slot (the whole part of the 8.8 cursor)
field object 95 spawn_arg_2_high u8      # spawn_arg_2's high byte: spawn_object_groups_2 puts the bridge number here (rope_bridge_plank/collapsing_bridge_plank 95 bridge), check_platforms picks bridge_1_load by it, player_state_hurt switches on the hurting object's; the join chooser's left turns. character_chooser_wait's left_count high byte prints as it until parts_1.txt's 'character_chooser 95 left_turns' is in
field object 9A var_9A u16               # the player byte and the next as one word, as several object types keep it: spawn_object_groups_2's previous plank (collapsing_bridge_plank 9A previous_plank), machine_fire_update's fireballs and spreading_flame_burn's flames (init_fireball_flight 9A target_y); with var_98 u16, init_object_fields_tail's mov.w #0, 98 becomes var_98 = 0 and var_9A = 0. player becomes its overlay
scoped object character_chooser 93 right_turns u8          # right_count's high byte: character_chooser_wait reads the turns it collected and clears it (the count's low byte keeps going)
scoped object burning_boulder 5E floor_block_high s16      # high half of floor_block (as player 5E): burning_boulder_update keeps the boulder on the floor while it is >= 0 (a floor block under it)
scoped object grate_step_update 86 copied_animation_speed u8   # build_grate_steps copies 42-45 to 84-87 with one mov.w (z_int to cut_z): this byte gets the step's animation_speed; nothing reads it
scoped object grate_step_update 87 copied_animation_timer s8   # ... and this one its animation_timer; nothing reads it
scoped object animate_ending_debris 32 timer_low u8        # timer's low byte: animate_ending_debris counts the timer with inc.b 32 (original code; it reads the word, as animate_ending 32 timer)
field player 1E scene_hop_z_int s16          # the whole part of scene_hop_z (16.16): player_state_stage_scene ends the hop when it is >= 0 (signed)
field player 30 step_accumulator_low u16     # step_accumulator's low half: player_object_climb_begin stores the object's x_int there (mov.h 4E, 30) at the climb's start; nothing reads it as a halfword (state 0x27 counts in the high half)
field player 66 ground_speed_int u16         # the whole part of ground_speed (16.16): set_speed_from_player_speed scales sine and cosine by it (movz.hw 66)
field player 69 walk_cycle_high u8           # walk_cycle's high byte: player_animate_walking and player_animate_run_from_table clear it at each walk step (>= 200 / the table's limit), keeping the rest
field player 7E previous_z_low u8            # previous_z's lowest byte: player_restart, player_state_dying, player_state_you_failed and player_state_final_stage clear it (mov.b #0, 7E) with in_game when the player leaves the game; nothing reads it as a byte (probably a leftover)
field player 94 push_xy u32                  # push_x and push_y as one long: tested and cleared together (test.w / mov.w #0, 94); add_push_to_player_motion adds the whole long to push_motion_x (add.w 94: push_y lands in the high word, original code). push_x becomes its overlay, push_y a part
field player BC scene_zoom_int u16           # the whole part of scene_zoom (16.16): player_state_opening_scene sets it from the position record (the fraction cleared) and copies it to sprite_scale
field player C8 escape_progress_low s16      # escape_progress's low half: the floor 8 sequence (state 0x25) counts the shaking there alone (+ speed >> 4; free at 400, signed; its high byte picks the escape animation's frame); the trapped state uses the high half (CA)
field player D0 side_step_accumulator_int s16    # the whole part of side_step_accumulator: player_state_climbing compares it signed with the side step threshold (2, x4 on fast_climb)
field player 132 climb_base_z_int s16        # the whole part of climb_base_z (16.16): start_climb and start_climb_at_spawn set it from the floor block's height (+10) and clear the fraction; start_climb takes z_int off it for climb_remaining
20800E  var  spawn_last_link  u16           # spawn_last's low half, the 16-bit link: add_object_pieces stores each new piece there (mov.h; spawn_last stays the 32-bit pointer)
2090F4  var  window_wipe_right_edge_fraction  u16   # the low half of window_wipe_right_edge (16.16): start_window_wipe, when closing, writes the target there and shifts the edge left 16 (the edge starts at the target)
2090FC  var  window_wipe_bottom_edge_fraction  u16  # the same for window_wipe_bottom_edge
20903B  var  loaded_tile_sets_low  u8      # loaded_tile_sets' low byte: init_stage_view adds each of the four sets there and rotates the long left 8 (add.b, rotate)
20CA1A  var  circle_wipe_radius_low  u8    # circle_wipe_radius's low byte: init_stage_view (A) and ending_eggman_fly_away (0) set the radius with a byte store; the high byte stays 0
20CCDA  var  flood_fill_direction_and_y  u16   # flood_fill_direction and flood_fill_y as one halfword: flood_fill pops a span record's first word into it (as the record's direction_and_y)
20E42F  var  scroll_speed_low  s8           # scroll_speed's low byte: run_game_mode, the stage events (event_0f_*, event_1c_scroll), how_to_play_trackball_hand_update and player_state_floor_8_sequence set the speed with byte stores (mov.b: 3, 5, F, 10, 1E); scroll_speeding_up compares it signed with scroll_ramp_speed
20E44B  var  scroll_mode_low  u8            # scroll_mode's low byte: run_game_mode and event_1c_follow/stop/scroll set the mode with byte stores (mov.b #0/1/7), update_scroll tests it once (test.b)
20E5A9  var  current_game_mode_word  u16    # current_game_mode read as a halfword: shadow_update's cmp.h #1A, 65A9 (the high byte is group_slot_index's low byte; original code)
20E5AE  var  continue_second_frames  s8     # game_mode_timer's low byte: game mode 0xE (continue) counts the frames of each countdown second there (inc.b, cmp.b #3C signed, 0 after; a button jumps to 3C)
20E5C7  var  stage_06_shake_pixels  u8      # stage_06_shake's high byte: the shake in pixels (the y offset of the stage 6 opening's ground, machine, players and NBG0/NBG3, negated on alternate frames by stage_06_shake_sign)
20E5F5  var  players_pulled_word  u16       # players_pulled and the next byte (moving_floor_object's low byte): run_game_mode clears both with one mov.h (original code)
20E626  var  bridge_swing_speed_int  s16    # the whole part of bridge_swing_speed (16.16): collapsing_bridge_plank_update tests its sign (test.h) for the end of a swing
20F003  var  unused_frame_byte  u8          # init_system clears it with frames_to_wait, frames_per_wait and debug_pause (one mov.w #0, 7000); nothing else uses it
20F00B  var  unused_system_byte  u8         # init_system clears it with sprite_list_ready, screen_mode and game_in_progress (one mov.w #0, 7008); nothing else uses it
20F04C  var  power_on_frames_top  u16       # the top 16 bits of the 48-bit power-on frame count (power_on_frames_high's high half): saved to and loaded from bookkeeping_power_on_time_high
20F404  var  shared_coin_counter_word  u16  # shared_coin_counter and unused_coin_byte as one halfword: apply_settings writes FC or 0 zero-extended (movz.bh R0, 7404)
20F407  var  coin_counter_1_off  u8         # frames coin counter 1 stays off after a pulse (pulse_coin_counter reads 7406-7407 as one halfword and sets both to 6: mov.h #606); apply_settings clears 7406-7409 with one mov.w
scoped global player_climbing_side_step 20F0EA side_step_screen_x u32   # the climbing side step's new screen_x (screen_x +- 2.0), checked against the wall's range and the screen before it is copied into screen_x (004A77: the "long, not named" of player_animate 20F0EA's comment)
field object 2E piece_value u8           # projected_y_int's low byte as a byte of its own for pool 2 pieces (never projected): the chooser and ranking portraits' character, the select title's step direction (+1/-1), the ranking letters' redraw flag, the mine shadow's flag (build/naming/parts_2.txt, blocked before overlays of parts)

# ======== links that say which object type they lead to (object.FAMILY) ========
scoped object button_box 26 parent u16 object.eggman_scene  # family link proposed in build/naming/last_names.txt (N1)
scoped object chaser_back_zone_check_parent 26 parent u16 object.burning_boulder  # family link proposed in build/naming/last_names.txt (N1)
scoped object crawler 26 parent u16 object.crawler_head  # family link proposed in build/naming/last_names.txt (N1)
scoped object eel_segment 81 stretch_step s8  # family link proposed in build/naming/last_names.txt (N1)
scoped object fire_vent 26 parent u16 object.fire_vent  # family link proposed in build/naming/last_names.txt (N1)
scoped object flame_machine_arm 84 rest_timer s16  # family link proposed in build/naming/last_names.txt (N1)
scoped object geyser_wait 26 parent u16 object.geyser_front  # family link proposed in build/naming/last_names.txt (N1)
scoped object group_member 8A group_slot u16  # family link proposed in build/naming/last_names.txt (N1)
scoped object group_member 91 silent_row u8  # family link proposed in build/naming/last_names.txt (N1)
scoped object group_member 96 depth_offset s16  # family link proposed in build/naming/last_names.txt (N1)
scoped object group_member 98 normal_palette u16  # family link proposed in build/naming/last_names.txt (N1)
scoped object handle_rod 26 parent u16 object.handle_rod_post  # family link proposed in build/naming/last_names.txt (N1)
scoped object handle_rod_handle 26 parent u16 object.handle_rod  # family link proposed in build/naming/last_names.txt (N1)
scoped object handle_rod_handle 84 rod_angle_speed s16  # family link proposed in build/naming/last_names.txt (N1)
scoped object handle_rod_handle 86 rod u16 object  # family link proposed in build/naming/last_names.txt (N1)
scoped object handle_rod_post 80 handle_index u8  # family link proposed in build/naming/last_names.txt (N1)
scoped object icicle_shadow 26 parent u16 object.icicle_update  # family link proposed in build/naming/last_names.txt (N1)
scoped object lava_rock 26 parent u16 object.group_leader_move  # family link proposed in build/naming/last_names.txt (N1)
scoped object metal_platform 26 parent u16 object.metal_platform  # family link proposed in build/naming/last_names.txt (N1)
scoped object push_crate 26 parent u16 object.push_crate  # family link proposed in build/naming/last_names.txt (N1)
scoped object ranking_entry 80 button_bit u8  # family link proposed in build/naming/last_names.txt (N1)
scoped object ranking_entry 82 out_of_rank_timer u16  # family link proposed in build/naming/last_names.txt (N1)
scoped object ranking_entry 84 time_left u16  # family link proposed in build/naming/last_names.txt (N1)
scoped object ranking_row 26 parent u16 object.ranking_display  # family link proposed in build/naming/last_names.txt (N1)
scoped object river_raft_foam 84 unused_state u8  # family link proposed in build/naming/last_names.txt (N1)
scoped object sand_arch 26 parent u16 object.sand_arch  # family link proposed in build/naming/last_names.txt (N1)
scoped object sand_streams 26 parent u16 object.sand_face  # family link proposed in build/naming/last_names.txt (N1)
scoped object saw_blade 26 parent u16 object.rising_saw  # family link proposed in build/naming/last_names.txt (N1)
scoped object saw_sparks 26 parent u16 object.saw_blade  # family link proposed in build/naming/last_names.txt (N1)
scoped object spike_panel 26 parent u16 object.spike_panel  # family link proposed in build/naming/last_names.txt (N1)
scoped object spiked_disc 26 parent u16 object.flame_machine_arm  # family link proposed in build/naming/last_names.txt (N1)
scoped object treadmill_roller 26 parent u16 object.treadmill_update  # family link proposed in build/naming/last_names.txt (N1)
scoped object whirlwind 81 self_copied_byte u8  # family link proposed in build/naming/last_names.txt (N1)

# ======== records the pointers walk, in the program ROM and the work RAM (build/naming/structs_1..5.txt) ========
struct floor_block 16 rom                 # one floor block of the stage: floor_blocks points at the count word, the records follow from +2, 0x16 apart (find_nearby_floor_blocks steps floor_block by 16, nearby_floor_blocks keeps their offsets); no_floor_block (10B70A) is one too (height 800); stage_01_02_03_07_floor_blocks (10B720): count 0039, the first block 80 C3 96 11 / 7F 08 79 12 / 00 69 3F 07 / FF EA 99 07 / 38 04 / 00 / 00 / 00 / 02; census ROM 10BDFE-10DF11
field floor_block 00 x_min s32            # find_floor 00C77B "cmp.w [R12+], R0" + blt: the object's x below it skips the block (16.16, as object x); spawn_object_groups 09149C subtracts it for a slope along x
field floor_block 04 x_max s32            # find_floor 00C780 + bgt: x above it skips the block
field floor_block 08 y_min s32            # find_floor 00C785 + blt: y below it skips the block; slopes along y measure from it (00C735, 0914A5)
field floor_block 0C y_max s32            # find_floor 00C78A + ble: y at most it, the block holds the point
field floor_block 10 height s16           # find_floor 00C7F7 "mov.h 10[R10], 56[R20]" (floor_z_int; no_floor_block 800), get_floor_z_at_point 00C897 (<< 10), spawn_object_groups 091473, spawn_object_groups_2 093A62 (z_int)
field floor_block 12 type_index u8        # find_floor 00C6EE / 00C7E0 "movz.bw 12[R10], R0": index into floor_types (x 4): floor_type (77) and floor_attribute (7A); spawn_object_groups_2 093A7F the same
field floor_block 13 slope u8             # find_floor 00C705 / 00C802 into floor_slope (76); 0 flat, else index into floor_slopes (x 8: flag bit 1 = along x, factor at +2): floor_z += (x - x_min, or y - y_min) x factor (00C70E-00C751)
struct bridge_load 50                     # a rope bridge's load list: bridge_0_load (209400) or bridge_1_load (209500), the 50 bytes before the bridge's sag table (bridge_0_sag 209450, bridge_1_sag 209550): a count byte, then plank numbers (0 and the plank count for the anchored ends, then each loaded plank's 6F); check_platforms appends (0021A4), compute_bridge_sag sorts them and turns them into the sag table; census RAM 209400-209505
field bridge_load 00 count u8             # entries in the list: check_platforms "inc.b [R11]" (00219F), then the plank goes to [R11](count) (0021A4); compute_bridge_sag "cmp.b #2, [R17]" (0021F9: only the two ends: clear the sag table), the sort's pass count (002203 "movz.bw [R11+], R9") and the fill's (002233)
struct stage_object_record 16 rom         # a group member's template in stage_object_records (093269, data line record=22): spawn_object_groups copies one into each member through member_record (0913AC "mov.w 709E[R25], R12" = group_record, then [R12+] 0913B1-0913DE); the C read them as ROM16 (group_record is u32 rom)
field stage_object_record 00 sprite_flags u16       # 0913B1 -> 2[R11]
field stage_object_record 02 sprite_frame u16       # 0913B6 -> 4[R11]
field stage_object_record 04 sprite_palette u16     # 0913BB -> 6[R11] (also copied to 98: the member's normal palette)
field stage_object_record 06 handler u32            # 0913C0 -> 1C[R11]
field stage_object_record 0A animation u32 rom      # 0913C5 -> 48[R11]
field stage_object_record 0E hitbox_size_x u16      # 0913CA -> 10[R11]
field stage_object_record 10 hitbox_size_y u16      # 0913CF -> 12[R11]
field stage_object_record 12 sprite_offset_x s8     # 0913D4 -> 64[R11]
field stage_object_record 13 sprite_offset_y s8     # 0913D9 -> 65[R11]
field stage_object_record 14 depth_offset s16       # 0913DE -> 96[R11] (group_member 96 depth_offset; type 2 rows add group_member_count x group_depth_step, 09145F)
struct stage_object_record_2 18 rom       # a group member's template in stage_object_records_2 (093F59, data line record=24): spawn_object_groups_2 adds group_record (an offset there) to 93F59 (0939D1 "add.w #93F59, R12") and copies it with [R12+] (0939D8-093A0A); census ROM 093F59-093FB7
field stage_object_record_2 00 sprite_flags u16     # 0939D8 -> 2[R11]
field stage_object_record_2 02 sprite_frame u16     # 0939DD -> 4[R11]
field stage_object_record_2 04 sprite_palette u16   # 0939E2 -> 6[R11]
field stage_object_record_2 06 handler u32          # 0939E7 -> 1C[R11]
field stage_object_record_2 0A animation u32 rom    # 0939EC -> 48[R11]
field stage_object_record_2 0E odd_member_animation u32 rom  # 0939F1 "mov.w [R12+], R1": replaces animation when group_member_number is odd (0939F4 test1 #0, 70A8[R25])
field stage_object_record_2 12 hitbox_size_x u16    # 093A00 -> 10[R11]
field stage_object_record_2 14 hitbox_size_y u16    # 093A05 -> 12[R11]
field stage_object_record_2 16 depth_offset s16     # 093A0A -> 96[R11] (rope_bridge_plank 96 depth_offset)
struct stage_object_template 1A rom       # stage_object_templates (01F4D2, data line record=26): spawn_ring_rows (01E400 "movea.b 1F4D2[PC](R0), R12", R0 = ring_row_template) and spawn_stage_template (01EE0C, template_spawn_index x 1A) copy one into each ring with [R12+]; census: spawn_stage_template never runs, spawn_ring_rows' reads are ROM16 already
field stage_object_template 00 sprite_flags u16     # 01E406 / 01EE0F -> 2
field stage_object_template 02 sprite_frame u16     # 01E40B / 01EE14 -> 4
field stage_object_template 04 sprite_palette u16   # 01E410 / 01EE19 -> 6
field stage_object_template 06 hitbox_size_x u16    # 01E415 / 01EE1E -> 10 (both clear hitbox_size_y)
field stage_object_template 08 hover_height u16     # 01E41F -> 82 (carried_ring hover_height: the ring floats this high over its platform); spawn_stage_template skips it (01EE23 add.w #2, R12)
field stage_object_template 0A animation_speed u8   # 01E425 / 01EE2B -> 44
field stage_object_template 0B animation_timer s8   # 01E42A / 01EE30 -> 45
field stage_object_template 0C animation_frame u8   # 01E42F / 01EE35 -> 46
field stage_object_template 0D kind u8              # 01E434 / 01EE3A -> 47 (3: a ring)
field stage_object_template 0E animation u32 rom    # 01E439 / 01EE3F -> 48
field stage_object_template 12 handler u32          # 01E43E / 01EE44 -> 1C
field stage_object_template 16 sprite_offset_x s8   # 01E443 / 01EE49 -> 64
field stage_object_template 17 sprite_offset_y s8   # 01E448 / 01EE4E -> 65
field stage_object_template 18 hitbox_height u16    # 01E44D / 01EE53 -> 66
struct sprite_part_list 82                # a count word, then up to 16 parts of 8 bytes (build_sprite_list halts from 11 parts on: 09A3F9 "cmp.h #11, R13"); in work RAM or in the program ROM (census mixed rom+work_ram 0851B5-20B80E: the ROM lists are read, the RAM ones also written)
field sprite_part_list 00 count u16       # the parts' dbr counter: build_sprite_list 09A3F6 "movz.hw [R14+], R13", cycle_part_palettes 00CB9C and animate_all_parts 00E13A "mov.h [R11+], R1"
struct sprite_part 8                      # one part, after the list's count: build_sprite_list reads frame, flags, dx, dy, palette with [R14+] (09A406-09A46D) into a sprite entry; cycle_part_palettes writes +6 (00CB9F) and animate_all_parts +0 (00E13D) of every part, 8 bytes on each time (movea.b 8[R11], R11)
field sprite_part 00 frame u16            # low half of the part's sprite_frame_record in sprite_frames' page (09A406 "mov.h [R14+], R12"); animate_all_parts writes the animation's frame into every part
field sprite_part 02 flags u16            # XORed with the object's sprite_flags & F0 into entry word 0 (09A439-09A43F)
field sprite_part 04 dx s8                # x offset: times scale_x (sprite_scale, negated with an x flip) >> 8, plus the object's screen_x: entry word 5 (09A44B "movs.bw [R14+], R2")
field sprite_part 05 dy s8                # y offset: times scale_y, plus screen_y: entry word 4 (09A451)
field sprite_part 06 palette u16          # entry word 7 (09A46D); cycle_part_palettes writes the palette list's colour into every part (00CB9F "mov.h R2, 6[R11]")
struct frame_animation 2 rom              # an animation of sprite frames (OBJECT->animation): the last frame's number, then the frames, a word each (sprite_frame_numbers); animate_step 00DEB3 reads the word, 00DEB7 moves on to the frames
field frame_animation 00 last_frame u16  # the number of the last frame (animate_step keeps the low byte)
field frame_animation 02 frames u16[]    # the sprite frames, a word each, after last_frame: the code reads animation + 2 + frame x 2 (animate_step 00DEC4 and on, through sprite_frame_numbers; river_raft_update, river_water_update in place)
elements sprite_frame_numbers u16        # the sprite frames of a frame_animation, after its last_frame word: animate_step reads frame_list + frame x 2 (00DEC4 and on)
elements part_list_offsets u16           # the words after a multi-part animation's last_frame (frame_animation), one per frame: the frame's part list is the animation's address plus it (animate_part_list_step 00E0CB-00E0D5 "movz.hw [R10](R1), R0", minus 2 plus R10, into part_list 04); census ROM 0851B1-085275
struct animation_step 4 rom               # a step of the animations that move the sprite: frame, then s8 dx, s8 dy (4 bytes a step): animate_and_move (00DF46 "movea.w [R10](R1), R10" = animation + 2 + frame x 4), set_step_around_anchor (00DFC1), animate_with_screen_offset (00E03E); apply_step_screen_offset hands add_step_screen_offset the step's dx (animation + 4 + frame x 4, 00E049-00E054); census ROM 0821E4-112C51
field animation_step 00 frame u16         # into sprite_frame (00DF4A, 00DFC5, 00E042 "mov.h [R10+], 4[R20]")
field animation_step 02 dx s8             # "movs.bh [R10+], R0", negated with sprite_flags bit 6 (x flip): added to projected_x (00DF5C), anchor_x (00DFD7) or screen_x (00E065)
field animation_step 03 dy s8             # negated with bit 7 (y flip): added to projected_y_int (00DF6D), anchor_y (00DFEC) or screen_y (00E076)
struct sign_kind C rom                    # a record of object_kinds (084FB5, data line record=12): sign_start reads the z word of object_kinds + 12 x kind (084B98, 084B9F) and hands the rest (+2) to init_sign_from_kind, as place_sign does (84FB7 + 12 x kind); census ROM 084FC3-085149
field sign_kind 00 z s16                  # sign_start 084B9F "mov.h [R7+], 42[R20]" (z_int)
field sign_kind 02 sprite_frame u16       # init_sign_from_kind 084BEC -> 4[R11]
field sign_kind 04 duration u16           # 084BF1 -> 34[R11] (speed_x_fraction: how long the sign stays)
field sign_kind 06 palette_list_offset u16  # 084BF6 + 8514D (sign_palette_lists) -> 80[R11] (animate_sign palette_list)
field sign_kind 08 handler u32            # 084C06 -> 1C[R11]
struct back_zone_record 6 rom             # a record of object_steps (08340D, data line record=6): spawn_chaser_back_zone takes record zone_index (082EA4 "movea.h [R10](R10), R10" = 3 x index, 082EA8 "movea.h 8340D[PC](R10), R12": 6 bytes a record) and reads it with [R12+]; spawn_roller_back_zone reads only the limit of records 3-7 (object_steps[index x 3]); census ROM 08340D-08341B
field back_zone_record 00 limit u16       # 082EAE -> 80: chaser_back_zone end_position (the scroll position it is removed at); roller_back_zone limit for records 3-7
field back_zone_record 02 handler u32     # 082EB4 -> 1C: the chaser back zone's handler
local check_platforms 00218F,00219A r11 load_list bridge_load  # 00218F "movea.b 1400[R25], R11" = bridge_0_load, 00219A = bridge_1_load when the platform's 95 is set: the bridge's load list, count + 1 and the plank appended
local check_platforms 0021E5 r11 load_list  # compute_bridge_sag's R11 (its sag curve cursor) after the second bridge, handed back as the first bridge's call hands it back in load_list: one variable with load_list
param compute_bridge_sag R17 load_list bridge_load  # 209400 / 209500: count, then the loaded plank numbers (check_platforms' load_list_2: 0021BE / 0021D6)
local compute_bridge_sag 002230,002233,00223D r10 load_entry  # the load list again (002230 "mov.w R17, R10"), past its count (002233), then each plank number (00223D "movz.bw [R10+], R2"): named here because, typed bridge_load at 002230, the decompiler would call this web bridge_load after its structure (param R10's name, load_entry, is what it had)
local find_floor 00C6EA,00C75B,00C7B6,00C7DD r10 block floor_block  # floor_blocks + 2, plus the found offset: the block's record (height +10, type +12, slope +13); no_floor_block when none
local find_floor 00C778 r12 record floor_block  # 00C778 "add.w R2, R12" = floor_blocks + 2 + the nearby entry: the candidate block, whose x min, x max, y min, y max the [R12+] steps compare (without it record would lose the ROM type block had)
local find_floor 00C6F2,00C7E4 r12 record floor_block_type  # floor_types + type_index x 4 ("movea.w 10E14C(R0), R12"): floor_type, floor_attribute at +2
local find_floor 00C77B,00C780,00C785 r12 record  # the [R12+] steps through the candidate block
local get_floor_z_at_point 00C855,00C88B,00C894 r10 block floor_block  # floor_blocks + 2, then the found block (+ its offset) or no_floor_block
local get_floor_z_at_point 00C870 r12 bounds floor_block  # the block's box: x min, x max, y min, y max read in turn (00C870 "add.w R2, R12": the candidate block)
local find_floor_block_at_point 00C8DE,00C914,00C91C r10 block floor_block  # floor_blocks + 2 (then the block, or no_floor_block)
local find_floor_block_at_point 00C8F9 r12 bounds floor_block  # the block's box read in turn (00C8F9 "add.w R2, R12": the candidate block)
param unlink_child_chain R0 object object  # the object whose child link (28) is cut first, then each child in turn ("mov.h 28[R0], R1" ... "mov.h R1, R0": the next one in the same page) (the name of its 08DE68 local line)
local unlink_child_chain 08DE68 r0 object object  # ->child) the next object of the chain ("mov.h R1, R0": its child, in the same page)
local drop_rings 08E54E r3 new_ring object.released_ring  # scatter_rings_random's ring (R17, 08E54E "mov.w R17, R3"): its parent, animation timer, angle, speed, release delay (84) and handler (released_ring_wait, 08E58E) are set, 92 gets its speed_z (released_ring bounce_speed), 28 leads to its shadow
local drop_rings 08E548 r17 ring object.released_ring  # what scatter_rings_random hands back in R17 (the same ring as new_ring): drop_rings sets its flip bits (6[R17], 08E5D2-08E608)
param link_object R11 object object       # the object put between last_object and first_object (its 20 and 22: previous, next)
local allocate_object_after_current 09927B r11 new_object object  # the object popped from the free stack (its 20 and 22 are set: previous, next)
local spawn_object_groups 09146F r10 floor_block_record floor_block  # floor_blocks + 2 + the member's floor block: floor z at +10, slope at +13, x/y bounds at 0/8
local spawn_object_groups 0913AC r12 member_record stage_object_record  # 0913AC "mov.w 709E[R25], R12" = group_record: the member's template
local spawn_object_groups 0913B1,0913B6,0913BB,0913C0,0913C5,0913CA,0913CF,0913D4 r12 member_record  # the [R12+] steps through the template: sprite flags, frame, palette, handler, animation, hitbox sizes, sprite offsets, depth offset
param spawn_group_leader R10 record leader_spawn  # object_group_spawns record past position and type: leader number (& 3), x, y, z, direction, animation offset (+ 9357D); census ROM 092124-093F1A (both object_group_spawns and stage_record_spawns)
local spawn_object_groups_2 093A5E r10 floor_block_record floor_block  # floor_blocks + 2 + the member's floor block: z at +10, floor type at +12, slope at +13
local spawn_object_groups_2 0939D1 r12 member_record stage_object_record_2  # 0939D1 "add.w #93F59, R12": stage_object_records_2 + group_record, the member's template
local spawn_object_groups_2 0939C8,0939D8,0939DD,0939E2,0939E7,0939EC,0939F1,093A00 r12 member_record  # group_record (an offset: its low byte goes to the member's kind), then the [R12+] steps through the template: sprite flags, frame, palette, handler, animation, second animation, hitbox sizes, depth offset
local cycle_part_palettes 00CB98 r11 part sprite_part_list  # 00CB98 "mov.w 4[R20], R11": the object's part_list (04-07), whose count word gives the parts
local cycle_part_palettes 00CB9C,00CBA3 r11 part sprite_part  # walks the parts after part_list's count, 8 bytes each (00CB9C past the count, 00CBA3 "movea.b 8[R11], R11")
local animate_step_with_flags 00DE60 r10 step_record frame_animation  # the animation ("mov.w 48[R20], R10"): its first word's low byte is the last step (00DE64 "mov.h [R10+], R0"); census ROM 009C4D-114848
local animate_step_with_flags 00DE64 r10 step_record flagged_step  # animation + 2: the steps (flags, frame), 4 bytes each
local animate_step_with_flags 00DE88 r10 step_record flagged_step  # animation + 2 + frame x 4 ("movea.h [R10](R1), R10", R1 = frame x 2): the step's sprite flags and sprite frame
local animate_step 00DEB3 r10 frame_list frame_animation  # the animation
local animate_step 00DEB7 r10 frame_list sprite_frame_numbers  # animation + 2: the animation, then animation + 2: the sprite frames, one word per frame; census ROM 0026F9-114B36
local animate_and_move 00DF0B r10 step_record frame_animation  # the animation ("mov.w 48[R20], R10"), last step in its first word (00DF0F "mov.h [R10+], R0"); census ROM 083123-1129D4
local animate_and_move 00DF0F r10 step_record animation_step  # animation + 2: the steps (frame, dx, dy), 4 bytes each
local animate_and_move 00DF46 r10 step_record animation_step  # "movea.w [R10](R1), R10": the step (animation + 2 + frame x 4): sprite frame, dx byte, dy byte
local animate_and_move 00DF4A,00DF4F r10 step_record  # the [R10+] steps through it
local animate_around_anchor 00DF80 r10 steps frame_animation  # the animation ("mov.w 48[R20], R10"): its first halfword is last_frame (00DF84 "mov.h [R10+], R0"); census ROM 0821E2-112C28
local animate_around_anchor 00DF84 r10 steps animation_step  # animation + 2: the step records set_step_around_anchor reads
local set_step_around_anchor 00DFC1 r10 steps animation_step  # 00DFC1 "movea.w [R10](R1), R10": steps + frame x 4, the step (frame, dx, dy); census ROM 0821E4-112C51
local animate_with_screen_offset 00E003 r10 step_record frame_animation  # the animation ("mov.w 48[R20], R10"), last step in its first word (00E007 "mov.h [R10+], R0"); census ROM 091FA9-0920B9
local animate_with_screen_offset 00E007 r10 step_record animation_step  # animation + 2: the steps (frame, dx, dy), 4 bytes each
local animate_with_screen_offset 00E03E r10 step_record animation_step  # "movea.w [R10](R1), R10": the step's record: sprite frame, then the offset bytes (handed to add_step_screen_offset at its dx)
local animate_part_list_step 00E08C r10 frame_offsets frame_animation  # the animation ("mov.w 48[R20], R10"): its first halfword is the last frame (00E090 "mov.h [R10+], R0"); census ROM 0851AF-085275
local animate_part_list_step 00E090 r10 frame_offsets part_list_offsets  # animation + 2: per frame a 16-bit offset from the animation to that frame's part list
local animate_all_parts 00E0EF r10 frame_sprites frame_animation  # the animation ("mov.w 48[R20], R10"): its first halfword is the last frame (00E0F3 "mov.h [R10+], R0"); census ROM 087D05-093FC9
local animate_all_parts 00E0F3 r10 frame_sprites sprite_frame_numbers  # animation + 2: one sprite frame per animation frame (00E132 "mov.h [R10](R1), R2")
local animate_all_parts 00E136 r11 part sprite_part_list  # 00E136 "mov.w 4[R20], R11": the object's part list (count word, then the parts)
local animate_all_parts 00E13A,00E140 r11 part sprite_part  # past the count (00E13A "mov.h [R11+], R1"), then 8 bytes on (00E140): each part gets frame_sprite in its frame word
local clear_sprite_buckets 099FAB,099FB4 r11 bucket object  # each bucket head from 20FA02 ("neg.h #1, [R11+]" past sprite_buckets_end, then "mov.h R11, [R11+]"): a list head linked through draw_next, read as the draw_next (00) of an object that is not there, as build_sprite_list reads it through R24 (OBJECT(last_object)->draw_next) and empties it; empty = its own address
local queue_sprite_at_depth 099FEB r0 bucket_head object  # 099FEB "movea.h 7A02[R25](R0), R0": sprite_buckets + (FF - key) x 2: the bucket head, the draw_next of a list head (as clear_sprite_buckets): "mov.h [R0], [R20]; mov.h R20, [R0]" pushes the object
local queue_sprite_in_layer 09A004 r0 bucket_head object  # sprite_buckets + sprite_layer * 2: the object goes in front of that bucket's list (the head read and written as draw_next)
local queue_sprite_in_bucket 09A01C r0 bucket_head object  # sprite_buckets + bucket * 2 (the head read and written as draw_next)
local queue_sprite_at_floor 09A066 r0 bucket_head object  # sprite_buckets + (0xFF - depth) * 2 (the head read and written as draw_next)
local queue_sprite_at_previous_floor 09A0B0 r0 bucket_head object  # sprite_buckets + (0xFF - depth) * 2 (the head read and written as draw_next)
local queue_sprite_at_base 09A0F4 r0 bucket_head object  # sprite_buckets + (0xFF - depth) * 2 (the head read and written as draw_next)
local queue_sprite_at_depth_offset 09A155 r0 bucket_head object  # sprite_buckets + ((FF - depth + bucket_offset) & FF) x 2 (the head read and written as draw_next)
local queue_sprite_at_previous_floor_2 09A1A2 r0 bucket_head object  # sprite_buckets + (0xFF - depth) * 2 (the head read and written as draw_next)
local load_sized_frame_address_at_row 09A235 r1 frame_record sprite_frame_record  # sprite_frames | sprite_frame: the frame's 4-word record (width at +0, address at +6)
local load_frame_size 09A26C r0 frame_record sprite_frame_record  # sprite_frames | sprite_frame: width (+0) to 1A, the height byte (+2) to 18
local build_sprite_list 09A317,09A34C,09A36C,09A406 r12 frame_data sprite_frame_record  # sprite_frames (130000) with the low half = sprite_frame (09A317, 09A34C, 09A36C "mov.h 4[R20], R12") or a part's frame (09A406 "mov.h [R14+], R12"): the frame record
local build_sprite_list 09A285,09A2B3,09A31F,09A32B,09A350,09A359,09A370,09A377,09A38A,09A409,09A415,09A436 r12 frame_data  # sprite_frames (130000) at the start, then the [R12+] steps through the frame record's words
local build_sprite_list 09A3F2 r14 part_list_head sprite_part_list  # R14 (r14 in C): 09A3F2 "mov.w 4[R20], R14", the object's part list (its count word)
local build_sprite_list 09A3F6 r14 part_cursor sprite_part  # R14 (r14 in C) past the count (09A3F6 "movz.hw [R14+], R13"): the first part; the [R14+] steps of a part move it 8 on, to the next
local sliding_door_wait 014453,01446F r11 player_base player  # the player's block (PLAYER_BLOCK(1), + 200 per player at 01446F): its 140 is stage_b_path_done
00FB08  table metal_wall_part_tables u32 metal_wall_part_sprites  # sprite tables of pieces 3, 2, 1 (FB90, FB60, FB30: metal_wall_part_sprites), then their offset tables (FBA0, FB70, FB40: metal_wall_part_offset pairs, which local metal_wall_draw 00F9E7 types where the frame's pair is picked); census ROM 00FB30-00FBBF
local metal_wall_draw 00F9E7 r2 offset_pair metal_wall_part_offset  # the part's offset table + frame_index x 2 + flip_offset: its signed x, y bytes (00F9EA "movs.bh [R2+], R3", 00F9ED); census ROM 00FB40-00FBBF
struct metal_wall_part_offset 2 rom          # a (screen dx, dy) byte pair of the metal wall's offset tables (metal_wall_part_tables entries 3-5: 00FBA0, 00FB70, 00FB40) by frame, 8 more for the flipped wall: metal_wall_draw 00F9D6-00F9E7 (the table + frame_index x 2 + flip_offset), "movs.bh [R2+], R3", "movs.bh [R2], R4" (00F9EA, 00F9ED); census ROM 00FB40-00FB4F, 00FB70-00FB7F, 00FBA0-00FBAF
field metal_wall_part_offset 00 dx s8         # added to the wall's screen_x for the part (00F9F6): 28 ...
field metal_wall_part_offset 01 dy s8         # added to its screen_y (00FA00)
elements metal_wall_part_sprites u16         # a part's sprite by frame (metal_wall_part_tables entries 0-2: 00FB90, 00FB60, 00FB30): metal_wall_draw 00FA04 "mov.w FB08[PC](R0), R2", 00FA0F "mov.h [R2](R1), 4[R20]" (R1 = frame_index); census ROM 00FB30-00FB3F (5840 5830 ... 5850), 00FB60-00FB6F, 00FB90-00FB9F
local how_to_play_trackball_hand_update 083F57 r10 step_list frame_animation  # the animation (the hand's 48, through object_cursor): census ROM 0852AD-0852B5
local how_to_play_trackball_hand_update 083F5B r10 step_list sprite_frame_numbers  # animation + 2: the steps' frame words
local spawn_stage_template 01EE0C r12 template_cursor stage_object_template  # 01EE0C "add.w R0, R12": stage_object_templates + template_spawn_index x 1A, the template copied into the new object (its [R12+] steps 01EE0F-01EE53 are the other sites of template_cursor)
local spawn_ring_rows 01E400 r12 template_cursor stage_object_template  # 01E400 "movea.b 1F4D2[PC](R0), R12": stage_object_templates + ring_row_template, the template copied into each ring
local spawn_ring_rows 01E406,01E40B,01E410,01E415,01E41F,01E425,01E42A,01E42F,01E434,01E439,01E43E,01E443 r12 template_cursor  # the [R12+] steps through the template's fields
local spawn_chaser_back_zone 082EA8 r12 step_record back_zone_record  # object_steps + 6 x chaser_zone_index: 80 word, then the handler
local player_pacer_update 084475 r11 player_2 player  # 084475 "xch.w R10, R11": the pair's other player block after the swap (R10 and R11 are 208400/208600/208800: PLAYER_BLOCK(1-3)); without it the exchange loses the type and speed_scale (C4) is written raw
local player_pacer_update 084497,0844B7,0844DD r11 middle_player player  # three players: the block in the middle along the pacing axis after the swaps (speed_scale E); its 14 is its object
local player_pacer_update 08449C,0844CA,0844DD r12 last_player player  # player_4 in the C: PLAYER_BLOCK(3) (08449C), then the block whose object has the smallest coordinate along the pacing axis after the swaps (xch.w at 0844CA, 0844DD): speed_scale D when the travel decreases
local sign_start 084B98 r7 kind_record sign_kind  # object_kinds + 12 x the kind (sprite_frame): its z word, then record (+ 2) for init_sign_from_kind (which gets the structure from it: place_sign's 84FB7 + 12 x kind says nothing)
20F0B2  var   wall_spawn_cursor     u32 wall_spawn  # spawn_walls: points into wall_spawns (903A2 + wall_spawns[stage], 090D02-090D13), 14-byte records (position, end position, x, y, z, kind, part count, depth_from_next) read inline (090D22 "sub.h [R10+], R0") and through record (090D3A); as group_spawn_cursor, rom: the RAM reads are the crafted_objects and walls scenarios' records poked into 20B000-20B20D
struct wall_spawn E rom                      # a wall_spawns record (14 bytes, 0903A2 + wall_spawns[stage]): spawn_walls reads the one wall_spawn_cursor points at, "sub.h [R10+], R0" (090D22), R12 = it + 2 (090D3A-090D3F), [R12+] (090D42-090D76), and moves the cursor 14 on; FFFF ends a list; census ROM 0903C8-090A71
field wall_spawn 00 position u16              # the scroll position it spawns at: scroll_position - it, bl (090D22-090D25, unsigned); skipped 100 or more behind
field wall_spawn 02 end_position u16          # "mov.h [R12+], 82[R11]" (wall_update end_position): 0 = removed off the left of the screen
field wall_spawn 04 x u16                     # "mov.h [R12+], 4E[R11]" (x_int): 0146 ...
field wall_spawn 06 y u16                     # "mov.h [R12+], 52[R11]" (y_int): 1504, 158B ...
field wall_spawn 08 z u16                     # "mov.h [R12+], 42[R11]" (z_int)
field wall_spawn 0A kind u16                  # "mov.h [R12+], 4[R11]" (sprite_frame): the kind's byte offset into wall_kinds (kind x 14: 12C ...)
field wall_spawn 0C part_count u8             # "movz.bh [R12+], 34[R11]" (speed_x_fraction): the wall's number of parts
field wall_spawn 0D depth_from_next u8        # "mov.b [R12+], 37[R11]" (wall_update depth_from_next)
local wall_editor_draw_arrow 091012 r11 marker object  # the arrow's slot (209602 + index x 80): its object fields are set (2, 4, 6, 64, 30, 74, 4C, 50) and it becomes the current object (091058 "mov.w R11, R20") for find_floor, project_object and queue_sprite_in_bucket
local wall_editor_draw_arrow 090FF9 r11 marker  # &wall_editor_arrow_count
local wall_editor_draw_arrow_at_z 091092 r11 marker object  # as wall_editor_draw_arrow (and 40 = z): the slot it makes the current object for project_object
local wall_editor_draw_arrow_at_z 091079 r11 marker  # &wall_editor_arrow_count
local spike_panel_update 00F28B,00F38B r1 offset_record spike_panel_screen_offset  # the frame's (dx, dy) record in spike_panel_head_offsets, then in spike_panel_part_2_offsets
local spike_panel_update 00F321 r1 offset_record spike_panel_part_offset  # spike_panel_part_1_offsets + frame x 6 (00F321 "add.w R2, R1"; the C works it out as frame x 4 + frame + frame): part 1's (dx, dy, dz) record; census ROM 00F4DA-00F4FD
local spike_panel_update 00F324 r1 offset_record  # past part 1's dx ([R1+])
scoped object stage_03_floor_block 48 animation u32 stage_03_floor_block_animation  # stage_03_floor_block_start sets it to its kind's record in stage_03_floor_block_kinds + 4, past the offsets and the depth word (080099 "mov.w R11, 48[R20]"), and reads its first frame at + 2 ("mov.h 2[48[R20]], 4[R20]", 0800D9); census ROM 0822A6-0822CA (as structs_2.txt's collapsing_bridge_plank 48)
local spiked_roller_start 080176 r10 record_cursor spiked_roller_kind  # spiked_roller_kinds + spiked_roller_kinds[kind] (080176 "movea.b 82066[PC](R11), R10"): the kind's record (parts, layout, first state, zone), then its chase script (path)
local spiked_roller_start 08017C,080182,080185 r10 record_cursor  # moving through the kind's record ([R10+])
local spiked_roller_start 0802AD r10 record_cursor spiked_roller_part  # roller_part_record (0802AD "mov.w 70EC[R25], R10"): the part record each part copies (sprite flags, hitbox sizes, path direction, flags, hitbox offsets); census ROM 08219E-0821DB
local spiked_roller_start 0802B2,0802B7,0802BC,0802C1,0802C7 r10 record_cursor  # moving through the part record ([R10+])
local spiked_roller_start 080196 r11 layout_cursor spiked_roller_layout  # spiked_roller_layouts + the kind's layout offset (080196 "add.w #8219C, R11"): step x, step y, then the part record (roller_part_record = it + 2); R11's variable runs on through find_floor's and allocate_object's R11, so it keeps the register's name
struct object_pair_template 1A rom            # the catapult's template: catapult_wait_for_platform_setup reads it with [R10+] from object_pair_templates + 0 / 1A (080EE1), the second object's half from catapult_pair_template + C (080FD8); census ROM 082250-082280
field object_pair_template 00 handler u32     # catapult_update (08104E): "mov.w [R10+], 1C[R20]" (080EEC)
field object_pair_template 04 animation u32 rom   # catapult_x/y animation (0821E2, 0821FC): "mov.w [R10+], 48[R20]" (080EF1)
field object_pair_template 08 sprite_offset_x s8  # "mov.b [R10+], 64[R20]" (080EF6): 15 / 08
field object_pair_template 09 sprite_offset_y s8  # "mov.b [R10+], 65[R20]" (080EFB): F7 / 0B
field object_pair_template 0A launch_kind u8  # "mov.b [R10+], 38[R20]" (080F00): catapult.launch_kind, 6 (+x) / 7 (+y)
field object_pair_template 0C base_hitbox_size_x u16  # the base part's (catapult_base_update) 10: "mov.h [R10+], 10[R11]" (080FDB); 1C
field object_pair_template 0E base_hitbox_size_y u16  # its 12 (080FE0); 18
field object_pair_template 10 base_hitbox_offset_x s8  # its 7C (080FE5); EC
field object_pair_template 11 base_hitbox_offset_y s8  # its 7D (080FEA); 0C
field object_pair_template 12 base_handler u32  # catapult_base_update (0811DD): its 1C (080FEF)
field object_pair_template 16 base_animation u32 rom  # its 48 (080FF4): 082230 / 08224A
struct fireball_shot C rom                    # one shot of a fireball_emitter_lists list (tools/formats/stages.py _SHOT): fireball_emitter_update reads the one its field 48 points at (082C37) and moves 48 on by C (082D1B); census ROM 08318D-0832B7
field fireball_shot 00 position u16           # scroll position that fires it: "cmp.h [R10+], 6435[R25]; bl" (082C3B, unsigned)
field fireball_shot 02 x s16                  # the fireball's x_int: "mov.h [R10+], 4E[R11]" (082C84)
field fireball_shot 04 dy s16                 # added to the emitter's y_int: "add.h [R10+], 52[R11]" (082C8E); F0 = -10
field fireball_shot 06 speed_y s16            # "movs.hw [R10+], R0; sha.w #8" into the fireball's 38 (082C93)
field fireball_shot 08 speed_z s16            # the same into its 3C (082CA2)
field fireball_shot 0A type u16               # offset of its fireball_types record (082CAC "movz.hw [R10+], R12; add.w #832B9"); a negative one ends the list: "test.h A[R10]; bp" (082D24) deletes the emitter (the last record's +A is FFFF at 0832B7)
scoped object fireball_emitter 48 shot u32 fireball_shot  # the next shot: fireball_emitter_start sets it past the list's header (082BAE "mov.w R11, 48[R20]"), fireball_emitter_update adds C after each (082D1B); the C called it animation
struct tornado_hazard 4 rom                   # one (scroll position, coordinate) pair of a tornado_hazard_lists list; tornado_move reads the one its 48 points at (089457, 08945B [R10+]) and moves 48 on by 4 (tornado_place_hazard 0894EC); census ROM 08A79E-08A7B6 (the RAM reads, 20B000-20B004, are the stage_0A_tornado_sand scenario's poked one-record list)
field tornado_hazard 00 position u16          # "cmp.h [R10+]" against scroll_position (unsigned)
field tornado_hazard 02 coordinate s16        # compared signed with x_int / y_int along the tornado's direction (089464 "mov.h [R10], R1")
local tornado_move 089457 r10 hazard_record tornado_hazard  # the hazard list (the tornado's 48: tornado_start sets it to tornado_hazard_lists + the record's offset, tornado_place_hazard adds 4): scroll word, then the coordinate a hazard box is placed at
local tornado_move 08945B r10 hazard_record  # the [R10+] step to the coordinate
struct sinkhole_growth 4 rom                  # by the hole's field 04 & 7FFF: sinkhole_start (089AC7) and rimmed_sinkhole_start (089CEA) "movea.w 8A66E[PC](R10), R10" then two [R10+]; census ROM 08A66E-08A684
field sinkhole_growth 00 zoom_step u16        # into sinkhole.zoom_step / rimmed_sinkhole.zoom_step (80)
field sinkhole_growth 02 open_delay u16       # scroll units: + scroll_position into open_position (84) ("mov.h [R10+], 84[R20]; add.h 6435[R25], 84[R20]")
struct centipede_path_point 4                 # one entry of centipede_path (20D0EA + index x 4): the head writes its projected x/y into [81] (centipede_chase 012448, centipede_move 012516), the segments read [82] (centipede_follow_path 012608), centipede_chase reads the entry before [81] (01242F); census RAM 20D0EA-20D1B4
field centipede_path_point 00 projected_x s16  # "mov.h 2A[R20], [R0+]" / "mov.h [R0+], 2A[R20]"
field centipede_path_point 02 projected_y s16  # the whole part of projected_y: "mov.h 2E[R20], [R0+]" / "mov.h [R0+], 2E[R20]"
struct clip_entry 12                          # a pseudo-object the player queues as a sprite (player_animate_standing & co: current_object = player + 102 / + F0, queue_sprite_at_depth) to cut the player's sprite at the ground: its 00 is the depth bucket link, 02-11 the 16-byte sprite entry, a clip command (MAME segas32_v sprite_render_list command 1). player_start (002875) and player_state_sinking (00A308) write both: "movea.b F0[R18], R10", then two rounds of "add.w #2, R10" and 8 "mov.h #n, [R10+]" (12 bytes a round); census RAM 2084F2-208912
field clip_entry 00 draw_next u16 object      # the link the queue_sprite_* routines write (as object 00) when it is queued
field clip_entry 02 command u16               # sprite entry word 0: 5000 = clip command (bits 15-14 01) setting the inclusive rect (bit 12); its low 12 bits the top (0)
field clip_entry 04 bottom u16                # word 1, the rect's bottom (MAME: clipin max_y = word 1): E0; player F4 (ground_clip_bottom) is the first entry's, set to the ground's screen y while sinking
field clip_entry 06 left u16                  # word 2, the left (clipin min_x): 0
field clip_entry 08 right u16                 # word 3, the right (clipin max_x): 140
struct grate_debris_piece A rom               # one piece: spawn_grate_step_debris reads the one grate_debris_record (20F0EA) points at (08BBE5 "mov.w 70EA[R25], R10" and four [R10+]); census ROM 08C0EB-08C11B
field grate_debris_piece 00 dx s16            # added to the debris' projected x (grate_step_debris_view.projected_x_whole, 08BBEA)
field grate_debris_piece 02 dy s16            # added to its projected_y_int (08BBF0)
field grate_debris_piece 04 speed_x s16       # "movs.hw [R10+], R0; sha.w #8" into speed_x (08BBF5)
field grate_debris_piece 06 speed_z s16       # the same into speed_z (08BBFF)
field grate_debris_piece 08 animation u16     # offset from grate_step_debris_animations (08BC22 "movz.hw [R10+], R1; add.w #8C11D")
struct opening_scene_position 6 rom           # a player's place in the opening scene: player_state_opening_scene "movea.h [R10](R0), R11" with R0 = player x 3 (006CA0) and three [R11+]; census ROM 11402E-114062
field opening_scene_position 00 screen_x s16  # "mov.h [R11+], E[R20]": A0 alone; 60 / A0 / E0 with more players
field opening_scene_position 02 projected_y s16  # the whole part of projected_y (2E): D0 / C0
field opening_scene_position 04 zoom u16      # the player's scene_zoom_int (BC): 100 / E0
struct leader_spawn B rom                     # an object_group_spawns record of type 1/8 (spawn_group_leader), 5 (spawn_sand_block_leader), 7 (spawn_waterfall) or 3 (spawn_group_slot_trigger, which reads only its +9) past its scroll position and type byte: spawn_object_groups hands record + 3 in R10 (tools/formats/stages.py _leader_rec: leader, x, y, z, direction, path at 3/4/6/8/A/C of the 14 bytes); census ROM 092B60-092B69, 0931BC-09321B, 092803-092837
field leader_spawn 00 leader u8               # group_leaders slot: "& 3" (spawn_sand_block_leader 089EB4, spawn_waterfall 08CC6A)
field leader_spawn 01 x s16                   # the leader's x_int
field leader_spawn 03 y s16                   # its y_int
field leader_spawn 05 z s16                   # its z_int (and floor_z)
field leader_spawn 07 direction u16           # its 24
field leader_spawn 09 path u16                # offset of its path in group_leader_data (09357D; sand_block_leader.path, waterfall.path, group_leader_move.path); the slot trigger's (type 3) offset of its group_slot_triggers list (093539, group_slot_trigger.next_trigger)
struct ice_leader_spawn B rom                 # the type 4 object_group_spawns record (spawn_ice_platform_leader, stage 9) past its scroll position and type byte (tools/formats/stages.py "ice_leader": leader, end, reference_x, x, y, z); census ROM 092B12-092B41
field ice_leader_spawn 00 leader u8           # group_leaders slot (& 3)
field ice_leader_spawn 01 end_position u16    # ice_platform_leader.end_position (8E): the scroll position where it is removed
field ice_leader_spawn 03 reference_x s16     # travel = x - it (ice_platform_leader.travel, 8C)
field ice_leader_spawn 05 x s16               # the leader's x_int
field ice_leader_spawn 07 y s16               # its y_int
field ice_leader_spawn 09 z s16               # its z_int (and floor_z)
struct credit_block 4                         # a chute's credit block (symbols.txt p1_coins: +0 coins, +1 credits, +2 bonus coins, +3 changed); pay_and_join takes p1/p2/p3_coins_ADDRESS (00D4FA, 00D561, 00D590, 00D5BD); census RAM 20F413-20F41C
field credit_block 00 coins u8                # coins toward the next credit
field credit_block 01 credits u8              # pay_and_join: "cmp.b R3, 7413[R25]" then minus the cost
field credit_block 02 bonus_coins u8          # coins toward a bonus coin: pay_and_join clears it
field credit_block 03 changed u8              # set when the credits change (add_service_credit)
struct trackball_motion 4                     # one player's trackball movement since the last frame (trackball_1_x/y, 2_x/y, 3_x/y: read_controls writes them, clamped to +-60): read_player_trackball "movea.w 7062[R25](R0), R10" (00BDD3: R0 = the player number, scaled by 4) then x, y ([R10+]); census RAM 20F062-20F06C
field trackball_motion 00 x s16               # into the player's trackball_x (& ~1, << 8)
field trackball_motion 02 y s16               # into trackball_y
struct flagged_step 4 rom                     # the player states that set a hurt/knock animation read its first or current step themselves: animation + 2 (+ 4 x animation_frame): "or.h [R10+], 2[R20]; mov.h [R10], 4[R20]" (00573D, 007CB0...); census ROM 1132CE-11362C
field flagged_step 00 flags u16               # ORed into sprite_flags (bits 6/7: x/y flip) after "and.h #FF3F, 2[R20]"
field flagged_step 02 frame u16               # into sprite_frame
struct player_palette_list 8 rom              # a character's sprite palette list (the sprite_palette_lists format: the last index, then the palettes) that player_reset keeps in the player object's 80 (palette_list, from player_palette_lists 113D59); the player states and the flashes put its first palette back with "mov.h 2[80[R20]], 6[R20]" (002B68, 003784 ... 00EAF2; flash_hurt_palette 00CCF7 into R0); census ROM 113E3D-113E6C: six lists, last index 1, then 8130 8340 8130 (Sonic), 8140 8720 8140 (Ray), 8150 8730 8150 (Mighty) and the stage 8 ones C1x0
field player_palette_list 00 last_index u16   # 1 in all six; no player code reads it (animate_sign would wrap after it)
field player_palette_list 02 normal_palette u16  # the character's own palette (8130 / 8140 / 8150, C1x0 on stage 8): into sprite_palette (6)
struct edge_voice_scroll_range 4 rom          # a stage's record of edge_voice_scroll_ranges (114B38): keep_player_on_screen "movea.w 114B38(R0), R10" (00AE84, R0 = stage x 4) then "sub.h [R10+], R1; cmp.h [R10+], R1" (00AE91, 00AE94); census ROM 114B38-114B83: stage 3 2000/4000, 4 6000/2000, 8 6300/1400, 9 1D00/2800, A 1300/2900, 10 2000/3000, the rest 0/0
field edge_voice_scroll_range 00 start u16    # scroll_position minus it (00AE91 "sub.h [R10+], R1")
field edge_voice_scroll_range 02 length u16   # the edge voice plays while scroll_position - start is below it (00AE94 "cmp.h [R10+], R1" + bnl: unsigned)
struct screen_edge_push 4 rom                 # screen_edge_push_directions' record for a screen edge (113EE0, by screen_edge 0-3): push_player_from_screen_edge "movea.w 113EE0(R0), R10" (00AF60) then [R10+] twice (00AF6C, 00AF93); census ROM 113EE0-113EEF: 0200/0280, 0300/0380, 0000/0080, 0100/0180
field screen_edge_push 00 limit_direction u16  # the player's direction minus it, & 3FF, above 100: back to the previous position first (00AF6C "sub.h [R10+], R0")
field screen_edge_push 02 push_direction u16  # polar_to_xy's angle for the 200-long push (00AF93 "mov.h [R10+], R5")
struct slope_speed_pair 4 rom                 # a (speed factor, direction) pair of slope_speed_direction_pairs (10DFCC): player_steer (00BBCC) and compute_ground_speed (00BDAE) "movea.w [R10](R0), R10" with R10 = 10DFCC + the slope's floor_slope_direction_tables offset and R0 = direction >> 6, then [R10+] twice; census ROM 10DFCC-10E14B (6 lists of 16 pairs)
field slope_speed_pair 00 speed_factor u16    # 00BBD0 / 00BDB2 "movz.hw [R10+], R3": ground_speed = (ground_speed >> 12, rounded) x it (1000 = 1.0)
field slope_speed_pair 02 direction u16       # 00BBE6 / 00BDC8 "mov.h [R10+], 24[R20]": the object's new direction
elements player_facing_animation_offsets u16  # a per-facing animation table of the players (anim_facing / anim_facing_jumps formats): for each facing 0-15 the offset from the table of its animation (of the jump's take-off step); the player code reads "movz.hw [R10](R0), R1" with R0 = facing and adds it to the table (0030FD, 003691, 0054A2, 00571B, 00C323...); census ROM 10F6CC-1128AD (standing, walking, running, jumps), 112C60-112FFF (skid), 113260-113917 (hurt, shocked, attack effects), 11430E-11487D (tumble)
struct floor_type_player_state 1 rom          # floor_type_player_states' entry for a floor type (113CD9): player_move "movea.b 113CD9(R0), R10" (002F3A, R0 = floor_type), then "test.b [R10]" and "mov.b [R10], [R18]" (002F42, 002F4B); census ROM 113CD9-113CE6: 02 -> 0A, 03 -> 10, 08 -> 25, the rest 0
field floor_type_player_state 00 state u8     # the player state the floor forces (0 none)
struct teeter_record 2 rom                    # a teetering record (anim_teeter): the frame shown at once, then the frame animation (animation = record + 2); player_state_teetering "add.w R11, R10" (003963: teetering_animations + the character's offset), then "mov.h [R10+], 4[R20]" (003966) and R10 into 48; census ROM 1128BA-1129D3
field teeter_record 00 first_frame u16        # into sprite_frame (4)
struct teeter_animation_offsets 4 rom          # a character's row of teetering_animations (1128AE, by character): player_state_teetering "movea.w [R11](R0), R12" (00395B, R0 = the character x 4), then "movz.hw [R12](R10), R10" (00395F, R10 = the variant) and "add.w R11, R10" (003963): the teeter_record's offset from the table; census ROM 1128AE-1128B9: 0040 000C (Sonic), 0108 00D4 (Ray), 00A4 0070 (Mighty)
field teeter_animation_offsets 00 offsets u16[2]  # by the variant: [0] the far edges (ledge sides 0/1: sonic_/ray_/mighty_far_edge_teeter_animation), [1] the near edges (2/3: the near_edge ones)
struct shocked_palette_row 10 rom              # a character's row of shocked_palettes (113F4F, by character): player_hurt_shocked "movz.bw 47[R20], R10; shl.h #4, R10; add.w #113F4F, R10" (005BC1-005BC8), then "mov.h [R10](R0), 6[R20]" (005BD3, R0 = animation_frame); census ROM 113F4F-113F7E: 8130 8370 four times (Sonic), 8140 8370 (Ray), 8150 8370 (Mighty)
field shocked_palette_row 00 palettes u16[8]   # by animation_frame (the shocked animation's 8 steps): into sprite_palette (6), the character's own palette and 8370 in turn
struct edge_hang_animations 3C rom             # a character's set of animations for hanging off an edge (sonic_/ray_/mighty_far_/near_edge_hang_animations, 1129E6-112B69) that player_animate_falling (0034FF-00352E) and player_state_over_edge (003FA8-003FD0) put in the object's 48: +0 the hang (a frame_animation, 10 frames), +16 climbing back (5 frames), +22 the slip effect's animation (9 frames: player_state_dropping hands 48 + 22 to the effect, 0043CC-0043D2), then single frames; Sonic's sets end at +3A, Ray's go on with the slip pieces' frame_animation at +3A (spawn_slip_pieces 00DBD3-00DBDE "add.w #3A, R10" into the pieces' 48), Mighty's with one more frame (slip_piece_frame); census ROM 1129E6-112B69
field edge_hang_animations 36 hold_frame u16   # player_state_dropping substate 0: "add.w #36, R10; mov.h [R10], 4[R20]" (00431E, 004325), the frame while it hangs on 1E more frames: Sonic 1608 / 15B8, Ray 4DF0 / 4DE0, Mighty 3230 / 31E0 (far / near sets)
field edge_hang_animations 38 let_go_frame u16  # substate 2, as it lets go: "add.w #38, R10; mov.h [R10], 4[R20]" (004416, 00441D): Sonic 1610 / 15C0, Ray 4DF8 / 4DE8, Mighty 3238 / 31E8
field edge_hang_animations 3A slip_piece_frame u16  # Mighty's sets only (spawn_slip_piece runs for character 2): "add.w #3A, R10; mov.h [R10], 4[R11]" (00DB12, 00DB19), the piece's sprite frame: 3240 / 31F0
struct player_bounce_record 6 rom             # a record of sonic_/ray_/mighty_bounce_records (1139E8, 113A28, 113A68) by bounce_direction: player_state_bouncing (005C47), player_stuck_jump_out (0079A6), player_stuck_on_object_jump_out (009F90) "movea.h [R12](R0), R10" with R0 = bounce_direction x 3, then three [R10+]; census ROM 1139E8-1139FF, 113A28-113A3F, 113A68-113A7F: 0200 0000 0018, 0300 0040 0018, 0000 0040 002C, 0100 0000 002C in all three
field player_bounce_record 00 direction u16   # "mov.h [R10+], 24[R20]": the object's direction
field player_bounce_record 02 sprite_flags u16  # "or.h [R10+], 2[R20]" after clearing bit 6 (40: x flip)
field player_bounce_record 04 animation_offset u16  # "movz.hw [R10+], R11; add.w R12, R11": the animation's offset from the table (18 or 2C)
struct knocked_out_entry 4 rom                # knocked_out_animations' record for facing >> 2 (sonic 11487E, ray 1148F6, mighty 11496E; anim_knocked_out): player_state_tumbling "movea.w [R10](R0), R11" (006953), then "or.h [R11+], 2[R20]" and "movz.hw [R11+], R11" (00695E, 006963); census ROM 11487E-11488D, 1148F6-114905, 11496E-11497D
field knocked_out_entry 00 sprite_flags u16   # ORed into sprite_flags after clearing bit 6 (40: x flip)
field knocked_out_entry 02 animation_offset u16  # the animation's offset from the table (10 or 44): animation = table + it
elements struggle_animation_pointers u32      # a character's struggle animations by escape level (anim_pointers): player_state_trapped "mov.w [R10](R1), 48[R20]" (0082EE, R10 = sonic_/ray_/mighty_trap_struggle_animations, R1 = the escape level capped at 4, so index 4 reads the next table's first entry); census ROM 114AA0-114ACF
struct restart_point 8 rom                    # a stage's restart point (tools/formats/stages.py _RESTART_GROUND / _RESTART_SCREEN): current_restart_point points at one: place_player_at_restart_point reads it ("mov.w 7088[R25], R10" 00CBFF, "add.w #2, R10", then [R10+]), update_restart_point walks the points 8 bytes at a time (001CD6-001CE6); census ROM 114BB0-115527: 248 points of kind 0, 45 of kind 1, none of kind 2
field restart_point 00 until u16              # update_restart_point (001CE6 "cmp.h [R10], R4" + bnl) stops at the first point whose until is above scroll_position (FFFF: to the end)
field restart_point 02 kind u8                # 00CC07 "movz.bw [R10+], R0": x 3 into the jump table at 00CC13: 0 (or 2) a ground point, 1 a screen point
field restart_point 03 screen_y u8            # kind 1: 00CC46 "movz.bh [R10+], R9", screen_point_to_ground's screen y (kind 0: a pad byte, skipped by "inc.w R10")
field restart_point 04 x u16                  # kind 0/2: 00CC1B "mov.h [R10+], 4E[R20]" (x_int, + 2 x player); kind 1: 00CC49 "mov.h [R10+], R8", the screen x
field restart_point 06 y_or_height u16        # kind 0/2: 00CC3B "mov.h [R10+], 52[R20]" (y_int); kind 1: 00CC4C "mov.h [R10+], R7" << 10, the height screen_point_to_ground takes off the screen y (0-528)
struct stage_start_point 6 rom                # the 6 bytes before a stage's restart points (tools/formats/stages.py _RESTART_START): place_player_at_restart_point at a stage start "sub.w #6, R10" (00CBD8), then three [R10+] (00CBDB, 00CBDE, 00CBF4); census ROM 114BAA-11551F (14 lists)
field stage_start_point 00 spacing_shift u16  # player << its low byte ("sha.h R2, R0") is added to x: 0, 2 or 5
field stage_start_point 02 x u16              # into x_int (4E)
field stage_start_point 04 y u16              # into y_int (52)
struct stage_goal C rom                       # a stage's goal record (tools/formats/stages.py _GOAL): load_stage_goal "add.w #11419A, R12" (00CFFD: stage_goals[stage] + 11419A), then [R12+]; its last two words (+8, +A: line min and max) are read through stage_goal_line_range; census ROM 1141C0-114243 (FFFF position: no goal)
field stage_goal 00 position u16       # 00D004 "mov.h [R12+], 65E4[R25]": goal_scroll_position
field stage_goal 02 direction u16             # 00D00A: goal_direction (0-3)
field stage_goal 04 line u16                  # 00D010: goal_line
field stage_goal 06 exit_path u8              # the path_request of the stage exit (read as the table stage_goal_exit_paths, 1141A0); load_stage_goal skips this word ("add.h #2, R12" 00D016)
struct stage_goal_line_range 4 rom            # a stage_goal's last two words (+8): load_stage_goal moves R12 there with "add.h #2, R12" (00D016), a 16-bit add the decompiler takes for a link to a record's start, so the pointer gets a structure of its own there; census ROM 1141C8-114243
field stage_goal_line_range 00 line_min u16   # 00D019 "mov.h [R12+], 664A[R25]": goal_line_min (0 or where the goal line starts)
field stage_goal_line_range 02 line_max u16   # 00D01F "mov.h [R12+], 664C[R25]": goal_line_max
elements fall_sprite_priority_sides s8        # a record of fall_sprite_priorities (113F7F, 4 bytes for a floor block's fall_priorities): set_fall_sprite_priority "movea.w 113F7F(R11), R10" (00D83E, 00D858), then "movz.bh [R10](R0), R1" (00D86E, R0 = the side 0-3); 0-2 go into sprite_palette bits 13-14, FF keeps them; census ROM 113F7F-113F9E
struct player_path_step 8 rom                 # a record of the players' autopilot paths (player_path_offsets 115528; tools/formats/stages.py _PATH_RECORD): follow_player_path keeps the current one in the player's path_pointer (5C) and moves it 8 on (00DDB8 "add.w #8, 5C[R18]"); census ROM 1155D4-115A31 (128 records)
field player_path_step 00 speed s16           # 00DDBD "mov.h [5C[R18]], B0[R18]" (path_speed; 0: a wait); 00DDAB "test.h 8[R10]" + bp on the next record's: negative ends the path
field player_path_step 02 x_or_wait u16       # the point's x (00DD89 "sub.h [R10+], R0", 00DDF9), or with speed 0 the frames to wait (00DDE2 "mov.h 2[5C[R18]], C0[R18]": path_wait)
field player_path_step 04 y u16               # the point's y (00DD9A, 00DE00)
field player_path_step 06 flags u16           # bit 0 a jump (00DDC4 "test1 #0, 6[5C[R18]]"), bit 1 passed at once (00DD7B "test1 #1, 6[R10]"); test1 reads 32 bits, the next record's speed with it; census 0 (102), 2 (24), 1 (2), the high byte 0
struct hud_portrait_sprite 4 rom              # hud_portrait_sprites' record for a character (113F38): player_hud_init (00A7B6) and player_hud_start (00A85E) "movea.w 113F38(R0), R10" (R0 = kind), then [R10+] twice; census ROM 113F38-113F43: 89C8/8850, 89D8/88B0, 89D0/8880
field hud_portrait_sprite 00 frame u16        # into the portrait's sprite frame (4: hud_portrait_tile)
field hud_portrait_sprite 02 palette u16      # into its palette (6: hud_portrait_palette)
struct hud_palette_cycle 2 rom                # a HUD palette cycle (sprite_palette_cycles: the last index, then the palettes) at animate_hud.anim: animate_hud_palette "mov.w 28[R20], R10" (00ACAF), then "mov.h [R10+], R0" (00ACB3); census ROM 00CD37-00CDA4 (hud_gauge_*_palettes, hud_portrait_*_palettes)
field hud_palette_cycle 00 last_index u16     # its low byte: past it the cycle wraps to entry 0 (00ACBC "cmp.b R0, R1" + ble)
elements hud_cycle_palettes u16               # the palettes after a hud_palette_cycle's last_index: animate_hud_palette "mov.h [R10](R1), 6[R20]" (00ACCF, R1 = the entry) into sprite_palette
field floor_block 14 fall_priorities u8       # index of the block's fall_sprite_priorities record (x 4): set_fall_sprite_priority "movz.bw 14[R10], R11; movea.w 113F7F(R11), R10" (00D83A, 00D854)
field floor_block 15 travel_direction u8      # the way the course runs over the block (0/4 -y, 1 +x, 2 +y, 3 -x): ember_emitter_move moves along it (travel_direction), get_pacing_axis takes the pacing axis from it, check_player_floor_edge stores its opposite (+ 2 & 3) in ledge_side when the player is past none of the block's sides (00B4EC), player_state_teetering teeters by it (0038F7 edge_direction); tools/formats/stages.py calls it ledge_side. Every read is inline (no typed access)
struct floor_slope 8 rom                  # one slope type of floor_slopes (10DF44): check_hit 001FCB, get_floor_z_at_point 00C8AB and spawn_object_groups 09148A point at floor_slopes + slope x 8 and read +0 and +2 (and the column tables floor_slope_factors, floor_slope_direction_tables read +2 and +6 by [slope x 2] / [slope x 4])
field floor_slope 00 flags u8             # bit 1: the slope runs along x (else along y): "and.w #2" on the long read at +0 (001FCF, 00C8B2, 091490)
field floor_slope 02 factor s32           # the slope factor: floor_z += (x - x_min, or y - y_min) x factor >> 16 (00C8C6, 0914B3); check_hit takes its absolute value for round hitboxes (001FFB)
field floor_slope 06 direction_table u16  # offset from slope_speed_direction_pairs (10DFCC) of the slope's (speed factor, direction) pairs (floor_slope_direction_tables, player_steer, compute_ground_speed)
field sprite_part_list 02 part_0_frame u16    # the first part's frame word (sprite_part 00): lava_flow_segment_follow copies the parent segment's (087884 "mov.h 2[R12], 2[R13]") into its own first two parts; biting_head_chase writes it too (part_list + 2, in place)
field sprite_part_list 0A part_1_frame u16    # the second part's frame word (2 + 8): lava_flow_segment_follow 08788A "mov.h 2[R12], A[R13]"; seesaw_tilt writes the plank part's here (part_list + A, in place)
scoped object vine_bar 24 vine u16 object.vine_swing  # vine_init stores the vine (R23, handler vine_swing) in the bar's 24 (00ED0C "mov.h R23, 24[R3]"); vine_bar_update reads its 80 (00EED9, 00EFA9: vine_swing.holding) and x, y, z (00EF1D-00EF29) through "mov.w R25, R0; mov.h 24[R20], R0"; census RAM 202DE0-2049A0. The C called it direction
00F07B table vine_scroll_stop_player_blocks u32 player  # 400, 600, 800: offsets from 208000 to the three player blocks; vine_scroll_stop_hold "add.w F07B[PC](R4), R1" on R25, then 14[R1] (player.object) and the object's 30 and 4E (census RAM 204E50-208814)
local catapult_wait_for_platform_setup 080EE1 r10 template_cursor object_pair_template  # object_pair_templates + R3 (0 or 1A): handler, animation, offsets, launch kind read with [R10+]
local catapult_wait_for_platform_setup 080EEC,080EF1,080EF6,080FDB,080FE0,080FE5,080FEA r10 template_cursor  # the [R10+] steps through the template's fields
local catapult_wait_for_platform_setup 080FD8 r10 template_cursor  # catapult_pair_template (20F0EA) + C (080FD8 "add.w #C, R10"): the base part's half of its object_pair_template (base hitbox sizes and offsets, handler, animation)
scoped object cage_bar 48 animation u32 cage_bar_animation   # cage_bar_animation (085171: 7058, 7060, 7068): cage_bar_update reads its steps through it (084B0D "mov.w 48[R20], R10; cmp.h [R10+], R0", 084B4E "mov.h 6[R10], 4[R20]"); census ROM 085171-085177
010BD0 table treadmill_player_blocks u32 player  # 400, 600, 800 from 208000: treadmill_update "add.w 10BD0[PC](R4), R1" on R25, then "cmp.b #22, [R1]" (player.state) and 14[R1] (player.object) whose 6E it sums (census RAM 204E8E-208814)
local lava_flow_segment_follow 08787C r12 part_list_2 sprite_part_list  # the parent segment's part list (087878-08787C: LINKED(parent)'s 04-07): its first part's frame (087884, 08788A "mov.h 2[R12], ..."); census RAM 207D62-207F1A
local lava_flow_segment_follow 087880 r13 part_list_3 sprite_part_list  # its own part list (087880 "mov.w 4[R20], R13"): the frames of its first two parts get the parent's
scoped object seesaw 48 animation u32 rom     # seesaw_animation (087BC5, frames B310-B330 for the plank part): seesaw_tilt reads its last step and frames (086CA7 "mov.w 48[R20], R10; cmp.h [R10+], R0", 086CBE); census ROM 087BC5-087BC9
param spawn_group_slot_trigger R10 record leader_spawn  # object_group_spawns record past position and type: word +9 is the offset of its list (+ 93539 into field 80); census ROM 092803-092837
param spawn_ice_platform_leader R10 record ice_leader_spawn  # object_group_spawns record past position and type: group leader number, end position (8E), reference x, x, y, z
local centipede_chase 01242F,012448 r0 path_cursor centipede_path_point  # the centipede_path entry (path_write_index - 1, 32 below 0) it moves back to, then the entry it writes
local centipede_chase 012421,012425,01244F,012454 r0 path_cursor  # the index before it is scaled, and the [R0+] steps (also call outputs, not read)
local centipede_move 012516 r0 angle_or_path_cursor centipede_path_point  # the write cursor into centipede_path (path_write_index x 4) that gets projected_x and projected_y
local centipede_move 0124F0,01251D,012522 r0 angle_or_path_cursor  # centipede.wobble_angle (- 10 a frame): direction_to_speed's direction for the wobble speeds; the [R0+] steps; centipede_hop's height then passes through it to spawn_centipede_sand_puff (unread)
local centipede_follow_path 012608 r0 path_cursor centipede_path_point  # centipede_path + path_index x 4: the entry it reads (x, then y); the next entry is handed back
local centipede_follow_path 01260F r0 path_cursor  # the [R0+] step to y
local sinkhole_start 089AC7 r10 growth_record sinkhole_growth  # sinkhole_growth_records + 4 x it: zoom step, open delay
local sinkhole_start 089ABD r10 growth_record  # sprite_frame without bit 15
local rimmed_sinkhole_start 089CEA r10 growth_record sinkhole_growth  # sinkhole_growth_records + 4 x it: zoom step (80), open delay (84)
local rimmed_sinkhole_start 089CE0 r10 growth_record  # sprite_frame without bit 15
param spawn_sand_block_leader R10 record leader_spawn  # object_group_spawns record past position and type: leader number, x, y, z, direction, offset into group_leader_data
local sand_block_leader_move 08A0C4 r11 block_record floor_block  # floor_blocks + 2 + floor_block: its x and y bounds (0/4, 8/C) center the leader across its path
local quicksand_swirl 089A49 r10 step_list frame_animation  # the swirl's animation (48): its last step word, then the frames (089A53 "cmp.h [R10+], R1", 089A5F "mov.h [R10](R1), R3"); census ROM 08A7DC-08A7E2
local quicksand_swirl 089A6A r11 part_cursor sprite_part_list  # the child's part list (its 04-07, 089A6A "mov.w 4[R10], R11"): its count word (089A6E "movz.hw [R11+], R8")
local quicksand_swirl 089A6E,089A74 r11 part_cursor sprite_part  # part_list + 2, + 8 a part: each part's frame word
local sand_block_update 08A289 r10 part_list sand_block_part_list  # the block's part list (its 04-07, from stage_object_records: ROM): its word +8 becomes the face's sprite_scale and hitbox_size_x (08A28D, 08A293); census ROM 09348B-0934AF
struct sand_block_part_list A rom            # the head of a sand block's part list in the program ROM (sand_block_parts, its 04-07 from stage_object_records): the count, then the first 8-byte part (sprite_part's layout); sand_block_update 08A289 "mov.w 4[R20], R10", 08A28D / 08A293 "mov.h 8[R10], 8[R11]" / "mov.h 8[R10], 10[R11]"; census ROM 093483-0934AF
field sand_block_part_list 00 count u16       # the parts' dbr counter (2, 2, A): what build_sprite_list reads first
field sand_block_part_list 08 first_palette u16  # the first part's palette word (part + 6): 1020, 3020; sand_block_update copies it into the palette words (+8, +10) of the face's two-part list (the copy of sand_face_parts)
local sand_block_update 08A2CA r13 part_cursor sprite_part_list  # the streams' part list ("mov.w 4[R11], R13": the copy of sand_stream_parts at the new object's 40): its count word (08A2CE "movz.hw [R13+], R2")
local sand_block_update 08A2CE,08A2D5 r13 part_cursor sprite_part  # the stream part list + 2, + 8 a part (its palette word gets part_scale: "mov.h R3, 6[R13]")
local sand_streams_update 08A4CC r10 part_cursor sprite_part_list  # its part list (08A4CC "mov.w 4[R20], R10"): the count word (08A4D0 "movz.hw [R10+], R2")
local sand_streams_update 08A4D0,08A4D6 r10 part_cursor sprite_part  # part_list + 2, + 8 a part: each part's frame word
local spawn_sink_swirl 08A55C r11 allocated object  # take_free_object's block: an object (sprite flags 2, palette 6, flags 30, animation 48, 44-46) chained as the swirl's child; census RAM 203C02-204508
local river_raft_spawner_update 08C27C r10 floor_block_record floor_block  # floor_blocks + 2 + the raft's floor block: x at +0, floor z at +10
scoped object river_raft_update 48 animation u32 frame_animation  # river_raft_fall_animation (08DAC6): river_raft_update sets it and reads its first frame in place ("mov.h 2[48[R20]], 4[R20]"); census ROM 08DAC8
local clipped_copy_sprite_start 08C5B2 r11 copy object  # take_free_object's block: the copy (palette bank 8000), kept in copy_object; before it find_floor's cursor
local geyser_front_init 08C768 r11 hitbox_object object  # take_free_object's block: an object (hitbox sizes 10/12/66, flags 30 = 2, offsets 7C, direction 24 = 300, kind 47 = 29) that becomes the front's child; census RAM 201E10-20363C
param spawn_waterfall R10 record leader_spawn  # object_group_spawns record past position and type: leader number, x, y, z, direction, offset into group_leader_data
scoped object river_water 48 animation u32 frame_animation  # the water's animation: river_water_update reads frame [parent's animation_frame] of it (08CF04 "mov.w 48[R20], R10; mov.h 2[R10](R0), 4[R20]"); census ROM 093465-093473
local propeller_machine_chase 08D526 r10 animation frame_animation  # the machine's animation (48): a word (last step), then 2-word steps (the frame of parts 6/9, of part 4); census ROM 08DBC6-08DBD4; "cmp.h [R10], R1" (08D530) its last step
local biting_head_init 08AAB3 r11 taken_object object  # the second take_free_object block: an object (hitbox sizes, direction, height, offsets, flags 40000000, kind 9) that becomes the head's child, its second zone box; census RAM 203CB0-2045DC
local spawn_grate_step_debris 08BBE5 r10 record grate_debris_piece  # the current grate_step_debris record (grate_debris_record): x and y offsets, speed_x, speed_z, animation
local spawn_grate_step_debris 08BC11 r10 record grate_debris_piece+8  # popped back after next_random: 8 bytes into the piece, at its animation word
local spawn_grate_step_debris 08BB86 r10 record grate_step_debris_list  # grate_step_debris ("movea.b 8C0E9[PC], R10"): the count word, then (08BB94 [R10+]) the pieces, saved in grate_debris_record
local spawn_grate_step_debris 08BBEA,08BBF0 r10 record  # grate_step_debris (a count, then records), then the [R10+] steps; saved in grate_debris_record
scoped object player 48 animation u32 rom  # the player object's animation (a ROM pointer: the per-facing animation tables, hurt/knock/bounce records): the player states read its steps in place ("mov.h 2[48[R20]], 4[R20]": player_object_climb_begin/_move, player_state_dropping +36/+38, player_state_bouncing, the stuck jump-outs +4, spawn_slip_piece +3A: those families have their own 48 below, frame_animation or edge_hang_animations), census ROM 009CB5-1144F6; all the player_* functions' 48 accesses then read player_view.animation
scoped object player_state_bouncing 48 animation u32 frame_animation  # the bounce animation (sonic_/ray_/mighty_bounce_records + the record's animation_offset, 005C5F): player_state_bouncing reads its first frame in place (005C98 "mov.h 2[48[R20]], 4[R20]") and the frame [animation_frame] (005D58 "mov.h 2[R10](R0), 4[R20]"); census ROM 113A00-113A27, 113A40-113A67, 113A80-113AA7 (last frame 8)
scoped object player_stuck_jump_out 48 animation u32 frame_animation  # the bounce animation for bounce_direction 1 (0079BE-0079C1): its second frame in place (0079F7 "mov.h 4[48[R20]], 4[R20]")
scoped object player_stuck_on_object_jump_out 48 animation u32 frame_animation  # the same (009FA8-009FAB, 009FE1 "mov.h 4[48[R20]], 4[R20]")
scoped object player_object_climb 48 animation u32 frame_animation  # object_climb_animation_sonic / _ray / _ray_2 / _mighty (009CB3-009CEA, last frame 5): player_object_climb_begin and _move set it and read its first frame in place (008ACA, 008BC7 "mov.h 2[48[R20]], 4[R20]")
scoped object player_object_climb 80 palette_list u32 player_palette_list  # as player 80: player_object_climb_begin puts back the normal palette
scoped object player_state_dropping 48 animation u32 edge_hang_animations  # the hang set the player hung by (states 2 and 7 set it): player_state_dropping reads its single frames (00431A-004325 "mov.w 48[R20], R10; add.w #36, R10; mov.h [R10], 4[R20]", 004412-00441D +38) and hands +22 to the slip effect (0043CC-0043D2)
scoped object spawn_slip_piece 48 animation u32 edge_hang_animations  # the player's (Mighty's) hang set: spawn_slip_piece reads the piece's frame at +3A (00DB0E-00DB19)
local player_state_scene_thrown 007CA6 r10 step_record flagged_step  # the new animation + 2: its first step's flags and sprite frame
local player_state_scene_thrown 007CA2 r10 step_record  # the new animation (its last-step word)
local player_state_scene_thrown 007D6F r10 step_record flagged_step  # the new animation + 2: its first step's flags and sprite frame
local player_state_scene_thrown 007D6B r10 step_record  # the new animation (its last-step word)
local player_state_carried_off 007FB6 r11 cursor flagged_step  # animation + 2 + 4 x animation_frame (2): the step's flags and sprite frame ("movea.w 2[R10](R1), R11"); census ROM 1132FC-1135C4
local player_state_carried_off 007FC2 r11 cursor flagged_step+2  # the [R11+] step to the frame word, 2 bytes into the step; R11 then goes to find_floor and player_state_hurt, whose R11 stays as their other callers hand it (objects)
local player_state_tripped 0086F5 r10 step_record flagged_step  # the new animation + 2: its first step's flags and sprite frame
local player_state_tripped 0086F1 r10 step_record  # the new animation (its last-step word)
local player_state_floor_8_sequence 00901D r10 step_record flagged_step  # the new animation + 2: its first step's flags and sprite frame
local player_state_floor_8_sequence 009019 r10 step_record  # the new animation (its last-step word)
local player_state_floor_8_sequence 009092 r10 step_record flagged_step  # animation + 2 + animation_frame x 4: the current step's flags and sprite frame
local player_state_caught 00A1B6 r10 step_record flagged_step  # the new animation + 2: its first step's flags and sprite frame
local player_state_knocked_off 00A540 r10 step_record flagged_step  # the new animation + 2: its first step's flags and sprite frame
local player_state_knocked_off 00A53C r10 step_record  # the new animation (its last-step word)
local player_state_knocked_off 00A5BB r10 step_record flagged_step  # animation + 2 + animation_frame x 4: the current step's flags and sprite frame
local player_state_sinking 00A308 r10 clip_cursor clip_entry  # player block + F0: the first of its two clip entries (5000, E0, 0, 140, zeros) rewritten when sinking
local player_state_sinking 00A30D,00A310,00A316,00A31C,00A320,00A326,00A32A,00A32E,00A332 r10 clip_cursor  # the add.w #2 and [R10+] steps (12 bytes a round: the next entry)
field player 86 hud_portrait u16 object.animate_hud  # 00A6EE: address of this block's portrait sprite record (+0x172), laid out as animate_hud_palette/animate_hud_tiles read it (their 25-28): player_hud_update reads its palette list's first palette (00AB0D "mov.h 2[28[R20]], 6[R20]", census ROM 00CD65-00CD75)
field player 88 hud_gauge u16 object.animate_hud  # 00A6EE: address of this block's health gauge sprite record (+0x142), the same layout: player_hud_update reads its list's first palette (00AAE7 "mov.h 2[28[R19]], 6[R19]", census ROM 00CD4F-00CD57)
field player 146 hud_gauge_tile u16 sprite_frame_record  # +04 (0x89E0): the gauge's sprite frame, the low half of its record's address in sprite_frames' page: player_hud_init reads the record's address word (+6) into hud_gauge_address (00A716 "mov.w 7112[R25], R12; mov.h 4[R11], R12; mov.h 6[R12], 8[R11]", R11 = the block + 142; census ROM 1389E6)
scoped object skid_dust 48 animation u32 frame_animation  # skid_dust_animation (113D13): spawn_skid_dust sets it (00A690) and reads its first frame back in place (00A699 "mov.h 2[48[R11]], 4[R11]"); census ROM 113D15
local spawn_skid_dust 00A669 r11 dust object.skid_dust  # allocate_object's object: handler skid_dust_update (00A6D5), so its 48 is the skid_dust animation
param give_result_bonus R16 player player  # the player block: update_ring_tally_panel passes the panel's 34, set to 208400/208600/208800; 14 its object (health 7E + 8), 75 joined_health_setting, 114 flags, 13A rings
local player_start 002875 r10 clip_cursor clip_entry  # player block + F0: the first of the two clip entries (5000, E0, 0, 140, then 0s) of the pseudo-objects at F0 and 102 that player_animate_standing queues
local player_start 00287A,00287D,002883,002889,00288D,002893,002897,00289B,00289F r10 clip_cursor  # the add.w #2 and [R10+] steps (12 bytes a round: the next entry)
param compute_scroll_drift R8 block floor_block  # floor_blocks + 2 + floor_block: the floor block record (it reads the slope at 13), saved in scroll_drift_block
local get_ledge_side 00B3B5 r10 bound_cursor floor_block  # floor_blocks + previous_floor_block + 2: the previous floor block's x min, x max, y min, y max in turn ([R10+])
local get_ledge_side 00B3BE,00B3C8,00B3D2 r10 bound_cursor  # the [R10+] steps
local get_ledge_side 00B3B8 r11 block rom  # the block record handed back (a copy of bound_cursor): a ROM pointer, so that the record's kind does not reach the callers' R11 (check_player_floor_edge hands it on; their R11 later carries objects); player_state_teetering types its own copy
local check_player_floor_edge 00B42C r10 block floor_block  # floor_blocks + 2 + previous_floor_block: the block the player walked off (type at +12)
local read_player_trackball 00BDD3 r10 trackball trackball_motion  # trackball_1_x + player * 4: the player's x word, then y
local read_player_trackball 00BDD9 r10 trackball  # the [R10+] step to y
local get_floor_block_side 00CAAF r10 floor_block floor_block  # the block's offset + floor_blocks (00CAAA) + 2: the record whose x min, x max, y min it compares ([R10+]); census ROM 10C4D2-10D900
local get_floor_block_side_or_corner 00CADF r10 block floor_block  # the block offset handed in, + floor_blocks + 2: the block's box
local load_stage_goal 00D016 r12 goal_record stage_goal_line_range  # "add.h #2, R12" past the goal record's skipped word (exit path, unused byte): line min, line max ([R12+]); census ROM 1141C8-114243. The 16-bit add is (R12 & FFFF0000) | (R12 + 2 & FFFF) to the decompiler, which its "or" rule takes for a link to a record's start, so the pointer gets the record's tail (stage_goal + 8) as a structure of its own
local load_stage_goal 00CFFD r12 goal_record stage_goal  # stage_goals[stage] + 11419A ("add.w #11419A, R12"): scroll position, direction, line ([R12+])
local load_stage_goal 00D004,00D00A r12 goal_record  # the [R12+] steps through the record
local pay_and_join 00D4FA,00D561,00D590,00D5BD r11 credit_block credit_block  # p1/p2/p3_coins: credits at +1 (minus cost), +2 cleared
local set_fall_sprite_priority 00D7F0,00D80C r10 record floor_block  # the floor block record (previous or current): type +12, x bounds, fall_priorities +14
local set_fall_sprite_priority 00D83E,00D858 r10 record fall_sprite_priority_sides  # then its fall_sprite_priorities record (by the byte at +14), read by side (00D86E "movz.bh [R10](R0), R1")
local start_climb_at_spawn 00D8F8 r10 block floor_block  # floor_blocks + 2 + floor_block: the block's bounds and floor height
scoped object tumble_effect 48 animation u32 frame_animation  # tumble_effect_animation (spawn_tumble_effect): tumble_effect_update reads frame [the player's animation_frame] ("mov.w 48[R20], R10; mov.h 2[R10](R0), 4[R20]" 00D276-00D27A: frames[0-A]); census ROM 1142F8-11430D
local player_tumbling_knock_with_toucher_speed entry r10 toucher object  # 34[R10]/38[R10]: the toucher's speed_x/speed_y (no caller; player_state_tumbling's toucher is LINKED(touched_by))
local player_state_opening_scene 006CA0 r11 position_record opening_scene_position  # opening_scene_player_positions + layout_row * 0x12 + player * 6: screen x, y and zoom
local player_state_opening_scene 006CA4,006CA9 r11 position_record  # the [R11+] steps
local player_state_opening_scene 006CB3 r11 position_record rom  # the last [R11+] step: + 6, the next record, handed to spawn_intro_marker (R11, where it allocates the marker): read as a ROM pointer so that the record's kind does not reach allocate_object's R11 (other callers hand objects there)
param player_animate_run_from_table R10 animation_table player_facing_animation_offsets  # per-facing offsets table of the run animation (player_animate_run_cycle R10's name): sonic_/ray_/mighty_*run_animations, read as [facing x 2] + the table; census ROM 110A4C-11205A
local player_state_teetering 003826 r11 block floor_block  # get_ledge_side's R11: the previous floor block's record (its x/y bounds give the teeter position); census ROM 10B77A-10DEE8
local player_state_falling_off_edge 003B80 r10 floor_block_record floor_block  # floor_blocks + 2 + floor_block: the block's corners (+0/+4/+8/+0xC) and kind (+0x12)
local player_state_falling_off_edge 003D05 r10 floor_block_record floor_block  # floor_blocks + 2 + floor_block: the block's corners (+0/+4/+8/+0xC) and kind (+0x12)
local player_state_over_edge 003F14 r10 animation flagged_animation_start  # the object's animation (48): its first two steps' flags words ("mov.h 2[R10], R0; or.h 6[R10], R0"); census ROM 10FDAE-1128C2
struct flagged_animation_start 8 rom       # the start of an animation of flagged steps (a last step word, then flags and frame a step): player_state_over_edge reads the animation the state before left in 48 (003F17 "mov.h 2[R10], R0; or.h 6[R10], R0") and runs animate_with_flags when both steps' flags have no bits but the flips (and #FF3F)
field flagged_animation_start 00 last_step u16
field flagged_animation_start 02 first_flags u16      # the first step's flags (flagged_step)
field flagged_animation_start 04 first_frame u16
field flagged_animation_start 06 second_flags u16     # the second step's flags
local player_state_falling_into_pit 004599 r10 floor_block_record floor_block  # floor_blocks + 2 + floor_block: the block's corners (+0/+4/+8/+0xC) and kind (+0x12)
local player_state_falling_into_pit 004623 r10 floor_block_record floor_block  # floor_blocks + 2 + floor_block: the block's corners (+0/+4/+8/+0xC) and kind (+0x12)
local player_state_climbing 004932 r10 floor_block_record floor_block  # floor_blocks + 2 + floor_block: the block's corners (+0/+4/+8/+0xC) and kind (+0x12)
local player_state_climbing 004DEB r10 floor_block_record floor_block  # floor_blocks + 2 + floor_block: the block's corners (+0/+4/+8/+0xC) and kind (+0x12)
local player_state_hurt 00572E r10 step_record flagged_step  # the new animation + 2: its first step's flags and sprite frame
local player_state_hurt 00572A r10 step_record  # the new animation (its last-step word)
local player_state_hurt 005826 r10 step_record flagged_step  # animation + 2 + animation_frame x 4: the current step's flags and sprite frame
param draw_ending_blast_frame R10 frame_entry ending_sprite_frames  # current entry of ending_tower_blast_frames / ending_horizon_blast_frames (callers: base + 2 x step): its word is the sprite frame; census ROM 01E22A-01E27E
elements ending_sprite_frames u16     # a list of sprite words of the ending (FFFF ends): ending_tower_blast_frames (01E22A), ending_horizon_blast_frames (01E254), ending_*_run_frames (01E006, 01E020, 01E03A), ending_*_body_frames and _leg_frames (01E054-01E076); read by index ("movea.h 1E254(R10), R10" 01C369, "movea.h [R11](R1), R11" 01DFD3) into sprite_frame; census ROM 01E006-01E07E
local draw_ending_blast_frame_copy entry r10 frame_entry ending_sprite_frames  # the frame list entry whose word is the sprite frame (no caller; the same code as draw_ending_blast_frame, which reads the ROM frame lists)
param animate_ending_runner R0 frame_entry ending_sprite_frames  # current entry of the runner's frame list (ending_sonic/ray/mighty_run_frames + 2 x step): its word is the sprite frame; census ROM 01E006-01E050
struct debris_path_step 8 rom           # ending_debris_1/2/3_path (01E078, 01E10E, 01E194): animate_ending_debris walks them 8 bytes at a time ("add.w #8, R10" 01C427) to the first whose time is above its timer, then reads 2[R10], 4[R10], 6[R10] (01C438-01C444); FFFF ends; census ROM 01E078-01E224
field debris_path_step 00 end_time s16  # "cmp.h [R10], 32[R20]; blt": the record shows while the object's timer (32) is below it (signed); FFFF ends the path ("cmp.h #FFFF, [R10]" 01C410)
field debris_path_step 02 screen_x s16  # -> screen_x (0E)
field debris_path_step 04 screen_y s16  # -> screen_y (0C)
field debris_path_step 06 sprite_frame u16  # -> sprite_frame (04): 9B90-9C18, 9C20-9C98 (ending/debris)
param animate_ending_debris R10 path debris_path_step  # ending_debris_1/2/3_path: walked by 8 bytes to the first record whose time is above the counter (x, y, sprite); FFFF ends
struct dust_cloud_frame 4 rom           # ending_dust_cloud_frames (01C636): (screen y, sprite) pairs that ending_dust_cloud_rise steps through every 7 frames, its cursor kept in the object's z (40); FFFF ends; census ROM 01C636-01C676
field dust_cloud_frame 00 screen_y s16  # "mov.h [R10+], C[R20]" (01C61C): A8, then A0; FFFF here ends the rise ("cmp.h #FFFF, [R10]" 01C60B)
field dust_cloud_frame 02 sprite_frame u16  # "mov.h [R10+], 4[R20]" (01C621): 9A10-9AA8
local ending_dust_cloud_rise 01C607 r10 frame_pointer dust_cloud_frame  # "mov.w 40[R20], R10": the cursor kept in z (the 01C61C line of symbols.txt names the post-increment, which moves it to the sprite word)
struct credit_part 8 rom                # credits_team_logo_parts (01CC3D) ... credits_special_thanks_parts (01D25D): the parts of a credit block, 8-byte records (sprite, screen y, palette, field of the block that keeps the part), FFFF ends; ending_credits_*_scroll read one with four [R10+] (01CBE1-01CBF0); census ROM 01CC3D-01D29D
field credit_part 00 sprite_frame u16   # "mov.h [R10+], 4[R11]": the part's sprite (9848, 9740...); FFFF ends the list ("cmp.h #FFFF, [R10]" 01CBD2)
field credit_part 02 screen_y s16       # "mov.h [R10+], C[R11]": its screen y (1D4, 110, 12C...: the block scrolls up from F4)
field credit_part 04 sprite_palette u16 # "mov.h [R10+], 6[R11]" (1010, 10A0)
field credit_part 06 slot u16           # "add.h [R10+], R20": the offset of the block's field that keeps the part (38, 40, 44 ... 58), where "mov.w R11, [R20]" stores it (credit_part_slot)
local ending_credits_team_logo_scroll 01CBCE r10 field_cursor credit_part  # "mov.w 34[R23], R10": the part list cursor kept in speed_x (credits_team_logo_parts on; the 01CBE1 line names the post-increments)
local ending_credits_director_scroll 01CC81 r10 field_cursor credit_part  # "mov.w 34[R23], R10": credits_director_parts on, kept in speed_x
local ending_credits_program_scroll 01CD4D r10 field_cursor credit_part  # "mov.w 34[R23], R10": credits_program_parts on, kept in speed_x
local ending_credits_design_scroll 01CE4B r10 field_cursor credit_part  # "mov.w 34[R23], R10": credits_design_parts on, kept in speed_x
local ending_credits_sound_scroll 01CF62 r10 field_cursor credit_part  # "mov.w 34[R23], R10": credits_sound_parts on, kept in speed_x
local ending_credits_voice_actor_scroll 01D060 r10 field_cursor credit_part  # "mov.w 34[R23], R10": credits_voice_actor_parts on, kept in speed_x
local ending_credits_special_thanks_scroll 01D177 r10 field_cursor credit_part  # "mov.w 34[R23], R10": credits_special_thanks_parts on, kept in speed_x
struct screen_point 4 rom               # a (screen x, screen y) word pair in the program ROM: ending_eggman_flight_path (103B76, one a frame: ending_eggman_fly_in) and the points of the stage_map_trails lists (after the list's frame count word: next_map_trail_segment); census ROM 103B76-1044AC and 096D16-096D78
field screen_point 00 x s16             # ending_eggman_fly_in "mov.h [R10+], E[R20]" (01D71F: screen_x, FFE0 up to A6); next_map_trail_segment's start_x / end_x (negative: the trail ends, signed test)
field screen_point 02 y s16             # ending_eggman_fly_in "mov.h [R10], C[R20]" (screen_y, 70 to 66); next_map_trail_segment's start_y / end_y
scoped object ending_eggman_fly_in 30 started u8  # as ending 30: 0 when spawned; the first frame sets the object up (allocates the nine parts)
scoped object ending_eggman_fly_in 31 step u8  # as ending 31: cleared on the first frame
scoped object ending_eggman_fly_in 32 timer s16  # as ending 32: 1E0 on the first frame, - 1 a frame: sound 75 at 104, the puffs shift when timer % A is 0 and are drawn while it is above 32 (signed); at 0 ending_eggman_turn takes over
scoped object ending_eggman_fly_in 34 exhaust_flame u32 object  # the first second-pool part: the flame behind the craft (9AB0, 9AB8 while timer bit 1 is set; 20 to its left), drawn until x reaches A6 (ending/eggman exhaust_flame)
scoped object ending_eggman_fly_in 38 path u32 screen_point  # ending_eggman_flight_path (103B76): the craft's (x, y) this frame, then + 4 ("mov.w R10, 38[R20]" after two [R10+]), + 4 more once x is A6; freed at the end as if it were a part (free_object_2 of its low half, 01D6BB: original code)
scoped object ending_eggman_fly_in 40 puff_1 u32 object  # the newest smoke puff (init_eggman_smoke_puff): every 10 frames it takes the craft's position, x - 34 (01D858-01D868)
scoped object ending_eggman_fly_in 44 puff_2 u32 object  # (ending 44 part_3) every 10 frames takes puff_1's position
scoped object ending_eggman_fly_in 48 puff_3 u32 object  # takes puff_2's position
scoped object ending_eggman_fly_in 4C puff_4 u32 object  # takes puff_3's position
scoped object ending_eggman_fly_in 50 puff_5 u32 object  # sprite 9B80; takes puff_4's position
scoped object ending_eggman_fly_in 54 puff_6 u32 object  # sprite 9B78
scoped object ending_eggman_fly_in 58 puff_7 u32 object  # sprite 9B70
scoped object ending_eggman_fly_in 5C puff_8 u32 object  # the oldest: sprite 9B68, takes puff_7's position
scoped object animate_ending 38 legs u32 object  # the across runners' second part (allocate_object_2 in ending_*_run_across, kept in speed_y: A820/A7F0/A7C0, 1A lower): animate_ending_runner_across draws it with the leg frame
scoped object animate_ending 4C body_frames u32 ending_sprite_frames  # the across runner's body frame list (ending_sonic/ray/mighty_body_frames 01E054/01E060/01E06C, set by ending_*_run_across): word (32 & 7) >> 2 is the sprite; census ROM 01E054-01E06E
scoped object animate_ending 50 leg_frames u32 ending_sprite_frames  # its leg frame list (ending_*_leg_frames 01E058/01E064/01E070): word (62 & 3) is the legs part's sprite; census ROM 01E058-01E076
param next_map_trail_segment R10 trail_point screen_point  # a trail point (x, y words) followed by the next one (negative x ends the trail): the segment between them; the next point's address goes to field 51; census ROM 096D16-096D78, and 000001-000007 for the stages without a trail (stage_map_trails FFFFFFFF + 2: the reads land on the ROM's first bytes, the same through rom())
struct map_marker_layout C rom          # map_marker_layouts (096BC7): 12 bytes per marker, 2 groups of 7 (draw_map_stage_markers): layer, x, y, sprite, palette with [R7+] (096652-096666), then the cleared palette at 0[R7] and "add.h #2, R7"; census ROM 096BC7-096C6E
field map_marker_layout 00 sprite_layer u16  # -> the marker's sprite_layer (0A)
field map_marker_layout 02 screen_x s16  # -> screen_x (0E)
field map_marker_layout 04 screen_y s16  # -> screen_y (0C)
field map_marker_layout 06 sprite_frame u16  # -> sprite_frame (04): records 7-D's are the numbered badges (map_number_badge_sprites 096C21 is record 7's)
field map_marker_layout 08 sprite_palette u16  # -> sprite_palette (06)
field map_marker_layout 0A cleared_palette u16  # "mov.h [R7], 6[R20]" (096682): the palette of the markers of the stages already played
local draw_map_stage_markers 096627 r7 layout map_marker_layout  # "movea.b 96BC7, R7": map_marker_layouts, the first record
local draw_map_stage_markers 096652,096657,09665C,096661,096666,096687 r7 layout  # the post-increments to the next field, and "add.h #2, R7" past the cleared palette to the next record
local draw_map_stage_markers 09660B,0966D4 r20 marker_sprite object  # R20 (current_object in the C): 209102 ("movea.b 1102[R25], R20"), + 30 per marker ("add.w #30, R20"): each marker is a 30-byte sprite block that queue_sprite_in_layer reads as an object's first bytes (flags 2, sprite 4, palette 6, zoom 8, layer A, y C, x E); census RAM 209104-20937C
struct map_marker_blink A rom           # map_marker_blink_sprites (096C6F): 10 bytes per marker, the stage name that blinks over the current stage's marker (blink_current_map_marker: 096C6F + marker x 8 + marker x 2): layer, x, y, sprite, palette with [R7+] (096741-096755); census ROM 096C79-096CB3
field map_marker_blink 00 sprite_layer u16
field map_marker_blink 02 screen_x s16
field map_marker_blink 04 screen_y s16
field map_marker_blink 06 sprite_frame u16  # 9F98-9FC8 (map/marker_labels)
field map_marker_blink 08 sprite_palette u16  # 0670
local blink_current_map_marker 096726 r7 record map_marker_blink  # "add.h R0, R7": 096C6F + current_marker * 10, the marker's record
local blink_current_map_marker 096723,096741,096746,09674B r7 record  # 096C6F + current_marker * 8 on the way (096723), then the post-increments to the next field
struct wipe_window 6 rom                # map_wipe_in_windows (096A4B), map_wipe_out_windows (096ABD), map_wipe_next_windows (096B2F): 6 bytes per stage (run_map_screen: table + stage x 6): the window start_window_wipe opens or closes; census ROM 096A4B-096B99
field wipe_window 00 left s16           # "movz.hw [R0+], R1": window_wipe_left (window_wipe_default_left when not above 0, signed); right = it + 100, at most 140
field wipe_window 02 top s16            # window_wipe_top (window_wipe_default_top when not above 0); bottom = it + 80, at most E0
field wipe_window 04 frames u16         # the wipe's length in frames (window_wipe_frames): divides the four edge distances
param start_window_wipe R0 record wipe_window  # the stage's wipe record (map_wipe_in/out/next_windows + 6 x stage): left, top, frame count
struct ranking_record 8                 # one of the 15 records of ranking (20CE00, lowest first; saved_ranking 701F82 and default_ranking 08F1B5 have the same layout, the latter with ASCII letters): start_ranking_entry and enter_ranking_initials compare the rings, build_ranking_display draws a row of each, reset_ranking writes them; census RAM 20CE00-20CE77
field ranking_record 00 rings s16       # the score: start_ranking_entry "cmp.h [R2], R0; blt" and enter_ranking_initials "cmp.h R2, R0" (signed); build_ranking_display draws it & 7FFF in decimal
field ranking_record 02 character u8    # 0 Sonic, 1 Ray, 2 Mighty (% 3: ranking_portrait_palettes, ranking_name_sprites); the entry block's 36
field ranking_record 03 stages_reached u8  # the player's stages_reached (% 9: ranking_stage_sprites, 8 ALL CLEAR); the entry block's 37
field ranking_record 04 initials u32    # the three letters and the byte after them as one long: enter_ranking_initials moves a record with two "mov.w" (the rings, character and stages, then this)
field ranking_record 04 first_initial u8  # ranking letter codes (00-19 a-z, 20-29 0-9, 2B blank): build_ranking_display draws them through ranking_letter_sprites (% 2C)
field ranking_record 05 second_initial u8
field ranking_record 06 third_initial u8
field ranking_record 07 initials_pad u8  # 0 in default_ranking, the entry block's 3B (0) in a new record; build_ranking_display skips it
local start_ranking_entry 08EADA,08EAE9 r2 record ranking_record  # walks ranking, 8 bytes a record ("add.w #8, R2"): the player gets an entry when the rings reach a record's rings word; census RAM 20CE00-20CE70
local enter_ranking_initials 08EE73,08EE86 r1 read_cursor ranking_record  # ranking, then its second record ("add.w #8, R1"): the records the score reaches are read here and moved one place up (dest)
local enter_ranking_initials 08EE8C,08EE94 r1 read_cursor  # the two post-increments (to the record's second long, then to the next record)
local enter_ranking_initials 08EE83 r5 dest ranking_record  # "mov.w R1, R5": ranking's first record: each record the score reaches moves one place towards the start, then the new record (rings, kind, stages, initials) goes where it stops
local enter_ranking_initials 08EE97,08EE9A,08EED2,08EED7,08EEDC,08EEE1,08EEE6 r5 dest  # the post-increments ([R5+])
local enter_ranking_initials 08EEF5 r0 copy_source ranking_record  # ranking, copied a long at a time into saved_ranking (copy_dest is the shared RAM: those writes stay on the bus)
local enter_ranking_initials 08EF06,08EF0A r0 copy_source  # the post-increments
local reset_ranking 08F123 r1 write_cursor ranking_record  # "mov.w #20CE00, R1": ranking: default_ranking's words copied, the letters through ascii_to_ranking_letter
local reset_ranking 08F12D,08F131,08F13B,08F144,08F14D,08F150 r1 write_cursor  # the post-increments (the last one reaches the next record)
local build_ranking_display 08F263 r19 record ranking_record  # "mov.w #20CE00, R19": ranking, one row per record
local build_ranking_display 08F2D9,08F3AB,08F3E9,08F416,08F437,08F46E r19 record  # the post-increments: rings word, character, stages, three initials, one byte skipped
local build_ranking_display 08F29B r1 rank_label ranking_row_number  # ranking_row_numbers (08F858) + row * 2: the rank label's two characters; census ROM 08F858-08F875
struct ranking_row_number 2 rom       # a row's rank label in ranking_row_numbers (08F858, 15 rows, the lowest first): two ranking letter codes, build_ranking_display "movz.bw [R1+], R0" (08F29E) and the next byte (08F2C7), each through ranking_letter_sprites; census ROM 08F858-08F875 (21 25 "15" ... 2B 21 " 1")
field ranking_row_number 00 tens_code u8   # 21 for 10-15, 2B (blank) for 1-9
field ranking_row_number 01 units_code u8  # 20-29
08EFE9  table ranking_entry_trackballs  u32 trackball_motion  # [player] address of trackball_N_x (20F062, 20F066, 20F06A) -> entry field 3C (trackball_motion: structs_2.txt)
scoped object ranking_entry 10 player_block u32 player  # start_ranking_entry stores current_player there as a long; enter_ranking_initials sets its game_over_done (135) when the entry ends ("mov.w 10[R20], R0; mov.b #1, 135[R0]"); census RAM 208535-208935
scoped object ranking_entry 24 rank u16  # start_ranking_entry's r14: 0 for first place up to E for 15th (15, less one per record the rings reach); ranking_rank_texts + it x A ("Rank TOP!"...) is drawn above the letters
scoped object ranking_entry 34 rings s16  # the player's rings: compared with the records' (signed) and written as the new record's first word
scoped object ranking_entry 36 character u8  # the player object's kind (0 Sonic, 1 Ray, 2 Mighty): the record's byte 2
scoped object ranking_entry 37 stages_reached u8  # the player's stages_reached: the record's byte 3
scoped object ranking_entry 38 initials u32  # the three letter codes (ranking_initial_fields 38 39 3A get the chosen ones) and 3B: 'sex', 'xxx', 'x x' and 'xx ' (170412, 171717, 172B17, 2B1717) become 'tak' (A0013) before the record is written
scoped object ranking_entry 38 first_initial u8  # the record's byte 4
scoped object ranking_entry 39 second_initial u8  # the record's byte 5
scoped object ranking_entry 3A third_initial u8  # the record's byte 6
scoped object ranking_entry 3B initials_pad u8  # the record's byte 7: 0 (the long compares above need it so)
scoped object ranking_entry 3C trackball u32 trackball_motion  # ranking_entry_trackballs[player]: the player's trackball words, x then y ("mov.w 3C[R20], R2; mov.h [R2+], R3 ..."); census RAM 20F062-20F06A
struct ring_tally_portrait 8 rom        # ring_tally_portraits' records (098EAE, 098EB6, 098EBE, one per character): the panel's palette, then the portrait's three faces (ring_tally/portraits: normal, frowning, laughing); census ROM 098EAE-098EC2
field ring_tally_portrait 00 sprite_palette u16  # build_ring_tally_panel: the panel's palette (| 8000) ("mov.h [R0+], 6[R20]")
field ring_tally_portrait 02 sprite_frame u16  # the portrait while the rings are counted ("mov.h [R0+], 4[R20]"): 9D90, 9DA8, 9DC0
field ring_tally_portrait 04 no_bonus_frame u16  # update_ring_tally_panel steps 3-4 (no bonus): 9D98... (frowning)
field ring_tally_portrait 06 bonus_frame u16  # step 2 (the bonus shown): 9DA0... (laughing)
local build_ring_tally_panel 098A7F r0 portrait_record ring_tally_portrait  # "mov.w [R0], R0": ring_tally_portraits[character], the record
local build_ring_tally_panel 098A82 r0 portrait_record  # the post-increment; past the frame (record + 4) it is kept in floor_block (5C)
scoped object build_ring_tally_panel 38 bonus_label u32 object  # the first part: RING BONUS (9EF0), NO BONUS (9EF8) when gets_bonus is 0, at 40,59
scoped object build_ring_tally_panel 40 backdrop u32 object  # the second part: the squares behind the portrait (9DD8, layer 1, at 40,A0); cycle_ring_tally_backdrop_palette cycles its palette
scoped object build_ring_tally_panel 48 units_digit u32 object  # the ring count's units (9E40 = 0, at 56,75)
scoped object build_ring_tally_panel 4C tens_digit u32 object  # its tens (at 4D,75)
scoped object build_ring_tally_panel 50 hundreds_digit u32 object  # its hundreds (at 44,75)
scoped object build_ring_tally_panel 5C result_frames u32 ring_tally_result_faces  # the portrait record + 4 ("mov.w R0, 5C[R20]"): its no-bonus face, then its bonus face, for update_ring_tally_panel
scoped object ring_tally_player 38 bonus_label u32 object  # as build_ring_tally_panel 38: players 2 and 3 move its screen x (A0 / 100); census RAM 2079AE-207EEE
scoped object ring_tally_player 40 backdrop u32 object  # as build_ring_tally_panel 40 (screen x A0 / 100)
scoped object ring_tally_player 48 units_digit u32 object  # as build_ring_tally_panel 48 (screen x B6 / 116)
scoped object ring_tally_player 4C tens_digit u32 object  # as build_ring_tally_panel 4C (screen x AD / 10D)
scoped object ring_tally_player 50 hundreds_digit u32 object  # as build_ring_tally_panel 50 (screen x A4 / 104)
scoped object update_ring_tally_panel 38 bonus_label u32 object  # as build_ring_tally_panel 38: drawn in steps 2-4
scoped object update_ring_tally_panel 40 backdrop u32 object  # as build_ring_tally_panel 40: drawn in step 2 (the bonus shown)
scoped object update_ring_tally_panel 48 units_digit u32 object  # ring_tally_digits[the count's units digit]; always drawn
scoped object update_ring_tally_panel 4C tens_digit u32 object  # ring_tally_digits[tens]: drawn unless both it and the hundreds show 0
scoped object update_ring_tally_panel 50 hundreds_digit u32 object  # ring_tally_digits[hundreds]: drawn unless it shows 0
scoped object update_ring_tally_panel 5C result_frames u32 ring_tally_result_faces  # as build_ring_tally_panel 5C: its first word is the panel's sprite in steps 3-4 (no bonus), the second in step 2 (the bonus); census ROM 098EB2-098EC2
struct ring_tally_result_faces 4 rom  # the last two words of a ring_tally_portrait (+ 4: build_ring_tally_panel "mov.w R0, 5C[R20]"): the portrait's faces once the rings are counted; update_ring_tally_panel reads them through result_frames; census ROM 098EB2-098EB5, 098EBA-098EBD, 098EC2-098EC5
field ring_tally_result_faces 00 no_bonus_frame u16  # steps 3-4 (no bonus): 9D98, 9DB0, 9DC8 (frowning)
field ring_tally_result_faces 02 bonus_frame u16     # step 2 (the bonus shown): 9DA0, 9DB8, 9DD0 (laughing)
local init_title_cloud_block entry r11 cloud object  # R11 at the start (no caller): the pool 2 block it sets up (zoom 80, layer 8, flags 0, palette D80: its sprite fields, as title_clouds_1_drift sets its clouds')
local title_sky_wave 097EDE r10 sine_cursor title_wave_sines  # "movea.b 101632, R10": sine_table's first entry
local title_sky_wave 097F15 r10 sine_cursor title_wave_sines  # "add.h #2, R10" after the [R10+]: skips an entry (the low half only; still an entry's start)
elements title_wave_sines u16         # sine_table's entries as title_sky_wave reads them, every other one: "movz.hw [R10+], R0; shl.w #3, R0" (097EFA, 097EFD, zero-extended), added to each row's offset; census ROM 101632-10182F (0000 0065 00C9 ...: 80 rows, the first eighth of a turn)
local init_stage_view 015BB4 r1 list section_scroll_speeds  # stage_section_speeds[stage] + 1A162 ("add.w #1A162, R1"): the first section's scroll_speed, + 1 kept in section_speed_list
local init_stage_view 015BCE r1 list section_follow_speeds  # stage_section_follow_speeds[stage] + 1A565: the first section's follow_base_speed, + 1 kept in follow_base_speed_list
local init_stage_view 015C0D,015C14,015C2A,015C31 r1 list section_camera_limits  # stage_section_limits_x[stage] + 18DFA (015C0D), then _y + 18E20 (015C2A), each moved past the start's word by "add.h #2, R1" (015C14, 015C31: a move on the low half loses the kind unless the line says it; it still points at an element): section_limit_x / _y
local init_stage_view 015C47 r1 list scroll_mode_step  # stage_scroll_modes[stage] + 197F2: the first (mode, duration), + 4 kept in scroll_mode_script
local init_stage_view 015C4E r1 list  # moved within the (mode, duration) record ([R1+])
local init_stage_view 015C7D r1 list section_directions  # stage_section_directions_x[stage] + 1884A: the first section's x direction, + 2 kept in section_direction_x_list
local load_tile_set 015E5A,015E5D r6 script_list rom  # stage_tile_sets[stage] (015E5A), get_tile_set_list's list for the set (015E5D)
local load_tile_set 015E60 r6 script_list tile_set_vram_scripts  # banked by get_tile_set_list_bank (015E60): a script address every 4 bytes until a negative one ([R6+] 015E63); census ROM 140910-170D0C
local load_tile_set 015E63 r6 script_list
param is_direction_in_quadrant R11 sine_entry scroll_quadrant_sines  # walks the quadrant's sine_table entries by 2; out: the entry it stopped at (is_player_facing_scroll reads it); census ROM 101632-101DE4
20907D  var   follow_base_speed_list u32 section_follow_speeds  # the next section's entry of stage_section_follow_speeds (a byte per section, as section_speed_list's): advance_scroll reads follow_base_speed there and moves it on; census ROM 01A5BE-01A62C
local load_camera_start_section 01A991 r1 list section_camera_limits  # stage_section_limits_x[stage] + 18DFA + section_offset ("add.w R3, R1"): the start section's x (camera_x), then its section_limit_x
local load_camera_start_section 01A997,01AA8E r1 list  # moves ([R1+]) within the list or the record
local load_camera_start_section 01A9FF r1 list section_directions  # stage_section_directions_x[stage] + 1884A + section_offset: the start section's x direction
local load_camera_start_section 01AA2A r1 list section_end_positions  # stage_section_ends[stage] + 193E0 + section_offset: the start section's start (scroll_position), + 2 kept in section_end_list
local load_camera_start_section 01AA4E r1 list section_scroll_speeds  # stage_section_speeds[stage] + 1A162 + section_offset / 2 ("shl.w #FF": the byte list's offset, which loses the kind unless the line says it)
local load_camera_start_section 01AA6B r1 list section_follow_speeds  # stage_section_follow_speeds[stage] + 1A565 + section_offset / 2
local load_camera_start_section 01AA8B r1 list scroll_mode_step  # stage_scroll_modes[stage] + 197F2 + section_offset twice (4 bytes a section): the start section's (mode, duration)
struct geyser_rise_step 4 rom           # stage_00_geyser_speeds (09569C): (frames, rise speed) pairs: 50 80, 40 D4, 7FFF 90; stage_00_geyser_rise keeps the cursor in floor_z (54); census ROM 09569C-0956A6
field geyser_rise_step 00 frames u16    # "mov.h [R10+], 38[R20]": frames at this speed (counted down in speed_y_fraction; the next record when it was <= 1)
field geyser_rise_step 02 rise_speed u16  # "movz.hw [R10+], R0; shl.w #C, R0": speed_z = it << 12 (z - speed_z a frame)
local stage_00_geyser_rise 094DB2 r10 cursor geyser_rise_step  # "mov.w 54[R20], R10": the rise script cursor kept in floor_z (the 094DB6 line of symbols.txt names the post-increment, which moves it to the speed word)
local run_game_mode 0943FC,094417 r11 event_12_player_block player  # game mode 12: PLAYER_BLOCK(1) ("movea.b 400[R25], R11"), + 200 per player ("movea.b 200[R11], R11"): its drift_done (55) counts the players still drifting; census RAM 208455-208855
local run_game_mode 094269,094290 r10 event_14_player_block player  # game mode 14 step 1: PLAYER_BLOCK(1), + 200 per player: its state 25 with sequence_bounced (12F) clear counts; census RAM 208400-20892F
local run_game_mode 09404D,094069 r11 player_block player  # game mode 19: PLAYER_BLOCK(1) on, + 0x200 per player: its object (14) past x 17C closes the barred gate; census RAM 208414-208814
local update_grey_screen 001A42 r10 player_block player  # "add.w #200, R10": each player's block, tested at 9F (grey_screen_request); census RAM 20849F-20889F
local update_grey_screen 001A3A r10 player_block  # 208200, the 200 bytes before the first block (only + 200 is read)
local update_player_summary 001B17,001B33 r10 player_block player  # PLAYER_BLOCK(1) on, + 0x200 per player: its object (+14) and mishap_flag (B4) tell whether it had a mishap; census RAM 208414-2088B4
scoped object stage_clear_banner 48 animation u32 frame_animation  # the banner's animation (stage_name_banners' third word, the object's 48): stage_clear_banner_hold reads its first word for banner_letters_left ("mov.h [48[R20]], 70EC[R25]"); census ROM 0026F9-002783
local read_trackball_step 001C96 r10 trackball trackball_motion  # "movea.w 7062[R25](R3), R10": trackball_1_x + player x 4, the player's pair (its x is added up); the param line R10 trackball names the rest
local reset_objects_from_list 001D15 r10 handler_cursor object_start_list  # object_handler_list (player_objects 002405 or player_objects_alt 00241F): its count word first
local reset_objects_from_list 001D1A r10 handler_cursor object_start_handlers  # past the count ("movz.hw [R10+], R13"): the handlers, one per object ([R10+] 001D23, 001D7B)
local reset_objects_from_list 001D23,001D7B r10 handler_cursor  # + 4 per handler
struct palette_set_cycle 10             # the state cycle_palette_set runs (palette_cycle_set points at it: 209090 for set 0, 2090A0 for set 1; update_stage_06_background step 5 fills both): every delay frames the current frame (16 colours) goes to rows consecutive palette rows; census RAM 209090-2090AD
field palette_set_cycle 00 frames u32 rom  # the frames in the ROM, 20 bytes each (stage_06_cycle_palettes_0/1): + frame x 20 is the source queue_vblank_copy copies
field palette_set_cycle 04 dest u32     # the first palette row (6083A0, 608400): + (rows - rows_left) x 20 per copy
field palette_set_cycle 08 timer u8     # frames to the next step, - 1 a frame: at 0 back to delay and the frame is copied
field palette_set_cycle 09 rows_left u8 # the copy loop's count: rows, - 1 per row copied
field palette_set_cycle 0A delay u8     # timer's reload (2, 7)
field palette_set_cycle 0B rows u8      # palette rows per frame (3, 2)
field palette_set_cycle 0C frame s8     # the current frame: + 1 per step, back to 0 once frame_count <= it (signed); palette_cycle_set_0_frame / _1_frame are set 0's and set 1's
field palette_set_cycle 0D frame_count s8  # frames in the cycle (6)
2090D0  var   palette_cycle_set     u32 palette_set_cycle  # the state cycle_palette_set runs (cycle_palette_set_0/_1 set 209090/2090A0; update_stage_06_background step 5 sets 209090 and fills both)
struct palette_cycle 5                  # cycle_palette's state (palette_cycle_0 at 20CA00, palette_cycle_1 at 20CA05; the animate_palette_stage_* functions, the title and the ending pass it in R9): every delay frames the next 16-colour frame; census RAM 20CA00-20CA09
field palette_cycle 00 delay u8         # frames per step: reloads timer ("mov.b [R9], 4[R9]")
field palette_cycle 01 frame_count s8   # frames in the cycle (palette_cycle_0_frames, _1_frames): the frame wraps there (signed compare)
field palette_cycle 03 frame s8         # the current frame (x 20 into the source): + 1 per step, 0 at frame_count
field palette_cycle 04 timer s8         # counted down; the step comes when it is not above 0 (signed)
param cycle_palette R9 cycle palette_cycle  # the 5-byte state: [0] delay, [1] frames, [3] frame, [4] timer (palette_cycle_0 at 20CA00, palette_cycle_1 at 20CA05)
local load_palette_set_and_backdrop 01AFE4 r3 tile_bank_entry tile_bank_selects  # stage_tile_bank_bits + stage_index ("movea.b 1B1C0(R1), R3"): the stage's byte, << 10 into video_control (its high half lands in tile_bank_bits); census ROM 01B1C1-01B1D1
local select_nbg3_pages 01B9C5,01B9C8,01B9CD,01BA3B r10 map_cursor nbg3_block_pages  # stage_nbg3_block_maps[stage] (01B9C5 "mov.w [R10], R10"), at the camera's block (01B9C8, 01B9CD "add.h"), the next row (01BA3B "add.h R3, R10"): the moves on the low half lose the kind unless the line says it; census ROM 01BB3D-01BCAE
local select_nbg3_pages 01BA21 r10 map_cursor  # + 1 per byte ([R10+])
local select_nbg3_pages 01BA02,01BA63 r11 quadrant_cursor nbg3_quadrants  # nbg3_quadrant_grid (01BCBC) + 3 x row parity + column parity (01BA02), the next row ("inc.h R11" 01BA63): the four quadrant numbers; census ROM 01BCBC-01BCC4 (the param line R11 quadrant_cursor names it)
local select_nbg3_pages 01BA51 r11 quadrant_cursor  # + 1 per byte ([R11+])
field credit_block 00 counts u16        # coins and credits as one halfword: copy_credit_counts saves it to saved_credits ("mov.h [R13], [R12+]"); apply_settings restores the credits (coins cleared) with one long store that also clears bonus_coins and changed
local apply_settings 0FF745,0FF75D r13 block_cursor credit_block  # walks p1_coins, 4 bytes per credit block ("movea.b 7412[R25], R13", "mov.w R1, [R13+]"); census RAM 20F412-20F41A
local update_coins 0FF765,0FF774 r10 block_cursor credit_block  # walks p1_coins, 4 bytes per credit block (its changed flag cleared); census RAM 20F415-20F41D
param add_service_credit R10 credit_block credit_block  # 20F412/20F416/20F41A: coins, credits, bonus coins, changed flag; census RAM 20F412-20F41B
param add_coin R10 credit_block credit_block  # as add_service_credit; census RAM 20F412-20F41D
param copy_credit_counts R13 credit_block credit_block  # 20F412: the first halfword (coins, credits) of each block, 4 apart (the saved_credits writes, shared RAM, stay on the bus)
param blink_start_prompt R12 credit_block credit_block  # its coins (+0) and credits (+1) pick the prompt; census RAM 20F412-20F41B
param draw_credit_count R12 credit_block credit_block  # 20F412/20F416/20F41A: picks the stage_init_flags bit (F, 12, 13) that forces the first draw; its changed flag forces a redraw
param draw_credit_count_now R12 credit_block credit_block  # credits (+1) and coins (+0) drawn
struct coin_counter 4                   # a coin counter's pulse state and its chute's multiplier (counter 1 at 20F406: coin_counter_1_on, _off, _pending, chute_1_multiplier; counter 2 at 20F40A): pulse_coin_counter runs the one in R7; add_coin reaches its pending byte and its multiplier by offsets 4 apart; census RAM 20F406-20F40C
field coin_counter 00 pulse_timers u16  # on and off frames as one halfword ("movz.hw [R7+], R0"): 0 = idle; a pending coin sets 606 ("mov.h #606, [-R7]": on 6 frames, off 6)
field coin_counter 00 on_frames u8      # frames the counter stays on: at 1 its port D bit is cleared
field coin_counter 01 off_frames u8     # frames it then stays off
field coin_counter 02 pending_coins u8  # coins still to count: add_coin + 1 (R25 + 7408/740C), - 1 per pulse
field coin_counter 03 chute_multiplier u8  # chute_1_multiplier / chute_2_multiplier: coins counted per coin (add_coin: chute_1_multiplier + 0/4)
param pulse_coin_counter R7 counter_state coin_counter  # coin_counter_n_on: frames on, frames off, coins pending (bytes); on 6 frames, off 6, per pending coin; census RAM 20F406-20F40C
param blink_start_prompt R13 prompt_texts start_prompt_text_offsets  # start_prompt_texts (09FEE6): halfword offsets (from here) of the prompt texts; census ROM 09FEE6-09FEEC
elements start_prompt_text_offsets u16  # start_prompt_texts (09FEE6) or common_start_prompt_texts (09FEDE): by prompt, the offset from the table to its text: blink_start_prompt "movz.hw [R13](R10), R10; movea.b [R13](R10), R10" (09FD64-09FD68, 09FD81-09FD85); census ROM 09FEDE-09FEED (0037 0022 0010 0022, 0044 005D 0075 0089)
param erase_start_prompt R13 prompt_texts start_prompt_text_offsets  # halfword offsets from here to the texts (start_prompt_texts or common_start_prompt_texts); never run: the tables blink_start_prompt reads
local build_fraction_glyph 09FE35 r0 numerator fraction_font_row_longs  # "movea.w 3[R2](R0), R0": fraction_digit_font + 3 + the numerator digit x 8: its rows, read as two longs (never run: the coin cost is 1 in the scenarios; the param line R0 numerator names the digit)
local build_fraction_glyph 09FE43 r0 denominator_rows fraction_font_row_longs  # fraction_digit_font + denominator x 8: the denominator digit's 8 row bytes, read as two longs (never run)
elements fraction_font_row_longs u32  # four pixel rows of fraction_digit_font (a byte a row) read as one long: build_fraction_glyph "mov.w [R0+], R4 / R5" (09FE3A, 09FE3D) from digit + 3, "mov.w [R0+], R6 / R7" (09FE47, 09FE4E) from the digit; census ROM 104B76-104BC5 (never run)
struct text_cell_position 2 rom         # a text layer cell as two bytes, column then row (TEXT_CELL(column, row)): the menus' cursor positions (test_menu_cursor_positions 115BA3, 115BBB, 115BCC, 115BD7, 115BE3: test_cursor_positions; FF ends a menu's list) and the coin credit table's cells (chute_1_credit_cell_positions 116F2D, chute_2_credit_cell_positions 116F3F); census ROM 115BA3-115BE7 and 116F2D-116F50
field text_cell_position 00 column u8   # erase_test_cursor/set_test_cursor "mov.b [R10+], 4A23[R25]" (test_cursor_column); draw_coin_credit_cell "movz.bw [R10+], R5", then x 2 into the text address; a negative column ends a cursor list (set_test_cursor 09ED41 "test.b [R10]; bge")
field text_cell_position 01 row u8      # "mov.b [R10], 4A24[R25]" (test_cursor_row); draw_coin_credit_cell "movz.bw [R10+], R6", then x 0x80
struct polygon_span 6                   # one row of a filled triangle or quadrilateral: fill_triangle_sorted (09B236), fill_flat_top_triangle (09B2C3) and fill_quad (09B520 and 8 more) write one per row with "mov.w R10, [R19+]; mov.h Rn, [R19+]" from bitmap_draw_buffer on; fill_span_list reads them back the same way (09B321 "mov.w [R10+], R11", 09B324 "movz.hw [R10+], R3") and fills each
field polygon_span 00 address u32       # bitmap address of the row's left pixel (span_address: bitmap_address + y * 200 + left x, moved by the left edge's step and 200 per row); fill_span_list fills from it
field polygon_span 04 length s16        # right x - left x: one pixel less than fill_span_list fills ("inc.w R3"); negative, nothing: fill_span_list skips the row (09B327 "test.h R3; blt")
struct quad_vertex 4                    # one corner of the quadrilateral: fill_quad pushes R7..R0 with "mov.h Rn, [-SP]" (09B382-09B397), so corner n's x is at SP + n * 4 and its y at + 2; with three corners on the top row it reads them back by number, "movea.w [SP](R3), R0" (09B48B, 09B49A, 09B4A7)
field quad_vertex 00 x s16              # "movs.hw [R0+], R4" (09B48F: the bottom corner's x, apex_x), "movs.hw [R0], R2" (09B49E), "movs.hw [R3+], R0" (09B4AB)
field quad_vertex 02 y s16              # "movs.hw [R0], R5" (09B492: the bottom corner's y, triangle_height), "movs.hw [R3], R1" (09B4AE: triangle_top_y)
struct picture_colour_slot 3            # one of the 16 slots: init_picture_colour_cache writes next, previous and colour with three "mov.b Rn, [R11+]" per slot (099B37-099B3D) from R15 = bitmap_draw_buffer (init_picture_unpacker); read_picture_colour reaches them by slot number ([R15](slot x 3), 1[R15](...), 2[R15](...): left untyped, see the keep lines)
field picture_colour_slot 00 next u8    # the slot after it in the ring (init: n + 1; slot F's goes back to 0 at 2D[R15]); read_picture_colour: the newest slot's next is the oldest one, where a new colour goes
field picture_colour_slot 01 previous u8  # the slot before it (init: n - 1; slot 0's is set to F at 1[R15], 099B4E)
field picture_colour_slot 02 colour u8  # the colour it holds (init 0; read_picture_colour stores a new colour at 2[R15](slot x 3), 099CF3, and takes a cached one from there, 099CD8)
struct saved_bitmap_rect 8              # its header: save_bitmap_rect writes "mov.w R2, [R4+]; mov.w R3, [R4+]" (09C30C) and then the rows (MOVBSU from the bitmap); draw_saved_bitmap_rect reads them back, "mov.w [R2+], R3; mov.w [R2+], R4" (09C362), and copies the rows to the bitmap
field saved_bitmap_rect 00 row_bits u32 # bits per row: (right - left + 1) x 8 in 8 bpp ("shl.w #3, R2"), x 4 in 4 bpp (two "add.b R2, R2"); each row takes (bits + 7) / 8 bytes after the header (draw_saved_bitmap_rect 09C368-09C36E)
field saved_bitmap_rect 04 rows u32     # bottom - top + 1: the rows that follow, counted down by both (dbr R3 / dbr R4)
local boot 000016 r10 source boot_register_values  # "movea.b 7E[PC], R10": boot_e00000_values, the first byte both copy loops read
local boot 000025,000029,00003B r10 source  # 00007E on ([R10+]): 16 bytes to E00000, then 12 to D00000; census ROM 00007E-000099 (the writes stay: E00000 is unmapped, D00000 the interrupt controller)
elements boot_register_values u8      # the bytes boot copies with "mov.b [R10+], [R11+]" (000025, 000029, 00003B): boot_e00000_values (00007E, 16) then boot_irq_controller_values (00008E, 12); census ROM 00007E-000099
20E5CE  var   demo_input  u32 rom       # demo recording read every frame: 0x10-byte header (stage at +0, stage_start_section at +2) then 1 button byte and 6 trackball bytes per frame; attract_demo_1/2 set 117876/118876 (attract_demo_1_inputs / attract_demo_2_inputs: ROM); census: play_demo_trackballs' reads ROM 117887-11983E, game_modes' stage reads (work list 3) ROM 117876-118878
struct demo_header 10 rom                 # the 16 bytes before a demo's frames (attract_demo_1_inputs 117876, attract_demo_2_inputs 118876): game_mode_start_game "mov.w 65CE[R25], R10; mov.h [R10], 706E[R25]" (0006E3, 0006E8), start_stage "mov.h 2[R10], 1024[R25]" (0008AB), then demo_input + 10 (0008B8); census ROM 117876-117885 (09 00 0B 00, then 0), 118876-118885 (08 00 10 00, then 0)
field demo_header 00 stage_number u16            # into stage (9 and 8)
field demo_header 02 start_section u16    # into stage_start_section (0B and 10), for move_camera_to_start_section
scoped global game_mode_start_game 20E5CE demo_input u32 demo_header  # demo_input as game mode 0 starts a demo: still at the recording's header (attract_demo_1/2 set it), whose stage it reads
scoped global start_stage 20E5CE demo_input u32 demo_header  # demo_input as a demo's stage starts: at the recording's header (its start section), then moved past it (+ 10)
local run_vblank_copies 0FF5CE r4 copy_entry vblank_copy  # "mov.h #0, [R4+]" (09F5CE, run from the 0FF5CE mirror) clears vblank_copy_count and leaves R4 at vblank_copies (20EE02), the first entry; the loop's three [R4+] reads (0FF5D6-0FF5DC) take source, dest and halfwords and move it to the next entry; census RAM 20EE02-20EE64 (struct vblank_copy: structs_5.txt, helper 5)
param compute_crc16_alternate_bytes R2 data rom_check_bytes  # 100000 (even bytes: the inc.b keeps it in 100000-1000FF) or 100001 (odd bytes: the [R2+] carry takes it on to 17FFFD); census ROM 100000-17FFFD: the ROM check of IC18/IC9 (check_rom_checksums, its only caller)
local compute_crc16_alternate_bytes 0FFAFC r2 data rom_check_bytes  # "mov.b [R2+], R0" then "inc.b R2" (09FAF9-09FAFC, run from the 0FFAF9 mirror; the C folds both into one assignment at 0FFAFC): the next ROM byte to read
elements rom_check_bytes u8           # the ROM bytes compute_crc16_alternate_bytes feeds its CRC: "mov.b [R2+], R0" (09FAF9, run from the 0FFAF9 mirror), "inc.b R2" (09FAFC); census ROM 100000-1000FF (the even pass) and 100001-17FFFD (the odd bytes)
local test_inputs 09D4D1,09D4D8 r10 text_entry input_test_texts  # "movea.w [R5](R3), R10" / "movea.w [R6](R3), R10": the input's entry in input_test_on_texts_* / input_test_off_texts_* (ROM tables of text record addresses), read with "mov.w [R10], R10" for print_text_list; census ROM 117091-117115
elements input_test_texts u32 rom     # an input_test_on_texts_* / input_test_off_texts_* entry: the address of the ON or OFF text record of one input line, "mov.w [R10], R10" (09D4DC) for print_text_list; census ROM 117091-117118 (117119 input_test_start_3_on_text ...)
local move_input_test_cursors 09D6D2,09D6EB,09D6F6,09D70F,09D71A,09D737,09D742,09D75F r20 cursor object  # R20 (current_object in the C): screen_sprite_block's block ("mov.h 1002[R25], R20"), then + 10 per cursor ("add.w #10, R20"): each trackball cursor is a 16-byte sprite header that queue_sprite_in_layer and build_sprite_list read as an object's first bytes (this function queues block + 10 the same way, 09D5F8), so E[R20] and C[R20] are that cursor's screen_x (kept in A5-132) and screen_y (kept between the limit tables); census RAM 207FAC-207FCE
local move_input_test_cursors 09D722 r10 bottom_limit input_test_cursor_limits  # input_test_cursor_bottom_limits + cursors_left * 2: the lowest y of that cursor; census ROM 117207-11720B
local move_input_test_cursors 09D74A r10 top_limit input_test_cursor_limits  # input_test_cursor_top_limits + cursors_left * 2: the highest y of that cursor; census ROM 1171FF-117203
elements input_test_cursor_limits s16  # input_test_cursor_top_limits or _bottom_limits (each cursor's screen_y limit): "movea.h 117205(R4), R10" / "movea.h 1171FD(R4), R10" (09D722, 09D74A), "cmp.h [R10], C[R20]; blt / bgt" (signed) and "mov.h [R10], C[R20]"; census ROM 1171FD-11720C (0000 00A8 0070 0038, 0000 00CD 0095 005D)
param draw_coin_credit_cell R10 cell_position text_cell_position  # (column, row) bytes of the cell in the table at 116F2D (chute 1) / 116F3F (chute 2); the next pair when it returns ("movz.bw [R10+], R5", "movz.bw [R10+], R6" at 09E6BE-09E6C1); census ROM 116F2D-116F50
param draw_blinking_test_cursor R10 cursor_char test_cursor_characters  # the cursor character's byte (115CEB '>'), replaced by the blank 115CED while test_cursor_blink is below 1 ("mov.b [R10], R1" 09ED08; test_cursor_arrow / test_cursor_space); census ROM 115CEB-115CED
local draw_blinking_test_cursor 09ECCE r10 cursor_char test_cursor_characters  # "movea.w 115CED, R10": test_cursor_space, the blank while test_cursor_blink is below 1
elements test_cursor_characters u8    # the character byte draw_blinking_test_cursor puts in the text layer with attribute 200: "mov.b [R10], R1" (09ED08); test_cursor_arrow 115CEB '>' (blink_test_cursor 09ECC0) or test_cursor_space 115CED ' ' (09ECCE, erase_test_cursor 09ED26); census ROM 115CEB-115CED
param erase_test_cursor R10 cursor_positions text_cell_position  # the menu's (column, row) pairs (115BA3, 115BBB, 115BCC, 115BD7, 115BE3): test_cursor's pair is read, then the blank drawn there
local erase_test_cursor 09ED17 r10 cursor_positions text_cell_position  # "add.w R11, R10": + test_cursor * 2, test_cursor's pair, read with "mov.b [R10+], 4A23[R25]" (column) and "mov.b [R10], 4A24[R25]" (row); census ROM 115BA3-115BE6
local set_test_cursor 09ED3E r10 cursor_entry text_cell_position  # test_cursor_positions + test_cursor * 2: column and row (negative = back to 0); census ROM 115BBB-115BE7
20CA1F  var   test_cursor_positions  u32 text_cell_position  # (column, row) pairs of the current menu, FF ends (115BA3, 115BBB, 115BCC, 115BD7, 115BE3)
local test_menu 09CAED r10 cursor_entry text_cell_position  # test_menu_cursor_positions + test_cursor * 2: column and row (negative = back to item 0); set_test_cursor's code inlined ("test.b [R10]", "mov.b [R10+], 4A23[R25]", "mov.b [R10], 4A24[R25]"): the C reads it with ROM8 already (not in the work list), the structure names the two bytes
local draw_coin_credit_table 09E606,09E624 r10 cell_position text_cell_position  # "movea.b 116F2D, R10" / "movea.b 116F3F, R10": chute_1/chute_2_credit_cell_positions, handed to draw_coin_credit_cell, which reads a pair and hands back the next (no access through it here)
param init_picture_colour_cache R15 colour_cache picture_colour_slot  # 16 slots of 3 bytes (next, previous, colour); "mov.w R15, R11" (099B29) walks them; 1[R15] is slot 0's previous (2D[R15], slot F's next, lies past the first slot: the C keeps it as an offset)
local init_picture_unpacker 099B08 r15 colour_cache picture_colour_slot  # "movea.b 3500[R25], R15": bitmap_draw_buffer, the first slot, handed back to draw_packed_picture for init_picture_colour_cache and unpack_picture_pixels (no access through it here)
param unpack_picture_pixels R15 colour_cache picture_colour_slot  # read_picture_colour's (only handed on: no access through it here)
param read_picture_colour R15 colour_cache picture_colour_slot  # 16 slots of 3 bytes: next, previous, colour (reached by slot x 3 with [R15](Rn) addressing, which the C leaves untyped: see the keep lines)
local fill_triangle 09B1A8 r19 span_cursor polygon_span  # bitmap_draw_buffer: fill_triangle_sorted writes a span record per row there
param fill_triangle_sorted R19 span_cursor polygon_span  # where the next span record goes (bitmap_draw_buffer); "mov.w R10, [R19+]; mov.h R12, [R19+]" (09B236-09B239)
param fill_flat_top_triangle R19 span_cursor polygon_span  # the next span record ("mov.w R10, [R19+]; mov.h R12, [R19+]" 09B2C3-09B2C6), from fill_triangle_sorted or fill_quad
local fill_span_list 09B312 r10 span_record polygon_span  # bitmap_draw_buffer, 6 bytes per record
local fill_quad 09B423 r19 span_cursor polygon_span  # 20B500 + 6 per row: bitmap address (u32) and length (u16) of each row's span
local fill_quad 09B48B r0 bottom_vertex quad_vertex  # three vertices on the top row: the bottom vertex's (x, y) among the saved ones on the stack
local fill_quad 09B4A7 r3 vertex_index quad_vertex  # "movea.w [SP](R3), R3": the bottom corner's other neighbour (corner (bottom + 3) & 3), its x read with "movs.hw [R3+], R0" and its y with "movs.hw [R3], R1"; the index of the 09B488 line becomes this address (one variable in the C)
local save_bitmap_rect entry r4 buffer saved_bitmap_rect  # where the rectangle goes: bits per row, rows, then the rows
local draw_saved_bitmap_rect entry r2 buffer saved_bitmap_rect  # save_bitmap_rect's buffer: row bits, rows, the rows
struct vblank_copy A                    # one entry of vblank_copies: queue_vblank_copy writes source, dest, halfwords (09A55D-09A563) at vblank_copies + vblank_copy_count x 10; run_vblank_copies reads them back (0FF5D6-0FF5DC) and moves 10 bytes on (structs_4.txt types its copy_entry with this structure)
field vblank_copy 00 source u32         # where the copy reads (queue_vblank_copy R10: cycle_palette's frame, show_nbg0_picture's 1B154)
field vblank_copy 04 dest u32           # where it writes (palette RAM); the top byte is how many copies run_vblank_copies makes, each going on where the last ended
field vblank_copy 08 halfwords u16      # halfwords per copy (queue_vblank_copy R12; run_vblank_copies' copy_halfwords)
struct sprite_frame_record 8 rom        # 4 words per frame (130000 data comment), records one after the other from sprite_frame_records: draw_queued_strings reads +0, +2, +4 and +6 of the character's record (08E956, 08E962, 08E983, 08E9A6; text_frames below gives it), build_sprite_list (09A27B, engine/sprites.c) the same of an object's (sprite_frame); census ROM 135F60-13800D here
field sprite_frame_record 00 width u16  # source width in pixels: entry word 1 = height << 8 | width / 4, and x scale for the entry's width (08E956-08E980)
field sprite_frame_record 02 height u16 # low byte: height in pixels (x scale: entry word 2's low byte); high byte: the address bits, entry word 2's high byte (08E962-08E97D; build_sprite_list and draw_queued_strings call the word height too)
field sprite_frame_record 04 bank_bits u16  # ORed into entry word 3 over the scaled width (08E983 "or.h [R12+], R10")
field sprite_frame_record 06 address u16    # entry word 6: the frame's address in its bank (08E9A6 "mov.h [R12], [R19+]")
struct string_record 8 rom              # the 4 words before the characters of a string queue_string_record queues (game_over_string 0025D0, you_failed_string 0025FA; unused_to_be_continued_string 0025E2): queue_string reads them in turn (08E8C3-08E8CC, census ROM 0025D0-002600), the characters follow (R4 = the record + 8)
field string_record 00 x u16            # screen x (58 GAME OVER, 50 YOU FAILED), copied to the queued string's x
field string_record 02 y u16            # screen y (68)
field string_record 04 palette u16      # sprite palette (8440)
field string_record 06 scale u16        # sprite scale (200: twice the size)
elements sprite_string_chars u8         # a string queued for sprites (queue_string_at's text, a string_record's characters after its 4 words): character codes, each indexing text_frames (0 there = a blank), 0 ends; draw_queued_strings reads it a byte at a time, "movz.bw [R5+], R6" (08E935) and "mov.b [R5+], R6" (08E9B0); census ROM 0025D8-0025E1 (GAME OVER), 002602-00260C (YOU FAILED) and the strings of game_modes, states, character_select, ranking
struct queued_string C                  # one entry of string_queue (20C802, 12 bytes each; string_count of them, at most 43): queue_string writes it at string_queue + string_count x 12 (08E8D4-08E8ED), draw_queued_strings reads it back the same way (08E919-08E932)
field queued_string 00 x s16            # screen x of the first character (draw_queued_strings: "movs.hw [R5+], R1" 08E926, + the advance per character)
field queued_string 02 y s16            # screen y ("movs.hw [R5+], R2" 08E929): each sprite entry's y word
field queued_string 04 palette u16      # each sprite entry's last word (08E92C)
field queued_string 06 scale u16        # 100 = 1:1: multiplies the frame sizes (08E92F "movz.hw [R5+], R4", then mulu)
field queued_string 08 text u32 sprite_string_chars # the characters, 0 ends (08E932 "mov.w [R5], R5"; census ROM: the strings of game_modes, states, character_select, ranking; 0 for the never written entries the ranking_initials scenario pokes string_count over)
param queue_string_record R0 record string_record       # 4 words (x, y, palette, scale), then the text: "GAME OVER" 0025D0, "YOU FAILED" 0025FA
local queue_string 08E8C0 r4 text string_record          # "mov.w R0, R4" (mode 0 only): the record; its 4 words are read through it (08E8C3-08E8CC), which leaves it at the characters
local queue_string 08E8DE r5 entry queued_string         # string_queue + string_count * 12 ("add.w R25, R5"): x, y, palette, scale written in turn, the text pointer after them
local queue_string 08E8E1,08E8E4,08E8E7 r5 entry         # the post-increments from x to y, palette and scale
local draw_queued_strings 08E923 r5 cursor queued_string  # string_queue + string_count * 12 ("add.w R25, R5" after "mul.w #C"): the entry read back, x to text (the param line R5 cursor names the rest)
08E9C9  table text_frames  u16 sprite_frame_record  # sprite_frame for each character code of a queued string; 0 = blank (skipped; the next character moves on by its width). Each is the low half of the character's record in sprite_frames' page: draw_queued_strings 08E93B "mov.h 8E9C9[PC](R6), R12" over R12 = sprite_frames (5F60 -> 135F60; census ROM 135F60-13800D)
struct placed_text 2 rom                # a text of the text layer with its place: print_text "mov.h [R10+], R11" (0995FB: 31F000 with it as the low half), then the string (print_text_here); a 0 position ends a list of them (print_text_list "test.h [R10]" 0995EA, erase_text_list 099616); census ROM 000644-117862 (title_copyright_text 000644, continue_text 0025C2, two_credits_one_play_text 09FF84, pause_text 09A65C, warning_screen_text 104BD2, the test mode's texts)
field placed_text 00 position u16       # the low half of the first cell's address in the text map (0x310000 | it: F000 + row x 80 + column x 2); print_text, erase_text (0995FB, 09962A), and the position words of the formatted texts (formatted_text_position_single 099686 "mov.h [R10+], R11": the same low half)
field placed_text 02 string u8[]        # the text_layer_string after it (attribute byte, characters, 0); a formatted text's attribute byte and runs
elements text_layer_string u8           # a string of the text layer: its attribute byte (the cells' high byte), then the characters (the cells' low bytes), 0 ends; print_text_lines' strings start the next row at a byte with bit 7 set. print_text_here "mov.b [R10+], R0" (0995FE: the attribute, then 099604, 09960E: the characters); census ROM 000646-11786A
elements formatted_text u8              # a formatted text after its position word: the attribute byte, then runs of characters that end in 0, each followed by a code byte (0 ends, 3/6/9 the next run at 1/2/3 cells a character, C/12/18/1E a new attribute byte, F/15/1B a new position word and attribute, 21/24 a skip: tilemap_skip / tilemap_long_skip); print_formatted_text and erase_formatted_text read it a byte at a time ([R10+]); never run (nothing calls them): no ROM data is known to be one
struct tilemap_skip 1 rom               # a skip of cells in a tilemap, one signed byte: the vram script's command 83 (vram_script_skip "movs.bw [R10+], R0" 0998C8; census: 549 in the scripts of assets/data) and a formatted text's code 21 (formatted_text_skip_single 0996D4 "movs.bw [R10+], R1" and the double, triple and erase ones)
field tilemap_skip 00 cells s8          # cells to move by: the write position + it x 2 ("movea.h [R11](R0), R11")
struct tilemap_long_skip 2 rom          # the same with a signed halfword: the vram script's command A7 (vram_script_skip_long "movs.hw [R10+], R0" 0998D0; none in the scripts of assets/data) and a formatted text's code 24 (formatted_text_skip_long_single 0996CB "movs.hw [R10+], R1", double, triple)
field tilemap_long_skip 00 cells s16    # cells to move by: the write position + it x 2
param print_text_list R10 text placed_text  # print_text records one after the other, a 0 halfword ends; past the last one at the end (census ROM 104BD2-11786B)
local print_text_list 0995EE r10 text placed_text # what print_text hands back ("movea.b 995EA[PC], [-SP]" makes print_text return to the test): the next record
param print_text R10 text placed_text       # the record: text map address 31F000 with the word as its low half (0x310000 | word), attribute byte, characters, 0; past it at the end (census ROM 000644-117862: title_copyright_text at 000644 too)
param print_text_here R10 text text_layer_string # attribute byte, characters, 0; past the 0 at the end (census ROM 000646-11786A)
param erase_text_list R10 text placed_text  # erase_text records one after the other, a 0 halfword ends (as print_text_list); never run, the records of print_text_list
local erase_text_list 09961A r10 text placed_text # erase_text's record cursor: each record in turn, a 0 halfword ends
param erase_text R10 text placed_text       # the print_text record whose cells it clears (census ROM 0025C2, continue_text)
param erase_text_here R10 text text_layer_string # attribute byte, characters, 0 (only the count of characters matters); handed back past the 0 (census ROM 00255B-11418F)
param print_text_lines R10 text text_layer_string # attribute byte, characters, 0; a character with bit 7 set starts the next row (census ROM 00CDA5-113FEA)
param erase_text_lines R10 text text_layer_string # as print_text_lines (census ROM 09FF2B-11402D)
elements font_rows_1bpp u8              # a 1-bit font: 8 row bytes per character, bit 7 first; expand_font "mov.b [R10+], R0" (0993C2), then add.b shifts the pixels out; census ROM 103476-10374D (text_font + 1340: 5B characters, load_text_font)
elements font_rows_2bpp u16             # a 2-bit font: 8 row halfwords per character, 2 bits a pixel, the top bits first; expand_font_2bpp "mov.h [R10+], R0" (09941B) and expand_font_2bpp_transparent (099462); never run (nothing calls either): no ROM data is known to be one
param expand_font R10 source font_rows_1bpp       # the 1-bit font rows (a byte per row), right after the copied font (load_text_font: the MOVCU's source end; census ROM 103476-10374D)
param expand_font_2bpp R10 source font_rows_2bpp  # the 2-bit rows (a halfword each); never run (nothing calls it): a font, as expand_font's
param expand_font_2bpp_transparent R10 source font_rows_2bpp # as expand_font_2bpp; never run (nothing calls it)
param formatted_text_position_single R10 text placed_text # the formatted text at its position word: past it and the attribute byte when the run starts
param formatted_text_attribute_single R10 text formatted_text # the formatted text after the position word: attribute byte, run, code
param formatted_text_single R10 text formatted_text # the run's characters, its 0 and the code byte; past them at the end
param formatted_text_skip_single R10 text tilemap_skip # at the s8 count of cells to skip; past it at the end
param formatted_text_skip_long_single R10 text tilemap_long_skip # at the s16 count of cells to skip; past it
param formatted_text_position_double R10 text placed_text # as formatted_text_position_single (code 15: width 2)
param formatted_text_attribute_double R10 text formatted_text # as formatted_text_attribute_single
param formatted_text_double R10 text formatted_text # a run at 2 cells per character, its 0 and the code byte
param formatted_text_skip_double R10 text tilemap_skip # at the s8; past it
param formatted_text_skip_long_double R10 text tilemap_long_skip # at the s16; past it
param formatted_text_position_triple R10 text placed_text # as formatted_text_position_single (code 1B: width 3)
param formatted_text_attribute_triple R10 text formatted_text # as formatted_text_attribute_single
param formatted_text_triple R10 text formatted_text # a run at 3 cells per character, its 0 and the code byte
param formatted_text_skip_triple R10 text tilemap_skip # at the s8; past it
param formatted_text_skip_long_triple R10 text tilemap_long_skip # at the s16; past it
param erase_formatted_text_position_single R10 text placed_text # at the position word, then past it
param erase_formatted_text_single R10 text formatted_text # only the count of characters matters, then the code byte
param erase_formatted_text_skip_single R10 text tilemap_skip # at the s8, then past it
param erase_formatted_text_position_double R10 text placed_text # at the position word, then past it
param erase_formatted_text_double R10 text formatted_text # a run erased at 2 cells per character, then the code byte
param erase_formatted_text_skip_double R10 text tilemap_skip # at the s8, then past it
param erase_formatted_text_position_triple R10 text placed_text # at the position word, then past it
param erase_formatted_text_triple R10 text formatted_text # a run erased at 3 cells per character, then the code byte
param erase_formatted_text_skip_triple R10 text tilemap_skip # at the s8, then past it
elements vram_script u8                 # a vram script read a byte at a time (tools/formats/scripts.py): 00-7F a vram_script_dictionary word, 80 + 3n a command, its operands after it (the vram_script_ records below), AA ends; run_vram_script "movz.bw [R10+], R0" (099870), erase_vram_script (09999D); census: the 654 vram script sections of assets/data (1352 scripts that come to their end)
struct vram_script_address 2 rom        # command 80's operand: vram_script_set_address "mov.h [R10+], R0" (0998BC), then "movea.h 300000(R0), R11"; census: 6005 in the scripts of assets/data
field vram_script_address 00 cell u16   # the cell's number in video RAM: the write position = 300000 + it x 2 (page x 200 + row x 20 + column)
struct vram_script_cell_run 3 rom       # the operands of the commands that write one cell several times: 86 fill, 89 count_up, 8C count_down, 9B fill_down, 9E fill_down_wide, A1 fill_up, A4 repeat: "movz.bw [R10+], R1" then "mov.h [R10+], R0" (0998D8-0998DB and the others; the erase_ ones read the count and add 2); census: 13103 fill, 25156 count_up, 2120 count_down, 3 repeat in the scripts of assets/data (none of the column fills)
field vram_script_cell_run 00 count u8  # cells to write (counted down by dbr R1)
field vram_script_cell_run 01 cell u16  # the first cell (tile number bits 0-12, flips bits 14-15); count_up adds 1 a cell, count_down takes 1
struct vram_script_byte_cells 2 rom     # command 8F's operands (bytes): vram_script_bytes "movz.bw [R10+], R1", "mov.b [R10+], R0" + shl.h #8 (099906-09990C), then count bytes (vram_script_low_bytes); census: 3324 in the scripts of assets/data
field vram_script_byte_cells 00 count u8  # the cells, one low byte each
field vram_script_byte_cells 01 high_byte u8  # every cell's high byte (tile number bits 8-12 and the flips)
elements vram_script_low_bytes u8      # command 8F's low bytes, after its count and high byte: one a cell, | the high byte << 8; vram_script_bytes "mov.b [R10+], R0" (09990F), the loop over them
struct vram_script_word_cells 1 rom     # the operands of commands 92 (words) and 95 (words_to_dictionary): vram_script_words "movz.bw [R10+], R1" (09991A), then count cells (vram_script_cells); census: 20 words, no words_to_dictionary in the scripts of assets/data
field vram_script_word_cells 00 count u8  # the cells
elements vram_script_cells u16         # the cells of commands 92 and 95 after their count, written one after the other (95 also stores them in vram_script_dictionary): vram_script_words "mov.h [R10+], [R11+]" (09991D), vram_script_words_to_dictionary "mov.h [R10+], R0" (099929)
struct vram_script_page_clear 3 rom     # command 98's operands (clear_pages): vram_script_clear_pages "movz.hw [R10+], R0" (09993A), "movz.bw [R10+], R0" (099945); census: 651 in the scripts of assets/data
field vram_script_page_clear 00 cell u16  # the first cell cleared: the write position = 300000 + it x 2
field vram_script_page_clear 02 pages u8  # pages of 200 cells set to 0 ("shl.w #9, R0", MOVCFU)
param run_vram_script R10 script vram_script        # the script: bytes 00-7F write vram_script_dictionary entries, 80 + 3n commands; past the end command (AA) at the end
local run_vram_script 099881 r10 script vram_script # what each command's routine hands back ("movea.b 99870[PC], [-SP]; jmp 9988E[PC](R0)"): past the command's data
param vram_script_set_address R10 script vram_script_address # the VRAM script cursor: the halfword cell address is read from it (+ 2); handed back
param vram_script_skip R10 script tilemap_skip      # read one signed byte, past it
param vram_script_skip_long R10 script tilemap_long_skip # read one signed word, past it
param vram_script_fill R10 script vram_script_cell_run # count byte, word; past them
param vram_script_count_up R10 script vram_script_cell_run # count byte, first word
param vram_script_count_down R10 script vram_script_cell_run # count byte, first word
local vram_script_bytes 099909 r10 script vram_script_low_bytes  # "mov.b [R10+], R0": past the count and the high byte, at the low bytes (the loop at 09990F reads one a cell)
local vram_script_words 09991A r10 script vram_script_cells  # "movz.bw [R10+], R1": past the count, at the cells (the loop at 09991D copies one a cell)
local vram_script_words_to_dictionary 099926 r10 script vram_script_cells  # "movz.bw [R10+], R1": past the count, at the cells (the loop at 099929 reads one a cell)
param vram_script_bytes R10 script vram_script_byte_cells # count byte, high byte, then count low bytes
param vram_script_words R10 script vram_script_word_cells # count byte, then count words
param vram_script_words_to_dictionary R10 script vram_script_word_cells # count byte, then count words
param vram_script_clear_pages R10 script vram_script_page_clear # address word, page count byte; past them
param vram_script_fill_down R10 script vram_script_cell_run # count byte, word
param vram_script_fill_up R10 script vram_script_cell_run # count byte, word
param vram_script_fill_down_wide R10 script vram_script_cell_run # count byte, word
param vram_script_repeat R10 script vram_script_cell_run # count byte, word
param erase_vram_script R10 script vram_script      # the vram script, as run_vram_script's: past each byte and command (never run: nothing calls it)
local erase_vram_script 0999AE r10 script vram_script # what each command's routine hands back (as run_vram_script's 099881)
param erase_vram_script_fill R10 script vram_script_cell_run # the command's count byte and data, skipped
param erase_vram_script_bytes R10 script vram_script_byte_cells # count byte, high byte, count bytes, skipped
param erase_vram_script_words R10 script vram_script_word_cells # count byte, count words, skipped
param erase_vram_script_fill_down R10 script vram_script_cell_run # count byte, word, skipped
param erase_vram_script_fill_up R10 script vram_script_cell_run # count byte, word, skipped
param erase_vram_script_fill_down_wide R10 script vram_script_cell_run # count byte, word, skipped
param erase_vram_script_repeat R10 script vram_script_cell_run # count byte, word, skipped
struct video_register_defaults_tail C rom  # video_register_defaults after the 16 bytes copy_blocks copies to 31FF00-0F (09A686): init_video_registers reads it as three longs, "mov.w [R10+], [R11+]" (0994DC, 0994F5, 0994F9); census ROM 09A686-09A691 (01F7 0200, 0000 00E0, 0010 0000)
field video_register_defaults_tail 00 text_backdrop_control u32  # 31FF5C-5F: video_text_control 01F7 (text map 31F000, characters 31C000) and video_backdrop_control 0200
field video_register_defaults_tail 04 bitmap_scroll u32  # 31FF88-8B: video_bitmap_scroll_x 0000 and video_bitmap_scroll_y 00E0
field video_register_defaults_tail 08 bitmap_control u32  # 31FF8C-8F: 0010 (the bitmap layer's palette: bitmap_palettes) and 0000
local init_video_registers 0994C8 r10 defaults_cursor video_register_defaults_tail # what copy_blocks hands back: video_register_defaults + 10, the 31FF5C long ("mov.w [R10+], [R11+]" 0994DC), then the two for 31FF88-8F (0994F5, 0994F9)
struct palette_load_list 2 rom          # the head of a load_palette_list list: "movz.hw [R12+], R13" (09A4BC), the palette_list_entry records follow; census ROM 104D60 (default_palette_list: 5), 1076AE (tile_bank_1_palette_list: 1), 1076BC (tile_bank_0_palette_list: 1)
field palette_load_list 00 entry_count u16  # the entries (dbr R13)
struct palette_list_entry 8 rom         # load_palette_list: after the list's count halfword, one per block of colours: source, palette RAM offset, copy mode (09A4C6-09A4CC); the long after an entry is the end of its colours, the next entry's source (09A4CF "mov.w [R12], R1": size = it - source); a copy mode 4 entry has one more long, its colours, which repeat_palette_block reads and skips (default_palette_list's second entry: counts 104F72, colours 104FCE)
field palette_list_entry 00 source u32 rom     # the colours (copy modes 0, 2, 6), or the halfword repeat counts (mode 4): copy_palettes_with_pen_0, copy_palette_block and repeat_palette_block read it in R10
field palette_list_entry 04 palette_offset u16  # where in palette RAM: 600000 | it (9000 the text palettes, 8000, 9800, 9200, 9400)
field palette_list_entry 06 copy_mode u16       # jsr 9A4DF[PC](R0): 0 copy_palettes_with_pen_0, 2 copy_palette_block, 4 repeat_palette_block, 6 copy_palettes_with_pen_0 with pen 0 = 6 (no list has it)
param load_palette_list R12 palette_list palette_load_list # halfword count, then 8-byte entries: source, palette RAM offset, kind (0/6 copy_palettes_with_pen_0, 2 copy_palette_block, 4 repeat_palette_block, which also reads a long after its entry); at the count
local load_palette_list 09A4BC,09A4D5 r12 palette_list palette_list_entry  # 09A4BC: past the count halfword, the first entry; 09A4D5: what the copy routine hands back, the next entry (repeat_palette_block also skips the long after a mode 4 entry)
struct palette_pens_1_15 1E rom         # a 16-colour palette without pen 0: pens 1-15 (copy_palettes_with_pen_0 writes pen 0 itself), read as 7 longs and a halfword, "mov.w [R10+], [R11+]" (09A4EC-09A504), "mov.h [R10+], [R11+]" (09A508); census ROM 104D92-104F71 (text_palettes: 16 of them)
field palette_pens_1_15 00 pens_1_2 u32
field palette_pens_1_15 04 pens_3_4 u32
field palette_pens_1_15 08 pens_5_6 u32
field palette_pens_1_15 0C pens_7_8 u32
field palette_pens_1_15 10 pens_9_10 u32
field palette_pens_1_15 14 pens_11_12 u32
field palette_pens_1_15 18 pens_13_14 u32
field palette_pens_1_15 1C pen_15 u16
param copy_palettes_with_pen_0 R10 source palette_pens_1_15 # 15 halfwords per palette, advanced (census ROM 104D92-104F70: text_palettes)
struct palette_repeat_source 4 rom      # the long after a copy mode 4 palette_list_entry: repeat_palette_block "mov.w [R12+], R10" (09A526); census ROM 104D72 (default_palette_list entry 1: 104FCE), 1076B8 (tile_bank_1_palette_list: 107750), 1076C6 (tile_bank_0_palette_list: 104FCE)
field palette_repeat_source 00 palettes u32 rom  # the 16-colour palettes the entry's counts repeat, one after the other (104FCE, 107750); its address is also where the counts end (load_palette_list's size)
elements palette_repeat_counts u16      # a copy mode 4 entry's source: one count per 16-colour palette, how many times it is written in turn; repeat_palette_block "movz.hw [R0+], R2" (09A52D); census ROM 104F72-104FCD (tile_bank_0_palette_repeats: 46 counts), 1076CA-10774F (67 counts)
param repeat_palette_block R12 palette_list palette_repeat_source # load_palette_list's cursor: the long after the entry (the palette data) is read and skipped (a mode 4 entry's extra long, not a palette_list_entry)
local repeat_palette_block 09A523 r0 repeat_counts palette_repeat_counts  # "mov.w R10, R0": the entry's source, the list of halfword repeat counts, one per palette (census ROM 104F72-104FCD, 1076CA-10774F)
local repeat_palette_block 09A52D r0 repeat_counts  # "movz.hw [R0+], R2": the next count
local queue_vblank_copy 09A559 r2 copy_entry vblank_copy  # vblank_copies + vblank_copy_count x 10 ("movea.h [R2](R1), R2", R1 = the count x 5): the new entry, written through it (09A55D-09A563); the post-increments leave it at the next one

# ---- the program ROM records of stages B-F and stage 8 (objects/stages_0b_0f.c, objects/stage_08.c: batch B3) ----
struct chase_step_with_limit C rom               # an entry of a chaser's path (tools/formats/stages.py _chase_path_recs, keys "at" then a coordinate): fire_front, rising_saw, lava_flow and the leading burning_boulder keep the next one in their field 80 (path) and step it with [R10+] (fire_front_read_script 08B038/08B045 then fire_front_load_record 08B0A2-08B0B4; rising_saw_read_script 08B5C5-08B5F5; lava_flow_read_script 087641-08767E; burning_boulder_update 0864E9-086510), path = the next one (+ C); an 8-byte "stop" (no base_speed, wait_timer) ends a path; census ROM 08BDAD-08BDF0 and 08BDF9-08BE24 (fire_front_paths), 08C00D-08C044, 08C04B-08C082 and 08C089-08C0CC (rising_saw_paths), 087B7F-087BAA (burning_boulder_speed_script), 087D21-087D40 (lava_flow_script); the geyser front (geyser_front_read_script 08C83A-08C84D) and the propeller machine (propeller_machine_read_script 08D44E-08D463) read the same entries
field chase_step_with_limit 00 position u16      # the scroll position that loads the entry: "cmp.h [R10+], 6435[R25]" + bl/bnl (unsigned; 08B038, 08B5C5, 087641, 0864E9); FFFF never
field chase_step_with_limit 02 limit s16         # the coordinate that loads it too, compared signed: fire_front x or y by direction (08B045, 0 = none), rising_saw z ("cmp.h 2[R10], 42[R20]; bgt" 08B5CD), lava_flow x (08764D + blt, 0 = none), burning_boulder y (0864F6 + blt)
field chase_step_with_limit 04 screen_threshold s16  # into the chaser's screen_threshold (84): "mov.h [R10+], 84[R20]"
field chase_step_with_limit 06 catch_up_factor s16   # into catch_up_factor (86); "test.h 2[R10]" (4 bytes in) + bp: a negative one ends the path (08B091, 08B5D8, 087657); fire_front_chase_step reads the loaded entry's (path - 6) for 0, the front stopping
field chase_step_with_limit 08 base_speed s16    # into base_speed (88)
field chase_step_with_limit 0A wait u16          # into wait_timer (90): frames before the next entry
struct biting_head_path_entry E rom         # an entry of a biting_head_paths path (tools/formats/stages.py _HEAD_ENTRY): biting_head_read_script reads the one biting_head.path (80) points at (08ABA3, 08ABB0 [R10+]), biting_head_load_record the rest from + 4 (08ABED, 08AC0C-08AC24), path = the next one (+ E); census ROM 08BE2D-08BE56 (stop 08BE57-08BE5E)
field biting_head_path_entry 00 position u16      # the scroll position that loads it: "cmp.h [R10+], 6435[R25]" (08ABA3, unsigned)
field biting_head_path_entry 02 limit s16         # x or y (by direction) that loads it, compared signed (08ABB0 "mov.h [R10+], R1"); 8000 = none
field biting_head_path_entry 04 screen_threshold s16  # into screen_threshold (84)
field biting_head_path_entry 06 catch_up_factor s16   # into catch_up_factor (86); negative ends the path ("test.h 2[R10]" 08ABED + bp)
field biting_head_path_entry 08 base_speed s16    # into base_speed (88)
field biting_head_path_entry 0A wait u16          # into wait_timer (90)
field biting_head_path_entry 0C direction u16     # into direction (24): "mov.h [R10+], 24[R20]" (08AC24); 0300 (-x)
struct bridge_fire_script_entry C rom       # an entry of bridge_fire_script (087CD9; tools/formats/stages.py _BRIDGE_FIRE_ENTRY, keys height then "at"): bridge_fire_read_script reads the one bridge_fire.path (80) points at (087373-0873B4 [R10+]), path = the next one (+ C); census ROM 087CD9-087D04
field bridge_fire_script_entry 00 height s16      # "cmp.h [R10+], R0; ble" (087373) with z_int - hitbox_height: the fire's top reaching it loads the entry
field bridge_fire_script_entry 02 position u16    # when not 0 ("test.h [R10]" 087378), the scroll position less hitbox_height that loads it too (087383 "cmp.h R0, 6435[R25]; bl", unsigned)
field bridge_fire_script_entry 04 screen_threshold s16  # into screen_threshold (84); negative: state 7 ("test.h [R10]; bp" 087398)
field bridge_fire_script_entry 06 catch_up_factor s16   # into catch_up_factor (86); negative ends: removed ("test.h 2[R10]" 08738D + bp)
field bridge_fire_script_entry 08 base_speed s16  # into base_speed (88)
field bridge_fire_script_entry 0A wait u16        # into wait_timer (90)
struct fire_front_path_start 8 rom          # the head of a fire_front_paths path (tools/formats/stages.py _FIRE_FRONT): fire_front_init reads it with [R10+] (08AEED-08AF11) and keeps what follows (the chase_step_with_limit records) in fire_front.path; census ROM 08BDA5-08BDAC, 08BDF1-08BDF8
field fire_front_path_start 00 x s16        # into x_int (4E)
field fire_front_path_start 02 y s16        # into y_int (52)
field fire_front_path_start 04 floor_z s16  # into floor_z_int (56): 1D8, 0
field fire_front_path_start 06 direction u16  # into direction (24): 0000
struct biting_head_path_start 4 rom         # the head of a biting_head_paths path (tools/formats/stages.py _BITING_HEAD): biting_head_init reads it with [R10+] (08A9EC, 08A9F6) and keeps what follows (biting_head_path_entry records) in biting_head.path; census ROM 08BE29-08BE2C
field biting_head_path_start 00 x s16       # into x_int (4E): 0841
field biting_head_path_start 02 y s16       # into y_int (52): 057A
struct rising_saw_path_start 6 rom          # the head of a rising_saw_paths path (tools/formats/stages.py _SAW): rising_saw_init reads it with [R12+] (08B3D2-08B3E6) and keeps what follows (chase_step_with_limit records) in rising_saw.path; census ROM 08C007-08C00C, 08C045-08C04A, 08C083-08C088
field rising_saw_path_start 00 x s16        # into x_int (4E): -3F0, -3AC, -360
field rising_saw_path_start 02 y s16        # into y_int (52): 509
field rising_saw_path_start 04 z s16        # into z_int (42): 1A0
struct brick_wall_brick C rom               # a brick of brick_wall_brick_records (tools/formats/stages.py brick lines): brick_wall_wait reads one per rock_debris_fly piece with [R12+] (08A8BF-08A8E0); the same six words as stage_00_rock_pieces, roller_wall_fragments and ram_target_debris (their data lines); census ROM 08BF03-08BFAA
field brick_wall_brick 00 sprite_flags u16  # "mov.h [R12+], 2[R11]": 1004, 0004 for the last two (no bits above FFF: they grow, rock_debris_fly)
field brick_wall_brick 02 offset_x u16      # "add.h [R12+], E[R11]": added to the wall's screen_x (0 to A0)
field brick_wall_brick 04 offset_y u16      # "add.h [R12+], C[R11]": added to its screen_y (-38 to 20)
field brick_wall_brick 06 speed_x u16       # into speed_x_fraction (34): -100 to -10 (12.4)
field brick_wall_brick 08 speed_y u16       # into speed_y_fraction (38): 8 to 80
field brick_wall_brick 0A look u16          # "movz.hw [R12+], R0; movea.b 8BED5[PC](R0), R10" (08A8E0): offset of its brick_look in brick_animations (0 or 10)
struct brick_look 2 rom                     # a brick's look in brick_animations (08BED5, +0 and +10; tools/formats/stages.py _BRICK_LOOK): the palette word, then a frame_animation (brick_wall_wait 08A8E9 "mov.h [R10+], 6[R11]", then "movea.b [R10], 48[R11]": the animation); census ROM 08BED5, 08BEE5
field brick_look 00 palette u16             # into sprite_palette (6): 10F0, 1100
struct brick_dust_puff 8 rom                # a puff of brick_wall_dust_records (tools/formats/stages.py _DUST): brick_wall_wait reads one per brick_dust_animate object with [R12+] (08A94B-08A962); census ROM 08BFAD-08BFD4
field brick_dust_puff 00 sprite_flags u16   # "mov.h [R12+], 2[R11]": 1000
field brick_dust_puff 02 offset_x u16       # added to the wall's screen_x (38 to A0)
field brick_dust_puff 04 offset_y u16       # added to its screen_y (-38 to 20)
field brick_dust_puff 06 delay u16          # into brick_dust.wait_timer (80): 0, 3, 9, 8, 4
struct biting_head_bite_step 4 rom          # biting_head_bite_step_records (08BE6B): the jaw's step, picked by bite_step x 4 ("movea.w [R10](R1), R10" 08ADEA) and read with [R10+]; census ROM 08BE6B-08BE82
field biting_head_bite_step 00 jaw_frame u16  # "mov.h [R10+], 1A[4[R20]]" (08ADEE): part 3's frame word in the part list
field biting_head_bite_step 02 hold u8      # "mov.b [R10+], 49[R20]" (08ADF4): bite_timer, the frames it holds (4, 4, 40, 1, 1, 40)
struct grate_step_debris_list 2 rom         # grate_step_debris (08C0E9): the count, then the grate_debris_piece records; spawn_grate_step_debris "cmp.h [R10], 6900[R25]; blt" (08BB8B) and "mov.h [R10+], 70EE[R25]" (08BB94); census ROM 08C0E9 (5)
field grate_step_debris_list 00 count s16   # how many pieces: compared signed with free_object_stack, into grate_debris_left
struct burning_boulder_record 8 rom         # a record of burning_boulder_records (087B61; tools/formats/stages.py _BURNING_BOULDER): burning_boulder_start reads the one of its sprite_frame with [R10+] (08627C-08628C); census ROM 087B61-087B78
field burning_boulder_record 00 x s16       # into x_int (4E): -7D, -ED, -7D
field burning_boulder_record 02 start_delay s16  # into burning_boulder.start_delay (98): 3C
field burning_boulder_record 04 path u32    # into burning_boulder.path (80, "mov.w [R10+], 80[R20]" 08628C): burning_boulder_speed_script (087B7F) for kind 0, FFFFFFFF (a follower) for 1 and 2
struct burning_boulder_flame_offset 6 rom   # a record of burning_boulder_flame_layout (087AA3; tools/formats/stages.py _FLAME_LAYOUT): burning_boulder_flame_start reads the one of its kind with [R10+] (086847-086853); census ROM 087AA3-087ABA
field burning_boulder_flame_offset 00 dx s16  # into burning_boulder_flame.offset_x (80): 20, -20, -40, -50
field burning_boulder_flame_offset 02 dy s16  # into offset_y (82): -C0, -C0, -A8, -90
field burning_boulder_flame_offset 04 animation u16  # "movz.hw [R10+], R11; add.w #87ABB, R11" (086853): offset into burning_boulder_flame_animations (0, 24)
struct falling_ember_speed 4 rom            # a record of falling_ember_speeds (087B11; tools/formats/stages.py _EMBER_SPEEDS): ember_emitter_move picks one at random ("movea.w 87B11[PC](R9), R10", R9 = random & 3) and reads it with [R10+] (086A5C, 086A66); census ROM 087B11-087B20
field falling_ember_speed 00 speed_z s16    # "movs.hw [R10+], R0; sha.w #8": into the ember's speed_z (3C): C00, C00, A00, 800
field falling_ember_speed 02 speed_y s16    # the same into speed_y (38): 600, 400, 355, 2AA
struct scene_carrier_record 14 rom          # a record of scene_carrier_records (087BD9 + its offset by sprite_frame; tools/formats/stages.py _CARRIER): scene_carrier_start reads it with [R12+] (086B7F-086BAC); census ROM 087BE1-087C30
field scene_carrier_record 00 x s16         # into x_int (4E)
field scene_carrier_record 02 y s16         # into y_int (52)
field scene_carrier_record 04 z s16         # into z_int (42)
field scene_carrier_record 06 speed_x s32   # "mov.w [R12+], 34[R20]": 16.16 a frame
field scene_carrier_record 0A speed_y s32   # into speed_y (38)
field scene_carrier_record 0E speed_z s32   # into speed_z (3C)
field scene_carrier_record 12 end_position u16  # into scene_carrier.end_position (80): 4D00, 6100
struct lava_steam_puff_offset 4 rom         # an entry of lava_steam_puff_offsets (087D51; tools/formats points): spawn_lava_steam reads one per puff with [R13+] (0879FB, 087A00); census ROM 087D51-087D60
field lava_steam_puff_offset 00 dx s16      # into the puff's anchor_x (14), from the lava flow's x: -8, -10, -8, -30
field lava_steam_puff_offset 02 dy s16      # into anchor_y (16): 38, 78, A8, 10
struct group_slot_trigger_record 6 rom      # an entry of group_slot_triggers (093539; tools/formats/stages.py): group_slot_trigger_update reads the one group_slot_trigger.next_trigger (80) points at (091F68-091F93 [R10+]) and moves on by 6; census ROM 093539-093558
field group_slot_trigger_record 00 position u16  # "cmp.h [R10+], 6435[R25]; bl" (091F68, unsigned): the scroll position that fires it
field group_slot_trigger_record 02 slot s16      # "test.h [R10]; bp" (091F70): negative ends (8000: goal_unlocked first, "cmp.h #8000, [R10]"); else "movz.hw [R10+], R0" indexes group_slots
field group_slot_trigger_record 04 delay u16     # "mov.h [R10+], 1F00[R25](R0)" (091F93): group_slots[slot], then | 8000

# ======== pieces of code the C writes as functions of their own (jumped to from several places) ========
0884B0  func  icicle_dropper_choose_speed       # the speed by the direction (dir >> 8): the base speed while the dropper is within its screen threshold, else catching up (icicle_dropper_catch_up); after the holding-back checks
08952E  func  tornado_choose_speed              # the same for the tornado: catching up while its scroll offset (C) is negative, else the base speed or catching up by the direction and the screen threshold
08AC93  func  biting_head_choose_speed          # the same for the biting head

# ======== parameters of the functions taking the gotos out made (game modes, pieces of handlers and states, the flood fill's and the line drawing's pieces) ========
param event_12_stage_frame R1 pressed  # as run_game_mode's: only handed back to run_game_mode when no player is left in play (game mode 7)
param event_12_stage_frame R2 position  # handed back to run_game_mode, or to update_scroll (its position) and the frame's calls
param event_12_stage_frame R3 in_play_count  # update_player_summary's R3 (players in play, low byte), handed back to run_game_mode or to update_scroll (its speed) and the frame
param event_12_stage_frame R5 next_scroll_position  # as run_game_mode's: only handed back to run_game_mode
param event_12_stage_frame R11 object  # update_player_summary's player_object, then the frame's (update_scroll's dest, find_nearby_floor_blocks' right, the spawners' new objects)
param event_12_stage_frame R4 speed_value  # update_scroll's speed_value, then the frame's (update_restart_point's scroll_position, check_hits, check_platforms' sag); handed back
param event_12_stage_frame R8 count  # update_player_summary's dbr count, then the frame's counters and check_platforms' load_count; handed back
param event_12_stage_frame R9 cycle  # update_scroll / update_stage_tile_layers (the palette cycle state), spawn_walls, check_platforms; handed back
param event_12_stage_frame R10 list_cursor  # update_player_summary's cursor; out: check_platforms' platform (run_game_mode's after a game over)
param event_19_check_rope_bridge R2 position  # game_mode_event_19's R2, handed to update_scroll (its position) and the frame's calls
param event_19_check_rope_bridge R3 in_play_count  # game_mode_event_19's update_player_summary result (players in play, low byte), handed to update_scroll (its speed)
param event_19_check_rope_bridge R11 object  # game_mode_event_19 passes its player block cursor (208A00 after its loop, or the block of the player past x 17C); only handed to the frame (update_scroll's dest, the spawners' objects)
param event_19_check_rope_bridge R4 speed_value  # update_scroll's speed_value, then the frame's; handed back
param event_19_check_rope_bridge R8 count  # game_mode_event_19's players_left (its dbr counter), then the frame's counters; handed back
param event_19_check_rope_bridge R9 cycle  # update_scroll / update_stage_tile_layers (the palette cycle state), spawn_walls, check_platforms; handed back
param event_19_check_rope_bridge R10 list_cursor  # out only: check_platforms' platform
param game_mode_character_select R2 position  # start_stage_view's direction_y on the first frame, then spawn_objects_by_type (player_index) and check_hits (distance); no update_scroll in this mode
param game_mode_character_select R3 speed  # start_stage_view's direction_x on the first frame, then spawn_objects_by_type's first
param game_mode_character_select R11 object  # load_stage_palette's dest on the first frame, then the spawners' new objects
param game_mode_character_select R4 speed_value  # start_stage_view's count on the first frame, update_restart_point (scroll_position, low half), spawn_objects_by_type, check_hits, check_platforms; handed back
param game_mode_character_select R8 count  # spawn_objects_by_type's object_type, check_hits, check_platforms' load_count; handed back
param game_mode_character_select R9 cycle  # load_stage_palette's palette_record on the first frame, check_platforms' passes_left; handed back
param game_mode_character_select R10 list_cursor  # out only: check_platforms' platform
param game_mode_continue R2 position  # only handed to check_hits (its distance)
param game_mode_continue R4 speed_value  # check_hits' edge_or_centre_y, check_platforms' sag; handed back (low half)
param game_mode_continue R8 count  # check_hits' centre_offset, check_platforms' load_count; handed back
param game_mode_continue R9 cycle  # check_platforms' passes_left; handed back
param game_mode_continue R10 list_cursor  # out only: check_platforms' platform, or erase_text_here's text cursor (string record 25C4) when the count runs out or a start continues
param game_mode_event_09 R3 speed  # only handed to update_scroll (its speed)
param game_mode_event_09 R11 object  # handed to update_scroll and update_stage_tile_layers (their dest)
param game_mode_event_09 R4 speed_value  # update_scroll's speed_value, update_stage_tile_layers' palette_count, find_nearby_floor_blocks' bounds_offset; handed back
param game_mode_event_09 R8 count  # update_scroll, update_stage_tile_layers (row_skip), find_nearby_floor_blocks (corners_left); handed back
param game_mode_event_09 R9 cycle  # update_scroll / update_stage_tile_layers (the palette cycle state); handed back
param game_mode_event_09 R10 list_cursor  # update_scroll's end_script, update_stage_tile_layers' block_cursor, continue_to_next_stage_slot's link_record, find_nearby_floor_blocks' left; handed back
param game_mode_event_0A R1 pressed  # as run_game_mode's: only handed back to run_game_mode (game mode 7 when no player is in play)
param game_mode_event_0A R2 position  # handed to update_stage_05_event (its position) or back to run_game_mode
param game_mode_event_0A R3 speed  # handed to update_stage_05_event (its speed) or back to run_game_mode
param game_mode_event_0A R5 next_scroll_position  # as run_game_mode's: only handed back to run_game_mode
param game_mode_event_0A R11 object  # handed to update_stage_05_event (its object) or back to run_game_mode
param game_mode_event_0A R4 speed_value  # update_stage_05_event's R4 (or run_game_mode's), handed back
param game_mode_event_0A R8 count  # update_stage_05_event's count (or run_game_mode's), handed back
param game_mode_event_0A R9 cycle  # update_stage_05_event's R9 (or run_game_mode's), handed back
param game_mode_event_0A R10 list_cursor  # update_stage_05_event's R10 (or run_game_mode's), handed back
param game_mode_event_0C R2 position  # event_0c_start's block_offset (steps 0/4), event_0c_first_quake's write_index_address (step 1), event_0c_second_quake's part_size (step 2), then update_scroll's position and the frame's
param game_mode_event_0C R3 speed  # event_0c_start's slope_height (steps 0/4), then update_scroll's speed
param game_mode_event_0C R11 object  # event_0c_start's last roller, event_0c_second_quake's wall, then update_scroll / update_stage_tile_layers (dest)
param game_mode_event_0C R4 speed_value  # update_scroll's speed_value, update_stage_tile_layers, update_restart_point, check_hits, check_platforms; handed back
param game_mode_event_0C R8 count  # event_0c_second_quake's point_y (step 2), then update_scroll / update_stage_tile_layers, check_hits, check_platforms; handed back
param game_mode_event_0C R9 cycle  # event_0c_second_quake's z (step 2), then update_scroll / update_stage_tile_layers (the palette cycle state), check_platforms; handed back
param game_mode_event_0C R10 list_cursor  # out only: check_platforms' platform
param game_mode_event_0D R2 position  # handed to update_scroll (its position) and the frame's calls
param game_mode_event_0D R3 speed  # find_nearby_floor_blocks' floor_block (it runs first here), then update_scroll's speed
param game_mode_event_0D R11 object  # find_nearby_floor_blocks' right, update_scroll's dest, the spawners' new objects
param game_mode_event_0D R4 speed_value  # find_nearby_floor_blocks' bounds_offset, update_scroll's speed_value, then the frame's; handed back
param game_mode_event_0D R8 count  # find_nearby_floor_blocks' corners_left, then the frame's counters, check_platforms' load_count; handed back
param game_mode_event_0D R9 cycle  # update_scroll / update_stage_tile_layers (the palette cycle state), spawn_walls, check_platforms; handed back
param game_mode_event_0D R10 list_cursor  # out only: check_platforms' platform
param game_mode_event_0F R2 position  # handed to update_scroll (its position) and the frame's calls
param game_mode_event_0F R3 speed  # handed to update_scroll (its speed)
param game_mode_event_0F R11 object  # event_0f_show_messages' box (step 2), then update_scroll / update_stage_tile_layers (dest), spawn_walls
param game_mode_event_0F R4 speed_value  # update_scroll's speed_value, then update_stage_tile_layers, update_restart_point, check_hits, check_platforms; handed back
param game_mode_event_0F R8 count  # event_0f_show_messages' frames (step 2), then the frame's; handed back
param game_mode_event_0F R9 cycle  # update_scroll / update_stage_tile_layers (the palette cycle state), spawn_walls, check_platforms; handed back
param game_mode_event_0F R10 list_cursor  # out only: check_platforms' platform
param game_mode_event_10 R3 random_value  # only handed to update_stage_06_background (its random_value: next_random's R3; the incoming value is not read)
param game_mode_event_10 R11 object  # spawn_objects_by_handler's object, spawn_stage_06_lightning's bolt (steps 2-3), handed to update_stage_06_background (its dest)
param game_mode_event_10 R4 rolls_left  # update_stage_06_background's R4, handed back
param game_mode_event_10 R9 words_left  # update_stage_06_background's R9 (next_random's), handed back
param game_mode_event_10 R10 list_cursor  # spawn_objects_by_handler's record (steps 0-1), spawn_stage_06_lightning's record (steps 2-3), then update_stage_06_background's source; handed back
param game_mode_event_12 R1 pressed  # as run_game_mode's: handed to event_12_stage_frame or back to run_game_mode (with spawn_sign's depth once the sign is spawned)
param game_mode_event_12 R2 position  # handed to event_12_stage_frame or back to run_game_mode
param game_mode_event_12 R3 done_count  # from scroll 80B1, low byte ("mov.b #0, R3" 0943F9, "inc.b R3" 094415): the players not in play or with drift_done set; 3 spawns the sign, ends the fast scroll and goes to game mode 3, else handed to event_12_stage_frame
param game_mode_event_12 R5 next_scroll_position  # as run_game_mode's: handed to event_12_stage_frame or back to run_game_mode (spawn_sign's x, 644, is another variable)
param game_mode_event_12 R11 object  # handed to event_12_stage_frame; from scroll 80B1 the loop's player block cursor (PLAYER_BLOCK(1) + 200 per player), then spawn_sign's sign
param game_mode_event_12 R4 speed_value  # handed to event_12_stage_frame or run_game_mode, which hand it back
param game_mode_event_12 R8 count  # the loop's dbr counter over the 3 players, else event_12_stage_frame's / run_game_mode's; handed back
param game_mode_event_12 R9 cycle  # handed to event_12_stage_frame or run_game_mode, which hand it back
param game_mode_event_12 R10 list_cursor  # the loop's player_status cursor ("cmp.b #FF, [R10+]" 094409), else event_12_stage_frame's / run_game_mode's; handed back
param game_mode_event_13 R1 pressed  # as run_game_mode's: handed to event_12_stage_frame or run_game_mode (step 2); steps 0-1 hand update_screen_wipe's clip registers instead
param game_mode_event_13 R2 position  # handed to event_12_stage_frame or run_game_mode (step 2)
param game_mode_event_13 R3 in_play_count  # handed to event_12_stage_frame (its in_play_count) or run_game_mode (step 2)
param game_mode_event_13 R5 edges_done  # update_screen_wipe's edges_done (steps 0-1), handed to event_12_stage_frame; the incoming value in step 2
param game_mode_event_13 R11 object  # handed to event_12_stage_frame or run_game_mode
param game_mode_event_13 R4 speed_value  # handed to event_12_stage_frame or run_game_mode, which hand it back
param game_mode_event_13 R8 count  # handed to event_12_stage_frame or run_game_mode, which hand it back
param game_mode_event_13 R9 cycle  # handed to event_12_stage_frame or run_game_mode, which hand it back
param game_mode_event_13 R10 list_cursor  # handed to event_12_stage_frame or run_game_mode, which hand it back
param game_mode_event_14 R1 pressed  # as run_game_mode's: spawn_sign's depth when the sign spawns (scene timer 3C); only handed back to run_game_mode
param game_mode_event_14 R2 position  # handed to update_scroll (its position) and the frame, or back to run_game_mode
param game_mode_event_14 R3 unbounced_count  # step 1, low byte ("mov.b #0, R3" 094263, "inc.b R3" 09428E): the playing players in state 25 (floor 8 sequence) with sequence_bounced clear; 0 (someone playing, 2 free objects) spawns the icicle dropper and Eggman's speech; then update_player_summary's in_play_count, handed to update_scroll
param game_mode_event_14 R5 x  # spawn_sign's x, FDEF, at scene timer 3C ("mov.h #FDEF, R5" 0942FF); else the incoming value; only handed back to run_game_mode
param game_mode_event_14 R11 object  # step 1: the player_status cursor, then the icicle dropper and speech objects (allocate_object_outside_loop); spawn_sign's sign; update_player_summary's player_object; the frame's
param game_mode_event_14 R4 speed_value  # update_scroll's speed_value, then the frame's; handed back
param game_mode_event_14 R8 count  # step 1's dbr counter (3 players), spawn_sign's z (28), update_player_summary's, the frame's; handed back
param game_mode_event_14 R9 cycle  # update_scroll / update_stage_tile_layers (the palette cycle state), spawn_walls, check_platforms; handed back
param game_mode_event_14 R10 list_cursor  # step 1: the player block cursor (PLAYER_BLOCK(1) + 200 per player); update_player_summary's cursor; out: check_platforms' platform
param game_mode_event_15 R1 pressed  # as run_game_mode's: only handed back to run_game_mode (no player in play, or check_stage_clear changed game_mode)
param game_mode_event_15 R2 position  # handed to update_scroll (its position) and the frame, or back to run_game_mode
param game_mode_event_15 R3 speed  # handed to update_scroll (its speed), or back to run_game_mode
param game_mode_event_15 R5 next_scroll_position  # as run_game_mode's: only handed back to run_game_mode
param game_mode_event_15 R11 object  # check_stage_clear's status cursor, then the frame's (update_scroll's dest, the spawners' new objects)
param game_mode_event_15 R4 speed_value  # update_scroll's speed_value, then the frame's; handed back
param game_mode_event_15 R8 count  # check_stage_clear's dbr count, then the frame's counters; handed back
param game_mode_event_15 R9 cycle  # update_scroll / update_stage_tile_layers (the palette cycle state), spawn_walls, check_platforms; handed back
param game_mode_event_15 R10 list_cursor  # check_stage_clear's finished_flag cursor; out: check_platforms' platform
param game_mode_event_16 R1 box_edge  # handed back to run_game_mode when no player is in play; else find_nearby_floor_blocks (corner_y), the spawners, check_zones / check_hits (box_edge), check_walls
param game_mode_event_16 R2 position  # the spawners, check_hits (distance); no update_scroll in this mode
param game_mode_event_16 R3 in_play_count  # update_player_summary's R3 (players in play, low byte), then find_nearby_floor_blocks, spawn_objects_by_type
param game_mode_event_16 R5 next_scroll_position  # as run_game_mode's: only handed back to run_game_mode
param game_mode_event_16 R11 object  # update_player_summary's player_object, find_nearby_floor_blocks' right, the spawners' new objects
param game_mode_event_16 R4 speed_value  # find_nearby_floor_blocks, update_restart_point (scroll_position, low half), spawn_objects_by_type, check_hits, check_platforms; handed back
param game_mode_event_16 R8 count  # update_player_summary's, then the frame's counters; handed back
param game_mode_event_16 R9 cycle  # spawn_walls' z, check_platforms' passes_left; handed back
param game_mode_event_16 R10 list_cursor  # update_player_summary's cursor; out: check_platforms' platform
param game_mode_event_18 R2 position  # handed to update_ending (its position)
param game_mode_event_18 R3 speed  # handed to update_ending (its speed)
param game_mode_event_18 R11 object  # handed to update_ending (its object)
param game_mode_event_18 R4 speed_value  # update_ending's R4, handed back
param game_mode_event_18 R8 count  # update_ending's R8, handed back
param game_mode_event_18 R9 cycle  # update_ending's R9, handed back
param game_mode_event_18 R10 list_cursor  # out only: update_ending's platform (check_platforms')
param game_mode_event_19 R1 pressed  # as run_game_mode's: only handed back to run_game_mode (game mode 7 when no player is in play)
param game_mode_event_19 R2 position  # handed to event_19_check_rope_bridge (its position) or back to run_game_mode
param game_mode_event_19 R3 in_play_count  # update_player_summary's R3 (players in play, low byte), handed to event_19_check_rope_bridge or back to run_game_mode
param game_mode_event_19 R5 next_scroll_position  # as run_game_mode's: only handed back to run_game_mode
param game_mode_event_19 R11 object  # update_player_summary's player_object, handed back to run_game_mode (event_19_check_rope_bridge gets the loop's player block instead)
param game_mode_event_19 R4 speed_value  # handed to event_19_check_rope_bridge or run_game_mode, which hand it back
param game_mode_event_19 R8 count  # update_player_summary's; out: event_19_check_rope_bridge's (the player loop's players_left goes in)
param game_mode_event_19 R9 cycle  # handed to event_19_check_rope_bridge or run_game_mode, which hand it back
param game_mode_event_19 R10 list_cursor  # update_player_summary's cursor; out: event_19_check_rope_bridge's (check_platforms' platform)
param game_mode_event_1A R1 pressed  # as run_game_mode's: only handed back to run_game_mode
param game_mode_event_1A R2 position  # handed to update_scroll (its position) and the frame, or back to run_game_mode
param game_mode_event_1A R3 in_play_count  # update_player_summary's R3 (players in play, low byte), handed to update_scroll (its speed) or back to run_game_mode
param game_mode_event_1A R5 next_scroll_position  # as run_game_mode's: only handed back to run_game_mode
param game_mode_event_1A R11 object  # event_1a_scroll_script_step_1 on the first frame (update_player_summary replaces it), update_player_summary's player_object; the frame gets scroll_script in R11 instead
param game_mode_event_1A R4 speed_value  # update_scroll's speed_value, then the frame's; handed back
param game_mode_event_1A R8 count  # update_player_summary's, then the frame's counters; handed back
param game_mode_event_1A R9 cycle  # update_scroll / update_stage_tile_layers (the palette cycle state), spawn_walls, check_platforms; handed back
param game_mode_event_1A R10 list_cursor  # update_player_summary's cursor; out: check_platforms' platform
param game_mode_event_1C R1 pressed  # as run_game_mode's: only handed back to run_game_mode
param game_mode_event_1C R2 position  # handed to update_scroll (its position) and the frame, or back to run_game_mode
param game_mode_event_1C R3 in_play_count  # update_player_summary's R3 (players in play, low byte), handed to update_scroll (its speed) or back to run_game_mode
param game_mode_event_1C R5 next_scroll_position  # as run_game_mode's: only handed back to run_game_mode
param game_mode_event_1C R11 object  # update_player_summary's player_object, then the frame's (update_scroll's dest, the spawners' new objects)
param game_mode_event_1C R4 speed_value  # update_scroll's speed_value, then the frame's; handed back
param game_mode_event_1C R8 count  # update_player_summary's, then the frame's counters; handed back
param game_mode_event_1C R9 cycle  # update_scroll / update_stage_tile_layers (the palette cycle state), spawn_walls, check_platforms; handed back
param game_mode_event_1C R10 list_cursor  # update_player_summary's cursor; out: check_platforms' platform
param game_mode_event_1F R1 pressed  # as run_game_mode's: only handed back to run_game_mode
param game_mode_event_1F R2 position  # start_escape_countdown's free_stack on the first frame, then update_scroll (its position) and the frame
param game_mode_event_1F R3 in_play_count  # update_player_summary's R3 (players in play, low byte), handed to update_scroll (its speed) or back to run_game_mode
param game_mode_event_1F R5 next_scroll_position  # as run_game_mode's: only handed back to run_game_mode
param game_mode_event_1F R11 object  # start_escape_countdown's digit, update_player_summary's player_object, check_stage_clear's status cursor, the frame's (check_hits' previous, check_platforms' load_list), then start_escape_alarm_flash's flash
param game_mode_event_1F R4 speed_value  # update_scroll's speed_value, then the frame's; handed back
param game_mode_event_1F R8 count  # start_escape_countdown's, update_player_summary's, check_stage_clear's, the frame's, then the stage exit loop's (3 players); handed back
param game_mode_event_1F R9 cycle  # update_scroll / update_stage_tile_layers (the palette cycle state), spawn_walls, check_platforms; handed back
param game_mode_event_1F R10 list_cursor  # update_player_summary's cursor, check_stage_clear's finished_flag; out: check_platforms' platform, which the stage exit check replaces with players_finished (finished_flags)
param game_mode_game_over R1 box_edge  # queue_sound's R1 on the first frame, queue_string_record's y (not used), then check_zones / check_hits (box_edge) and check_walls
param game_mode_game_over R2 position  # queue_sound's write_index_address on the first frame, queue_string_record's palette, check_hits' distance
param game_mode_game_over R3 scale  # only handed to queue_string_record (its scale: not used, the record's replaces it); low half
param game_mode_game_over R4 speed_value  # queue_string_record's text, check_hits, check_platforms' sag; handed back
param game_mode_game_over R8 count  # check_hits, check_platforms' load_count, or game_mode_start_game's count when a start begins a new game; handed back
param game_mode_game_over R9 cycle  # check_platforms' passes_left; handed back
param game_mode_game_over R10 list_cursor  # out only: check_platforms' platform, or game_mode_start_game's handler cursor
param game_mode_intro R2 position  # first frame: move_camera_to_start_section's y, play_sound's write_index_address; then update_scroll's position and the frame
param game_mode_intro R3 speed  # first frame: move_camera_to_start_section's section_offset, find_nearby_floor_blocks' floor_block; then update_scroll's speed
param game_mode_intro R11 object  # first frame: load_stage_palette's dest (60B800); then update_scroll's dest, the spawners' new objects
param game_mode_intro R4 speed_value  # first frame: start_stage_view's count, find_nearby_floor_blocks' bounds_offset; then update_scroll's speed_value and the frame's; handed back
param game_mode_intro R8 count  # first frame: find_nearby_floor_blocks' corners_left; then the frame's counters; handed back
param game_mode_intro R9 cycle  # first frame: load_stage_palette's palette_record; then update_scroll / update_stage_tile_layers (the palette cycle state), check_platforms; handed back
param game_mode_intro R10 list_cursor  # out only: intro_status_cursor (walks player_2_status, player_3_status for their buttons)
param game_mode_play_stage R1 pressed  # as run_game_mode's: only handed back to run_game_mode when the mode changes before the frame (no player in play, check_stage_clear); the frame's R1 is another value
param game_mode_play_stage R2 position  # handed back to run_game_mode, or to update_scroll (its position) and the frame's calls
param game_mode_play_stage R3 in_play_count  # update_player_summary's R3 (players in play, low byte), handed back to run_game_mode when the mode changes before the frame; the frame's speed is update_grey_screen's count
param game_mode_play_stage R5 next_scroll_position  # handed back to run_game_mode, or to update_scroll (its next_scroll_position) and the frame's calls (update_stage_tile_layers' stage_number...), then to run_game_mode after a stage event
param game_mode_play_stage R11 object  # update_player_summary's player_object, check_stage_clear's status cursor, then the frame's (update_scroll's dest, find_nearby_floor_blocks' right, the spawners' new objects)
param game_mode_play_stage R4 speed_value  # update_scroll's speed_value, then update_stage_tile_layers, find_nearby_floor_blocks, update_restart_point (scroll_position, low half), spawn_objects_by_type, check_hits, check_platforms (sag); handed back
param game_mode_play_stage R8 count  # update_player_summary's and check_stage_clear's dbr counts, then the frame's (corners_left, spawn_object_groups' slot search, check_platforms' load_count); handed back
param game_mode_play_stage R9 cycle  # update_scroll (player 3's object in scroll modes 7/8), update_stage_tile_layers (the palette cycle state the stage's palette function ran), spawn_walls (z), check_platforms (passes_left); handed back
param game_mode_play_stage R10 list_cursor  # update_player_summary's cursor, check_stage_clear's finished_flag; out: run_stage_events' event (stage_event_cursor), or run_game_mode's after a mode change
param game_mode_screen_wipe R2 position  # handed to update_scroll (its position) and the frame's calls
param game_mode_screen_wipe R3 speed  # handed to update_scroll (its speed) and the frame's calls
param game_mode_screen_wipe R5 edges_done  # through update_scroll (next_scroll_position) and the frame, then update_screen_wipe's edges_done: game mode 3 once its low 4 bits are F ("and.b #F, R5" 000D38)
param game_mode_screen_wipe R11 object  # update_scroll's dest, then the frame's (the spawners' new objects)
param game_mode_screen_wipe R4 speed_value  # update_scroll's speed_value, then the frame's; handed back
param game_mode_screen_wipe R8 count  # the frame's counters, check_platforms' load_count; handed back
param game_mode_screen_wipe R9 cycle  # update_scroll / update_stage_tile_layers (the palette cycle state), spawn_walls, check_platforms; handed back
param game_mode_screen_wipe R10 list_cursor  # out only: check_platforms' platform
param game_mode_stage_change_fade R1 delta  # update_fade's R1 (fade_speed or -fade_speed at the fade's first step, as fade_delta), handed to run_game_mode when the fade in ends
param game_mode_stage_change_fade R2 position  # update_scroll's position on the fade frames; start_stage's R2 (write_index_address) when the next stage starts
param game_mode_stage_change_fade R3 in_play_count  # update_player_summary's R3 (players in play, low byte) on the fade frames, then update_scroll's speed; start_stage's floor_block when the next stage starts
param game_mode_stage_change_fade R5 next_scroll_position  # as run_game_mode's: handed to start_stage (its stage_offset, not read) and back to run_game_mode
param game_mode_stage_change_fade R11 object  # update_player_summary's player_object, start_stage's object, then the frame's
param game_mode_stage_change_fade R4 speed_value  # update_scroll's speed_value, then the frame's (start_stage's bounds_offset when the next stage starts); handed back
param game_mode_stage_change_fade R8 count  # update_player_summary's, start_stage's corners_left, the frame's counters; handed back
param game_mode_stage_change_fade R9 cycle  # update_scroll / update_stage_tile_layers (the palette cycle state), check_platforms; start_stage's palette_record; handed back
param game_mode_stage_change_fade R10 list_cursor  # out: check_platforms' platform (start_stage's source on stage 11), or run_game_mode's when the fade in ends
param game_mode_stage_clear R2 position  # play_sound's write_index_address on the first frame (sounds 94, 7B), then update_scroll's position and the frame
param game_mode_stage_clear R3 speed  # handed to update_scroll (its speed) and the frame's calls
param game_mode_stage_clear R11 object  # first frame: the two banner objects (allocate_object_outside_loop: stage_clear_banner_reveal, cleared_banner_wait); then the frame's
param game_mode_stage_clear R4 speed_value  # update_scroll's speed_value, then the frame's; handed back
param game_mode_stage_clear R8 count  # the frame's counters, check_platforms' load_count; handed back
param game_mode_stage_clear R9 cycle  # update_scroll / update_stage_tile_layers (the palette cycle state), spawn_walls, check_platforms; handed back
param game_mode_stage_clear R10 list_cursor  # first frame: the stage_name_banners record (screen x, y, then the banner's animation: "mov.h [R10+], E[R11]" 000DAA); out: check_platforms' platform
param game_mode_stage_end_fade R4 bottom_speed  # handed to run_map_screen and returned: its R4 (start_window_wipe's window_wipe_bottom_speed at step 0, else the caller's)
param game_mode_stage_end_fade R11 object  # handed to run_map_screen (its object: the map intro object it allocates at step 2)
param game_mode_stage_results R2 position  # start_ring_tally's stage_rings_3 on the first frame, then update_scroll's position and the frame
param game_mode_stage_results R3 speed  # start_ring_tally's player_3_out_flags on the first frame, then update_scroll's speed
param game_mode_stage_results R11 object  # start_ring_tally's last object on the first frame, then the frame's
param game_mode_stage_results R4 speed_value  # start_ring_tally's ring_total on the first frame, then update_scroll's speed_value and the frame's; handed back
param game_mode_stage_results R8 count  # start_ring_tally's p2_object on the first frame, then the frame's counters; handed back
param game_mode_stage_results R9 cycle  # start_ring_tally's p3_object on the first frame, then update_scroll / update_stage_tile_layers (the palette cycle state), check_platforms; handed back
param game_mode_stage_results R10 list_cursor  # out only: check_platforms' platform
param game_mode_stage_select R8 count  # first frame: the dbr counter of the VIDEO_RAM + E000 clear (E00 rounds, 0 after); else unchanged
param game_mode_stage_select R10 list_cursor  # out only: read_trackball_step's R10 (trackball 1's motion, 20F062), or erase_text_here's text cursor past stage_number_text once start 1 picks the stage
param game_mode_start_game R8 count  # out only: clear_player_blocks' dbr counter (0)
param game_mode_start_game R10 list_cursor  # out only: start_player_objects' handler_cursor (past the player handlers it read)
param game_mode_you_failed R1 box_edge  # queue_sound's R1 on the first frame, find_nearby_floor_blocks (corner_y), the spawners, check_zones / check_hits (box_edge), check_walls, check_platforms, play_sound, queue_string_record (y: not used)
param game_mode_you_failed R2 position  # queue_sound's write_index_address on the first frame, the spawners, check_hits, check_platforms, play_sound, queue_string_record (palette); no update_scroll in this mode
param game_mode_you_failed R3 in_play_count  # update_player_summary's R3 (players in play, low byte), then find_nearby_floor_blocks, the spawners, check_hits, check_platforms, queue_string_record (scale: not used)
param game_mode_you_failed R11 object  # update_player_summary's player_object, then find_nearby_floor_blocks' right, the spawners' new objects
param game_mode_you_failed R4 speed_value  # find_nearby_floor_blocks, update_restart_point (scroll_position, low half), spawn_objects_by_type, check_hits, check_platforms, queue_string_record (text); handed back
param game_mode_you_failed R8 count  # update_player_summary's, then the frame's counters; handed back
param game_mode_you_failed R9 cycle  # spawn_walls' z, check_platforms' passes_left; handed back
param game_mode_you_failed R10 list_cursor  # out only: check_platforms' platform
param reload_changed_tile_set R4 set_shift  # "mov.w #0, R4" at the start (015DDA), never read: handed back as 0 in place of update_stage_tile_layers' set_shift (the block byte rotation, 20 after the block map read: its R4)
param run_frame_check_p2_start R1 credits  # run_frame's R1 after draw_attract_credits (its credits); handed to the system mode handlers (the demos' and run_game_mode's R1) and run_frame_start_game
param run_frame_check_p2_start R2 blink_bit  # run_frame's R2 after draw_attract_credits (its blink_bit); handed to the system mode handlers and run_frame_start_game
param run_frame_check_p2_start R3 credits_needed  # run_frame's credits_needed (credits_per_start in the low half, 0 on free play): p2_credits / p1_credits below its low byte cannot start; handed on as R3
param run_frame_check_p2_start R8 count  # run_frame's R8 (read_controls' count); handed to attract_title and the demos / run_game_mode, then build_sprite_list (scale_x) and draw_queued_strings
param run_frame_check_p2_start R10 message_text  # draw_attract_credits' message_text (run_frame's R10); handed to the system mode handlers, then build_sprite_list (sprite_flags) and draw_queued_strings
param run_frame_check_p2_start R11 text_position  # draw_attract_credits' text_position (run_frame's R11): only handed to the demos and run_game_mode (their R11)
param run_frame_check_p2_start R4 scale  # run_frame's R4 (the last frame's draw_queued_strings scale), through the mode handler and build_sprite_list; out: draw_queued_strings' scale
param run_frame_check_p2_start R5 cursor  # run_frame's R5 (draw_queued_strings' string cursor), handed to the demos / run_game_mode (low half); out: draw_queued_strings' cursor
param run_frame_check_p2_start R9 height  # run_frame's R9, through the mode handler and build_sprite_list; out: draw_queued_strings' height
param run_frame_start_game R1 credits  # as run_frame_check_p2_start's: draw_attract_credits' R1, handed to the system mode handler (run_game_mode once system_mode is 6)
param run_frame_start_game R2 blink_bit  # as run_frame_check_p2_start's: draw_attract_credits' R2, handed to the system mode handler
param run_frame_start_game R3 credits_needed  # run_frame's credits_needed, only handed on as R3 here
param run_frame_start_game R8 count  # run_frame's R8 (read_controls' count); through the mode handler and build_sprite_list (scale_x) to draw_queued_strings
param run_frame_start_game R10 message_text  # draw_attract_credits' message_text (run_frame's R10); through the mode handler and build_sprite_list (sprite_flags) to draw_queued_strings
param run_frame_start_game R11 text_position  # draw_attract_credits' text_position (run_frame's R11): only handed to the mode handler (its R11)
param run_frame_start_game R4 scale  # run_frame's R4, through the mode handler and build_sprite_list; out: draw_queued_strings' scale
param run_frame_start_game R5 cursor  # run_frame's R5 (draw_queued_strings' string cursor), handed to the mode handler (low half); out: draw_queued_strings' cursor
param run_frame_start_game R9 height  # run_frame's R9, through the mode handler and build_sprite_list; out: draw_queued_strings' height
param run_game_mode R1 pressed  # run_frame's R1 (read_controls' pressed bits; draw_attract_credits' R1 on the start and demo paths), read by no mode: the modes that change game_mode before their frame hand it back here; game over, you failed and event 16 hand it to the contact checks (box_edge), stage_change_fade to update_fade (delta)
param run_game_mode R2 position  # run_frame's R2 (released), not read: the modes hand it to update_scroll (its position), which replaces it, then to the frame's other calls; start_stage leaves play_sound's write_index_address in it
param run_game_mode R3 speed  # run_frame's R3 (input_cursor / credits_needed): the modes hand it to update_scroll as its speed (read only on scroll_speeding_up's last ramp frame); play_stage and most event modes first let update_player_summary put the players in play in its low byte
param run_game_mode R5 next_scroll_position  # run_frame's R5 (draw_queued_strings' cursor, low half), not read: play_stage and screen_wipe hand it to update_scroll (its next_scroll_position), start_stage takes it as stage_offset unread, the event modes hand it back here
param run_game_mode R11 object  # run_frame's R11 (trackball_cursor / draw_attract_credits' text_position): the modes hand it to update_player_summary (player_object), the spawners and start_stage (the objects they allocate; kept when none is free), run_map_screen
param run_game_mode R4 speed_value  # in: run_frame's R4 (draw_queued_strings' scale), not read; out: what the mode leaves: update_scroll's speed_value, update_restart_point's scroll_position, check_hits', check_platforms' sag; run_map_screen's bottom_speed (mode 4), start_stage's bounds_offset (mode 2)
param run_game_mode R8 count  # in: run_frame's R8 (read_controls' count, 0); out: the modes' counters: update_player_summary's / check_stage_clear's, spawn_object_groups' slot search, check_platforms' load_count, clear_player_blocks' (mode 0), the modes' own player loops
param run_game_mode R9 cycle  # in: run_frame's R9 (draw_queued_strings' height); out: the stage frame's R9: update_stage_tile_layers' palette cycle state (update_scroll's player 3 object in scroll modes 7/8, spawn_walls' z, check_platforms' passes_left); start_stage's palette_record (mode 2)
param run_game_mode R10 list_cursor  # in: run_frame's R10 (trackball_port / message_text), not read; out: the cursor the mode's callees leave: check_platforms' platform, run_stage_events' event (modes 3/5/6), start_player_objects' handler_cursor (mode 0), intro_status_cursor (mode 1), start_stage's source (mode 2), the text cursors of modes 8 and 0E
param start_stage R2 write_index_address  # out: play_sound's R2 (queue_sound's &sound_queue_write for stage_start_sounds[stage]); before it apply_stage_layer_blending's layer_bit, start_stage_view's direction_y, move_camera_to_start_section's y (demo); the caller's value is not read
param start_stage R3 floor_block  # out: find_nearby_floor_blocks' R3; before it start_player_objects_alt's player_number, start_stage_view's direction_x, move_camera_to_start_section's section_offset (demo); the caller's value is not read
param update_ending R2 position  # build_ending_scene's write_index_address (step 0), then spawn_objects_by_type, spawn_ring_rows, spawn_walls, spawn_object_groups, check_hits (distance); no update_scroll here
param update_ending R3 speed  # build_ending_scene's tile_bank_bits (step 0), find_nearby_floor_blocks' floor_block, spawn_objects_by_type's first
param update_ending R4 speed_value  # build_ending_scene's color (step 0), find_nearby_floor_blocks, update_restart_point (scroll_position, low half), spawn_objects_by_type, check_hits, check_platforms (sag); handed back
param update_ending R8 count  # build_ending_scene's row_pairs_left (step 0), find_nearby_floor_blocks' corners_left, the spawners', check_platforms' load_count; handed back
param update_ending R9 cycle  # build_ending_scene's blocks_per_row (step 0), spawn_walls' z, check_platforms' passes_left; handed back
param update_scroll_at_end R2 position  # update_scroll's R2 (scroll_position once it reached scroll_end; the caller's when the scroll had ended): scroll_slowing_down's in step 2; out on stage 3's end: velocity_y, the camera_velocity_y it stores; other stages hand it back
param update_scroll_at_end R3 speed  # stage 3's end speed: scroll_speeding_up's (steps 0/4), 25 then scroll_speed (step 1), scroll_slowing_down's (step 2); handed to advance_camera_y and multiplied into the camera velocities
param update_scroll_at_end R4 speed_value  # scroll_slowing_down's R4 in step 2; out: the speed's low 16 bits it multiplied by; unchanged on the other stages
param update_scroll_at_end R5 next_scroll_position  # update_scroll's R5, passed through scroll_speeding_up / scroll_slowing_down
param update_scroll_at_end R10 end_script  # stage_03_end_script_2 when stage 3's end starts, _3 at the end of step 1, run through run_vram_script; scroll_speeding_up's / scroll_slowing_down's range cursor in between; handed back
param update_scroll_at_end R11 dest  # run_vram_script's video RAM write address for the end scripts; scroll_speeding_up's / scroll_slowing_down's quadrant_cursor; spawn_stage_03_end_pillar's pillar at the end of step 2; handed back
param update_stage_05_event R2 position  # update_scroll's position (steps 0-1), run_stage_05_switch's scroll_y (step 2), spawn_eggman_booth's free_count and the message box / spawn_walls' part_size (step 3), check_hits' distance; not handed back
param update_stage_05_event R3 speed  # update_player_summary's in_play_count then update_scroll's speed (steps 0-1; step 4 the incoming value); run_stage_05_switch reads it as update_background_scroll's speed (step 2); spawn_eggman_booth's R3 (step 3)
param update_stage_05_event R11 object  # update_player_summary's player_object, update_scroll's / run_stage_05_switch's dest, the objects it allocates (stage_00_platform_start, the geyser, the rock pieces), the message box, spawn_walls' wall, update_stage_00_exit_scene's dest
param update_stage_05_event R4 speed_value  # update_scroll's / run_stage_05_switch's / update_stage_00_exit_scene's speed_value, find_nearby_floor_blocks', check_hits', check_platforms' sag; handed back
param update_stage_05_event R9 cycle  # update_scroll / update_stage_tile_layers (the palette cycle state), spawn_walls' z (step 3), check_platforms' passes_left; handed back
param update_stage_05_event R10 list_cursor  # out: check_platforms' platform (check_stage_clear's finished_flag after it, steps 0-1), the stage_00_platform object (step 5), update_stage_00_exit_scene's script (steps 6-7)
param check_player_contacts_attack R10 toucher object  # check_player_contacts' LINKED(touched_by), straight (kinds 11, 19 when jumping) or through 00E634/00E65A when jumping: last_damaged = it, its flag 6, health - attack_power, kind (sturdy ones throw the player back) and flags/direction read (player_tumbling_launch's name for this value)
param check_player_contacts_attack_or_hurt R3 new_substate  # the hurt (state 0xD) substate set once per object: 0 from check_player_contacts_hurt, 1 for kind B (check_player_contacts' new_substate)
param check_player_contacts_attack_or_hurt R10 toucher object  # check_player_contacts' LINKED(touched_by) (kind B, or 9, D, 18, 28 through check_player_contacts_hurt): to check_player_contacts_attack when jumping, else compared with and kept in last_hazard
param check_player_contacts_hurt R10 toucher object  # check_player_contacts' LINKED(touched_by) (kinds 9, D, 18, 28; 29 with flag 12) or check_player_contacts_hurt_unless_invulnerable's: handed on to check_player_contacts_attack_or_hurt
param check_player_contacts_hurt_unless_invulnerable R10 toucher object  # check_player_contacts' LINKED(touched_by) (kinds 4, F, 10, 16; 27 with flag 12): handed on to check_player_contacts_hurt
param check_player_contacts_solid R1 health_limit  # check_player_contacts' R1 (its name), unchanged on these paths (the player states' depth), handed back; aim_at_screen_centre's cos_part on the push-out paths (also what it returns)
param check_player_contacts_solid R10 toucher object  # check_player_contacts' LINKED(touched_by) (kinds 5, C, 20, 21; 11 when slow): compared with and kept in last_hazard, kind 20 never bounces; to check_player_contacts_attack when jumping
param check_player_step_against_floor R0 height_diff  # check_player_step's height_diff: z_int - step_floor_z_int (+8 for the -8..8 test, then back): below 0 falls (state 2), above 8 blocks; 0 after a stop; the caller's on a path
param check_player_step_against_floor R11 cursor  # check_player_step's cursor (player_move's, find_floor's) handed back; the player's x (get_floor_block_side's) on the bounce path
param player_climbing_animate_vertical_step R11 cursor  # player_state_climbing's cursor (check_player_contacts' R11), only handed on to player_climbing_update_position
param player_climbing_animate_vertical_step R12 attribute_record floor_attribute_record  # player_state_climbing's attribute_record (floor_types + floor_attribute): +4 the screen shift per z step (x 2 or -2), byte 0 the wall's axis (the shift's sign); handed on
param player_climbing_begin_climb R11 cursor  # player_state_climbing's cursor, only handed on to player_climbing_move
param player_climbing_begin_climb R12 attribute_record floor_attribute_record  # player_state_climbing's attribute_record (floor_types + floor_attribute), only handed on to player_climbing_move
param player_climbing_check_vertical_step R11 cursor  # player_state_climbing's cursor, only handed on
param player_climbing_check_vertical_step R12 attribute_record floor_attribute_record  # player_state_climbing's attribute_record (floor_types + floor_attribute), only handed on to the step's start / animation
param player_climbing_descend_over_edge R11 cursor  # player_state_climbing's cursor, only handed on to player_climbing_begin_climb
param player_climbing_descend_over_edge R12 attribute_record floor_attribute_record  # player_state_climbing's attribute_record (floor_types + floor_attribute), only handed on to player_climbing_begin_climb
param player_climbing_grab_wall R11 cursor  # player_state_climbing's cursor (or player_climbing_start's), only handed on
param player_climbing_grab_wall R12 attribute_record floor_attribute_record  # player_state_climbing's attribute_record (floor_types + floor_attribute): byte 0 the wall's axis (x or y kept in the wall, the flip, the block edge), +A/+C the wall's start and length, +6 the climb height into climb_remaining
param player_climbing_move R11 cursor  # player_state_climbing's cursor (or player_climbing_begin_climb's), only handed on
param player_climbing_move R12 attribute_record floor_attribute_record  # player_state_climbing's attribute_record (floor_types + floor_attribute), only handed on to the vertical step, the side step or player_climbing_update_position
param player_climbing_pull_up R12 attribute_record floor_attribute_record  # player_state_climbing's attribute_record (floor_types + floor_attribute), or player_climbing_update_position's: +8 the top's z, byte 0 the wall's axis (placed A past the block's x max or y min, heading 80 or 380)
param player_climbing_side_step R1 cos_speed  # player_climbing_move's set_climb_speed_from_trackball (or set_speed_from_direction) result: cosine(direction) x speed >> 6, speed_y negated; added to the stepped projected y and z for the on-screen test (screen y 10..CF)
param player_climbing_side_step R11 cursor  # player_climbing_move's (player_state_climbing's cursor), only handed on to player_climbing_update_position
param player_climbing_side_step R12 attribute_record floor_attribute_record  # player_climbing_move's (floor_types + floor_attribute): byte 0 the wall's axis (the y step's sign, x or y tested), +A/+C the wall's start and length the step must stay in
param player_climbing_start R11 cursor  # player_state_climbing's cursor (check_player_contacts' R11), only handed on to player_climbing_grab_wall
param player_climbing_start R12 attribute_record floor_attribute_record  # player_state_climbing's attribute_record (floor_types + floor_attribute), only handed on to player_climbing_grab_wall
param player_climbing_start_vertical_step R11 cursor  # player_climbing_check_vertical_step's (player_state_climbing's cursor), only handed on
param player_climbing_start_vertical_step R12 attribute_record floor_attribute_record  # player_climbing_check_vertical_step's (floor_types + floor_attribute), only handed on to player_climbing_animate_vertical_step
param player_climbing_update_position R11 cursor  # player_state_climbing's cursor through the climbing pieces: spawn_climb_zone_effect allocates the splash zone effect in it (its R11)
param player_climbing_update_position R12 attribute_record floor_attribute_record  # player_state_climbing's attribute_record (floor_types + floor_attribute), straight (touched by kind 5) or through the pieces: +6 the climb height (back at the foot), byte 0 the wall's axis; handed to player_climbing_pull_up
param player_dispatch_state R1 depth  # the player chain's R1 (player_animate's / player_move's name): player_update's (player_hud_update's R1), or the state code's when a state changes; handed to every state that takes R1
param player_dispatch_state R11 cursor  # the player chain's R11 (player_animate's / player_move's name): player_update's (check_scene_states'), or the state code's (find_floor's / check_player_contacts' cursor); handed to every state that takes R11
param player_hurt_fly R1 depth  # player_state_hurt's depth (substate 2), or the hurt animation offset player_hurt_set_animation loads (as player_state_hurt loads the shocked one into depth): find_floor's R1, handed to player_dispatch_state
param player_hurt_fly R11 cursor  # player_state_hurt's cursor (straight, or through player_hurt_knockback / player_hurt_set_animation): find_floor's cursor, handed to player_dispatch_state
param player_hurt_knockback R11 cursor  # player_state_hurt's cursor (substates 0, 9 and 1), only handed on to player_hurt_set_animation
param player_hurt_set_animation R11 cursor  # player_state_hurt's cursor (end of the shocked substates 7/8) or player_hurt_knockback's, only handed on to player_hurt_fly
param player_in_zone_steer R1 depth  # player_state_in_zone's depth (back from check_player_contacts): update_screen_position's R1 (its low half the animation speed for the state choice before that), handed to player_animate
param player_in_zone_steer R11 cursor  # player_state_in_zone's cursor (check_player_contacts'), handed to player_animate
param player_move_check_edge R1 depth  # player_move's R1 (update_sinking_floor's): check_player_floor_edge's, project_object's and keep_player_on_screen's depth, handed to player_dispatch_state / player_animate
param player_move_check_edge R11 cursor  # player_move's cursor (find_floor's): check_player_floor_edge's R11, handed to player_dispatch_state / player_animate
param player_over_edge_check_pull_up R11 cursor  # player_state_over_edge's cursor (the near-edge hang animation table on the hang's first frame, substate 2), only handed on to player_over_edge_drop / player_over_edge_hang_finish
param player_over_edge_drop R11 cursor  # player_state_over_edge's cursor (or player_over_edge_check_pull_up's), only handed on to player_over_edge_hang_finish
param player_over_edge_hang_finish R11 cursor  # player_state_over_edge's cursor through check_pull_up / drop: check_player_contacts' R11 (unless object flag 0x20000)
param player_steer_approach_speed R0 wanted_speed  # player_steer's wanted_speed (follow_player_path's, on a path) or player_steer_speed_from_trackball's (trackball + push speed squared x 1.5, max 5000000, scaled on floor types A and 4): apply_speed_scale's target_speed, then speed approaches it
param player_tumbling_start R11 cursor  # player_state_tumbling's cursor (substates 0, 9 and 1), only handed on to player_tumbling_set_launch_speed / player_tumbling_launch (their effect: spawn_tumble_effect allocates in it)
param push_player_out_of_wall_deflect R0 angle  # out: the angle between the reversed direction and the wall's push direction (< 200 picks the side), or the straight-back direction (wall direction << 8 + 200) on stage 8 below scroll 4000 (push_player_out_of_wall's angle)
param push_player_out_of_wall_deflect R1 push_direction  # out: the sonic_bounce_records word for the wall's direction byte, where the wall pushes (push_player_out_of_wall's push_direction)
param push_player_out_of_wall_deflect R11 record_index  # out: the wall's direction byte x 3, the index of its word in sonic_bounce_records (push_player_out_of_wall's record_index)
param biting_head_load_record R10 path_cursor  # biting_head_read_script's path_cursor: its path record + 4 (past the scroll position and x/y limit words): threshold, factor, base speed, wait, direction with [R10+] (08AC0C-08AC24), path = R10 + A (the next 7-word record); a negative factor word (2[R10]) ends the path: part list and second box freed, removed. Untyped here: the callers' kind comes in (a biting_head_path_entry, 4 bytes in, from the scoped biting_head 80 path); stage_0C_saws' path poked into RAM 20B300 reads through rom() too
param biting_head_move_at_speed R1 move_speed  # base_speed (biting_head_choose_speed) or max(base_speed, camera_slope_speed x catch_up_factor >> 8) (biting_head_catch_up_speed): chaser_speed = it (08AD26), x/y moved by it << 12 along direction once the wait is over (local biting_head_chase 08AD16,08AD21 r1 move_speed)
param carried_ring_keep_spinning R11 sparkle  # only handed to spawn_ring_glint (its sparkle: where allocate_object puts the glint); in: carried_ring_spin's R11, the touching player's block (a player in state D/20 with hurt_contact_delay) or what the object loop left
param check_hit R3 dy  # check_hits' R3 (its entry name), handed back (low half): round test the hitter's centre x, then the other's centre y - the hitter's, slope-corrected, squared into distance; box tests the hitter's hitbox_offset_x / _y (local check_hit 001F87,001FAF,002051 r3 dy)
param check_hit R4 edge_or_centre_y  # check_hits' R4, handed back (low half): box/box the other's box start (x_int + hitbox_offset_x), then its end (+ hitbox_size_x), the same in y; round test the hitter's centre y (local check_hit 001E64,001FAC r4 edge_or_centre_y)
param enter_ranking_initials_count_down R0 button_bit  # enter_ranking_initials' button_bit ("movz.bw 80[R20], R0" 08ECA3: the entry block's button, tested in inputs_pressed); only its high half (0) stays: "mov.h E[R20], R0" (08ECE2) makes it the x of the countdown text (queue_string_at's R0)
param find_floor_no_block R0 x  # find_floor's R0 (the object's x from its block loop, or the caller's R0 when no block is near), handed to find_floor_set_floor_z and back unchanged (no_floor_block is flat); find_floor's name, which it hands back
param find_floor_no_block R3 slope_height  # out only: find_floor_set_floor_z's R3 for no_floor_block, 0 (no slope); find_floor's slope_height
param find_floor_no_block R11 cursor  # find_floor's cursor in nearby_floor_blocks (past the count word and the entries it read, 00C75E-00C775), handed to find_floor_set_floor_z and back (unchanged on the flat no_floor_block)
param find_floor_set_floor_z R10 block floor_block  # find_floor's block (floor_blocks + 2 + the found offset) or find_floor_no_block's no_floor_block (10B70A): height (+10) into floor_z, slope (+13) into floor_slope, x/y min (+0/+8) for the slope distance
param find_floor_set_floor_z R0 x  # find_floor's R0 register (the block's type index from find_floor, the x find_floor_no_block hands on), not read: out the slope factor (floor_slopes record +2, local find_floor 00C839 r0 slope_factor) on a slope, else unchanged; find_floor hands it back as its x
param find_floor_set_floor_z R3 slope_height  # out only: 0 on flat floor, else ((y - y min, or x - x min with the slope's flag bit 1) >> 12, rounded) x factor >> 4, added to floor_z (find_floor's slope_height)
param find_floor_set_floor_z R11 cursor  # find_floor's cursor, handed back: on a slope the floor_slopes record (flags, factor at +2), left past the factor (+6); unchanged on flat floor
param fire_front_load_record R10 path_cursor  # fire_front_read_script's cursor: its path record + 4 (past the scroll position and coordinate words): threshold, factor, base speed, wait with [R10+] (08B0A2-08B0B4), path = R10 + 8 (the next 6-word record); a negative factor word ends it (chaser_object = 0, removed). Untyped here: the callers' kind comes in (a chase_step_with_limit, 4 bytes in, from the scoped fire_front 80 path; census of its path - 6 reads: ROM 08BDB3-08BE17, and the path stage_0C_saws pokes into RAM 20B200, which rom() reads too)
param flame_machine_destroyed R2 free_count  # only handed to spawn_object_with_pieces (its R2: only the high half survives the free_object_stack load), as flame_machine_arm_take_disc's; in: flame_machine_draw's R2 (unproject_object's, play_sound's) or flame_machine_update's leftover
param flame_machine_destroyed R3 first  # spawn_object_with_pieces' R3: the explosion every 8 frames (copy_position_to it, next_random's values into its scale and offsets, flame_machine_explosion_start) or at 130 the wreck (flame_machine_wreck_draw, two pieces chained); in: unproject_object's leftover (flame_machine_draw)
param flame_machine_destroyed R11 block  # spawn_object_with_pieces' R11: the last block it allocated, as flame_machine_arm_take_disc's; in: flame_machine_draw's R11 (unproject_object's cursor) or the leftover
param icicle_dropper_catch_up_speed R3 slope_height  # icicle_dropper_move_step's R3 (find_floor's slope_height; what the object loop left while it rides the ice platform), handed to compute_camera_slope_speed (its octant, handed back) and on to icicle_dropper_move_at_speed
param icicle_dropper_catch_up_speed R11 cursor  # icicle_dropper_move_step's R11 (find_floor's cursor, or the leftover while riding), handed on to icicle_dropper_move_at_speed
param icicle_dropper_check_scroll_position R10 path_cursor icicle_dropper_step  # icicle_dropper_move's path: the script record's scroll position word ("cmp.h [R10+], 6435[R25]" 0883F9; its coordinate word is at -2); reached: R10 + 2 to icicle_dropper_load_record; census ROM 088FC1-0890B1 (icicle_dropper_scripts)
param icicle_dropper_choose_speed R3 slope_height  # icicle_dropper_move_step's R3 (find_floor's slope_height), handed on to icicle_dropper_move_at_speed / icicle_dropper_catch_up_speed
param icicle_dropper_choose_speed R11 cursor  # icicle_dropper_move_step's R11 (find_floor's cursor), handed on to icicle_dropper_move_at_speed / icicle_dropper_catch_up_speed
param icicle_dropper_coordinate_reached R10 path_cursor icicle_dropper_step  # icicle_dropper_move's path (at the record's scroll position word): + 2 (0883F4) to icicle_dropper_load_record
param icicle_dropper_load_record R10 path_cursor  # the script record + 4 (past the coordinate and scroll position words): threshold, factor, base speed, wait with [R10+] (08841A-08842C), path = R10 + A (the next 12-byte record's scroll word); a negative factor word ends it (icicle_dropper_ready = FF; removed, or icicle_dropper_finish for the last dropper) (untyped: its callers hand it icicle_dropper_step + 2, past the scroll position)
param icicle_dropper_move_at_speed R1 move_speed  # base_speed (icicle_dropper_choose_speed) or max(base_speed, camera_slope_speed x catch_up_factor >> 8) (icicle_dropper_catch_up_speed): chaser_speed = it (08853C; 400 for the last dropper once every player finished) (local icicle_dropper_move 08852C,088537 r1 move_speed)
param icicle_dropper_move_at_speed R3 slope_height  # icicle_dropper_move_step's R3 (find_floor's slope_height; compute_camera_slope_speed's octant after the catch-up), only handed to drop_icicle_row, whose next_random replaces it
param icicle_dropper_move_at_speed R11 cursor  # icicle_dropper_move_step's R11 (find_floor's cursor), only handed to drop_icicle_row, whose allocate_object replaces it (its block)
param lava_rock_erupt R11 block  # spawn_lava_column's R11 (the lava column, then its pool 2 sprite block), chained into the second call for the wide rock (D000 sprite flags); in: lava_rock_update's block_or_slot_entry (the group_slots entry when its triggered slot's delay ran out, else what the object loop left)
param ring_spin_keep_spinning R11 sparkle  # only handed to spawn_ring_glint (its sparkle: where allocate_object puts the glint); in: ring_spin's R11, the touching player's block (a player in state D/20 with hurt_contact_delay) or what the object loop left
param ring_tally_percent_set_digits R3 shown_percent  # ring_tally_percent_update's shown_percent: the percentage once ring_tally_counted, else next_random's value while the digits roll; its low byte through convert_to_bcd into the three digits' percent_digit_sprites frames
param spawn_object_groups R1 list_offset  # object_group_spawns[stage] on the stage's first call (group_spawn_cursor = 0920F9 + it), as spawn_object_groups_2's and spawn_objects_by_handler's R1 (the callers hand one variable to all three); later spawn_ice_platform_leader's R1 or the build pieces' (slot index, depth step); else the caller's value back
param spawn_object_groups_2_build_group R10 record  # (a group_spawn_record + 3, as the callers point at it) spawn_object_groups_2's record (group_spawn_cursor_2 + 3, past the scroll position and type byte): -3 scroll position, leader, slot flag, floor block (+3), member count (+7), steps, direction, member x, y, template offset (+13, + 93F59) (local spawn_object_groups_2 09391C-09395C r10 record)
param spawn_object_groups_2_build_group R12 plank_count_var  # the address of bridge_0_plank_count or bridge_1_plank_count (spawn_object_groups_2 by record type, spawn_object_groups_2_pick_bridge by the free bridge): + the member count (local spawn_object_groups_2 0938C7,0938D4,0938E8,0938F4 r12 plank_count_var); a work RAM u16, no structure to type it with
param spawn_object_groups_2_build_group R1 list_offset  # spawn_object_groups_2's R1, not read: out the last member's odd_member_animation (template +0E) once the members are made
param spawn_object_groups_2_build_group R8 count  # spawn_object_groups_2's R8: the group_slots search counter (40, counted down) when the group takes a slot, then + 1
param spawn_object_groups_2_build_group R11 object object.group_member  # spawn_object_groups_2's R11: each member (allocate_object_outside_loop: the planks), the last one at the end (also in bridge_0/1_last_plank)
param spawn_object_groups_2_pick_bridge R10 record  # (a group_spawn_record + 3, as the callers point at it) spawn_object_groups_2's type 0 record past the scroll position and type byte, handed to spawn_object_groups_2_build_group; the word it compares with free_object_stack (+5) is the floor block's high half (0), not the member count (+7)
param spawn_object_groups_2_pick_bridge R1 list_offset  # spawn_object_groups_2's R1, handed to spawn_object_groups_2_build_group and back
param spawn_object_groups_2_pick_bridge R8 count  # spawn_object_groups_2's R8, handed to spawn_object_groups_2_build_group and back
param spawn_object_groups_2_pick_bridge R11 object object.group_member  # spawn_object_groups_2's R11, handed to spawn_object_groups_2_build_group and back (the last plank)
param spawn_object_groups_build_group R10 record  # (a group_spawn_record + 3, as the callers point at it) spawn_object_groups' record (group_spawn_cursor + 3, past the scroll position and type byte): -3 scroll position, leader, slot flag, floor block (+3), member count (+7), steps, direction, member x, y, template offset (+13, + 93269) (local spawn_object_groups 0912F0-09132D r10 record)
param spawn_object_groups_build_group R1 list_offset  # spawn_object_groups' R1, handed back (low half): the group slot index (40 - the search counter) when the group takes a slot, then group_depth_step (F8 for type 2 rows) x the members left, added to each member's depth_offset
param spawn_object_groups_build_group R3 slope_z  # spawn_object_groups' R3: for each member on a slope (y - y min, or x - x min with the slope's flag bit 1) >> 16 x the slope factor, added to its floor_z; the last member's
param spawn_object_groups_build_group R8 count  # spawn_object_groups' R8: the group_slots search counter (40, counted down) when the group takes a slot, then + 1
param spawn_object_groups_build_group R11 object object.group_member  # spawn_object_groups' R11: the member template's address (stage_object_records + the record's offset word, kept in group_record), then each member (allocate_object_outside_loop): the last one at the end
param spawn_object_groups_mark_silent R10 record  # (a group_spawn_record + 3, as the callers point at it) spawn_object_groups' type 6 record (past the scroll position and type byte), handed to spawn_object_groups_no_depth_step
param spawn_object_groups_mark_silent R1 list_offset  # spawn_object_groups' R1, handed through spawn_object_groups_no_depth_step (low half back)
param spawn_object_groups_mark_silent R3 slope_z  # spawn_object_groups' R3, handed through spawn_object_groups_no_depth_step
param spawn_object_groups_mark_silent R8 count  # spawn_object_groups' R8, handed through spawn_object_groups_no_depth_step
param spawn_object_groups_mark_silent R11 object object.group_member  # spawn_object_groups' R11, handed through spawn_object_groups_no_depth_step (the last member)
param spawn_object_groups_no_depth_step R10 record  # (a group_spawn_record + 3, as the callers point at it) spawn_object_groups' type 0 record or spawn_object_groups_mark_silent's type 6 one, handed to spawn_object_groups_build_group
param spawn_object_groups_no_depth_step R1 list_offset  # spawn_object_groups' R1, handed through spawn_object_groups_build_group (low half back)
param spawn_object_groups_no_depth_step R3 slope_z  # spawn_object_groups' R3, handed through spawn_object_groups_build_group
param spawn_object_groups_no_depth_step R8 count  # spawn_object_groups' R8, handed through spawn_object_groups_build_group
param spawn_object_groups_no_depth_step R11 object object.group_member  # spawn_object_groups' R11, handed through spawn_object_groups_build_group (the last member)
param test_coin_assignments_manual_page R4 color  # test_coin_assignments' color (run_test_mode's fill colour), returned unchanged while nothing is pressed and when leaving the page (item 4); draw_coin_credit_table's R4 (0) once the page is drawn or a setting changed (local test_coin_assignments 09E3E2,... r4 coins_left)
param tornado_load_record R10 path_cursor  # tornado_move's path_cursor: its path record + 4 (past the scroll position and coordinate words): threshold, factor, base speed, wait, direction with [R10+] (089361-089379), path = R10 + A (the next 7-word record); a negative factor word ends it (a negative base speed word sets goal_unlocked), removed with its columns; u32 rom path (scoped tornado 80)
param tornado_move_at_speed R1 move_speed  # base_speed (tornado_choose_speed) or max(base_speed, camera_slope_speed x catch_up_factor >> 8) (tornado_catch_up_speed): x 4 once every player finished (0895C7), then chaser_speed (local tornado_move 0895AB,0895B6,0895C7 r1 move_speed)
param draw_line_shallow_even_pixel R1 pixel_address  # the byte of the current pixel pair (draw_line's start_y made y x 100 + x / 2 + bitmap_address): read, written with the even pixel when the row changes, + row_step on a carry; handed back where the line ends
param draw_line_shallow_even_pixel R2 fraction  # the 0.16 accumulator (draw_line's fraction_4bpp, 0 at the start): + slope every pixel; its carry steps one row
param draw_line_shallow_even_pixel R3 low_nibble_color  # color & F (draw_line's low_nibble_color): the even pixel's colour, ORed into the byte's low nibble here
param draw_line_shallow_even_pixel R4 high_nibble_color  # color << 4 (draw_line's color after shl.b #4): the odd pixel's colour, handed on to draw_line_shallow_set_high_nibble
param draw_line_shallow_even_pixel R5 slope  # ((|dy| + 1) << 16) / (|dx| + 1) (draw_line's slope): added to fraction every pixel
param draw_line_shallow_even_pixel R6 steps_left  # |dx| + 1, the pixels left to draw (draw_line's R6, which it calls steep_slope; steps_left in its 8 bpp part): dbnl counts it down, 0 ends the line
param draw_line_shallow_even_pixel R7 high_nibble_mask  # F0 (draw_line's high_nibble_mask): keeps the odd pixel when the even one goes into the low nibble
param draw_line_shallow_even_pixel R10 row_step  # 100, or -100 when the line goes up (draw_line's row_step_4bpp): added to pixel_address when the fraction carries
param draw_line_shallow_odd_pixel R1 pixel_address  # the byte of an odd x pixel after a row step: read, handed on to draw_line_shallow_set_high_nibble; handed back where the line ends
param draw_line_shallow_odd_pixel R2 fraction  # the 0.16 accumulator (draw_line's fraction_4bpp), handed on
param draw_line_shallow_odd_pixel R3 low_nibble_color  # the even pixel's colour (color & F), handed on
param draw_line_shallow_odd_pixel R4 high_nibble_color  # the odd pixel's colour (color << 4), handed on
param draw_line_shallow_odd_pixel R5 slope  # ((|dy| + 1) << 16) / (|dx| + 1), handed on
param draw_line_shallow_odd_pixel R6 steps_left  # the pixels left to draw, handed on
param draw_line_shallow_odd_pixel R7 high_nibble_mask  # F0, handed on
param draw_line_shallow_odd_pixel R10 row_step  # 100 or -100, handed on
param draw_line_shallow_set_high_nibble R0 pixel_byte  # the byte at pixel_address: from draw_line_shallow_even_pixel with the even pixel (low nibble) set, from draw_line_shallow_odd_pixel as read; high_nibble_color goes into its high nibble and it is written
param draw_line_shallow_set_high_nibble R1 pixel_address  # the pixel pair's byte: written ([R1+]: on to the next pair), + row_step when the fraction carries; handed back where the line ends
param draw_line_shallow_set_high_nibble R2 fraction  # the 0.16 accumulator: + slope after the odd pixel; its carry steps one row
param draw_line_shallow_set_high_nibble R3 low_nibble_color  # the even pixel's colour, handed on to draw_line_shallow_even_pixel
param draw_line_shallow_set_high_nibble R4 high_nibble_color  # color << 4: ORed into pixel_byte's high nibble (the odd pixel)
param draw_line_shallow_set_high_nibble R5 slope  # added to fraction after the odd pixel
param draw_line_shallow_set_high_nibble R6 steps_left  # the pixels left to draw: dbnl counts it down, 0 ends the line
param draw_line_shallow_set_high_nibble R7 high_nibble_mask  # F0, handed on to draw_line_shallow_even_pixel
param draw_line_shallow_set_high_nibble R10 row_step  # 100 or -100: added to pixel_address when the fraction carries
param fill_span_at_point_fill_run R2 fill_color  # fill_span_at_point's (low nibble): into an odd first pixel's high nibble, x 11 for the aligning byte, & F x 11111111 (fill_span_at_point's fill_word) for the words, the low nibble of an even last pixel
param fill_span_at_point_fill_run R10 left_x  # the run's first x (the left scan's end): odd = it starts in the high nibble of R14's byte; fill_count = right_x - left_x
param fill_span_at_point_fill_run R16 right_x  # the run's last x (the right scan's end): fill_count = right_x - left_x
param fill_span_at_point_left_byte R1 scan_count  # pixels left to bitmap_window_left (fill_span_at_point's x - window_left): - 1 after the high nibble (0: the window edge), dbne in fill_span_at_point_left_even_pixel
param fill_span_at_point_left_byte R2 fill_color  # a nibble of the fill colour ends the scan (the run starts right of it); handed on to the fill
param fill_span_at_point_left_byte R3 border_color  # a nibble of the border colour ends the scan too
param fill_span_at_point_left_byte R10 left_x  # the x of the byte's even pixel: - 2 per byte read ([-R14]), back past the pixel that ends the scan: the run's left end (fill_span_at_point hands in get_point_outcode's R10, 0, not point_x)
param fill_span_at_point_left_byte R15 right_cursor  # the right scan's byte cursor, only handed on (fill_span_at_point never sets it)
param fill_span_at_point_left_byte R16 right_x  # the point's x as fill_span_at_point copied it from R10, handed on to the right scan
param fill_span_at_point_left_even_pixel R0 pixel_byte  # the byte just read (fill_span_at_point's [R14], fill_span_at_point_left_byte's [-R14]): its low nibble, the even pixel, is tested
param fill_span_at_point_left_even_pixel R1 scan_count  # pixels left to bitmap_window_left: dbne counts it, 0 = the window edge
param fill_span_at_point_left_even_pixel R2 fill_color  # a nibble of the fill colour ends the scan
param fill_span_at_point_left_even_pixel R3 border_color  # a nibble of the border colour ends the scan
param fill_span_at_point_left_even_pixel R10 left_x  # the x of the even pixel tested; + 1 when it ends the scan: the run's left end
param fill_span_at_point_left_even_pixel R15 right_cursor  # the right scan's byte cursor, only handed on
param fill_span_at_point_left_even_pixel R16 right_x  # the point's x, handed on to the right scan
param fill_span_at_point_right_byte R1 scan_count  # pixels left to bitmap_window_right (fill_span_at_point_scan_right's window_right - right_x): - 1 after the low nibble (0: the window edge), dbne in fill_span_at_point_right_odd_pixel
param fill_span_at_point_right_byte R2 fill_color  # a nibble of the fill colour ends the scan (the run ends left of it); handed on to the fill
param fill_span_at_point_right_byte R3 border_color  # a nibble of the border colour ends the scan too
param fill_span_at_point_right_byte R10 left_x  # the run's left end (the left scan's), only handed on to fill_span_at_point_fill_run
param fill_span_at_point_right_byte R15 right_cursor  # the byte read ([R15+], its low nibble tested first); never set from the point's address
param fill_span_at_point_right_byte R16 right_x  # + 2 per byte read, moved back when a pixel ends the scan: the run's right end for the fill
param fill_span_at_point_right_odd_pixel R0 pixel_byte  # the byte just read ([R15+]): its high nibble, the odd pixel, is tested
param fill_span_at_point_right_odd_pixel R1 scan_count  # pixels left to bitmap_window_right: dbne counts it, 0 = the window edge
param fill_span_at_point_right_odd_pixel R2 fill_color  # a nibble of the fill colour ends the scan; handed on to the fill
param fill_span_at_point_right_odd_pixel R3 border_color  # a nibble of the border colour ends the scan
param fill_span_at_point_right_odd_pixel R10 left_x  # the run's left end, only handed on to fill_span_at_point_fill_run
param fill_span_at_point_right_odd_pixel R15 right_cursor  # the right scan's byte cursor, handed on to fill_span_at_point_right_byte
param fill_span_at_point_right_odd_pixel R16 right_x  # - 1 when the odd pixel ends the scan: the run's right end for the fill
param fill_span_at_point_scan_right R2 fill_color  # the fill colour: a nibble of it ends the right scan; handed on to the fill
param fill_span_at_point_scan_right R3 border_color  # a nibble of the border colour ends the right scan
param fill_span_at_point_scan_right R10 left_x  # the run's left end the left scan found, handed on to fill_span_at_point_fill_run
param fill_span_at_point_scan_right R15 right_cursor  # [R15+] reads the bytes right of the point (+ 1 first when the next x is even); fill_span_at_point never sets it
param fill_span_at_point_scan_right R16 right_x  # the point's x: the scan goes from right_x + 1 to bitmap_window_right (already there: straight to the fill); the run's right end at the end
param flood_fill_span_fill_run R10 left_x  # the run's left end x: right_x - left_x + 1 bytes are written from R14 (its address)
param flood_fill_span_fill_run R12 fill_pattern  # the fill colour in all 4 bytes: written a byte, halfword or word at a time (16-byte blocks)
param flood_fill_span_fill_run R15 right_address  # result: the last byte written, the run's right end (R14 + right_x - left_x)
param flood_fill_span_fill_run R16 right_x  # the run's right end x
param flood_fill_span_left_pair R1 scan_count  # pairs left to scan (flood_fill_span's (left_x - window_left) >> 1, + 1 for an odd first pixel): dbne in flood_fill_span_left_pixel
param flood_fill_span_left_pair R2 pixel_count  # left_x - window_left (- 1 for an odd first pixel): bit 0 = one pixel left after the pairs
param flood_fill_span_left_pair R3 border_color  # a pixel of the border colour ends the scan
param flood_fill_span_left_pair R6 window_right  # bitmap_window_right, handed on to the right scan
param flood_fill_span_left_pair R10 left_x  # in: the seed's x; out: the run's left end x (flood_fill_span_set_left_x)
param flood_fill_span_left_pair R12 fill_pattern  # the fill colour in all 4 bytes: a pixel of it (low byte) ends the scan too
param flood_fill_span_left_pair R15 right_address  # out only: the run's right end address (flood_fill_span_fill_run's last byte written)
param flood_fill_span_left_pair R16 right_x  # in: the seed's x (flood_fill_span copies left_x into it); out: the run's right end x
param flood_fill_span_left_pair R17 seed_address  # R14 when flood_fill_span started (the seed pixel): left_x -= seed_address - R14 once the scan stops; the right scan starts at seed_address + 1
param flood_fill_span_left_pixel R0 pixel  # the pixel tested: the farther of the pair flood_fill_span_left_pair read ([-R14], bytes swapped back), or the odd pixel left of the seed
param flood_fill_span_left_pixel R1 scan_count  # pairs left to scan: the dbne here counts it
param flood_fill_span_left_pixel R2 pixel_count  # bit 0: one more pixel ([-R14]) to test after the pairs
param flood_fill_span_left_pixel R3 border_color  # a pixel of the border colour ends the scan
param flood_fill_span_left_pixel R6 window_right  # bitmap_window_right, handed on to the right scan
param flood_fill_span_left_pixel R10 left_x  # in: the seed's x; out: the run's left end x
param flood_fill_span_left_pixel R12 fill_pattern  # a pixel of the fill colour (low byte) ends the scan
param flood_fill_span_left_pixel R15 right_address  # out only: the run's right end address
param flood_fill_span_left_pixel R16 right_x  # in: the seed's x; out: the run's right end x
param flood_fill_span_left_pixel R17 seed_address  # the seed pixel's address: the run's ends are measured from it
param flood_fill_span_right_pair R1 scan_count  # pairs left to scan (flood_fill_span_scan_right's (window_right - right_x) >> 1, + 1 for an odd first pixel): dbne in flood_fill_span_right_pixel
param flood_fill_span_right_pair R2 pixel_count  # window_right - right_x (- 1 for an odd first pixel): bit 0 = one pixel left after the pairs
param flood_fill_span_right_pair R3 border_color  # a pixel of the border colour ends the scan
param flood_fill_span_right_pair R10 left_x  # the run's left end x (the left scan's), only for flood_fill_span_fill_run's length
param flood_fill_span_right_pair R12 fill_pattern  # a pixel of the fill colour (low byte) ends the scan; written by the fill
param flood_fill_span_right_pair R15 right_cursor  # in: the right scan's cursor ([R15+] reads the pair; flood_fill_span_scan_right's right_cursor); out: the run's right end address (flood_fill_span_fill_run's last byte written)
param flood_fill_span_right_pair R16 right_x  # in: the seed's x; out: the run's right end x
param flood_fill_span_right_pair R17 seed_address  # low half: right_x += right_cursor - seed_address when the scan stops
param flood_fill_span_right_pixel R0 pixel  # the pixel tested: the farther of the pair flood_fill_span_right_pair read (bytes swapped), or the odd pixel right of the seed
param flood_fill_span_right_pixel R1 scan_count  # pairs left to scan: the dbne here counts it
param flood_fill_span_right_pixel R2 pixel_count  # bit 0: one more pixel ([R15]) to test after the pairs
param flood_fill_span_right_pixel R3 border_color  # a pixel of the border colour ends the scan
param flood_fill_span_right_pixel R10 left_x  # the run's left end x, only for flood_fill_span_fill_run's length
param flood_fill_span_right_pixel R12 fill_pattern  # a pixel of the fill colour (low byte) ends the scan
param flood_fill_span_right_pixel R15 right_cursor  # in: past the pair just read; moved back onto the run's last pixel; out: the run's right end address (flood_fill_span_fill_run's)
param flood_fill_span_right_pixel R16 right_x  # in: the seed's x; out: the run's right end x
param flood_fill_span_right_pixel R17 seed_address  # low half: right_x += right_cursor - seed_address when the scan stops
param flood_fill_span_scan_right R3 border_color  # a pixel of the border colour ends the right scan
param flood_fill_span_scan_right R6 window_right  # bitmap_window_right: the scan covers window_right - right_x pixels (none: straight to the fill)
param flood_fill_span_scan_right R10 left_x  # the run's left end x (the left scan's, or the seed's at window_left), for flood_fill_span_fill_run
param flood_fill_span_scan_right R12 fill_pattern  # a pixel of the fill colour (low byte) ends the scan; written by the fill
param flood_fill_span_scan_right R15 right_address  # out only: the run's right end address (flood_fill_span_fill_run's last byte written)
param flood_fill_span_scan_right R16 right_x  # in: the seed's x; out: the run's right end x
param flood_fill_span_scan_right R17 seed_address  # the seed pixel's address: the scan's cursor starts at seed_address + 1
param flood_fill_span_set_left_x R3 border_color  # handed on to the right scan
param flood_fill_span_set_left_x R6 window_right  # bitmap_window_right, handed on to the right scan
param flood_fill_span_set_left_x R10 left_x  # in: the seed's x; out: - (seed_address - R14), the run's left end x (R14 on its first pixel)
param flood_fill_span_set_left_x R12 fill_pattern  # handed on to the right scan and the fill
param flood_fill_span_set_left_x R15 right_address  # out only: the run's right end address (flood_fill_span_fill_run's)
param flood_fill_span_set_left_x R16 right_x  # in: the seed's x; out: the run's right end x
param flood_fill_span_set_left_x R17 seed_address  # the seed pixel's address: left_x -= seed_address - R14
param flood_fill_span_set_right_x R10 left_x  # the run's left end x, for flood_fill_span_fill_run
param flood_fill_span_set_right_x R12 fill_pattern  # handed to flood_fill_span_fill_run
param flood_fill_span_set_right_x R15 right_cursor  # in: the run's last pixel's address (where the right scan stopped); out: flood_fill_span_fill_run's last byte written, the same pixel (the run's right end address)
param flood_fill_span_set_right_x R16 right_x  # in: the seed's x; out: + right_cursor - seed_address, the run's right end x
param flood_fill_span_set_right_x R17 seed_address  # low half: right_x += right_cursor - seed_address

# ======== local variables those functions still had by their register ========
local run_frame_start_game 00020F r5 string_cursor  # draw_queued_strings' cursor (the tail jmp 8E8F8): past the last queued string's characters, handed back through *p_cursor; the cursor parameter is the incoming low half the mode handler gets (uint16_t), another variable
local game_mode_start_game 000735 r10 handler_cursor  # start_player_objects' handler_cursor (reset_objects_from_list's: past the last player handler it read from object_handler_list), handed back as list_cursor
local game_mode_start_game 00072C r11 control_register  # set_screen_mode's R11 (its control_register: video_control, 31FF00, the register it works through), only handed to start_player_objects as its object (reset_objects_from_list keeps it only when no object is free)
local game_mode_play_stage 000996,00099C,0009A2,0009A8,0009AE,0009B4,0009BA,0009C0,0009C6,0009CC,0009CF,0009D2,0009D5,0009D8 r1 box_edge  # the stage frame's R1, handed from call to call (update_stage_tile_layers, find_nearby_floor_blocks, continue_to_next_stage_slot, the spawners, check_zones / check_hits' box_edge, check_walls, check_platforms, run_stage_events), then to run_game_mode as its R1 when an event changes the mode: box_edge, as game_mode_game_over / you_failed / event_16 name the same R1 going through the same calls (pressed is the R1 run_game_mode handed in)
local game_mode_play_stage 00098D,000990,000996,00099C,0009A8,0009AE,0009C0,0009CF,0009D5 r3 speed  # the stage frame's R3: update_grey_screen's dbe count (0 unless a player block has 9F set), update_scroll's speed, then through update_stage_tile_layers, find_nearby_floor_blocks, the spawners, check_hits and check_platforms to run_game_mode (its R3, speed) when an event changes the mode; in_play_count is update_player_summary's R3 before the frame
local game_mode_play_stage 00098A,0009D8 r10 event  # run_stage_events' event (stage_event_cursor: the stage's next (scroll position, game mode) record), handed to run_game_mode when the event changed the mode (it hands back the new mode's list_cursor); handed back as list_cursor
local game_mode_continue 000B64 r10 platform  # check_platforms' platform (the last one it tested), handed back as list_cursor (count_text instead when the count runs out or a start continues)
local game_mode_screen_wipe 000D2F r10 platform  # check_platforms' platform, handed back as list_cursor
local game_mode_stage_clear 000DA3 r10 banner_record stage_name_banner  # first frame: stage_name_banners[stage] + 0026CF ("movz.hw 26CF[PC](R0), R10", "add.w #26CF, R10" 000DA3): the stage's name banner record, its screen x and y read with [R10+] (000DAA, 000DAF), then its animation (field 48) for the stage_clear_banner_reveal object
local game_mode_stage_clear 000DAA r10 banner_record  # moved past the screen x ([R10+])
local game_mode_stage_clear 000E8D r10 platform  # check_platforms' platform, handed back as list_cursor (another web than banner_record: check_platforms writes R10 whole first)
local game_mode_game_over 000F00 r10 platform  # check_platforms' platform, handed back as list_cursor while no start begins a new game
local game_mode_game_over 000FF8 r10 handler_cursor  # game_mode_start_game's list_cursor (jmp 6C2 once a start begins a new game): start_player_objects' handler_cursor, handed back as list_cursor
local game_mode_game_over 000FF8 r8 rounds_left  # game_mode_start_game's count (jmp 6C2): clear_player_blocks' dbr counter, 0 once the three player blocks are cleared; handed back as count
local game_mode_character_select 0010E3 r10 platform  # check_platforms' platform, handed back as list_cursor
local game_mode_stage_results 00116F r10 platform  # check_platforms' platform, handed back as list_cursor
local game_mode_stage_change_fade 0011DA,0012C9 r10 platform  # check_platforms' platform on the fade frames (0012C9), handed back as list_cursor; on stage 11, where no frame follows start_stage, start_stage's source (0011DA) reaches the same rsr instead
local game_mode_you_failed 00137E r10 platform  # check_platforms' platform, handed back as list_cursor
local game_mode_stage_select 0014D0 r10 trackball  # read_trackball_step's trackball (player 0's trackball pair, trackball_1_x at 20F062, whose x it adds up), handed back as list_cursor unless start 1 picks the stage (the erased text's cursor then)
local game_mode_event_0C 094875 r10 platform  # check_platforms' platform, handed back as list_cursor
local game_mode_event_0D 09472A r10 platform  # check_platforms' platform, handed back as list_cursor
local game_mode_event_1A 095030 r10 platform  # check_platforms' platform, handed back as list_cursor
local game_mode_event_0F 0945FF r10 platform  # check_platforms' platform, handed back as list_cursor
local game_mode_event_10 09452A,094540,0945B4,0945B7 r10 record  # steps 0-1 spawn_objects_by_handler's record (09452A), steps 2-3 spawn_stage_06_lightning's record (0945B4); handed through update_stage_06_background (its source: the vram script's end on its steps 1, 3 and 5) and back as list_cursor
local game_mode_event_12 094425 r5 x  # 644 ("mov.h #644, R5"): spawn_sign's x for the sign (kind F, y 149E), then handed to run_game_mode as its R5 (next_scroll_position); x, as game_mode_event_14 names the R5 it spawns its sign at
local game_mode_event_12 094432,09444F r8 z  # 168 into the low half ("mov.h #168, R8" 094432): spawn_sign's z, then handed to run_game_mode as its R8, which hands back the new mode's count (09444F): handed back as count
local game_mode_event_13 0943CD r1 clip_right  # update_screen_wipe's clip_right (the bitmap window's right edge it wrote), handed to event_12_stage_frame as its R1 (pressed) on steps 0-1
local game_mode_event_13 0943CD r2 clip_top  # update_screen_wipe's clip_top (the window's top edge), handed to event_12_stage_frame as its R2 (position) on steps 0-1
local game_mode_event_13 0943CD r3 clip_bottom  # update_screen_wipe's clip_bottom (the window's bottom edge), handed to event_12_stage_frame as its R3 (in_play_count) on steps 0-1
local game_mode_event_14 094391 r10 platform  # check_platforms' platform, handed back as list_cursor
local game_mode_event_15 09423E r10 platform  # check_platforms' platform, handed back as list_cursor
local game_mode_event_16 094190 r10 platform  # check_platforms' platform, handed back as list_cursor
local game_mode_event_18 001636 r10 platform  # update_ending's platform (its check_platforms'), handed back as list_cursor
local game_mode_event_19 094074,094076 r10 platform  # event_19_check_rope_bridge's R10 (its check_platforms' platform), from both ways into it (094074 br, 094076 after barred_gate_closing = FF); handed back as list_cursor
local game_mode_event_1C 0950EB r10 platform  # check_platforms' platform, handed back as list_cursor
local event_19_check_rope_bridge 09410B r10 platform  # check_platforms' platform, handed back as list_cursor
local event_12_stage_frame 0944FB r10 platform  # check_platforms' platform, handed back as list_cursor
local update_stage_05_event 094982,09498F,0949FC,094A35,094A46,094AE2 r10 platform  # check_platforms' platform (094982 steps 0-1, 0949FC step 2, 094AE2 step 4; stage_05_event_step_3_frame's and stage_05_event_show_message's platform, their check_platforms', step 3), handed back as list_cursor; on steps 0-1 check_stage_clear (09498F) walks players_finished with it before
local update_stage_05_event 094AC4,094ACA,094AD0,094AD6 r2 scroll_y  # step 4: update_camera_with_shake's result (the shaken camera y, set_camera_velocity's scroll_y), update_scroll's position (what the scroll routines leave), then through update_stage_tile_layers and check_hits (distance); not handed back (position is the other steps' R2)
local player_move 002E23 r0 state  # the player's state ("movz.bw [R18], R0"): indexes player_state_stops_move; then only handed through push_player_out_of_wall (its angle comes back in it) and check_player_step (height_diff) to player_steer (wanted_speed: a leftover that only goes into follow_player_path)
local player_move 002F25,002F2B r1 sink_offset  # update_quicksand_object's / update_sinking_floor's sink_offset (set_speed_from_player_speed's cos_speed and find_floor's y before them), only handed on: player_move_check_edge's / player_dispatch_state's depth
local player_over_edge_hang_finish 004103 r1 projected_depth  # project_object's depth (sprite_offset_y in the low half), handed through check_player_contacts (its health_limit) and not read after: the sprite is queued with the other depth (touched_by's draw_depth + 1 or fall_draw_depth)
local player_climbing_move 004975,00498F r1 cos_speed  # set_climb_speed_from_trackball's cos_speed (set_speed_from_direction's), or set_speed_from_direction's after a push-out (direction 0, speed 3000000): handed to player_climbing_side_step (its cos_speed)
local player_climbing_start_vertical_step 004B83,004B91 r1 step_dz  # 180000 (18 in z) with flags bit 7 (climbing down), negated climbing up: + z is step_target, the z after the climb step (past climb_base_z, or its screen y off 10..D0, blocks it)
local player_climbing_animate_vertical_step 004BED,004BF6 r1 animation_speed  # 4 - climb_speed (|speed_y_int| capped at 3), x 4 with fast_climb: the climb animation's speed, stored in animation_speed before animate
local player_climbing_animate_vertical_step 004C59,004C60 r1 screen_x_shift  # screen_shift (attribute record +4 x z_step), negated when the record's byte 0 (the wall's axis) is 0: added to screen_x
local player_hurt_knockback 00568C r0 knock_direction  # the toucher's direction (24): the player's new direction; 0, 200, 100 or else pick the knockback's axis and sign (speed_y -, +, speed_x +, -); handed to face_movement_direction (facing: only its high half read)
local player_hurt_set_animation 00571B r1 facing_offset  # the hurt animation table's halfword for the player's facing: animation = hurt_animations + it; handed to player_hurt_fly (its depth, find_floor's R1 there)
local player_floor_8_sequence_struggle 00943A r0 trackball_angle  # trackball_x as angle_of_vector's dx (dy trackball_y), back as the angle: target_heading; one variable (partial writes) with target_heading - 280 & 3FF (below 101 clears escape_progress_low), speed_int >> 4 (added to escape_progress_low), escape_progress_low >> 8 capped at the escape animation's last frame (the sprite frame index) and face_movement_direction's facing
local push_player_out_of_wall 00B7A3 r0 drift_angle  # states 25/26: scroll_drift_direction + 200 & 3FF, minus the wall's sonic_bounce_records direction, & 3FF: below 200 picks the side (drift_bounce_direction - 80, else + 80); then push_player_out_of_wall_deflect's angle, handed back as angle
local push_player_out_of_wall 00B771,00B7E9 r1 wall_push_direction  # push_player_out_of_wall_deflect's push_direction (the sonic_bounce_records word for the wall's direction byte), handed back in push_direction's low half
local push_player_out_of_wall 00B771,00B7E9 r11 wall_record_index  # push_player_out_of_wall_deflect's record_index (the wall's direction byte x 3), handed back as record_index
local player_steer_speed_from_trackball 00B984 r0 wanted_speed  # trackball_x + push_x, then with input_y squared, square-rooted, squared, x 1.5, at most 5000000; / 4 with flags bit 31 (0 with player flags bit 2), scaled by the sink depth on floor type A, x 60 >> 8 on floor type 4: player_steer_approach_speed's wanted_speed
local find_floor 00C76A r0 object_x  # the object's x (4C), tested against each nearby block's x min/max; then the found block's type index (byte 12) into floor_types and find_floor_set_floor_z's x (the slope factor on a slope; find_floor_no_block's: unchanged), handed back as x
local find_floor 00C763,00C775 r11 nearby_cursor  # walks nearby_floor_blocks: past the count word ([R11+] 00C763), one offset word per block ([R11+] 00C775); then find_floor_no_block's / find_floor_set_floor_z's cursor (the floor_slopes record on a slope), handed back as cursor
local find_floor 00C775 r2 candidate_offset  # the nearby_floor_blocks entry: the candidate block's offset from floor_blocks + 2; the block found is stored in floor_block and handed back as block_offset
local find_floor 00C766,00C7F1 r3 block_slope_height  # find_floor_no_block's (0 for no_floor_block) or find_floor_set_floor_z's slope height of the block found, handed back as slope_height
local find_floor_no_block 00C7BD r3 slope_height  # find_floor_set_floor_z's slope_height for no_floor_block (0: flat), handed back as slope_height
local follow_player_path_run 00DD65 r0 target_speed  # 0 while path_wait counts down (speed cleared below 20); else follow_player_path_next_record's / follow_player_path_aim_at_point's target speed: returned (follow_player_path's target_speed, player_steer's wanted_speed)
local follow_player_path_target_speed 00DE30,00DE46 r0 target_speed  # path_speed << 16, or 0 when it is at most 200000 and speed_int is below 20 (speed cleared): returned as the path's target speed
local check_player_contacts 00E5B4 r1 max_health  # max_health_by_setting[joined_health_setting x 2] in the low byte (health_limit's upper bytes above): health is capped at it (ring kinds 3, 2A); handed back as health_limit
local flame_machine_draw 00FE52 r11 cursor  # unproject_object's cursor (find_floor's), only handed to flame_machine_destroyed (its block)
local flame_machine_draw 00FE52 r2 block_offset  # unproject_object's block_offset (find_floor's R2), or play_sound's R2 on the hit and flash frames: only handed to flame_machine_destroyed (its free_count)
local flame_machine_draw 00FE52 r3 slope_height  # unproject_object's slope_height (find_floor's), only handed to flame_machine_destroyed (its first)
local lava_flow_move_step 087727,087770 r11 flame  # animate_all_parts' part (past the last part), then spawn_lava_flame's flame (the flame object; unchanged when none is free): handed to extend_lava_flow (its block)
local lava_flow_move_step 0876DE r3 octant  # compute_camera_ground_speed's octant (the caller's R3 when the camera velocity did not change), only handed to spawn_lava_flame (its random_value, which next_random replaces)
local tornado_move_step 089448,08944E r0 old_head  # project_object's screen_y, then add_to_zone_list's old_head (the zone list head before the push): only handed to tornado_throw_whirlwinds (its spawned: only the upper bytes survive)
local tornado_move_step 089429,089454 r11 object  # find_floor's cursor, then tornado_throw_whirlwinds' object (spawn_whirlwind's block when a whirlwind is thrown): handed to spawn_tornado_hazard (its hazard, which it allocates)
local tornado_move_step 089429 r2 block_offset  # find_floor's block_offset, only handed to tornado_throw_whirlwinds (its free_count)
local tornado_move_step 089429 r3 slope_height  # find_floor's slope_height, only handed to tornado_throw_whirlwinds (its whirlwind, which spawn_object_with_pieces replaces)
local geyser_front_rise_step 08C9A1 r11 geyser  # spawn_listed_geyser's geyser (the listed geyser it spawned; unchanged when none was), handed to spawn_geyser_line (its line, which it allocates)
local propeller_machine_chase_step 08D4FA,08D505 r1 move_speed  # camera_slope_speed_int x catch_up_factor >> 8, at least base_speed (base_speed while screen_x is at or past screen_threshold): chaser_speed = it, y moved by it << 12
local propeller_machine_chase_step 08D52A,08D52E,08D535 r1 next_frame  # animation_frame + 1, 0 past the animation's last frame (its first word): stored in animation_frame, picks the step (frame_entry)
local enter_ranking_initials_count_down 08ECE2 r0 countdown_x  # the entry block's screen_x (low half, over button_bit's high half): queue_string_at's x for the countdown seconds text
local character_chooser_idle 08FD0C r1 character_bit  # character_taken_bits[the chooser's character]: its bit in characters_taken (set: taken, the chooser turns); on a step frame the idle_step, which indexes chooser_idle_steps; handed to queue_sprite (depth: not read)
local lava_rock_update 091993 r11 parts  # allocate_object_2's pool 2 block (D000 sprite flags): lava_rock_parts (10 halfwords) copied in, stored in part_list; unchanged (and the rock deleted) when none is free
local draw_line_shallow_even_pixel 09AA50 r0 pixel_byte  # the pixel pair's byte & high_nibble_mask | low_nibble_color (the even pixel set): written, or handed to draw_line_shallow_set_high_nibble (its pixel_byte)
local flood_fill_span 09BDA5,09BDB1,09BDBA r15 right_address  # the scan pieces' right_address (flood_fill_span_fill_run's last byte written, the run's right end): handed back
local flood_fill_span_left_pair 09BDCC,09BDF3 r15 right_address  # flood_fill_span_left_pixel's / flood_fill_span_set_left_x's right_address (the run's right end address): handed back
local flood_fill_span_left_pixel 09BDD7,09BDE0,09BDEF,09BDF3 r15 right_address  # flood_fill_span_left_pair's / flood_fill_span_set_left_x's right_address (the run's right end address): handed back
local flood_fill_span_set_left_x 09BDFB r15 right_address  # flood_fill_span_scan_right's right_address (the run's right end address): handed back
local flood_fill_span_scan_right 09BE04 r15 right_address  # flood_fill_span_fill_run's right_address when the seed is already at window_right (no scan): the run's right end, handed back
local flood_fill_span_set_right_x 09BE5A r15 right_address  # flood_fill_span_fill_run's right_address (the last byte written, the run's right end), handed back as right_cursor

# ======== the last locals the C named by their register (build/naming/locals6_1..3.txt) ========
local find_floor 00C7E0 r0 floor_type_index  # 00C7E0 "movz.bw 12[R10], R0": the found block's type index (byte 12), indexes floor_types like the platform path's (00C6EE); then find_floor_set_floor_z's x (the slope factor on a slope, else unchanged), handed back as x: one variable with floor_type_index
local project_point 00C560 r7 screen_x  # point_to_screen's screen_x (projected x - view_x): its low half handed back in point_x
local project_point 00C560 r8 screen_y  # point_to_screen's screen_y ((projected y + z) >> 16 - view_y): its low half handed back in point_y
local delete_object_and_children 08E0EA r2 free_stack  # free_object_2's free_stack (free_object_stack_2) after a pool 2 child, the usual kind (pieces, shadows); delete_object's previous after a pool 1 child (08E105), int_default's dest on the overflow trap (08E0FF); never read, handed back in R2 (block_offset)
local spawn_debris 08E782 r3 random_value  # next_random's random_value for each piece: its low byte added to sprite_scale, & 0x3FFFF off speed_z, then the direction, speed and frame set bits; before the loop spawn_objects' first (08E734), copied to r14
local spawn_objects_by_type 08DC70 r0 scroll  # "movz.hw 6435[R25], R0": scroll_position, compared with the record's trigger_scroll (08DC80 "cmp.w R0, R1"); then spawn_object_with_pieces' spawned (08DCAE: only the low byte cleared on failure), tested (uint8_t) at 08DCB1
local unproject_object 08DFEF r2 scaled_x_copy  # "mov.w R0, R2": projected_x << 8 kept for y = (scaled_y + it) * 0xB5 (08DFF5) once R0 became x's difference; then find_floor's block_offset (the caller's R2 when no block is found), handed back as block_offset
local build_sprite_list 09A44E r2 scaled_dx  # "mul.w R8, R2": the sprite part's x offset (signed byte) times scale_x, shifted by scale_shift and added to screen_x for the entry's x
local init_sprites 099F2D r0 end_mark  # init_sprites_from_frame_wait's end_mark (init_sprite_hardware's FFFF, low half), handed back as end_mark
local init_sprites_from_frame_wait 099F35 r0 end_mark  # init_sprite_hardware's end_mark (FFFF, the end mark of the sprite list buffers), handed back as end_mark
local character_chooser_turn 08FF03 r1 taken_bit  # "movz.bw 90354[PC](R0), R1": character_taken_bits[the middle portrait's character], set in characters_taken turns on again; otherwise queue_sprite's depth (08FF4E, 08FF6F, 08FF91), which it only hands on to the next queue_sprite
local start_character_chooser 08F9DC r2 free_count  # spawn_object_with_pieces' free_count (08F9DC, 08FA30), whose high half the next one's no-free-objects test reads; count_free_characters' character (08F9AB) and show_nbg0_picture's copy_entry (08F9CC) before that; never read here
local ending_tower_animate 01BF3A r11 spawned_object  # handed to run_timed_spawns_2 as object (the slot allocate_object_outside_loop fills; the value passed only stays when none is free): R11 at entry, or run_vram_script's dest on the first frame
local draw_map_markers_and_trail 095D31 r11 badge_offset  # draw_map_stage_markers' badge_offset (the blinking marker's offset in map_number_badge_sprites, else the caller's), only handed on to draw_map_trail_dot as its object
local draw_ranking_title 08F578 r1 depth  # queue_sprite's depth (screen_y + 0x10 clamped) after each letter, handed to the next queue_sprite; its high half goes into layout_offset
local run_sega_logo 09818A r0 end_mark  # init_sprites' end_mark (FFFF), handed to clear_bitmap_layer as is_8bpp (not 0: the 8 bpp bitmap)
local update_scroll 016042 r0 check_result  # is_direction_in_quadrant's found flag, then each is_player_facing_scroll / is_player_pushing_edge result (low byte tested for players_facing_scroll / players_pushing_edge, handed to the next test): one variable with check_result (016039, the 0 given to set_screen_push_edges)
local update_scroll 015F8E,015F99,015FA9,015FB2,015FB5,01696F,01699C,016A23,016A41,016A4D,016A74 r2 position  # "movz.hw 6435[R25], R2": scroll_position, compared with scroll_end; then what update_scroll_at_end, scroll_at_scroll_speed, scroll_slowing_down, scroll_speeding_up or set_camera_velocity leave in R2 (other_list, scroll_y), handed back as position: one variable with position
local attract_ranking 0002CE r11 dest  # clear_page_0_and_scroll's dest (clear_blocks' write position, 20F300 at the end), only handed to start_ranking_display as display (kept only when no object is free)
local boot 0000B8 r4 scale  # run_frame's scale (draw_queued_strings' last string scale), handed back to it every frame; init_system's polynomial (0000A9) before, start_test_mode's / run_test_mode's color (0000BD, 0000C9) in test mode
local boot 0000B8 r5 cursor  # run_frame's cursor (draw_queued_strings' string cursor), handed back to it every frame; init_system's words_left (0000A9) before
local boot 0000B8 r9 height  # run_frame's height (draw_queued_strings' height word), handed back to it every frame; start_test_mode's / run_test_mode's blocks_per_row (0000BD, 0000C9) in test mode
local init_system 000413 r0 sound_hold  # init_sound's sound_hold (0), handed to load_settings as its crc (a leftover); then reset_objects' block and read_controls' inputs (000446-000458), which nothing reads
local init_system 000446,00044C,000452,000458 r0 inputs  # reset_objects' block, which read_controls takes in as its inputs and hands back (three times); nobody reads it after
local run_frame 0000F3 r10 message_text  # draw_attract_credits' message_text, handed to the mode handler (its trackball_port), build_sprite_list (sprite_flags) and draw_queued_strings (width), as run_frame_start_game's message_text
local run_frame 0000F3 r11 text_position  # draw_attract_credits' text_position, handed to the mode handlers (their trackball_cursor) and run_frame_start_game as text_position
local start_test_mode 09C95F r0 clear_value  # clear_text_layer's value (clear_blocks' fill, 0), handed to clear_bitmap_layer as is_8bpp
local start_test_mode 09C944 r8 count  # wait_for_frames' count (read_controls' counter, 0); then clear_bitmap_layer's row_pairs_left (09C965), handed to run_test_mode as count
local test_memory 09CC24 r0 clear_value  # clear_text_layer's value (clear_blocks' fill, 0), handed to clear_bitmap_layer as is_8bpp
local test_memory 09CBF7 r4 font_pixels  # load_text_font's pixels (expand_font's R4), handed to clear_bitmap_layer as color (09CC2A), which clears its low byte and gives back the fill colour; not read after
local eggman_speech_run 0857AE r11 block  # spawn_eggman_booth's R11 block (the last block spawn_object_with_pieces allocated), or spawn_message_box's R11 box (also an allocated object); both are tail calls, nothing reads it
local metal_wall_update 00F901 r11 allocated  # scatter_rings_random's R11 allocated (the last block scatter_rings allocated), else the caller's; only handed to spawn_debris (its block, which spawn_objects replaces)
local push_crate_break 01130B r11 allocated  # push_crate_release_all_rings' allocated (drop_rings' R11, scatter_rings_random's last allocated block); handed to push_crate_move as its cursor, which move_object_projected replaces
local push_crate_move 01130E r3 slope_height  # move_object_projected's R3 slope_height (find_floor's slope part of floor_z), handed through delete_if_off_screen (slope_height); then push_crate_release_rings' seed (drop_rings' shadow in)
local spike_panel_update 00F20B r0 chaser_distance  # face_object's result: distance_to_object's distance to chaser_object (r14), also nearest_player_distance; only handed to spawn_debris as its R0 (spawned, replaced by spawn_objects)
local spike_panel_update 00F1F1 r11 cursor  # unproject_object's R11 cursor (find_floor's), from the panel and from part 1 (00F343); handed to spawn_debris as its block
local flame_machine_arm_update 0101CE r11 cursor  # unproject_object's R11 cursor (find_floor's); only handed to flame_machine_arm_take_disc (its block, spawn_object_with_pieces')
local flame_machine_arm_update 0101CE r2 block_offset  # unproject_object's R2 block_offset (find_floor's floor block offset); only handed to flame_machine_arm_take_disc (its free_count)
local flame_machine_arm_update 0101CE r3 slope_height  # unproject_object's R3 slope_height (find_floor's); only handed to flame_machine_arm_take_disc (its disc, spawn_object_with_pieces' object)
local flame_machine_cannon_update 0103F0 r11 cursor  # unproject_object's R11 cursor (find_floor's), then handed in and out of spawn_object_with_pieces (01043A, its block: the last block allocated); never read
local flame_machine_cannon_update 0103F0 r2 block_offset  # unproject_object's R2 block_offset (find_floor's), then handed to spawn_object_with_pieces (01043A, its free_count comes back); never read
local vine_bar_update 00EF53 r11 cursor  # unproject_object's R11 cursor (find_floor's); handed to spawn_sign (00EFC5), whose R11 sign comes back
local spiked_press_start 0847F8 r0 x  # find_floor's R0 x (out: the found block's floor type index, the slope factor on a slope, or the x when no block holds it); only handed to spawn_eggman_booth (its spawned, replaced)
local spiked_press_start 0847F8 r11 cursor  # find_floor's R11 cursor; handed to spawn_eggman_booth (084816), whose R11 block (the last block allocated) comes back
local spiked_press_start 0847F8 r2 block_offset  # find_floor's R2 block_offset; handed to spawn_eggman_booth (084816), whose R2 free_count comes back
local spiked_press_start 0847F8 r3 slope_height  # find_floor's R3 slope_height; only handed to spawn_eggman_booth (its booth, spawn_object_with_pieces' object)
local lava_flow_segment_follow 087890 r11 flame  # spawn_lava_flame's R11 flame (allocate_object's new flame, unchanged when none is made); handed to extend_lava_flow as its block, which it replaces
local rock_wall_update 013263 r11 cursor  # unproject_object's R11 cursor (find_floor's); handed to drop_rings (0132E9), whose R11 allocated comes back
local rock_wall_update 013263 r3 slope_height  # unproject_object's R3 slope_height (find_floor's), handed through delete_if_off_screen (013269, slope_height); then drop_rings' shadow (in: scatter_rings_random's random_value)
local ice_launcher_update 011CE7 r11 cursor  # unproject_object's R11 cursor (find_floor's); handed to spawn_object_with_pieces (011D77), whose R11 block comes back; never read
local snow_mound_update 013056 r11 cursor  # move_object_projected's R11 cursor (find_floor's); handed to drop_rings (0130A2), whose R11 allocated comes back
local snow_mound_update 013056 r3 slope_height  # move_object_projected's R3 slope_height (find_floor's), handed through delete_if_off_screen (013062, slope_height); then drop_rings' shadow
local centipede_chase 0123CC r0 screen_y  # move_object_projected's R0 screen_y; handed to add_to_touchable_list (012485), whose old_head comes back and goes to face_nearest_player (its direction, replaced)
local centipede_chase 0123CC r11 cursor  # move_object_projected's R11 cursor (find_floor's); handed to centipede_move (its block) and spawn_centipede_sand_puff (its block)
local centipede_chase 0123CC r2 block_offset  # move_object_projected's R2 block_offset (find_floor's); through centipede_hop (01253A, its z: fall_to_floor's while still falling) to spawn_centipede_sand_puff (its free_count)
local centipede_chase 0123CC r3 slope_height  # move_object_projected's R3 slope_height (find_floor's); handed to face_nearest_player (01248B, its octant, unread after) and spawn_centipede_sand_puff (its puff)
local centipede_leave 0123E6 r11 cursor  # move_object_projected's R11 cursor (find_floor's); handed to centipede_move (its block)
local centipede_segment_follow 01257E r0 path_cursor  # centipede_follow_path's result (the address after the path entry it copied), as centipede_chase's path_cursor; before it move_object_projected's screen_y (012561), after it centipede_hop's height (012581); only handed to spawn_centipede_sand_puff (spawned)
local centipede_segment_follow 012561 r11 cursor  # move_object_projected's R11 cursor (find_floor's); handed to spawn_centipede_sand_puff (its block)
local centipede_segment_follow 012561 r2 block_offset  # move_object_projected's R2 block_offset (find_floor's); through centipede_hop (012581, its z) to spawn_centipede_sand_puff (its free_count)
local centipede_segment_follow 012561 r3 slope_height  # move_object_projected's R3 slope_height (find_floor's); handed to spawn_centipede_sand_puff (its puff)
local centipede_segment_wait 012581 r0 height  # centipede_hop's R0 height (fall_to_floor's floor_z - z while the hop timer is 0), else what run_objects left; only handed to spawn_centipede_sand_puff (spawned)
local centipede_segment_wait 012581 r2 z  # centipede_hop's R2 z (fall_to_floor's z while still falling), else what run_objects left; only handed to spawn_centipede_sand_puff (free_count)
local centipede_tail_follow 0125E9 r0 path_cursor  # centipede_follow_path's result (the address after the path entry it copied), as centipede_chase's path_cursor; before it move_object_projected's screen_y (0125A8), after it centipede_hop's height (0125EC); only handed to spawn_centipede_sand_puff (spawned)
local centipede_tail_follow 0125A8 r11 cursor  # move_object_projected's R11 cursor (find_floor's); handed to spawn_centipede_sand_puff (its block)
local centipede_tail_follow 0125A8 r2 block_offset  # move_object_projected's R2 block_offset (find_floor's); through centipede_hop (0125EC, its z) to spawn_centipede_sand_puff (its free_count)
local centipede_tail_follow 0125CA r3 octant  # direction_to_object's R3 octant (toward segment_ahead) while following; move_object_projected's slope_height (0125A8) while centipede_command is 1; only handed to spawn_centipede_sand_puff (its puff)
local centipede_tail_wait 0125EC r0 height  # centipede_hop's R0 height (fall_to_floor's floor_z - z while the hop timer is 0), else what run_objects left; only handed to spawn_centipede_sand_puff (spawned)
local centipede_tail_wait 0125EC r2 z  # centipede_hop's R2 z (fall_to_floor's z while still falling), else what run_objects left; only handed to spawn_centipede_sand_puff (free_count)
local sand_rock_update 012EA9 r11 cursor  # unproject_object's R11 cursor (find_floor's); handed to drop_rings (012EEE), whose R11 allocated comes back
local sand_rock_update 012EA9 r3 slope_height  # unproject_object's R3 slope_height (find_floor's), handed through delete_if_off_screen (012EB5, slope_height); then drop_rings' shadow
local mine_fall 013C77 r0 height  # fall_to_floor's R0 height (floor_z - z while above the floor, 0 on it); handed to add_to_touchable_list (013CBC, old_head comes back unread) or spawn_mine_splash (its spawned)
local mine_fall 013C66 r11 cursor  # move_object_projected's R11 cursor (find_floor's); handed to spawn_mine_splash (its block)
local mine_fall 013C66 r2 block_offset  # move_object_projected's R2 block_offset (find_floor's), handed through delete_if_off_screen (013C6C, block_offset) and fall_to_floor (013C77: its z replaces it while still falling); then spawn_mine_splash's free_count
local mine_fall 013C66 r3 slope_height  # move_object_projected's R3 slope_height (find_floor's), handed through delete_if_off_screen (013C6C); then spawn_mine_splash's splash
local spike_gate_beam_update 015630 r11 cursor  # move_object_projected's R11 cursor (find_floor's); handed to spawn_debris (its block)
local spike_gate_pillar_update 015544 r11 cursor  # unproject_object's R11 cursor (find_floor's); handed to spawn_debris (its block)
local player_move_with_scroll 009734 r0 screen_y  # project_object's R0 (the new screen_y), handed to player_steer, whose wanted_speed only reaches follow_player_path from it
local player_move_with_scroll 0097B8 r1 depth  # keep_player_on_screen's depth, through animate (frame) into queue_sprite and back through *p_depth; one variable with depth. Before it the register holds the drift step along y (cosine x speed >> 8, taken off y) and in state 26 the direction 100/300, then heading_16 for the turn tests
local player_state_floor_8_sequence 009194 r1 depth  # player_move_with_scroll's depth, through animate (frame) into player_dispatch_state's depth when the player dies (state 16)
local player_state_pulled 008EF9,008E64 r1 depth  # compute_ground_speed's R1 (a speed table entry), through play_sound (write_index) into player_animate_skidding's depth; one variable with depth (player_move_with_scroll's, handed to player_dispatch_state)
local player_state_tripped 008709 r1 depth  # project_object's depth, handed to check_player_contacts (its health_limit, the caller's R1 back on most paths); not read after
local compute_ground_speed 00BD73,00BD96 r1 speed_table_entry  # heading_speed_factors[rounded_heading >> 6] (ground_speed = speed >> 8 x it), then on a slope floor_slope_direction_tables[slope x 4] (offset of the slope's pairs in slope_speed_direction_pairs); handed back
local player_place 002BD3 r0 player_offset  # place_player_at_restart_point's player_offset, through find_floor's x (it leaves the floor type index, or the caller's R0) into start_climb_at_spawn's climbed_height, where only its high half survives
local player_update 002D0F r1 digit_attribute  # player_hud_update's digit_attribute (in/out, the handler's R1 at entry), handed to player_dispatch_state as its depth
local player_update 002D0F r11 text_position  # player_hud_update's text_position (in/out, the handler's R11 at entry), through check_scene_states (its object: spawn_player_ride's object for event 1C) into player_dispatch_state's cursor
local player_state_stunned 00730D,007317,007323 r0 stunned_frame  # 8410, 8450 (kind 1) or 83D0 (kind 2): the character's stunned sprite_frame
local player_state_tumbling 006917,006928,00693B r1 knocked_out_palette  # A90, AA0 (kind 1) or A80 (kind 2): ORed into the low 12 bits of sprite_palette with the knocked_out_animations
local player_animate_jumping 0036A3,0036A7,0036AC r0 sprite_flags  # sprite_flags with bits 6-7 replaced by the take-off step's flags, written back; first (00368D) the facing that picks the per-facing offset in jump_animation
local player_state_hurt 0058B8,0058C6 r0 gravity  # substate 4: 0x6000 (/4 on floor type 4) added to speed_z
local player_state_over_edge 003EC8,003F7B,003F27,003F3E,004294,0042A0 r1 depth  # project_object's depth, through animate_with_flags (frame) into check_player_contacts (health_limit); one variable with depth (fall_draw_depth / the toucher's draw_depth + 1 for queue_sprite_at_depth)
local player_state_teetering 003AA4 r1 player  # the object's player number: indexes player_status for new_status; also update_ledge_pullback's cos_speed, handed with it to check_player_contacts (health_limit)

# ======== the last object fields by their offset and the fields read or written at once (build/naming/varfields_1.txt, wide_1.txt) ========
local spawn_object_groups_build_group 09137A r11 object object.group_member  # allocate_object_outside_loop's member, filled from the stage_object_records template: 96 depth_offset (0913DE mov.h [R12+], 96[R11]; + group_member_count x group_depth_step, 091462 add.h), 98 normal_palette (0913F9 its palette 6), 91 silent_row (091400 group_silent), 8A group_slot (091413 group_slot_index)
local spawn_object_groups_2_build_group 09399A r11 object object.group_member  # allocate_object_outside_loop's plank: 96 depth_offset (093A0A mov.h [R12+], 96[R11]), 9A previous_member (093A49), 8A group_slot (093AA2 group_slot_index)
scoped object group_member 9A previous_member u16 object.group_member  # group_previous_member (FFFF, then each member once made: 093A49 mov.h 70F6[R25], 9A[R11], 093A51 mov.h R11, 70F6[R25]): the member made before it; the planks read it as collapsing_bridge_plank 9A previous_plank
local spawn_ring_rows 01E3F8 r11 object object.row_ring  # allocate_object_outside_loop's ring, filled from its stage_object_templates record; made current_object for its z (01E498 mov.w R11, R20); then allocate_object_2 puts its shadow in R11 (another definition, not typed)
scoped object row_ring 80 glint_timer u16  # ring_row_next_80 (0, + 20 a ring: 01E459 mov.h 650C[R25], 80[R11]): the rings of a row start with staggered values; ring_spin writes 78 there at each glint (ring_spin 80 unused_glint_timer, mov.h), carried_ring_keep_spinning as two bytes (carried_ring 80/81); no reader found (probably a glint timer)
scoped object row_ring 82 hover_height u16  # stage_object_template word 8 (01E41F mov.h [R12+], 82[R11]): z = floor_z - it (01E4A7 sub.h 82[R20], 42[R20]), every ring of the row floats this high; carried rings keep reading it (carried_ring 82 hover_height)
local crawler_segment_update 0129E1 r20 segment object.crawler  # R20 (current_object), only typed: delete_if_off_screen changes it only when it deletes the piece, and then the function returns (0129EA be 12ACF); typed again, the parent read at 012A06 (crawler 26 parent: object.crawler_head) is the head and its 80 (012A0A test.b 80[R0]) is crawler_head over_pit, as at 0129DB before the call
local spawn_chaser_back_zone 082E87 r11 zone object.chaser_back_zone  # allocate_object's object: its 80 (082EAE mov.h [R12+], 80[R11], the object_steps record's limit) is chaser_back_zone 80 end_position, which chaser_back_zone_update compares with scroll_position (082ED3 cmp.h 80[R20])
local door_roller_update 082A9D r20 roller object.door_roller  # R20 (current_object), only typed: after the state's jump table (door_roller_drop may jmp delete_current_object, 082B26, which leaves R20 = the same block) it is still the roller: 83 off_screen (082AA1 test.b 83[R20]) and 80 state (082AB9 test.b 80[R20])
local handle_rod_init 011484 r15 grip_handle object.handle_rod_handle  # the first spawned object's child (011484 mov.h 28[R3], R15): the second handle (handle_rod_handle_init from the loop), made the grip: 81 grip = 1 (0114FF), 86 rod = the rod (011505 mov.h R23, 86[R15]), 94 the pivot's x, hitbox 10, kind 1A
scoped object icicle_shadow 86 blink_timer_word u16  # blink_timer as a halfword (its high byte 87 stays 0): drop_icicle_row clears it on the shadow it makes (0887FE mov.h #0, 86[R11]), icicle_shadow_follow sets 2 (088E45 mov.h #2, 86[R20]) when the byte count runs out (088E3F dec.b 86[R20]); same place as blink_timer u8, which becomes its overlay (as geyser_front 98)
local eel_anchor_stretch 014C23 r20 piece object.eel_piece  # R20 (current_object), only typed: each piece's first part down the chain (014C23 mov.h 28[R20], R20, past the anchor's three parts): the eel_segment_move pieces, the last one the head (eel_head_move); it gets the anchor's direction, speed and stretch_step (014C31 mov.b 81[R23], 81[R20])
scoped object eel_piece 81 stretch_step s8  # as eel_segment 81 and eel_head 81 (every piece, segment or head): eel_anchor_stretch copies the anchor's stretch_step into it every frame (014C31)
local spreading_flame_burn 084F2B r11 fireball object.init_fireball_flight  # allocate_object's fireball (this can replace 'local spreading_flame_burn entry r11 fireball', the same variable named by its entry value): made current (084F35 movea.b [R11], R20), 96 target_x = its x_int (084F44), 9A target_y = its y_int (084F4B) - target_dy (084F69 sub.h), 7F state = 0 (084F74), then start_fireball_flipped -> init_fireball_flight reads them
local check_player_contacts 00EA4C r11 rod object.handle_rod  # kind 1A, the touched grip (handle_rod_handle): its parent (00EA4C mov.h 26[R10], R11; handle_rod_handle 26 parent: the rod), handed back in cursor; the climb (state 27) starts only while the rod's angle (00EA50 cmp.h #300, 82[R11]: handle_rod 82 angle) is 300, level
scoped object player_state_stuck 6A touched_by u16 object.propped_board  # state 1D comes from touching kind 11 (check_player_contacts, 00E8F7), which only propped_board_start gives (0824F1 mov.b #11, 47[R20]); the stuck player is put on no touch list, so it stays the board: its speed_z (0076C1) and shake_dx (0076EB add.h 80[R10], E[R20]); player_state_stuck_on_object falls in this family by its name but never reads it
scoped object player_stuck_wait_release 6A touched_by u16 object.propped_board  # as player_state_stuck 6A: waits for the board's shake_dx to be 0 (0077FD test.h 80[R10], one halfword the C showed as var_80 | var_81)
local game_mode_stage_clear 000DC8 r11 object object.stage_clear_banner  # the second banner (CLEARED): handler cleared_banner_wait (000DE0, a function of no family), which hands it to stage_clear_banner_reveal; its sparkle_offset is set to 10 in one halfword (000DEE mov.h #10, 80[R11]: stage_clear_banner 80), as the first banner's
scoped object row_ring 7E spawn_scroll_position u16  # spawn_ring_rows (01E452 "mov.h 6435[R25], 7E[R11]"): scroll_position when the row spawned it; nothing reads it (health's byte and the next one; the rings never use health)
scoped object ring_spin 7E spawn_scroll_position u16     # as carried_ring 7E: spawn_stage_template_0/spawn_stage_template store scroll_position here (01ED6A, 01EE58); nothing reads it
local spawn_stage_template_0 01ED12 r11 object object.ring_spin    # allocate_object's object: stage_object_templates[0], handler ring_spin (stage_template_0_handler)
local spawn_stage_template 01EDF1 r11 object object.ring_spin      # allocate_object's object: any stage_object_templates record (all six are rings; ring_spin is template 0's handler; this function never runs)
scoped object electric_pole 80 frame_index_word s16  # frame_index (80) with last_frame (81, 0 while the pole rises) as one halfword: electric_pole_draw 00F64D "cmp.h #A, 80[R23]" + blt, the risen test; every other access is a byte (frame_index becomes its overlay, last_frame a part)
scoped object spiked_roller 80 state_word u16  # state (80) and holding_back (81) as one halfword: spiked_roller_start 080324 "mov.h 70F6[R25], 80[R23]" puts roller_first_state (the kind's first_state: 0, C, E, 0, 0, B) in the leader's state and clears holding_back; the only halfword access (state becomes its overlay, holding_back a part)
scoped object machine_fire 92 projected_x_fraction u16  # the low word of fixed_projected_x (16.16; projected_x_whole at 94): machine_fire_update 083645 "mov.h #0, 92[R20]" when it sets the whole part from projected_x (as geyser_front 98)
scoped object propped_board 34 shake_strength_low u8  # the low byte of shake_strength: propped_board_update 082619 "mov.b #3C, 34[R20]" when it is touched (the high byte stays 0); every other access is the halfword (an overlay of shake_strength, as title 32 timer_low)
scoped object bridge_fire 94 projected_x_fraction u16  # the low word of fixed_projected_x (16.16; projected_x_whole at 96): bridge_fire_start 0872A0 "mov.h #0, 94[R20]" when it sets the whole part from projected_x
scoped object group_slot_trigger 80 next_trigger_low u16  # the low half of next_trigger (its ROM address): group_slot_trigger_update 091FA3 "mov.h R10, 80[R20]" steps to the next record rewriting only the low half (091F63 reads the whole long); an overlay of next_trigger
scoped object handle_rod_handle 80 index_word u16  # index (80) and grip (81) as one halfword: handle_rod_init 0114D2 "mov.h R0, 80[R20]" gives each handle its number (handle_count down to 1) and clears grip, which the loop's end sets on the grip; the handles read index as a byte (011822 movz.bw). index becomes its overlay, grip a part
scoped object checker_pad 80 frame_index_word u16  # frame_index (80) and the byte after it (nothing writes 81: 0) as one halfword: checker_pad_animate 015274 "movz.hw 80[R20], R0", the checker_pad_frames index; every other access is a byte (frame_index becomes its overlay)
scoped object rising_saw 94 projected_x_fraction u16  # the low word of fixed_projected_x (16.16; projected_x_whole at 96): rising_saw_init 08B418 "mov.h #0, 94[R20]" when it sets the whole part from projected_x
scoped object grate_step_debris 94 projected_x_fraction u16  # the low word of fixed_projected_x (16.16): spawn_grate_step_debris 08BBCD "mov.h #0, 94[R11]" on each debris piece (handler grate_step_debris_fly, so this family's view) as it sets the whole part
scoped object build_saw_shaft_base 94 projected_x_fraction u16  # the low word of fixed_projected_x (16.16): build_saw_shaft_base 08BCD0 "mov.h #0, 94[R20]" when it sets the whole part from projected_x
scoped object character_chooser 10 player_block u32 player  # start_character_chooser 08FA36 "mov.w R18, 10[R3]": the player block of the player choosing (current_player), as ranking_entry 10; nothing reads it back as a long. Its bytes 12/13 keep their names (name_changed, turn_direction: parts of it)
scoped object join_character_chooser 10 player_block u32 player  # start_join_character_chooser 0900D5 "mov.w R18, 10[R3]" on the player's join_chooser_blocks block (typed object.join_character_chooser): the joining player's block; nothing reads it back as a long
scoped object join_character_chooser 13 turn_direction s8  # needed with player_block (13 lies inside it; without a name its byte accesses would print as AT8 of player_block): +1 (start_join_character_chooser 527) or the trackball's right minus left turns (join_character_chooser_update 607), added to character to skip taken ones (623); as character_chooser 13 (today the generic hitbox_centre_y)
field object 2A letter_fraction u8  # projected_x's low byte for the ranking letters: start_ranking_entry 08EBFC/08EC28/08EC54 "mov.b #80, 2A[R3]" (the three letters), enter_ranking_initials_count_down 08EDD3 when the code changed; an overlay of projected_x
field object 2B letter_code s8  # projected_x's high byte for the ranking letters (0-2B): 08EDBE "cmp.b #2B, 2B[R20]" + bgt (signed, back to 0 above 2B: 08EDC6), compared with piece_frame (the previous code, 08EDCB), ranking_letter_sprites[it] (08EE09 movz.bw), written to the initials (08EDE8); a part of projected_x
field ranking_record 00 rings_long u32  # rings, character and stages_reached as one long: enter_ranking_initials moves a record with two mov.w (08EE8C "mov.w [R1+], R2", compared as the rings with cmp.h R2, R0; 08EE97 "mov.w R2, [R5+]") and copies ranking to saved_ranking the same way (08EF06 "mov.w [R0+], [R1+]"); rings becomes its overlay, character_and_stages a part
field ranking_record 02 character_and_stages u16  # character and stages_reached as one halfword: reset_ranking 08F131 "mov.h [R0+], [R1+]" copies default_ranking_record's character_and_stages there; character becomes its overlay, stages_reached a part
field credit_block 00 counts_long u32  # the whole credit block as one long (coins, credits, bonus_coins, changed): apply_settings 09F75D "mov.w R1, [R13+]" restores the saved credits with the coins cleared and also clears bonus_coins and changed; counts (and coins) become its overlays
20F038 var frame_counter_long u32  # frame_counter with the three bytes after it (display_off_frames...) as one long: blink_start_prompt 09FD0A/09FD2F "test1 R2, 7038[R25]" (the V60 bit test reads a long; the bit is 4-6, inside frame_counter); every other access is a byte (inc.b, mov.b), so frame_counter becomes its overlay
scoped object door_roller 82 roll_out_timer s16  # 8 (spawn_door_rollers 082A56 mov.h #8, 82[R20]): frames of the roll-out (door_roller_roll_out, dec.h); off_screen (83) is its high byte, used once the roll-out is over
scoped object icicle_dropper 96 record_flags u16  # icicle_dropper_record flags (icicle_dropper_start 0882DA mov.h [R10+], 96[R20]): rides_platform (96) and last_dropper (97) in one halfword
scoped object drop_icicle_row 96 dropper_flags u16  # the dropper's rides_platform and last_dropper, copied as one halfword into each icicle (0886AE mov.h 96[R20], 96[R11])
local drop_icicle_row 08864B r11 block object.icicle_update  # allocate_object's icicle (handler icicle_update): its 96-97 get the dropper's two flags at once (0886AE)
scoped object icicle_update 96 dropper_flags u16  # rides_platform and counts_goal as the halfword drop_icicle_row copies from the dropper's record_flags (0886AE mov.h 96[R20], 96[R11])
local player_state_stuck 0076DE r20 stuck_object object.player_state_stuck  # R20 (current_object) after update_screen_position, only typed: still the stuck player's object, whose touched_by (6A) leads to the propped board (0076E7-0076EB add.h 80[R10], E[R20]: the board's shake_dx)

# ======== numbers by name (constant FUNCTION VALUE NAME): what the code divides by, counts or compares with ========
constant check_rom_checksums 20000 IC17_BYTES            # IC17, the program ROM's first chip: 000000-01FFFF (compute_crc16)
constant check_rom_checksums 80000 IC8_START             # IC8, the second: from 080000
constant check_rom_checksums 1F000 IC8_BYTES             # ... to 09EFFF (the rest of the chip is not checked)
constant check_rom_checksums 100000 IC18_START           # the data ROM's two chips, every other byte (compute_crc16_alternate_bytes): IC18 from 100000
constant check_rom_checksums 40000 IC18_BYTES
constant check_rom_checksums 100001 IC9_START            # IC9 from 100001, one byte less
constant check_rom_checksums 3FFFF IC9_BYTES
constant split_frame_count 4E9FA0 FRAMES_PER_DAY         # the game's own divisors: 59.64 frames a second for the first three
constant split_frame_count 346A7 FRAMES_PER_HOUR
constant split_frame_count DFB FRAMES_PER_MINUTE
constant split_frame_count 3C FRAMES_PER_SECOND          # 60, not 59.64: the remainder of the minute, as the original divides it
constant enter_ranking_initials_save_record 170412 INITIALS_SEX    # three letter codes and a 0 byte (ranking_entry.initials), the first letter in the low byte
constant enter_ranking_initials_save_record 171717 INITIALS_XXX
constant enter_ranking_initials_save_record 172B17 INITIALS_X_X
constant enter_ranking_initials_save_record 2B1717 INITIALS_XX_
constant enter_ranking_initials_save_record A0013 INITIALS_TAK     # what the four become
constant run_stage_05_switch 32A03 STAGE_05_SWITCHED_TILE_SETS     # loaded_tile_sets once the switch is done: sets 03, 2A, 03 and 00 in the four slots

# ======== what the C says above each function (build/naming/comments_1..8.txt) ========
comment add_to_hitter_list Puts current_object at the head of hitter_list (linked by hit_list_next) and clears its flags bits 2 (hit) and 27 (in a zone) for this frame's check_hits and check_zones.
comment add_to_platform_list Puts current_object at the head of platform_list (linked by platform_list_next) and clears its flags bit 3, which check_platforms sets when a rider stands on it.
comment add_to_rider_list Puts current_object at the head of rider_list (linked by platform_list_next) and clears its flags bits 3 (on a platform) and 5 (in a wall) for check_platforms and check_walls.
comment add_to_touchable_list Puts current_object at the head of touchable_list (linked by hit_list_next), which check_hits tests the hitters against, and clears its flags bits 2 (hit) and 6 (hit by a player's attack).
comment add_to_wall_list Puts current_object, a wall, at the head of wall_list (linked by platform_list_next) and clears its flags bit 5, which check_walls sets when a rider is inside it.
comment add_to_zone_list Puts current_object at the head of zone_list (linked by hit_list_next) and clears its flags bit 27, which check_zones sets when a hitter is inside its box.
comment check_hit Tests hitter against the list from other on, skipping pairs whose kinds are both 2 or less: circle against circle (skewed on a slope), point against box, box against round or box against box (flags bit 1: a box) in x and y, then the heights. The first hit sets flags bit 2, touch_distance and touched_by (the other) in both.
comment check_hits Tests each object of hitter_list against the hitters after it (check_hit) and then, unless that has already hit it (flags bit 2; the last hitter is always tested), against touchable_list. Empties both lists for the next frame.
comment check_platforms Matches each rider with the first platform whose box (no offsets) holds its x and y: rider flags bit 3, platform pointing at each other. A rider not above floor_z sets the platform's bit 3, the first adding its member_number to a bridge load list (by spawn_arg_2_high). Then compute_bridge_sag per bridge; both lists emptied.
comment check_walls Matches each rider of rider_list with the first wall of wall_list whose box holds its x and y and whose height (hitbox_height up from its z) holds the rider's z: flags bit 5 on both and wall pointing at each other. Empties wall_list.
comment check_zones Matches each hitter of hitter_list with the first object of zone_list whose box holds its x and y and whose height overlaps its own: flags bit 27 on both and anchor_x pointing at each other. Empties zone_list (check_hits empties hitter_list).
comment compute_bridge_sag Fills a bridge's sag table, which check_platforms passes in current_player, from load_list: with only the ends 0 and plank_count (no plank loaded) it is cleared; else the loaded planks are sorted and the table gets straight lines between consecutive ones, each sagging bridge_sag_curve[distance to the nearer end] << 8.
comment biting_head_catch_up_speed Moves the biting head (biting_head_move_at_speed) at the camera's speed on its slope (compute_camera_slope_speed with its floor_slope_factors entry) times catch_up_factor / 0x100, or at base_speed when that is higher.
comment biting_head_chase Each frame of the biting head's chase: biting_head_read_script, or biting_head_chase_step while it holds back. From its second frame, when no_player_in_play, player_mishap or player_dropping_in is set, it starts holding back: base_speed 0 and screen_threshold 0x160 (-0x20 in directions 1 and 2), the old ones saved.
comment biting_head_chase_step Projects the biting head, puts its second box 0x40 further in x (same y and z) and both boxes in the zone list. A hold-back ends, restoring screen_threshold and base_speed, after 0x300 of scroll or when hold_timer runs out (it only counts in game_mode 3), going to biting_head_catch_up_speed; otherwise biting_head_choose_speed.
comment biting_head_choose_speed Moves the biting head at base_speed while it is within screen_threshold (screen_x at most it in directions 0 and 3, at least it in 1, 2 and 4); beyond it, or above the screen (screen_y below 0), it drops its wait and any hold-back and catches up (biting_head_catch_up_speed).
comment biting_head_init Sets up the biting head: x, y and path from biting_head_paths (by its sprite_frame), kind 9 (hurts) with a 0x80 x 0x40 box 0xF0 high, a part block from take_free_object (biting_head_parts and two clip entries) and a second 0x200 x 0x130 box as its child; biting_head = it, biting_head_done = 0; then biting_head_wait.
comment biting_head_load_record Loads the biting head's next path record from path_cursor: screen_threshold, catch_up_factor, base_speed, wait_timer and direction, path moving past it. A negative factor ends the chase: the part block (sprite_frame here is the low half of part_list) and the second box are freed and the head deleted.
comment biting_head_move_at_speed Sets chaser_speed = move_speed; after the wait the head moves that much along its direction. Moving, parts 0 and 4 cycle biting_head_move_frames and it bobs; the jaw (part 3) bites, step 5 shaking it (biting_head_bounce). Boxes 0x50 high from x -0x355, biting_head_done from -0x431; drawn (with its clip entries before scroll 0x1500).
comment biting_head_read_script Counts the biting head's wait_timer down; once it is out, loads the next path record (biting_head_load_record) when scroll_position reaches its scroll word or the head reaches its coordinate along its direction (y in 0, 2 and 4, x in 1 and 3); then biting_head_chase_step.
comment biting_head_wait Keeps the biting head waiting until chaser_stopped is set, or sets it itself once scroll_position reaches 0x1200; then biting_head_chase.
comment brick_dust_animate A dust puff of the bursting brick wall: hidden while its wait_timer runs, then plays brick_dust_animation once and is deleted.
comment brick_wall_init Sets up the brick wall: frame 0x7D90, palette 0x10D0, standing at z 0x1D8 (its floor_z too), projected; brick_wall_wait runs it.
comment brick_wall_wait Draws the brick wall until chaser_stopped is set, then bursts it: brick_wall_bricks bricks from brick_wall_brick_records (screen offsets, speeds, animations) fly off as rock_debris_fly, brick_wall_dust dust puffs start from brick_wall_dust_records (brick_dust_animate), and the wall is deleted.
comment build_grate_steps Builds the grating steps, one row a frame once 6 objects are free: three steps (frame 0x7FF0, grate_step_update) 0x35 apart in projected x, each with its column 0-2 in kind and cut_z 0x1E below it; each row 0x1E lower down the floor's slope. Deletes itself after 15 rows.
comment build_saw_shaft_base Waits like rising_saw_init (scroll_position 0x3780 or biting_head_done), then builds the base of the saw's shaft: three pieces (0x7FE8, 0x7FB8, then 0x7FE8 with sprite_flags 0x80) from 0x3A above its floor, each 0x1D lower down the floor's slope (saw_shaft_base_draw); then deletes itself.
comment checker_pad_animate Animates the checkered pad: the five checker_pad_frames in turn, nine frames each, at its projected position; deleted far off screen.
comment checker_pad_init Sets the checkered pad's palette (0x9040) and checker_pad_animate as its handler.
comment falling_grate_update A grate in a row led by its parent (current_player is borrowed for the leader): a platform, drawn at screen_y + depth_offset; within 0x60 of the leader it rattles (z up and down 1), and when the leader reaches it in y or x by its direction it plays sound 0xD and falls (falling_grate_animation), a platform at first, until off the bottom.
comment fire_front_chase Each frame of the fire front's chase, as biting_head_chase: fire_front_read_script, or fire_front_chase_step while it holds back (base_speed and speeds 0). Its hold screen_threshold is direction ^ (direction >> 1), 0x160 only in direction 0, not the biting head's -0x20 or 0x160.
comment fire_front_chase_step Projects the fire front into the zone list and picks its speed as the biting head does (base_speed within screen_threshold, else the camera's ground speed x catch_up_factor / 0x100), ending hold-backs alike. After the wait it moves along its direction (speed_x/y set); a record with factor 0 narrows its box to 0xF2 and sets chaser_stopped.
comment fire_front_init Sets up the fire front, an invisible chaser: x, y, floor_z, direction and path from fire_front_paths (by its sprite_frame), a 0x132 x 0x200 box of kind 0x14 (tumbles the players), chaser_object = it; with 3 or more objects free, four flames (spreading_flame_start) 0x38 apart in x from 0x28; then fire_front_chase.
comment fire_front_load_record Loads the fire front's next path record from path_cursor: screen_threshold, catch_up_factor, base_speed and wait_timer (a wait stops its speeds), path moving past it. A negative factor ends the chase: chaser_object = 0 and the front is deleted.
comment fire_front_read_script As biting_head_read_script for the fire front: once wait_timer is out, the next record loads when scroll_position reaches its scroll word or the front reaches its coordinate (y + hitbox_offset_y in directions 0 and 4); a coordinate of 0 waits for the scroll only.
comment grate_step_debris_fly A piece of grate step debris: falls (gravity 0x6000), moves speed_x in projected x, animates at depth 0xE0 and is deleted off screen.
comment grate_step_update A grating step: once the top of the saw of its column (rising_saws[kind], 0x20 above its z) reaches cut_z it is cut: cut_z climbs 7 a frame with grate_step_cut_animation, debris and sound 0x28 while within 0x20 of the saw's top. Drawn at depth 0xFFF8 unless above the screen; deleted below it.
comment grate_steps_init Places the grate steps' builder at x -0x3BD, y 0x4F4, z 8 (row_projected_x/y from there), finds its floor, sets rows_left 0xF and clears rising_saws (rising_saws[0], rising_saw_1 and rising_saw_2); build_grate_steps runs it.
comment railing_draw The railing's handler: placed from its projected position (unproject_object) and, unless deleted far off screen with its second box, drawn and put in the touchable list.
comment railing_init Sets up the railing (frame 0x7838): a solid box (kind 0xC) 0xF x 0x57, 0x100 high, and a second invisible box 0x56 x 0x11 at the same place, linked as its child (railing_side_update).
comment railing_side_update The railing's second box: placed from its projected position and put in the touchable list each frame; it is deleted with the railing, its parent.
comment rising_saw_init Waits (as its own handler) for scroll_position 0x3780 or biting_head_done; then the rising saw: x, y, z and path from rising_saw_paths (by its sprite_frame), rising_saws[sprite_frame] = it, a 0x50 x 0x40 box 0x20 high of kind 0x26 (knocks players off); with 4 or more objects free its blade and sparks; then rising_saw_rise.
comment rising_saw_rise Each frame of the rising saw: reads its path records (on scroll_position or its z reaching the record's height; a negative factor deletes it) and holds back like the chasers (screen_threshold 0x130). After the wait it rises at base_speed, or with the camera below screen_threshold, along its floor's slope; touchable.
comment saw_blade_update The saw's blade by saw_blade.state: 0 starts 0x30 back in y with two clip entries and sound 0xA; 1 slides 2 a frame to the saw, clipped at the saw's line; 2 frees the clips; 3 follows the saw, leaving a shaft piece (saw_shaft_update) every 0x1D it rises, sound 8 every 0x3C frames, drawn while screen_y is under 0x140.
comment saw_shaft_base_draw Draws a piece of the shaft base at depth 0xFFF7 until it has left the screen (check_left_screen), then deletes it.
comment saw_shaft_update A piece of shaft left under the blade: steps saw_shaft_animation each time its parent (the blade) rises 5 more, until the animation wraps (drawn at depth 0xFFE9), then stays, drawn at depth 0xFFF7, until off screen.
comment saw_sparks_update The blade's sparks: at the blade's screen position plus their sprite offsets, animated, drawn at the blade's draw_depth + 1 while screen_y is under 0x168 and the saw's base_speed is not 0.
comment spawn_grate_step_debris Spawns a grate step's debris when enough objects are free: the pieces of grate_step_debris (its first word the count), each at the step's projected position plus the record's offsets and at cut_z, with the record's speeds plus a random part and its animation (grate_step_debris_fly).
comment spike_gate_beam_drop The spike gate's beam drops: gravity plus 0x2000 a frame (fall_to_floor). Each landing makes it kind 0xC (solid) and lifts it 6 (a bounce); the third leaves it 3 above its floor, plays sound 7 and waits 0xA frames there (spike_gate_beam_wait).
comment spike_gate_beam_hold Keeps the beam at the top until pause_timer runs out (8 frames from spike_gate_beam_rise), then spike_gate_beam_drop.
comment spike_gate_beam_rise The beam rises 3 a frame; more than 0x20 above its floor it becomes kind 0x24 (hurts), and past 0x50 it holds there (spike_gate_beam_hold).
comment spike_gate_beam_update Moves the beam (move_object_projected) and breaks it up as the pillars break; else draws its three sections at screen offsets (0, 0), (0x30, 0x19) and (0x60, 0x2D), at depths from its height above the floor (cursor_floor_screen_y), and its shadow on the floor, and puts it in the touchable list.
comment spike_gate_beam_wait Keeps the beam on the ground until pause_timer runs out (0xA frames after a drop), then spike_gate_beam_rise; spike_gate_init starts it here.
comment spike_gate_init Sets up the spike gate: this object is the left pillar (frame 0xA200, a solid box 0x2D x 0x21, kind 0xC), plus a right pillar at projected +0x94, +0x4A (0xA208) and the beam at +0x1A, +0x14: three sections and a shadow (0xA788 at scale 0x180), a 0x2D x 0xDA box 0x28 high, kind 0xC, starting in spike_gate_beam_wait.
comment spike_gate_pillar_update A spike gate pillar: when the biting head comes within 0x20 in x or scroll_position passes spawn_arg_2, it breaks into five pieces of debris (spawn_debris, from 0x90, 0x40 back in projected x and y) and is deleted with its children; else drawn (depth screen_y - 8) and touchable.
comment spreading_flame_burn A flame of the spreading fire. Once chaser_object is level with it in y, it throws a fireball (start_fireball_flipped) max(random 0-0x3F, its gap to the chaser) on in -y, 0x40 more in stage 0xC at chaser_speed over 8, landing as a new flame (spreading_flame_start); then, or with no chaser, it burns until off screen.
comment spreading_flame_start Starts a flame of the spreading fire on its floor: flame_animation, palette 0x10C0, projected; then spreading_flame_burn. fire_front_init starts four, and each fireball a flame throws becomes one.
comment static_triangle_animate Animates the static triangle: static_triangle_frames in turn, three frames each; its child is drawn every other frame (its direction field counts them) 0x68 to the right. Deleted far off screen.
comment static_triangle_init Sets the static triangle's palette (0x9050) and handler (static_triangle_animate), and its child's frame 0x7828 and palette 0x9060.
comment white_puff_animate Animates the white puff: the ten white_puff_frames, four frames each, holding the last one 0x5A frames before starting over; placed from its projected position, deleted far off screen.
comment white_puff_init Sets the white puff's palette (0x90A0, overwritten at once by 0x2000) and white_puff_animate as its handler.
comment animate_hud_palette Every animate_hud.anim_delay frames puts the next palette of the list at animate_hud.anim (its first word the last index) into the HUD record's sprite_palette; at the end of the list it starts over and wrapped is -1.
comment animate_hud_tiles As animate_hud_palette for the sprite's frame: every anim_delay frames puts the next frame of the list at animate_hud.anim into sprite_frame; past the last it goes back to index 0 without changing the frame, and wrapped is -1.
comment blink_player_hud_message Every 0x1E frames (hud_blink_timer) toggles hud_blink_on and prints or erases text at the player's HUD prompt position (hud_prompt_positions_2p/3p); hands back that position, or text_position in between.
comment check_player_stalled Counts the frames scroll_position has not moved (stall_frames; never in stage 5). After 0x1C20 of them the player loses all its health and is hurt (state 0xD).
comment climb_zone_effect_start Projects the climb zone splash and makes climb_zone_effect_update its handler, running it at once.
comment climb_zone_effect_update The climb zone splash: deleted once its player (the block in parent) stops climbing (state 0xA) or leaves the splash_zone; else it follows the player's y, animates and is drawn at depth screen_y + 0x28.
comment draw_player_stage_rings Unused: when stage_rings differs from stage_rings_shown, copies it there and draws it as two hex digits (draw_hex_2, attribute 0) at the player's hud_stage_rings_positions entry.
comment draw_player_start_prompt Blinks the start prompt (blink_start_prompt, start_prompt_texts) at the player's start_prompt_positions entry, with the credits of p1_coins, or of the player's own credit block when cabinet_config is 2 or 3; hands back the text position.
comment give_result_bonus The ring tally's bonus for the player block player: health + 8 (at most max_health_by_setting for its joined_health_setting), rings + 2 x ring_tally_percent, and block flags bit 6, so the HUD flashes for rings.
comment intro_marker_update Keeps the intro marker above its player (parent): the player's screen position plus its sprite offsets, the y one scaled by the player's sprite_scale, which it takes; animates; deleted once scene_step reaches 2.
comment player_hud_init Fills the player's three HUD sprite records in its block (hud_gauge at +0x142, hud_portrait at +0x172, hud_ring_effect at +0x1A2): the health gauge and the character's portrait, placed for 2 or 3 players by cabinet_config, and the ring sparkle beside the gauge; resets the HUD blink and stage_rings_shown.
comment player_hud_start Sets the portrait's frame and palette for the player's character (kind), then player_hud_update. A player in play (player_status with bit 7, not 0x82) first gets its HUD message lines erased and, with no game in progress, its bit of stage_init_flags (15, 18 or 19) cleared.
comment player_hud_update Draws the player's HUD. In play: the gauge (2 x health wide), the portrait and the ring sparkle, flashing their palettes when health drops, rings come (block flags bit 6) or health is 8 or less, and the ring count; waiting (player_status 0x82) the wait message blinks; out of play the start prompt or the no-continue text.
comment player_select_character The joining player's character select (state 0x34): the first frame starts the chooser (start_join_character_chooser) and notes join_time (game_frames, reset when only one player counts as playing); each frame runs join_character_chooser_update and is done (substate and is_joining 0) once the player's bit leaves players_in_join_chooser.
comment skid_dust_update Plays the skid dust's animation once, drawn meanwhile, then deletes it.
comment spawn_climb_zone_effect Spawns the climb zone splash (climb_zone_effect_animation) at x 0x1CC, z 0x4CC and the player's y; its parent is the player block (current_player). It starts in climb_zone_effect_start.
comment spawn_intro_marker Spawns the intro marker (frame 0xA0C0, intro_marker_animation), 0x50 above and 8 right of the player object, its parent; intro_marker_update runs it.
comment spawn_player_ride With no ridden_object yet, when the player's x is past 0xAE0 or before 0xA60, spawns the object it will ride (ridden_object_start) at that x, y 0xEB2, z 0x530, and keeps it in ridden_object.
comment spawn_skid_dust Spawns a skid dust cloud (skid_dust_animation, skid_dust_update) at the player's projected position and z, with the player's sprite priority bits.
comment update_hurt_output With setting_moving_seat on, sets the player's bit of io_port_d (2 for player 0, 3 for player 1; never player 2) while a game is in progress and it is hurt (states 0xD, 0x20, 0x28, 0x2C, 0x33), and clears it otherwise.
comment add_player_to_lists Puts the player object in hitter_list and rider_list for this frame's contact checks.
comment add_push_to_player_motion Unused: polar_to_xy(heading_snapped, speed_int) plus the push, each >> 4, gives speed (length << 12, 0 when negative, at most 0x5000000) and heading; the push is then cleared. It reads push_x and push_y as 32-bit words, each taking the halfword after it, and the clear also zeroes animation_speed's low half.
comment aim_at_screen_centre Hands back in dx and dy a step of 0x200 from the player toward the ground point under the screen centre (0xA0, 0x70) at its z; check_player_contacts_solid pushes the player out with it.
comment apply_floor_push Sets push_x and push_y from the floor: cleared, then the floor_types record's first two words added on floor types 4, 0xB, 5 (not while hopping) and 7 (with an attribute), not while invulnerable. With flags bit 31 set it leaves the push alone.
comment apply_moving_floor Carries the player with a moving floor: on floor type 7 whose floor attribute's ride is 1, x += moving_floor_object's speed_x; on floor type 0xC, y += its platform's speed_y (not while jumping, nor while teetering off a block not of type 0xC).
comment apply_speed_scale Hands back target_speed x speed_scale / 16 when speed_scale is set, else target_speed.
comment bounce_off_wall_in_air When the player has moved over a floor block at least 8 higher than its last and is below its top, puts it back at its previous position and floor and turns it 0x80 to one side of straight away from the side it hit (get_floor_block_side), facing that way, with control_lock 0xA and flags bit 13.
comment check_fall_into_pit Over a pit (floor_z 0x7FF or more, not floor type 6), a player falling or still (speed_z not negative) at or below its previous floor's height goes to state 9, falling into the pit.
comment check_player_contacts What touching an object does to the player, by the kind of the object in touched_by: rings (3 and 42) give rings and health, hazards hurt (state 0xD) or tumble it, solid kinds stop or bounce it, others launch, hold, catch or sink it. Nothing on a path (but 0x14-0x16), untouched, or when the object has flags bit 9.
comment check_player_contacts_attack A jumping player hits toucher once (last_damaged): toucher gets flags bit 6 and loses attack_power health. If that finishes it and it is not a sturdy kind (9, 0xB-0xE, 0x11, 0x18, 0x19), the player gets hit_stop 3; else it is thrown back with a hop, along toucher's direction (flags bit 30) or back the way it came.
comment check_player_contacts_attack_or_hurt A jumping player attacks toucher (check_player_contacts_attack); otherwise it is hurt (state 0xD), taking new_substate and hurt_contact_delay 2 only for a new toucher (last_hazard).
comment check_player_contacts_clear_state Nothing holds the player: pushed_out 0, last_hazard and last_damaged 0xFFFF (the next touch counts as new), flags bits 18, 23 and 31 off and block flags bit 1 off.
comment check_player_contacts_hurt Hurts the player with substate 0 (check_player_contacts_attack_or_hurt), so a jumping player attacks instead.
comment check_player_contacts_hurt_unless_invulnerable Ignores toucher while invulnerable_timer runs (check_player_contacts_clear_state); else check_player_contacts_hurt.
comment check_player_contacts_solid The player against a solid object: jumping, it attacks it; otherwise it goes back to its previous place and floor and bounces (state 0xE) at speed 0x500 or more (not off kind 0x20) or stops. After 0x1E frames on the same object it is pushed out toward the screen centre (pushed_out); climbing, it undoes the climb step.
comment check_player_floor_edge Jumping with no floor block under it, the player goes over the edge (state 7, substate 1, ledge_side and fall_draw_depth set); on the ground over a pit (floor_z past 0x7FF) it teeters (5), or goes over (7) faster than 0x200 off most block types or with block flags bit 1. Hopping or jumping over a floor: check_fall_into_pit.
comment check_player_jump Unless players_frozen: on floor type 6 the player hops (state 0x17) unless jump_delay is 2; elsewhere, once jump_delay has run out, not falling and with no control_lock, start_jump_if_pressed.
comment check_player_reached_goal Ends the stage for the player (state 0x21) once scroll_position reaches goal_scroll_position and it crosses goal_line by goal_direction (between goal_line_min and goal_line_max unless 0). Stage 0: 0x18 at once; 4: when goal_unlocked; 5: 0x18 with scene_path 0xB, or 0x11 once the spiked press has stood 0x258 frames.
comment check_player_step Sets step_floor_z, the height the player stands on: its floor_z, or a flat platform's z (snapped to at once over a block of type 0x24); then check_player_step_against_floor. In stage 8 before scroll 0x4000 the player just snaps to its floor.
comment check_player_step_against_floor Puts the player on step_floor_z: on a path or within 8, z snaps to it (previous_z keeps the old z); a lower floor starts a fall (state 2); a higher one blocks it unless sloped: back to its previous place and floor, bouncing (state 0xE, away from the block's side) at speed 0x500 or more, else stopping (state 0).
comment check_scene_states Forces the states of the current game mode and events on a playing player: 0x19 (mode 0xD), 0x1E (1), 0x2B (0x18), 0x30 (0x19, bridge collapsing), 0x31 (0x1A), the ride of mode 0x1C (spawn_player_ride, then 0x35), 0x21 (0x17, 0x1D, 4), 0x26 (players_pulled), 0x38 (mode 3 in stage 0xB, y up to 0x500), 0x39 (0x20).
comment check_stage_entry_path With enter_on_respawn set, a stage that has an entry path (stage_player_entries) sends the player to state 0x2A (entering the stage); otherwise enter_on_respawn is cleared.
comment check_zone_contact When check_zones put the player in a zone (flags bit 27, the zone in anchor_x): kind 0x16 sends it to state 0x22; kinds 9, 0xC, 0x14, 0x18, 0x1E and 0x21 count as a touch (flags bit 2, touched_by) for check_player_contacts.
comment compute_ground_speed Sets ground_speed = (speed >> 8) x heading_speed_factors[(heading + 0x20) >> 6]; on a slope it is scaled by the slope_speed_direction_pairs entry for floor_slope and direction, whose second word becomes direction.
comment compute_scroll_drift Turns the camera's movement (camera_velocity_x/y) into scroll_drift_speed and scroll_drift_direction for floor block block: on a flat block its length and angle (y counting double), on a slope y slowed by the slope's factor and the block's own direction; also camera_scroll_direction. Skipped when block and velocity are unchanged.
comment draw_attack_effect Draws the attack effect (the shadow's child) over the player while it attacks (flags bit 6) with attack_power 3 or more: the frame for its attack_power, facing and animation_frame, its character's palette, just in front of it; on floor type 7 the second child (stage 9) copies it. current_player is the player object here.
comment face_movement_direction Turns the player to its movement: heading, target_heading and facing_angle = direction - 0xA0, then set_facing.
comment flash_hurt_palette Every flash_period frames swaps the player's sprite_palette between its own (from palette_list) and 0x83B0.
comment follow_player_path The player's autopilot: takes a new path from path_request or all_players_path_request (follow_player_path_start) or runs the current one (follow_player_path_run), and hands back the speed to aim for: the path's, or target_speed with no path.
comment follow_player_path_aim_at_point Turns target_heading and facing_angle toward the point of the current path record (its x and y words) and, on a path's first record (flags bit 0, cleared here), heading too; then follow_player_path_target_speed.
comment follow_player_path_load_record Starts the path record at path_pointer: path_speed from it; its flag bit 0 makes the player jump (state 4); a speed of 0 makes it wait path_wait (the record's second word) frames, else it heads for the record's point (follow_player_path_aim_at_point).
comment follow_player_path_next_record Moves path_pointer to the next 8-byte record unless its speed word is negative, which ends the path (path = 0).
comment follow_player_path_run Runs the current path: during a wait the speed to aim for is 0 (speed stopped below 0x20) until path_wait runs out; else the player heads for the record's point until within 8 in x and y (at once with the record's flag bit 1), then the next record.
comment follow_player_path_start Starts path on the player: path_pointer from player_path_offsets[path + player], speed 0, z on its floor, state 1, flags bits 6 and 13 off and bit 0 on (so the first aim sets heading too); then its first record.
comment follow_player_path_target_speed Hands back path_speed << 16 as the speed to aim for; a slow path (0x20 or less) gives 0, and stops the player, once its speed is under 0x20.
comment get_floor_block_side Hands back which side of floor block floor_block the point x, y is on: 3 before x_min, 1 past x_max, else 0 before y_min and 2 otherwise (y_max is not checked: the point is taken to be outside the block).
comment get_floor_block_side_or_corner Unused: as get_floor_block_side with the corners: hands back the outcode of x, y against block (1 past x_max, 2 before x_min, 4 before y_min, 8 past y_max) and side_code = floor_block_side_codes[outcode].
comment get_ledge_side Hands back in side which side of its previous floor block the object has gone past (3 before x_min, 1 past x_max, 0 before y_min, 2 past y_max, 4 still inside) and in block that block's record.
comment keep_player_on_screen Off screen (but in stage 8 before scroll 0x4000) the player loses 8 health and respawns, or dies (state 0x16) when that empties it; stage 5 only respawns it. Near an edge it sets screen_edge, says its edge voice once with 2 or more players in the stage's range, is pushed back and, on edges the scroll pushes, moves with the camera.
comment load_stage_goal Loads the stage's goal from its stage_goals record: goal_scroll_position, goal_direction, goal_line, then, past a word it skips, goal_line_min and goal_line_max.
comment pay_and_join Joins the player when it has cost credits (none on free play) and its start button (trackball button with setting_no_start_buttons) is newly pressed, from the shared or its own credit block by cabinet_config: credits taken, cabinet event written, io_port_d bit cleared on 2-player setups, credits saved, -1 handed back; else joined.
comment place_player_at_restart_point Puts the player at its restart point: the record at current_restart_point as map x, y (kinds 0, 2) or a screen point on the ground (kind 1), players 2 apart in x (the third on the other side); at a stage start in a game (flags bit 25, not game mode 1) the 6 bytes before the stage's list, x + player shifted by their first word.
comment player_1_start Player 1's object starts: player = 0, then player_start.
comment player_2_start Player 2's object starts: player = 1, then player_start.
comment player_3_start Player 3's object starts: player = 2, then player_start.
comment player_dispatch_state Runs the code of the player's state: player_move for the moving states (0, 1, 2, 4, 12, 19, 20, 23 and 58), one player_state_ function for each other state. State code that changes the state calls it again, so the new state runs in the same frame.
comment player_join The handler of a player who paid to join: player_status 4 and the character select (player_select_character) until it is done, except when current_game_mode and game_mode are both 0x1B; then player_reset_joined.
comment player_place Places the player at its restart point on its floor (start_climb_at_spawn on a wall) and sets entry_status and player_status: 0x81 over a pit or while touched or in a zone, 0x82 waiting for the scene (set_player_entry_by_scene), else 0xFF, staying the handler while it waits; then add_player_to_lists and player_hud_start.
comment player_reset Resets the player for a start or a respawn: speeds, movement, path and timers cleared, state 0, 0x78 frames invulnerable and blinking unless a stage starts (flags bit 25), the stage's palette for its character; plays its character's join or respawn voice unless a stage starts or current_game_mode is 0x1B; then player_place.
comment player_reset_joined After joining: block flags bit 4, health = starting_health, rings_to_health 0 and, unless a stage exit is under way, unused_player_flags and players_finished cleared for it; then player_reset.
comment player_respawn The handler set for a player that comes back after losing health (off screen, sunk, fallen): player_restart without the stage-start flag (flags bit 25).
comment player_restart Restarts the player: in a game (in_game above 0) player_reset; otherwise it does not play: in_game 0, state 0xB (0x37 for the third player on a 2-player cabinet), player_update as its handler.
comment player_resume Makes player_update the handler, for a player already in the stage (reset_objects_from_list sets it).
comment player_start A new game for the player object: links it with its player block (current_player), health = starting_health, rings 0, kind = player (its character), state 0xB (0x37 for the third player on a 2-player cabinet), two full-screen clip entries; in_game 1 when try_join succeeds. Next comes player_start_stage.
comment player_start_stage The player's handler at a stage start: flags bit 25 (a stage start), block flags bit 4 off, stage_rings 0, the HUD records (player_hud_init), then player_restart.
comment player_steer Steers the player on the ground: a path gives the speed (player_steer_approach_speed); else the trackball (zeroed while players_frozen) or a push turns target_heading (not with flags bit 13) and sets the speed (player_steer_speed_from_trackball); with no input it slows down and, under speed 0x20, stops and stands (state 0).
comment player_steer_approach_speed Brings speed toward wanted_speed (apply_speed_scale): up at once and down 1/32 of the gap (up 1/32, down 1/64 on floor type 7), sets animation_speed, and picks standing (0), walking (1), running (0x13) or fast running (0x14) by it, except in states 4, 0x17, 0x25, 0x26 and 0x2D; then player_steer_turn_and_ground_speed.
comment player_steer_in_air Steers the player in the air: speed goes to the trackball's (its length squared, at most 0x5000000) at once when higher, by 1/32 of the gap when lower; the trackball turns target_heading unless flags bit 13 is set; then turn_player.
comment player_steer_speed_from_trackball The wanted speed is 1.5 x the length of trackball + push squared, at most 0x5000000; a quarter of it with flags bit 31 (0 with block flags bit 2), slowed by sink_depth on floor type 0xA (setting struggle_speed), 0x60/0x100 of it on floor type 4; then player_steer_approach_speed.
comment player_steer_turn_and_ground_speed Runs turn_player, then sets ground_speed: speed scaled by heading_speed_factors for the heading (speed itself with flags bit 31), and on a slope by the slope_speed_direction_pairs entry, which also sets direction (as compute_ground_speed).
comment player_update The player object's handler: the seat output and the HUD, invulnerable_timer counting down to -1, the entry path, goal and scene checks that force states, then player_dispatch_state runs the code of its state.
comment push_player_from_screen_edge Pushes the player 0x200 along the angle screen_edge_push_directions gives for screen_edge; when its direction is not within a quarter turn after that entry's direction, it first goes back to its previous position, floor block and floor_z.
comment push_player_out_of_wall In a wall's box (flags bit 5, wall) the player goes back to its previous place and floor, for 0x1E frames per wall. Jumping or hopping it bounces off (speed 0x4000000, a hop, control_lock 0xA); at speed 0x200 or more or on floor type 4 it turns along the wall (push_player_out_of_wall_deflect); else it stops. Not on a path.
comment push_player_out_of_wall_deflect Turns the player along the wall: direction = the wall's sonic_bounce_records angle - or + 0x80, by the side the player came from, with control_lock 4; in stage 8 before scroll 0x4000 it is sent straight back instead (the wall's direction + 0x200).
comment read_player_trackball Reads the player's trackball motion (trackball_1_x on, 4 bytes per player) into trackball_x and trackball_y: each axis with its lowest bit dropped and scaled by 0x100, sign kept.
comment remember_platform Copies flags bit 3 (on a platform this frame) into bit 4, so the next frame knows the player was on one.
comment set_climb_speed_from_trackball As set_speed_from_trackball for climbing: speed at most 0x3000000 and direction the trackball's angle itself.
comment set_facing Sets facing to facing_angle rounded to one of 16 directions (((facing_angle + 0x20) >> 6) & 0xF) and hands it back.
comment set_fall_sprite_priority Sets the player's sprite priority bits for its fall into a pit from fall_sprite_priorities: the entry of the block it fell from, by ledge_side (ledge_direction when 4), or by the half of the block it is over when the block it left is of type 0x11 or 0x1F, or its current one 0x1D or 0x1E; a negative entry leaves them.
comment set_floor_sprite_priority Sets the player's sprite priority bits (the top three of sprite_palette) for its floor: 0x4000 on floor type 4, 0xA000 on floor type 0xC when the block it came from is of type 0xC too.
comment set_heading_direction Sets heading_snapped to heading rounded to one of 16 directions and direction to that + 0x80, then set_facing.
comment set_player_entry_by_scene Lets the scene decide how the player enters: it waits (entry_status 0x82, player_place staying its handler) in several game modes and events (a stage exit, modes 0xA, 0x1A, 0x1D, the collapsing rope bridge, the ride...), or starts in a scene state: 0x1C in game mode 0x10, 0x1E in 1, 0x2B in 0x18, 0x34 in 0x1B.
comment set_speed_from_trackball Sets the player's speed from its trackball: the length of (trackball_x, trackball_y) squared, at most 0x5000000, along its angle (direction = angle + 0x80), then speed_x and speed_y (set_speed_from_direction).
comment set_target_heading_from_trackball Sets facing_angle to the angle of the trackball (x, 1.5 y) when it moves; target_heading = facing_angle, or with a push the angle of trackball plus push_x/push_y (y 1.5 times unless flags bit 31).
comment shadow_1_start Player 1's shadow object starts: player = 0, then shadow_start.
comment shadow_2_start Player 2's shadow object starts: player = 1, then shadow_start.
comment shadow_3_start Player 3's shadow object starts: player = 2, then shadow_start.
comment shadow_start Sets up the player's shadow object (frame 0x5D90, shadow_update) and a child block (allocate_object_2) for the attack effect; in stage 9 a second child (sprite_flags 0x1088) for a copy of the effect on floor type 7.
comment shadow_update Draws the player's shadow (current_player is borrowed for the player's object): on its floor, flickering on some floors, sinking on floor types 0xA and 0xB (setting ground_clip_bottom), the player's reflection in stage 9 on floor types 7 and 8, or game mode 0x10's by scene_step; none over a pit. Then draw_attack_effect.
comment slip_effect_update The slip effect player_state_dropping spawns: animates and is deleted when its animation is back at frame 0; drawn on top (bucket 0) when its direction (the ledge_side) is 2 or more, else in bucket 0xFF.
comment slip_piece_update Mighty's slip piece: deleted off screen; waits while speed_x_fraction (a timer here) counts down from 0x10, then falls (gravity 0x6000); drawn at its base when its direction (the ledge_side) is 2 or more, else in bucket 0xFF.
comment slip_pieces_update Ray's two slip pieces: each moves by speed_x and speed_y, falls (gravity 0x6000), animates and is deleted off screen (margins 0x20 and 0x50); drawn at its base when its direction (the ledge_side) is 2 or more, else in bucket 0xFF.
comment spawn_slip_piece For Mighty (kind 2) slipping off a ledge (player_state_dropping): one piece with the frame at its animation + 0x3A, at the player's position, that waits 0x10 frames before it falls (slip_piece_update).
comment spawn_slip_pieces For Ray (kind 1) slipping off a ledge: two pieces (the player's animation + 0x3A) thrown up (speed_z -0x80000) and apart at 0x8000 in x (ledge sides 0, 2 and 4) or y (1 and 3), run by slip_pieces_update; needs 2 free objects.
comment spawn_tumble_effect Spawns the tumble effect over the player (tumble_effect_animation, tumble_effect_update), flipped when the player's facing is 1 to 7.
comment start_climb On floor type 2 (a wall to climb) starts the riding player climbing: its character's climb animation, x + 0x41000, climb_base_z from the floor block, climb_remaining from its floor attribute's height less its height above climb_base_z; state 0xA, substate 3.
comment start_climb_at_spawn On floor type 2 (a wall to climb) a player placed there starts climbing: its character's climb animation, z raised by the wall's rise (its floor_types record) times how far it is into the block, climb_remaining and climb_base_z set; state 0xA, substate 3.
comment start_jump_if_pressed Starts a jump (state 4) when the player's button is newly pressed (inputs_pressed bit 0x10, 0x18 or 0x12 by player in a game, 0x19 to 0x1B outside one), unless pushed_out or on a path.
comment toggle_hidden Flips flags bit 19, which keeps the player from being drawn: the blink.
comment try_continue Unless the player is in state 0x37: in a game, pay_and_join with cost (the continue); with no game in progress it counts as joined at once. Hands back -1 when joined, else 0.
comment try_join As try_continue with the cost of a start (credits_per_start).
comment tumble_effect_update The tumble effect: at its player's screen position, deleted once the player leaves state 0x20 or its animation_frame reaches 0xB; its frame follows the player's animation_frame (moved by tumble_effect_y_offsets), drawn by the player's draw_depth and facing.
comment turn_player Turns heading toward target_heading: at once at speed 0x200 or less, while free_turn_timer runs, with flags bit 31, or in states 2, 4, 0x17, 0x25 and 0x26; else by at most 0x6000 / speed_int a frame, a quarter turn or more making it skid (state 0xC). Then set_heading_direction.
comment update_ledge_pullback Sets the speed from the trackball (set_speed_from_trackball) and adds to ledge_pullback the part of it that points back from the ledge on ledge_side (side 4: none).
comment update_player_blink While invulnerable_timer runs and flags bit 29 is set, toggles hidden each frame (the blink); once it is out, clears both, so the player shows.
comment update_quicksand_object While the quicksand holds the player (flags bit 31) its floor_z and z go 0x4EC4 x (0x68 - touch_distance) lower, and with block flags bit 2 it sinks: sink_depth + 0x33 a frame added to z; at 0x2000 deep it loses 8 health and respawns, or dies (state 0x16).
comment update_sinking_floor On floor type 0xA the player sinks: sink_depth grows 0x33 a frame less struggle_speed / 16 (at least 0x100, and 0xA0 back when past the floor's sink_depth_limits entry), z = floor_z + sink_depth << 8; at 0x2000 deep it loses 8 health and respawns, or dies (state 0x16).
comment clear_bitmap_layer Sets the bitmap layer up and clears it: scroll 0, 0 at the depth is_8bpp gives, bitmap_address at 0, 0, video_layer_disable bits 5, 10, 15 off, the bitmap clip 0, 0 to 0x13F (0x19F with video_control bit 15), 0xDF, then the window 0, 0 to 0x1FF, 0xDF filled with 0 (at 4 bpp only its bottom row: fill_rect_unclipped).
comment draw_dotted_line_8bpp Unused: draw_line's 8 bpp part for a dotted line: clipped the same way, it writes every other pixel (2 bytes apart across, 0x400 down, double steps on diagonals, other lines skipping a pixel between writes).
comment draw_line Draws a line on the bitmap layer from start_x, start_y to end_x, end_y in color, clipped to bitmap_window (get_window_outcode, clip_line_to_window; start_x comes back -1 when none of it is inside), at 4 or 8 bits per pixel (bitmap_8bpp): the CRT test's border and crosshatch.
comment draw_line_shallow_even_pixel Part of draw_line at 4 bpp, more columns than rows: puts color in the low nibble (an even x) of the pixel's byte; if the line stays on the row it goes on to the odd pixel of the same byte (draw_line_shallow_set_high_nibble), else it writes the byte and steps a row (draw_line_shallow_odd_pixel).
comment draw_line_shallow_odd_pixel Part of draw_line at 4 bpp, shallow: an odd x pixel after a row step: reads its byte, then draw_line_shallow_set_high_nibble.
comment draw_line_shallow_set_high_nibble Part of draw_line at 4 bpp, shallow: puts color in the high nibble (an odd x) of pixel_byte, writes it and moves to the next byte, a row further when the step carries; then the next even pixel (draw_line_shallow_even_pixel) until the steps run out, handing back the address.
comment draw_packed_picture Unused: draws the packed picture at picture on the bitmap layer at 8 bpp: init_bitmap_layer, the tile layers off (video_layer_disable bits 0-3), init_picture_unpacker and init_picture_colour_cache, read_picture_size and unpack_picture_pixels, then rotate_bitmap_for_monitor_flip.
comment draw_pixel Unused: puts color at x, y when it is inside bitmap_window (get_point_outcode): at 8 bpp the byte at y x 0x200 + x, at 4 bpp the low (even x) or high (odd x) nibble of the byte at y x 0x100 + x / 2.
comment draw_rect_outline Unused: the outline from left, top to width, height (the far corner, made a size by clip_rect_to_window) in color: by the edges still inside (1 left, 2 right, 4 top, 8 bottom), rows by fill_span_8bpp and columns by draw_vertical_line_8bpp. At 4 bpp only a lone left or right edge, with the edge code (3 or 6) as row step.
comment draw_rect_sides_8bpp Unused: draw_rect_outline's left and right sides: color at pixel_address and pixel_address + width, height + 1 rows down (0x200 apart); hands back the address of the bottom-left pixel.
comment draw_rect_top_bottom_8bpp Unused: draw_rect_outline's top and bottom: width + 1 bytes of color at pixel_address and height rows below it (long stores where it can, as fill_span_8bpp); hands back the last byte of the top row.
comment draw_vertical_line_8bpp Runs draw_vertical_span_8bpp with a row step of 0x200 (one 8 bpp row): draw_line's vertical lines and draw_rect_outline's sides; hands back the address of the last pixel.
comment draw_vertical_span_4bpp The vertical line of draw_line at 4 bits per pixel: the byte of start_x on row start_y, then draw_vertical_span_4bpp_here.
comment draw_vertical_span_4bpp_here Puts length + 1 pixels of color down from pixel_address, row_step apart, in the low nibble for an even start_x or the high nibble for an odd one: draw_vertical_span_4bpp's and draw_rect_outline's 4 bpp sides.
comment draw_vertical_span_8bpp Puts length + 1 pixels of color down from pixel_address, row_step apart (0x200: one 8 bpp row); hands back the address of the last.
comment fill_rect Fills the rectangle left, top to right, bottom with color on the bitmap layer, clipped to bitmap_window (clip_rect_to_window, then fill_rect_unclipped); hands back -1 in left when none of it is inside. The test mode's panels and bars.
comment fill_rect_unclipped Fills left, top to right, bottom with color, unclipped (fill_rect, clear_bitmap_layer): at 8 bpp two rows at a time, by 16, 8, 4, 2 and 1 bytes. At 4 bpp the rows of 4-pixel blocks go up from the bottom, each starting a row above where the last ended: they slant, and a full-width fill rewrites only the bottom row.
comment fill_span_4bpp The horizontal line of draw_line at 4 bits per pixel, from start_x to end_x on row start_y (two pixels a byte, the even one in the low nibble): a lone odd first pixel, a whole byte at an odd address, then 8 pixels a long store and a masked long for the rest.
comment fill_span_8bpp Fills length + 1 bytes of the 8 bpp bitmap with color from pixel_address: an odd first byte, blocks of 16 in long stores, then 8, 4, 2 and 1; hands back pixel_address at the last byte written. Used for draw_line's horizontal lines and the rectangles' rows.
comment get_bitmap_address Sets bitmap_address to the address of x, y on the bitmap layer: 0x300000 + y x 0x200 + x at 8 bpp, 0x300000 + y x 0x100 + x / 2 at 4 bpp (bitmap_8bpp).
comment get_bitmap_address_2 Unused: a copy of get_bitmap_address (bitmap_address for x, y by bitmap_8bpp), init_bitmap_layer's.
comment init_bitmap_layer Unused: clear_bitmap_layer's set-up without its fill: set_bitmap_scroll_2(0, 0, the depth from is_8bpp), get_bitmap_address_2(0, 0), video_layer_disable bits 5, 10 and 15 off, the bitmap clip 0, 0 to 0x13F (0x19F with video_control bit 15), 0xDF, and set_bitmap_window_2(0, 0, 0x1FF, 0xDF).
comment init_picture_colour_cache Unused: links the 16 three-byte slots of the colour cache at colour_cache (next, previous, colour) into a ring: slot n's next is n + 1 and its previous n - 1, colours 0; slot 0xF's next is 0 and slot 0's previous 0xF.
comment init_picture_unpacker Unused: sets up the picture unpacker: the colour cache in bitmap_draw_buffer, newest slot 0, the bit position (current_player, borrowed for it) 0, and the done map, a bit per byte of the 0x200 x 0xE0 bitmap, cleared at SPRITE_RAM + 0x100; hands back colour_cache, newest_slot and done_bits.
comment read_picture_colour Unused: the next colour of the picture through its 16-slot cache: a 0 bit and 8 bits are a new colour, stored in the oldest slot (the one after the newest in the ring), which becomes the newest; a 1 bit and 4 bits name a slot, whose colour it takes, and that slot moves to the newest place. Hands back colour and newest_slot.
comment read_picture_size Unused: reads two 16-bit numbers from the picture's bit stream at the bit position in current_player: the width, handed back, and the height, left in r14.
comment read_pixel Unused: hands back the pixel at x, y (the 8 bpp byte, or the 4 bpp nibble by x's parity), or -1 outside bitmap_window.
comment rotate_bitmap_for_monitor_flip Unused: with setting_monitor_flip set, turns the 320 x 224 picture on the 8 bpp bitmap layer half a turn: each of the top 0x70 rows swaps its 320 bytes, byte-reversed, with the matching bottom row taken backwards.
comment set_bitmap_clip Unused: puts left, top, right, bottom in order into the bitmap clip (video_bitmap_clip_left/top/right/bottom), with video_layer_disable bit 15 on (clear_bitmap_layer turns it off).
comment set_bitmap_clip_2 Unused: a copy of set_bitmap_clip (the corners in order into video_bitmap_clip_left/top/right/bottom, video_layer_disable bit 15 on).
comment set_bitmap_scroll Sets the bitmap layer's scroll (video_bitmap_scroll_x/y) and its depth: is_8bpp bit 0 into bitmap_8bpp and video_control bit 11.
comment set_bitmap_scroll_2 Unused: a copy of set_bitmap_scroll (video_bitmap_scroll_x/y, bitmap_8bpp and video_control bit 11 from is_8bpp), init_bitmap_layer's.
comment set_bitmap_window Puts left, top, right, bottom in order into bitmap_window_left/top/right/bottom: the window that draw_line, fill_rect and the pixel routines clip to.
comment set_bitmap_window_2 Unused: a copy of set_bitmap_window (the corners in order into bitmap_window_left/top/right/bottom), init_bitmap_layer's.
comment unpack_lz_bytes Unused: unpacks the bit stream at packed into dest (current_player is the bit position): a gamma code gives the byte count, then 0 and 8 bits a literal (kept in recent_literals, a ring of 16), 10 and 4 bits a byte from recent_literals, 11 and two gamma codes a copy of earlier output (distance, length) that can overshoot the count.
comment unpack_picture_pixels Unused: paints the picture at bitmap_address (rows 0x200 apart, r14 of them) in runs: gamma code n, n - 1 pixels in the current colour (a done one keeps its own, which becomes current), one in read_picture_colour's colour; then a 1 bit starts a chain a row down per pixel (2-bit steps: left, down, right, or 0: end, two left, two right).
comment add_object_pieces Allocates piece_count pool 2 blocks (init_object_2) chained through child after block, each with parent = spawn_first (block) and no child of its own; first = spawn_first, spawned = 1. Nothing (spawned 0) when free_object_stack_2 holds fewer than piece_count.
comment add_pieces_to_current_object add_object_pieces with spawn_count pieces chained after object_cursor.
comment allocate_object Pops a block off free_object_stack and links it at the end of the object list (link_object): object = 0x200000 | its entry. free_count is how many were free; 0 means none, and object is left as it was.
comment allocate_object_2 Pops a block off free_object_stack_2, the second pool (object = 0x200000 | its entry), without linking it into the list; free_count is how many were free (0: none, object left as it was).
comment allocate_object_after_current Unused: allocate_object, but the block is linked right after the current object instead of at the end (last_object moves to it when the current object was last); object_count + 1.
comment allocate_object_or_start_list allocate_object, or when object_count is 0 pops a block and starts the list with it: first_object and last_object (and their addresses) = it, linked to itself (link_object). Hands back the object.
comment allocate_object_outside_loop allocate_object for code outside run_objects (the spawners the game modes run): it first reloads free_objects, first_object and last_object from free_object_stack and first/last_object_address.
comment check_left_screen Hands back 0xFFFFFFFF once the current object, having been on screen (flag 0x800), is more than 0x20 outside the 0x140 x 0xE0 screen, else 0; sets flag 0x800 while it is on the screen (no margin until then).
comment check_left_screen_margin check_left_screen with margin instead of 0x20 once the object has been on screen (0 before).
comment check_left_screen_margins check_left_screen with margin once the object has been on screen and unseen_margin before.
comment clear_object_speeds Unused: init_object_fields_tail on the current object with mode 2: only speed_x, speed_y and speed_z are cleared.
comment copy_position_from_cursor The current object takes object_cursor's x, y, projected_x, projected_y, z and floor_z.
comment copy_position_to Copies object_cursor's x, y, projected_x, projected_y, z and floor_z into object.
comment copy_position_to_cursor Copies the current object's projected_x, projected_y, z and floor_z into object_cursor (x and y are left alone).
comment copy_screen_position_from_cursor The current object's screen_x and screen_y = object_cursor's. Its only caller, draw_eel_piece_parts, is unused.
comment count_active_players active_player_count = how many of the three players' objects (player_block_offsets) lack flag 0x400 (out of the game).
comment create_child_shadow Gives the current object a shadow: a pool 2 block (allocate_object_2) with sprite 0x5D90, palette 0x87A0, sprite_flags 0, sprite_scale = scale and parent = the object, kept as its child (0xFFFF when pool 2 is empty). It leaves current_player = the object.
comment create_child_shadow_9ffe Unused: create_child_shadow with palette 0x9FFE instead of 0x87A0.
comment create_child_shadow_unscaled Unused: create_child_shadow with sprite_flags 0x1000 and no sprite_scale, palette 0x9FFE, and without touching current_player.
comment crumbling_floor_crack Within 0x60 of the leader: the cracking steps (crumbling_floor_play_crack_steps); once they have stopped (animation_timer negative) the block holds the last step's screen offset (apply_step_screen_offset) and is drawn.
comment crumbling_floor_draw Puts the block in the platform list and, while screen_x is within -0xA0..0x1E0, queues it at screen_y + depth_offset - sprite_offset_y, kept in draw_depth (the players standing on it read it).
comment crumbling_floor_play_crack_steps Advances the cracking steps (animate_with_screen_offset); at step 4 they stop (animation_timer -1) and with flag 0x400000 the block falls at once (crumbling_floor_start_falling); else crumbling_floor_draw.
comment crumbling_floor_start_falling The block starts to fall: state + 1 (falling), speed_z 0, the crumbling steps from step 4 at speed 6, and sound 0xD unless silent_row is set or screen_x is past an edge that depends on direction; then crumbling_floor_draw.
comment crumbling_floor_update A crumbling floor block of a group (stage 4 rock ledges, stage 9 ice blocks), a platform: within 0x60 of its leader (parent) it cracks, once the leader passes it falls with gravity 0x2000 and is removed below the screen; a rider with flag 0x400000 makes it fall at once. While grey_screen_active it is only drawn, in palette 0x83A0.
comment cursor_floor_screen_y Hands back the current object's screen_y moved down by object_cursor's floor_z - z (whole pixels): where object_cursor's floor is on the screen.
comment debris_fly A debris piece: move_object_projected, removed off screen (delete_if_off_screen), fall_to_floor. In the air its direction and speed are added to speed_x/speed_y every frame and it steps through debris_frames every 4 frames (back 9 past last_frame); on the floor it stops and blinks for lifetime frames, then goes.
comment delete_current_object Deletes the object at object_cursor: unlinks it (fixing first_object and last_object), object_count - 1, pushes its block on free_object_stack (current_object = it, push_free_object) and moves object_cursor back to the previous object, so run_objects goes on with the right next one.
comment delete_if_off_screen Deletes the current object and its children (delete_object_and_children, deleted = 1) when screen_x leaves -0xC0..0x200 or screen_y leaves -0xC0..0x1A0; deleted = 0 otherwise.
comment delete_next_object Unused: delete_object of the object after the current one (its next).
comment delete_object Deletes object: unlinks it (fixing first_object and last_object), object_count - 1, and pushes its block on free_object_stack; free_count is the new count and previous the object before it. object_cursor is left alone.
comment delete_object_and_children The default handler (init_object_fields): frees the current object's child chain (pool 1 children with delete_object, pool 2 ones with free_object_2), then delete_current_object; deleted = 1. Halts in int_default if free_object_stack_2's count goes past 0x100.
comment delete_object_outside_loop delete_object for code outside run_objects: reloads free_objects, first_object and last_object first, and stores first_object and last_object back into first/last_object_address after.
comment direction_to_object The current object's direction = the angle (angle_of_vector) to the object at r14 in projected coordinates; hands back angle_of_vector's octant.
comment direction_to_speed speed_x = sine_table[direction] * speed >> 6 and speed_y = -(cosine_table[direction] * speed >> 6), with direction taken & 0x3FF.
comment distance_to_object nearest_player_distance = the distance from the current object to the object at r14 (square_root of dx * dx + dy * dy, projected coordinates), also handed back.
comment distance_xy_to_object distance_x = |dx| and distance_y = |dy| (whole x and y) from the current object to the object at r14, also kept in distance_to_player_1 and distance_to_player_2.
comment draw_child_shadow Places the current object's shadow (its pool 2 child) on its floor, at projected_x and projected_y + floor_z with the sprite offsets, palette 0x9FFE, queued at its screen_y - 0x10. With flag 0x10: palette 0x87A0, drawn every other frame (shadow_blink), and on the skipped frames current_object is left at the shadow.
comment drop_rings Releases rings_to_release rings from object_cursor, one scatter_rings_random ring at a time: at its position, heading released_ring_angle at speed 0x60 (spread at random with released_rings_random), each 0x10 frames after the last (released_ring_wait); released_ring_flip marks their palettes. It never checks that a ring was made.
comment explode_object Turns the current object into the big explosion: explosion_frames 0x1F-0x2B, palette 0x86D0, handler explosion_animate; sound 7.
comment explode_object_medium Unused: sets explosion frames 0xF-0x1F, palette 0x82A0 and handler explosion_animate, then runs on into explode_object, which puts frames 0x1F-0x2B and palette 0x86D0 over them (and plays sound 7).
comment explosion_animate An explosion: unproject_object, the next explosion_frames frame every other frame until last_frame, then removed (delete_object_and_children); drawn every frame (queue_sprite).
comment face_nearest_player Distance and angle (square_root, angle_of_vector) to each player in play, players 3, 2, 1, into the per-player and nearest_player variables. Its direction then becomes the last angle worked out plus direction_to_player_offsets[nearest_player], an offset (0x1C-0x20) rather than that player's angle, so it does not face the nearest player.
comment face_object The current object's direction = the angle to the object at r14 (projected coordinates), then distance_to_object, whose distance it hands back.
comment fall_to_floor Gravity for the current object: speed_z + 0x6000 each frame z is not floor_z, and when z + speed_z would reach floor_z it lands (z = floor_z, speed_z 0); z itself is moved elsewhere. height = floor_z - z (0 on the floor); over_pit = 1 when floor_z_int is 0x800 or more.
comment find_nearest_player_x nearest_player_distance = the smallest |player x - x| (whole x) over the three players, in the game or not.
comment free_object_2 Pushes object back on free_object_stack_2 (pool 2 blocks are never linked into the list).
comment group_leader_move An invisible marker leading a group along its row (direction 0/4 +y, 1 -y, 2 +x, 3 -x) by its group_leader_data entries (group_leader_read_script, group_leader_move_step); the members fall as it passes. It holds back (base_speed 0) in stage 8 in game modes 0x17 and 0x1D, or while no_player_in_play, player_mishap or player_dropping_in.
comment group_leader_move_step project_object, then chaser_speed: base_speed while screen_x is past screen_threshold, else the camera's ground speed * catch_up_factor / 0x100, at least base_speed (which also ends a hold-back, as do 0x300 scroll units or hold_timer running out); the leader moves by it along its direction unless waiting (wait_timer) or grey_screen_active.
comment group_leader_read_script The leader's wait_timer counts down; then, once scroll_position reaches its next group_leader_data entry, the entry gives screen_threshold, catch_up_factor, base_speed and wait_timer (a negative catch-up factor ends the path and removes the leader). Then group_leader_move_step.
comment group_leader_wait Holds a new group leader: leader 0 (sprite_frame 0) until group_leader_0_held is clear, the others until group_leaders_1_3_held is; then handler group_leader_move.
comment init_object init_object_fields with mode 0: the whole object.
comment init_object_2 init_object_fields with mode 1: only the first 0x30 bytes, the size of a pool 2 block.
comment init_object_fields Clears an object for use: draw_next 0, sprite_flags 4, sprite_frame and sprite_palette 0, sprite_scale 0x100, the layer, screen position, hitbox, anchor and size 0, handler = delete_object_and_children, direction and projected_x/projected_y 0. With mode 0 also flags and init_object_fields_tail; previous, next, parent and child are kept.
comment init_object_fields_tail speed_x, speed_y and speed_z = 0; unless mode is 2 (clear_object_speeds), also z and every field after it up to next_handler.
comment is_floor_block_off_screen Unused: 0 when the current object's floor_block (low half) is one of nearby_floor_blocks, the floor blocks near the screen, else 0xFFFFFFFF (also when there are none).
comment is_off_screen 0xFFFFFFFF when the current object is more than 0x20 outside the 0x140 x 0xE0 screen (screen_x + 0x20 >= 0x180 or screen_y + 0x20 >= 0x120, unsigned), else 0.
comment is_off_screen_margin is_off_screen with margin_x and margin_y in place of 0x20.
comment is_on_screen 1 while screen_x is within -0xC0..0x200 and screen_y within -0xC0..0x1A0 (delete_if_off_screen's bounds), else 0.
comment link_object Links object at the end of the circular list, between last_object and first_object (previous and next), makes it last_object (and last_object_address) and adds 1 to object_count.
comment move_object_projected Moves the current object in projected space: speed_x adds up in projected_x_carry, whose whole pixels go to projected_x; projected_y + speed_y, z + speed_z; then unproject_object (x and y, the floor, the screen position).
comment move_object_projected_no_floor move_object_projected without unproject_object: x, y and the floor stay as they were and only screen_x/screen_y follow (without the sprite offsets).
comment move_object_to_next_stage_slot Unused: while next_stage_slot_request is set, moves the current object's x, y, z_int, previous_floor_z_int and previous_floor_block from this stage's link point to the next one's (- stage_link_from_*, + stage_link_to_*), the object side of continue_to_next_stage_slot.
comment next_random Two steps through the random number area at 0xD80000: random_position moves on by the word it points at (within 0x80000) and the word at the new place is taken. random_value hands back the two words, the first in the low half.
comment place_sprite_at_cursor_floor The current object's screen position = object_cursor's, moved down to object_cursor's floor (floor_z - z): the place for its shadow. Hands back 0xFF, the bucket drawn first (under everything).
comment place_sprite_near_cursor The current object's screen_x and screen_y = object_cursor's + offset_x and offset_y.
comment push_free_object Pushes current_object's block on free_object_stack (count + 1, the entry on top).
comment released_ring_bounce A released ring that can be collected, for lifetime frames: with flag 0x20 its direction turns by 0x300 (speeds cleared for the frame); touched (flag 4) it plays sound 0x13 and is removed, unless its toucher is a player in state 0xD or 0x20 with hurt_contact_delay. Else released_ring_move; at the end it goes on as scattered_ring_vanish.
comment released_ring_move Moves a released ring (move_object_projected, fall_to_floor): on landing it bounces up again at bounce_speed - bounce_speed / 4 + 0x10000, and in the air animation_frame gains 3 a frame. Speeds from direction and speed, animate (spawn_ring_glint at frame 4), drawn at its floor with its shadow, in the touchable and rider lists.
comment released_ring_start A released ring that cannot be collected yet: released_ring_move for pickup_delay frames, then it clears flag 0x200 and goes on as released_ring_bounce.
comment released_ring_wait A released ring waiting release_delay frames, undrawn; then sound 0x13 and it goes on as released_ring_start.
comment reset_linked_objects_flag electric_fence_state = 0 when a game starts (fence_post_init sets 1, flame_machine_remove 0xFF; fence_post_update switches on it).
comment reset_object_list Empties the object list: first/last_object_address = 0x200000, object_count 0, and free_object_stack holds all 0x80 blocks of 0xA0 bytes (offsets 0x0000-0x4F60 from 0x200000).
comment reset_object_pool_2 Fills free_object_stack_2 with all 0xFF pool 2 blocks of 0x30 bytes from 0x205000 (offsets 0x5000, 0x5030 ...).
comment reset_object_pool_2_alt Unused: reset_object_pool_2 with 0x1D8 blocks of 0x34 bytes (0x205000-0x20AFE0).
comment reset_objects Empties the object list and both pools (reset_object_list, reset_object_pool_2); the run_objects after it finds no objects and only reloads free_objects. Called when the game mode changes.
comment reset_objects_alt Unused: reset_objects with reset_object_pool_2_alt for the second pool.
comment run_objects Runs every object once: from the one after last_object round to last_object, object_cursor and current_object = each and its handler is called (call_code); a handler that deletes its object moves object_cursor back. Then first/last_object_address take first_object and last_object. Nothing when object_count is 0.
comment spawn_debris Throws count debris pieces (spawn_objects, handler debris_fly) from the current object: each at its position, palette 0x8300, thrown up at a random speed_z, with random extra zoom, direction and speed on top of the object's own, debris_frames 0-9 or (odd random) 0xA-0x13.
comment spawn_group_leader Spawns group leader n (the record's first byte & 3), kept in group_leaders[n]: sprite_frame = n, the record's x, y, z (floor_z = z), direction and path in group_leader_data, handler group_leader_move (group_leader_wait while group_leader_0_held or group_leaders_1_3_held). spawned 0xFFFFFFFF, or 0 when no object is free.
comment spawn_object_groups Spawns the stage's object_group_spawns records as the scroll reaches them. Types 0, 2 and 6 build a group (spawn_object_groups_build_group; 2 with group_depth_step -8, 6 silent), 1 and 8 spawn a group leader, 3 a group slot trigger, 4 an ice platform leader, 5 a sand block leader, 7 a waterfall.
comment spawn_object_groups_2 Spawns the stage's stage_record_spawns records as the scroll reaches them; the first call also clears the bridges' plank counts and the group slots. Type 0 builds a group on a free bridge (spawn_object_groups_2_pick_bridge), 2 and 3 on bridge 0 and bridge 1, and 1 and 4 spawn a group leader.
comment spawn_object_groups_2_build_group Builds bridge group_bridge_number's planks when the scroll is within 0x100 past the record (else skips it): a row of stage_object_records_2 members on the floor block, each linked to the one before (var_9A); plank_count_var + the count, and the last plank (spawn_arg_2_low 0xFF) goes in bridge_0/1_last_plank. Free objects are never checked.
comment spawn_object_groups_2_pick_bridge Type 0: the group goes to bridge 0 if bridge_0_plank_count is 0, else to bridge 1 if bridge_1_plank_count is 0 (else it waits); that count + 1, then spawn_object_groups_2_build_group. Its free-object wait reads the record's word at +5, the floor block's high half (0), not the member count, so it never waits.
comment spawn_object_groups_build_group Builds a group when the scroll is within 0x100 past the record (else skips it) and free_object_stack has room: a row of stage_object_records members sharing the leader, floor block and direction, each numbered and set on the floor (slope too), depth_offset stepped by group_depth_step; a group slot when the record asks.
comment spawn_object_groups_mark_silent Type 6 records: group_silent = 0xFF, which the members keep (crumbling_floor_start_falling then plays no sound 0xD), then as type 0 (spawn_object_groups_no_depth_step).
comment spawn_object_groups_no_depth_step Types 0 and 6: group_depth_step = 0 (type 2 sets -8), then spawn_object_groups_build_group.
comment spawn_object_with_pieces Spawns a pool 1 object (init_object, parent = object_cursor, next_handler = return_address) and spawn_count - 1 pool 2 pieces chained from it (add_object_pieces): first, spawned 1. Nothing when spawn_count <= 0 or the pools are short, but its empty-stack test keeps the high half of the caller's free_count: a non-zero one passes.
comment spawn_objects Spawns spawn_count pool 1 objects chained through child (each init_object, parent = object_cursor, next_handler = return_address); first = the first one, spawned 1. Nothing when spawn_count <= 0 or free_object_stack holds fewer than spawn_count (spawned 0).
comment spawn_objects_by_handler Spawns the stage's stage_spawns_by_handler records as the scroll reaches them, one a frame (the list starts on the stage's first call): an object at the record's x and y with its word in sprite_frame (a parameter its handler reads) and its handler. A record 0x100 or more behind the scroll is skipped; it waits while no object is free.
comment spawn_objects_by_type Spawns the next stage_spawns_by_type record once scroll_position passes it: object_types[type] gives spawn_count and the handler (spawn_object_with_pieces); its words go to projected_x, projected_y, z, spawn_arg_1 and spawn_arg_2 (unproject_object works out x and y). A record the pools cannot take is dropped. Then count_active_players.
comment start_spawns_by_type Starts the stage's stage_spawns_by_type list: type_spawn_cursor at its start, type_spawn_stage = stage; also clears string_count, stage_trigger_bits and unused_type_spawn_byte.
comment take_free_object Pops a block off free_object_stack (object = 0x200000 | its entry) without linking it into the list; free_count is how many were free (0: none, object left as it was).
comment unlink_child_chain Unused: breaks up the child chain from object: each child link becomes 0xFFFF, until one already is.
comment unproject_object Works the current object's x and y out from projected_x and projected_y, the inverse of project_object: x = (projected_x * 0x100 - whole projected_y * 0x200) * 0xB5, y the same with +; then find_floor and update_screen_position_no_offset.
comment update_screen_position_no_offset screen_x = projected_x - view_x and screen_y = (projected_y + z) >> 16 - view_y, without the sprite offsets update_screen_position adds; hands back screen_y.
comment blink_current_map_marker While step_timer (the map screen's timer) counts down, draws the stage's name over its marker (map_marker_blink_sprites for stage_map_markers[stage]: layer, x, y, sprite, palette) on the frames its bit 3 is set, so it blinks every 8 frames.
comment draw_map_eggman Queues the map's Eggman with the sprite_frame it was given: palette 0x680, zoom 0x100, layer 1, at 0xB7,0xDF, sprite_flags 0x107 (queue_sprite_in_layer).
comment draw_map_markers_and_trail draw_map_stage_markers, then start_map_trail and draw_map_trail_dot: the markers with the trail's first dot, the trail starting over at every call (map_intro_update's fade-in).
comment draw_map_stage_markers Draws the map's 7 markers and their number badges (map_marker_layouts) from the sprite blocks at 0x209102: those before stage_map_markers[stage] - 1 in cleared_palette, that one too once object_cursor's trail is done, when the stage's badge (map_number_badge_sprites) blinks every 8 frames. Nothing for a stage with no marker.
comment draw_map_trail_dot Draws the trail's dot for object_cursor at base + offset, moving it one step along the segment while trail_done is clear (sprite 0x64E0, palette 0x660, layer 1, through a pool 2 block taken and freed again); when segment_timer runs out, the next segment (next_map_trail_segment).
comment map_eggman_draw_plain Unused: sprite 0x5760 (the pose before the gestures), then draw_map_eggman.
comment map_eggman_draw_plain_2 Unused: the same code again: sprite 0x5760, then draw_map_eggman.
comment map_eggman_hands_update Eggman's hands on the map (map_eggman_update spawns them): for timer (0xF0) frames, sprite 0x5798 + 8 * frame_step (0-5, a step every 4 frames), palette 0x690, layer 0, at 0xB7,0x9B; then the object stays without drawing.
comment map_eggman_update Eggman on the map (from behind): stage_map_sounds_1; after 0x3C frames his hands (map_eggman_hands_update) and 0x186 frames of map_eggman_gesture_frames (sound 0x21 for gesture 7); a pose held 0x50 frames (stage_map_sounds_2), then a loop (stage_map_sounds_3); gone at map_screen_step 4. Its stage 9 count for sound 0x95 only reaches 1.
comment map_intro_update The map screen's first object, by animation_frame. Step 0 adds 4, so steps 1-3 (the icon from stage_map_icons flying to the corner) never run; 4 loads the island map (map_monitor_script) and Eggman's head (map_eggman_head_script) and starts the fade; 6 fades in over the markers, then spawns map_screen_update; 7 removes it.
comment map_screen_draw_flipping_icon Unused: map_screen_update's step 4 drawing alone: sprite 0x65C8, palette 0x6C0 at 0x20,0x10, zoom 0x100, layer 0, sprite_flags 0x10A; blink_phase cycles 0-2 and timer counts down; sprite_flags gets 0x40 while flags bit 1 (in timer's low byte) is set, else 0x80 while blink_phase bit 1 is.
comment map_screen_update The map's icon, by animation_frame: spawns map_eggman_update; the markers, the trail to the next stage, the stage name blinking; sprite 0x65C8 flipping 0x28 frames; a wipe (map_screen_step + 1); the next stage's icon flying to its marker (map_stage_positions); the last wipe, map_screen_step = 5, then step 10 for good (11 is never reached).
comment move_map_stage_icon The stage icon's flight: offset + step each frame, timer - 1 (the caller's animation_frame + 1 at 0), drawn at base + offset blinking with sprite 0x6538 every other frame (palette 0x6B0, layer 1). At 0x30,0x10 it stays put and only blinks.
comment next_map_trail_segment Starts the trail's next segment, from the point at trail_point to the one after it (next_trail_point): base = start, target = end, steps = (base - target) / segment_frames in 16.16, offsets 0 (offset_y through z_int and the animation bytes that share it). A negative end x is the trail's end: trail_done = -1.
comment run_map_screen Game mode 4, the map screen, by map_screen_step: 0 the window wipe in around the stage (map_wipe_in_windows, start_window_wipe), bit 0x4000 set in the mixer blend words, 1 the fade (update_fade), 2 spawns map_intro_update, 3-5 waits while the map's objects run. Its caller (game_mode_stage_end_fade) goes to game mode 2 at step 5.
comment run_map_screen_wait_for_wipe Unused: a step of run_map_screen that its jump table does not list: update_screen_wipe until the four edges are done (edges_done & 0xF = 0xF), then map_screen_step + 1.
comment start_map_trail Starts the trail of stage_map_trails[stage] on the current object: its first word is the frames per segment (segment_frames, segment_timer), then next_map_trail_segment from its first point. A stage without a trail (0xFFFFFFFF) sets trail_done, but that address is read all the same.
comment start_window_wipe Starts a window wipe: the bitmap layer as a black frame (video RAM from 0xE000 filled with 0x1111) whose window moves from the screen's edges to the record's (left, top, right = left + 0x100, bottom = top + 0x80, kept on the screen) over its frames, or out from it with window_wipe_closing (update_screen_wipe).
comment update_screen_wipe Moves the wipe's four window edges by their speeds and stops each at its end (the record's window, or the screen's edges with window_wipe_closing), writing them to the bitmap layer's clip window; edges_done gets a bit per edge that has arrived (0xF for all four). Its test for 0xFFFF, which would turn the layer off, never passes.
comment draw_title_emblem_part Queues the current object as a part of the title's emblem: palette 0, sprite_flags 0x200 (8 bits per pixel), zoom 0x100 (queue_sprite_in_layer).
comment drift_title_cloud Moves the current cloud up 1 and left 3 and grows it by 2 (sprite_scale), up to 0x140.
comment drift_title_cloud_2 drift_title_cloud, but a sprite_scale past 0x120 jumps to 0x140.
comment init_title_cloud_block Unused: sets up cloud as a title cloud: zoom 0x80, layer 8, sprite_flags 0, palette 0xD80 (the clouds' own set-up is the same with other zooms).
comment init_title_puff_sprite Unused: sets up puff as a smoke puff like title_smoke_puff_animate's, but at y 0x7A: layer 7, zoom 0x100, sprite_flags 0, palette 0xEB0.
comment run_sega_logo The SEGA logo, by sega_logo_step: 0 set-up (8 bpp bitmap filled with 0x8E, colour offsets 0x20), 1 fade in (+ 1 every 2 frames to 0x3F), 2 zoom in (sega_logo_scale + 0x18 a frame), 3 animation (sega_logo_frame to 0x1E), 4 0x69 frames, 5 fade out, 6 0x3C frames, 7 done. The sprite is a pool 2 block taken and freed every frame.
comment run_title_screen Every frame of the title: run_sega_logo; once sega_logo_step is 7 and title_scene_built is 0, builds the title: its variables, tile_bank_1_palette_list, title_sky_script and title_waving_background_script, NBG2/NBG3 scroll and pages, and the objects title_land_slide, title_emblem_animate, timed_spawner_run and title_sky_wave.
comment set_sega_logo_sprite The SEGA logo's sprite on the current object: at 0xA0,0x70, palette 0x420, layer 0, zoom 0x100, sprite_flags 0, frame 0x53D0 + 8 * sega_logo_frame.
comment timed_spawner_run Spawns the title's timed objects: counts frames in timer and, for each timed_spawns entry whose frame has passed, allocates an object whose handler is the entry's offset from timed_spawner_run (flags_low_byte 0). Removed at the list's 0xFFFF end or once title_done.
comment title_clouds_1_drift Twelve white clouds (sprites 0xB6D8, 0xB6E0, 0xB6F0 at several zooms, palette 0xD80, layer 8): itself and 11 pool 2 blocks kept in its fields, moved by drift_title_cloud every 4 frames and drawn every frame. Once title_done it frees the blocks and goes.
comment title_clouds_2_drift Seven more clouds (sprites 0xB6D8-0xB700, palette 0xD80, layer 8): itself and 6 pool 2 blocks kept in its fields, moved by drift_title_cloud_2 every 4 frames and drawn every frame. Once title_done it frees the blocks and goes.
comment title_clouds_3_drift Seven clouds coming in from the right (x 0x146-0x1E7, palette 0xD80, layer 8): itself and 6 pool 2 blocks, moved by drift_title_cloud every 4 frames and drawn every frame. Once title_done it frees the blocks and goes.
comment title_emblem_animate The title's emblem in four 8 bpp quarters (0x52E0-0x52F8), Sonic rising (0x5300 + 8 * n, title_sonic_rise_heights) then looping (title_sonic_loop_frames), and from step 0x13 Ray and Mighty popping up (title_ray_frames, title_mighty_frames). At title_timer 0x258 it sets title_done, frees its 7 pool 2 blocks and goes.
comment title_emblem_animate_spare_return Unused: a lone return inside title_emblem_animate that nothing reaches: the code before it always branches past it.
comment title_flying_craft_move A small craft (sprite 0xBB58, palette 0xEB0, layer 7) flying left from x 0x154 at y 0x70, one pixel a frame; every 0xB frames it leaves a title_smoke_puff_animate puff at its x + 4. Removed once title_done.
comment title_land_slide The strip of land with green hills (sprite 0xB6D0, palette 0xD70, layer 7) at y 0x7A, sliding left from x 0x190 half a pixel a frame (speed_y holds x in 16.16) until title_done, then removed.
comment title_sky_wave The waving sky: first clears the row scroll table and its sums in video RAM and turns NBG2's row scroll on (video_rowscroll_control 0x401); then each frame its 0x80 rows add a sine_table value to their offsets and scroll by them, NBG3 moves 1/4 pixel and the colours cycle (title_sky_cycle_palettes). Removed once title_done.
comment title_smoke_puff_animate A puff of the craft's smoke (palette 0xEB0, layer 7, y 0x70): steps through title_smoke_puff_frames every 3 frames and is removed at its 0xFFFF end or once title_done.
comment escape_alarm_flash Stage 0xB's escape alarm: on its first frame sets bit 0x4000 in the NBG0-3 and bitmap blend words (and in escape_alarm_stray_flags, which nothing reads), colour offsets red 0, blue and green 0x40; then every 2 frames blue and green swing between 0x28 and 0x3F, sound 0x19 at each end. Removed when escape_seconds is 0.
comment event_0c_finish Game mode 0x0C step 3: when scene_timer runs out (0xA frames), game mode 3 and scroll_mode 7 (following the players).
comment event_0c_first_quake Game mode 0x0C step 1: the scroll stops (scroll_mode 1) once scene_timer is down to 0x1E; at 0x3C, its first frame, camera_shake_active and sound 0xF; when scene_timer runs out, step 2 with scene_timer 0x3C.
comment event_0c_second_quake Game mode 0x0C step 2: camera_shake_active and sound 0xF at scene_timer 0x3C (its first frame), spawn_walls every frame; when scene_timer runs out, step 3 with scene_timer 0xA and sound 0xA.
comment event_0c_start Game mode 0x0C step 0: scene_timer 0x3C, the rollers behind the door (spawn_door_rollers), scene_step + 1, scroll_mode 0 at scroll_speed 2.
comment event_0f_show_messages Game mode 0x0F step 2: scene_timer + 1, and the next event_0f_messages record whose time has come opens a message box (spawn_message_box with its x, y, kind and frames); at 0x258 scroll_mode 0 at speed 5 and game mode 3. Nothing sets game mode 0x0F.
comment event_0f_start Game mode 0x0F step 0 (nothing sets that mode): scene_timer 0, scroll_mode 0 at speed 3 (scroll_speed_low), scene_step + 1.
comment event_0f_stop_scroll Game mode 0x0F step 1: scene_timer + 1; from 0x3C the scroll stops (scroll_mode 1); at 0x78 event_0f_message_cursor = event_0f_messages, step 2 and scene_timer 0.
comment event_1c_follow Game mode 0x1C step 1: follow_base_speed 5, scroll_mode_low 7 (1 while no_player_in_play); at scroll_position 0x7E9F step 2, scene_timer 0x1E and event_1c_ride_phase 0xFF.
comment event_1c_scroll Game mode 0x1C step 3: scroll_mode_low 0 at scroll_speed_low 0xF.
comment event_1c_stop Game mode 0x1C step 2: follow_base_speed 0, scroll_mode_low 1 (stopped); when scene_timer runs out, step 3 and event_1c_riding 0xFF.
comment falling_rock_bounce Unused: a rock from spawn_falling_rocks: moves by speed_x/speed_y and falls (gravity 0x6000, find_floor), bouncing off its floor at half the speed while that is over 0x20000, then lies still (flag 0x400); animated while it moves and drawn (queue_sprite) until check_left_screen sees it leave.
comment message_box_show A message box: while its frames (speed_x_fraction) last, sprite_width grows 0x18 a frame up to the full width (speed_x_int); then it shrinks 0x18 a frame and is removed. Drawn in bucket 0, on top.
comment place_message_box Sets up box as a message box: at screen_x, screen_y, frames kept in speed_x_fraction, and from message_box_kinds[kind] its sprite, palette, full width (speed_x_int) and height (sprite_height); sprite_flags 0x9000, width 0, handler message_box_show, and sprite_scale = the word at +6 of its frame's record (sprite_frames).
comment rock_debris_fly A stone fragment in screen space: its 1/16-pixel position (projected_x, projected_y_fraction) moves by speed_x_fraction/speed_y_fraction, screen = position / 16, removed 0x20 off the screen. Pieces whose sprite_flags have no bits above 0xFFF grow (zoom + 0x30 a frame, removed at 0x600). Animated, bucket 1.
comment spawn_falling_rocks Unused: falling_rock_count rocks from falling_rocks: x, y, z, speed x and y (<< 12) and an animation each, palette 0x8240, sprite_flags 0x1000, animation speed 4, handler falling_rock_bounce; stops when no object is free.
comment spawn_message_box Opens a message box: allocate_object_outside_loop, then place_message_box with screen_x, screen_y, kind and frames; hands back the box (left as it was when no object is free).
comment spawn_message_box_2 Unused: spawn_message_box with allocate_object instead of allocate_object_outside_loop.
comment spawn_stage_06_lightning Stage 6's lightning: the first call (stage_init_flags 0x1000) starts stage_06_lightning_cursor at stage_06_lightning_strikes; when scene_timer reaches the next record's time, a bolt at its screen x/y with its animation, palette 0x9110, sprite_flags 0x1008, handler stage_06_lightning_animate, and sound 0xB. Waits while no object is free.
comment stage_00_geyser_column_rise A segment of the water column under the geyser's top (parent): rises with it (z - its speed_z), takes stage_00_geyser_column_sprites[its animation_frame] and is drawn, until it is below screen y 0x130, when it is removed.
comment stage_00_geyser_column_start A new column segment: sprite_flags 0x1000, palette 0x1010, at stage_00_platform's projected position with z + 8, then stage_00_geyser_column_rise.
comment stage_00_geyser_rise The top of the water spout bursting from stage_00_platform: starts at its position, z - 8, with stage_00_geyser_animation, and rises by speed_z from the stage_00_geyser_speeds steps (cursor kept in floor_z); every 0x1C of rise, 8 times, it adds a column segment (stage_00_geyser_column_start). Removed below screen y 0x130.
comment stage_00_platform_break The platform breaking up: animates and is queued 0x3C buckets in front of its depth (depth_to_bucket - 0x3C); removed once screen_y reaches 0x130.
comment stage_00_platform_stand The stone platform under the players: while game_mode is below 0x0A or scene_step below 5 it is queued 0x3C buckets behind its depth (at most bucket 0xFF); at step 5 it takes palette 0x9030 and stage_00_platform_break_animation, moves (projected_x + 4, projected_y - 0x13) and goes on as stage_00_platform_break.
comment stage_00_platform_start Waits while stage_05_switch_step is 2, then sets up the stone platform: sprite 0x6FF8, palette 0x9040, sprite_flags 0x1000, at x 0x185, y 0x1545, z 0 (project_object); stage_00_platform = it, handler stage_00_platform_stand.
comment stage_05_event_show_message update_stage_05_event's step 3 at scene_timer 0xA: spawn_message_box(0xA0, screen_y, kind, frames) and sound 0x70, then stage_05_event_step_3_frame.
comment stage_05_event_show_message_11 Unused: like step 3's message, with message box kind 0x11 (sprite 0x65E8) at y 0x40 for 0x78 frames.
comment stage_05_event_show_message_12 Unused: the same with message box kind 0x12 (sprite 0x65F0) at y 0x40 for 0x1E frames.
comment stage_05_event_step_3_frame A frame of update_stage_05_event's step 3: spawn_walls and the contact checks (check_hits, check_walls, check_platforms); scene_timer + 1, and at 0xA0 the next step with scene_timer 0.
comment stage_06_lightning_animate A lightning bolt: animates and is drawn while current_game_mode is 0x10; removed at the end of its animation or once the game mode changes.
comment start_escape_alarm_flash Spawns the escape alarm (allocate_object_outside_loop): an object with handler escape_alarm_flash, started 0.
comment start_escape_countdown Sets up the escape's countdown: two pool 2 digit sprites (palette 0xF60, centre x 0xA0 in projected_x, y 0x80) into escape_tens_digit and escape_units_digit; escape_countdown_frames 0, escape_seconds 0x14 (20), escape_started and escape_countdown_running 0.
comment update_ending Game mode 0x18 (stage 0x11, the ending): scene_step 0xFF on its first frame, next_stage_slot_request 0, joining_closed 0xFF, build_ending_scene, the floor blocks, the restart point, every spawner and the contact checks; when every player_status is 0, game mode 7 (saved_game_mode keeps the mode).
comment update_escape_countdown While escape_started: escape_countdown_frames + 1 (held at 0x3B until escape_countdown_running is set) and at 0x3C escape_seconds - 1. The seconds are drawn on top as two digits (escape_countdown_digits) whose zoom grows with the frames (frames * 9); the tens only when not 0, the units centred then.
comment update_stage_05_event Stage 5's end, by scene_step: 0-1 the stage frame with goal_unlocked set until check_stage_clear moves to game mode 0x17 (kept at 0x0A); 2 run_stage_05_switch to stage 0 and its stone platform, 0xB4 frames; 3 Eggman's booth and a message box; 4 a 0x3C-frame quake; 5 the geyser and the rocks; 6-7 update_stage_00_exit_scene, then game mode 2.
comment apply_coin_setting Applies the chosen coin/credit setting: setting_coin_number wraps past 0x19 and setting_coin_number_individual past 0xE to 0; prints its text and number (common or individual chutes) and copies its 4 bytes of coin_setting_values into setting_coins_per_credit, setting_bonus_adder and the two chute multipliers. test_settings_changed = 1.
comment blink_test_cursor Draws the blinking '>' (test_cursor_arrow) at test_cursor_column/test_cursor_row (draw_blinking_test_cursor).
comment check_moving_seat_dip Reads the moving seat DIP switch (io_port_f bit 0, on when low); when it differs from moving_seat_dip it is stored there and in setting_moving_seat, an on switch also sets setting_cabinet_type 0 (setting_cabinet_config too), and the EEPROM is saved (eeprom_save_and_lock).
comment check_rom_checksums The ROM part of the MEMORY TEST: compute_crc16 of 0x000000-0x01FFFF and of 0x080000-0x09EFFF, compute_crc16_alternate_bytes from 0x100000 and from 0x100001, each against its word of rom_checksums; a mismatch sets memory_test_errors 1, 4, 2 or 8 in that order.
comment clamp_coin_setting Keeps the setting byte at setting within 1-9.
comment draw_blinking_test_cursor Writes cursor_char's character with attribute 0x200 at test_cursor_column/test_cursor_row, or a blank (test_cursor_space) while test_cursor_blink, counted down at each call, is below 1; the count goes back to 0x1E at -0xF.
comment draw_coin_credit_cell Prints the credits for coins (the count) in a chute with multiplier, by setting_coins_per_credit and setting_bonus_adder: the whole credits, 9 at most, and any part as a fraction over setting_coins_per_credit (draw_fraction), at the cell cell_position names; hands back the next cell.
comment draw_coin_credit_table The manual page's "1 COIN COUNTS AS" table: draw_coin_credit_cell for 9 down to 1 coins in chute 1 and, with common chutes (setting_coin_chute_type 0), in chute 2.
comment draw_coin_setting_number Prints setting_number in decimal (convert_to_bcd, draw_hex_2) at text cell 31,9 with attribute 0x1200.
comment draw_difficulty_setting Draws setting_difficulty (0-3, back to 0 when out of range) as its number + 1 at text cell 27,5 and as a bar on the bitmap layer: colour 0x17 from x 0x78 to 0xF8, then colour 0x49 over (n + 1) * 0x20 pixels, rows 0x38-0x40; flipped coordinates on a flipped screen.
comment draw_test_hex_2 draw_hex_2 of value at text_position with attribute 0x200.
comment draw_test_hex_4 draw_hex_4 of value at text_position with attribute 0x200.
comment draw_test_menu Prints the TEST MODE menu (test_menu_text) on a cleared text layer, its OUTPUT TEST line only with moving_seat_dip, and eeprom_write_count (decimal, at 31,26) when show_eeprom_write_count is set; test_cursor_positions = test_menu_cursor_positions.
comment draw_vitality_setting Draws setting_initial_vitality (0-3, back to 0 when out of range) as its vitality_setting_numbers value at text cell 25,9 and as a bar on the bitmap layer: colour 0x17 from x 0x78 to 0xF8, then colour 0x49 over (n + 1) * 0x20 pixels, rows 0x58-0x60; flipped coordinates on a flipped screen.
comment erase_test_cursor Writes a blank at test_cursor's place in cursor_positions (test_cursor_column/test_cursor_row loaded from it), through draw_blinking_test_cursor, which also counts test_cursor_blink down.
comment exit_test_mode EXIT: clears the three players' credits and bonus coins and test_mode, leaving the test mode; when eeprom_write_count is 10000 (0x2710) or more it first shows the EEPROM caution and waits for test.
comment flip_test_coordinates Mirrors the rectangle left, top, right, bottom about the screen's centre (0xA0, 0x70), for drawing on a flipped screen; the callers test the flip (video_control bit 9).
comment get_trackball_quadrant quadrant's low byte = the direction bit of a trackball move: 1 (dx and dy not negative), 2 (dy negative), 4 (dx negative) or 8 (both negative); a trackball at rest counts as 1.
comment move_input_test_cursors Queues the input test's cursors (the third for 3 players), prints GOOD for each trackball whose quadrant mask is 0xF, draws the sprites (build_sprite_list) and moves each cursor by its trackball / 4, within x 0xA5-0x132 and its panel's rows. Cursors 2 and 3 show as handler_high/handler_low and projected_y_int/projected_y_fraction.
comment run_test_mode One frame of the test mode: runs the screen test_screen names: 0 test_menu, 1 toggle_test_continue_mode, 2 test_memory, 3 test_inputs, 4 test_sound, 5 test_crt, 6 test_game_assignments, 7 test_coin_assignments, 8 test_bookkeeping, 9 test_backup_clear, 0xA test_outputs, 0xB exit_test_mode.
comment set_test_cursor Loads test_cursor's column and row from test_cursor_positions into test_cursor_column/test_cursor_row, test_cursor going back to 0 at the list's 0xFF end.
comment start_test_mode Enters the test mode: display off, show_eeprom_write_count when service is held, palettes, video registers, sprites, init_sound and sound 0x94, objects reset, text and bitmap layers cleared (8 bpp, filled with 9), port D outputs and colour offsets off, test_mode 0xFF on the menu; then the first run_test_mode.
comment test_backup_clear BACKUP DATA CLEAR: service moves the cursor between YES and NO; test on YES prints COMPLETED, clears the bookkeeping and the ranking (reset_bookkeeping, reset_ranking) and leaves 0x78 frames later, on NO at once. In continue mode it goes on to OUTPUT TEST only with moving_seat_dip, else to the menu on EXIT.
comment test_bookkeeping BOOKKEEPING, printed once from bookkeeping_coin_credits: the coins per chute (chute 3 only for individual chutes on a 3-player cabinet) and their total, coin, service and total credits, games, then the total, play, average, longest and shortest times (split_frame_count). Test leaves.
comment test_coin_assignments COIN ASSIGNMENTS: service moves the cursor, test acts on the item: chute type (COMMON/INDIVIDUAL; setting 1 again unless manual), credits to start (1 or 2), the next coin/credit setting (apply_coin_setting), MANUAL SETTING (test_coin_assignments_manual_page while coin_manual_page is set) or EXIT, saving the EEPROM if test_settings_changed.
comment test_coin_assignments_draw_main_page Draws the COIN ASSIGNMENTS main page (coin_manual_page 0): clamps setting_coins_per_credit, setting_bonus_adder and the chute multipliers to 1-9 (an adder of 1 then becomes 0), then prints the chute type, the coin/credit setting and its number (MANUAL SETTING when setting_coin_number is negative) and the credits to start.
comment test_coin_assignments_manual_page The MANUAL SETTING page, printed on its first frame: coins per credit (1-9), bonus adder (0 or 2-9) and the chute multipliers (1-9, chute 2 only with common chutes), each changed by test (setting_coin_number -1: manual) with the credit table redrawn; EXIT goes back to the main page.
comment test_coin_assignments_spare_return Unused: a second return right after the end of test_coin_assignments' credits-to-start item; nothing jumps to it.
comment test_coin_assignments_spare_return_2 Unused: a second return right after the end of the manual page's coins-per-credit item (test_coin_assignments_manual_page); nothing jumps to it.
comment test_crt C.R.T. TEST, by test_screen_ready: 1/2 is four rows of 0x20 colour bars (crt_test_color_bars) on the bitmap layer; test shows 2/2, a border (crt_test_border_lines) and a crosshatch of lines every 8 pixels in colour 0x7E; test again fills the bitmap with 9 and leaves.
comment test_game_assignments GAME ASSIGNMENTS: service moves the cursor (past MOVING SEAT without moving_seat_dip), test changes difficulty, vitality, advertise sound, monitor flip, cabinet type (type 1 turns the moving seat off), start buttons or moving seat; EXIT saves the EEPROM when test_settings_changed. Item 8, setting_small_large, is past the cursor list.
comment test_inputs INPUT TEST: on its first frame prints the texts, fills its panels on the bitmap layer (a third for 3 players) and sets up three trackball cursors in one pool 2 block (screen_sprite_block); then every frame each switch shows ON or OFF from input_ports_active and the cursors move (move_input_test_cursors). Test, pressed and released, leaves.
comment test_memory MEMORY TEST, all on its first frame: with interrupts off and the sound CPU held (sound_cpu_hold), check_rom_checksums and the work, video, sprite, palette and sound RAM tests; then video and sound set up again and the 0x13 results printed from memory_test_errors, whose clearing also clears show_eeprom_write_count, its second byte.
comment test_menu test_screen 0, the TEST MODE menu: drawn on its first frame with the cursor on EXIT (item 0xA); service moves test_cursor down the items, wrapping, and skips OUTPUT TEST (item 9) without moving_seat_dip; test opens item n as test_screen n + 1 (test_menu_item = n).
comment test_outputs OUTPUT TEST: prints whether the moving seat is in use; every frame port D bits 2-3 are cleared and, with setting_moving_seat, held 1P and 2P attack (input_ports_active 0x10000, 0x1000000) set them (0x4, 0x8) with ON/OFF texts. Test goes back to the menu (in continue mode on EXIT, with CONTINUE MODE printed).
comment test_palette_ram Fills palette RAM from 0x600000 with ram_test_patterns, then test_palette_ram_verify_first_half. The 8 passes of 4 * 0xF9 words end at 0x603E40, so its exit at 0x604000 is never taken and 0x603E40-0x603FFF goes untested (0x607E40-0x607FFF likewise in the second half).
comment test_palette_ram_fill_second_half Fills palette RAM from 0x604000 with the patterns (8 passes, up to 0x607E40), then test_palette_ram_verify_second_half.
comment test_palette_ram_verify_first_half Compares palette RAM from 0x600000 (up to 0x603E40, as filled) with the patterns: a wrong low byte sets memory_test_errors 0x100, a wrong high byte 0x80; then test_palette_ram_fill_second_half.
comment test_palette_ram_verify_second_half Compares palette RAM from 0x604000 (up to 0x607E40, as filled) with the patterns: a wrong low byte sets memory_test_errors 0x40000, a wrong high byte 0x20000.
comment test_sound SOUND TEST: sound_test_number shown at 20,12; service steps it 0-0x8C (wrapping) and plays it after sounds 0x94 and 0x95 (number 0: 0x94 and 0x95 twice); test plays 0x94 and leaves.
comment test_sound_ram Saves sound RAM 0x700000-0x701FFF in video RAM and fills it with ram_test_patterns, then test_sound_ram_verify.
comment test_sound_ram_verify Compares sound RAM 0x700000-0x701FFF with the patterns (any wrong byte sets memory_test_errors 0x10), then copies the saved contents back from video RAM.
comment test_sprite_ram Fills sprite RAM 0x400000-0x41FFFF with ram_test_patterns, then test_sprite_ram_verify.
comment test_sprite_ram_verify Compares sprite RAM 0x400000-0x41FFFF with the patterns: a wrong byte at an even address sets memory_test_errors 0x1000 (low nibble) or 0x800 (high nibble), and any wrong byte, even ones too, sets 0x400 or 0x200.
comment test_video_ram Fills video RAM 0x300000-0x31FFFF with ram_test_patterns, then test_video_ram_verify.
comment test_video_ram_verify Compares video RAM 0x300000-0x31FFFF with the patterns: a wrong byte at an even address sets memory_test_errors 0x10000 (low nibble) or 0x8000 (high nibble), and any wrong byte, even ones too, sets 0x4000 or 0x2000.
comment test_work_ram Saves work RAM 0x200000-0x20FFFF in video RAM, fills it with ram_test_patterns and checks it (test_work_ram_verify, which copies it back).
comment test_work_ram_verify Compares work RAM with the patterns test_work_ram wrote: a wrong byte sets 0x20 (even address) or 0x40 (odd) in memory_test_video_word, the saved copy of memory_test_errors, so the bits land in memory_test_errors when work RAM is then copied back from video RAM.
comment toggle_test_continue_mode Menu item 0: flips test_continue_mode and prints INDIVIDUAL MODE or CONTINUE MODE, then back to the menu at once (test_screen 0, test_screen_ready 1). In continue mode each test goes on to the next instead of the menu.
comment wrap_sound_test_number sound_test_number past 0x8C goes back to 0.
comment wrap_sound_test_number_down Unused: sound_test_number past 0x8C (0 stepped down gives 0xFF) becomes 0x8C, the downward twin of wrap_sound_test_number; test_sound only steps up.
comment draw_opening_machine_shadow Places the opening machine's shadow, its child, as the machine flies away: the shadow's sprite_width and sprite_height hold its x and y offsets, scaled by the machine's sprite_scale and added to the machine's screen position; it takes the same sprite_scale and is queued at the machine's screen_y - 0x10.
comment draw_opening_machine_shadow_spare_return Unused: an empty function, a lone return the original leaves after draw_opening_machine_shadow.
comment how_to_play_button_hand_start Sets up stage 7's how-to-play hand over the button: a multi-part sprite (part_list how_to_play_button_hand_parts, animation how_to_play_button_hand_animation) at screen 0x42, 0xB5, with next_press at the first pair of how_to_play_button_presses; handler how_to_play_button_hand_update.
comment how_to_play_button_hand_update Stage 7's hand on the button, removed once stage is not 7. When game_mode_timer reaches the time of next_press's (time, frame) pair it sets that animation_frame and steps it (animate_part_list_step, so the part list after it shows), then moves to the next pair; at 0xC8 it opens message box kind 0xC. Queued at depth 0xEE.
comment how_to_play_panel_show Stage 7's control panel strip: queued at depth 0xEC while stage is 7, removed otherwise.
comment how_to_play_panel_start Sets up stage 7's control panel strip (sprite 0x71F0, palette 0x8780) at screen 0, 0x88 and shows it: handler how_to_play_panel_show.
comment how_to_play_trackball_hand_start Sets up stage 7's hand on the trackball: sprite 0x71C8 at screen 0xFB, 0xB3 (projected_x keeps that x, the hand's rest point), and as its child a second-pool block for the ball, sprite 0x71C0 0x38 left of and 8 above it, which how_to_play_trackball_animation turns every 4 frames.
comment how_to_play_trackball_hand_update Stage 7's hand on the trackball, removed with the ball once stage is not 7. From game_mode_timer 0x32 it rubs the ball (step: down-left to 0x20 left of projected_x, 8 frames' rest, back) while the ball turns. At timer 0xA it lights up until 0xBE and opens message boxes kinds 0xD and 0xB; from 0x20 the scroll runs at speed 0x10.
comment metal_wall_broken The broken metal wall: removed once off screen, else placed on screen (update_screen_position_no_offset) and drawn (metal_wall_draw).
comment metal_wall_draw Draws the metal wall: its sprite by frame_index (metal_wall_sprites) at depth screen_y - 0x14 (bucket 0xFE once broken); pieces 1-3, the left half, shadow and floor cracks, from metal_wall_part_tables (the flipped offsets when flipped); and while dust_timer counts down, every other frame, four dust puffs, two at its base and two above them.
comment metal_wall_init Sets up stage 7's metal wall and its seven pieces (left half, shadow, floor cracks, four dust puffs, the last two starting at dust frame 3): kind 5, health 4, a box hitbox, and with flipped set an x-flipped sprite with another hitbox; handler metal_wall_wait.
comment metal_wall_rise Raises the metal wall out of the floor: frame_index steps through frames 0-4, one every 4 frames, then rise_limit = 7 and the handler becomes metal_wall_update. Runs metal_wall_update every frame, handing it octant.
comment metal_wall_update The risen metal wall. When health drops: frame_index from metal_wall_damage_frames, a hop if it lost more than 1, (4 - health) * 2 debris chunks (spawn_arg_1 is last_health as a halfword) and a 2-pixel screen_x nudge that move_object_projected undoes; at 0 it scatters ring_count rings and becomes metal_wall_broken. Moves, lands, touchable while health remains; drawn.
comment metal_wall_wait The metal wall waiting in the floor: measures the nearest player from 0x10 right of and below its projected position (face_nearest_player); within 0x80 it starts the dust (dust_timer 0x14) and rises (metal_wall_rise), else it runs metal_wall_update as it is.
comment opening_ground_run Stage 6's rushing ground: removed once scene_step reaches 2; moves by speed_y, shows at its x/y shaken up or down by stage_06_shake_pixels (stage_06_shake_sign picks which), animates and is queued at depth 0x20.
comment opening_ground_start Sets up stage 6's opening ground rushing toward the viewer: opening_ground_animation, a step every frame, palette 0x9120, at x 0xA0, y 0xB8 (screen coordinates); handler opening_ground_run.
comment opening_machine_approach The landed opening machine comes closer: it grows by zoom_speed a frame (sprite_scale = zoom) while the landing shake decays (stage_06_shake - 0x80 a frame down to 0, stage_06_shake_sign flipping every frame); the next phase once scene_timer passes 0xC0.
comment opening_machine_engulf The machine's open jaws swallow the screen: zoom grows by zoom_speed, itself + 1 a frame (sprite_scale = zoom); at 0x5B0 the next phase (opening_machine_hold) and palette 0x87A0, black.
comment opening_machine_hold The opening machine's last phase: an empty function; the machine stays as opening_machine_engulf left it, still drawn by opening_machine_update.
comment opening_machine_leap_in The opening machine leaps in from below: from scene_timer 0x3C it moves by speed_y under gravity 0x6000 (at depth 0x10, behind, while going up); landing at y 0x63 it starts the shake (stage_06_shake 0x800), the landed animation and sound 0xA, and moves to the next phase.
comment opening_machine_lunge The opening machine lunges at the viewer: it grows faster every frame (zoom_speed + 0x1000), moves up once sprite_scale reaches 0x1B3, and goes to the next phase at zoom 0x32B.
comment opening_machine_show_mouth Turns the machine's multi-part sprite into the single sprite of its open jaws (0x7198, palette 0x8510), moved down by 0x1E pixels times the zoom, and goes straight on to the next phase, opening_machine_engulf.
comment opening_machine_start Sets up stage 6's opening machine: scene_step_seen = 0xFF, so that its handler, opening_machine_update (run at once), sets it up for the current scene_step.
comment opening_machine_update Stage 6's opening machine, set up again on entering scene_step 0, 1, 2 or 4. In steps 0, 1 and 4 the giant head (a multi-part sprite, opening_machine_object) runs its phase (leap in, approach, lunge, mouth, engulf, hold), shaken with the ground; in steps 2 and 3 it flies off into the distance, sprite_scale 0x4000 / (zoom + 0x10), with its shadow.
comment animate_sinking_mine Draws a sinking mine: flash_mine_palette for a sinking mine, a new frame every frame (mine_frames by mine_sink_steps[sink_step], sink_step looping over 8), then draw_mine_with_bubbles.
comment barred_gate_init Sets up stage 0x10's barred gate: sprite 0x9F00 (palette 0x92F0) at height 0x158, with barred_gate_animation ready at 2 frames a step; handler barred_gate_update.
comment barred_gate_update The barred gate: removed once off screen (margin 0x50); once barred_gate_closing is set it plays barred_gate_animation once, then sound 0x29 (animation_frame 0xFF stops it). Queued at screen_y - 0x40.
comment bubble_column_animate A bubble column: every 3 frames the next of 0x14 frames of mine_bubble_frames from entry spawn_arg_1 * 0x13 on, looping; queued at its screen_y and never removed.
comment bubble_column_init Sets up a bubble column, an object type no stage list spawns: palette 0x1060, sound 0x24; handler bubble_column_animate.
comment clipped_copy_sprite_start Unused: sets up sprite 0x7058 (palette 0x5050) standing on its floor; given a free object and two second-pool blocks it keeps a copy of its whole block in palette bank 0x8000 (copy_object) and chains two clip entries (sprite_flags 0x5000) as its child and grandchild, as icicle_buried_part_draw does; handler clipped_copy_sprite_update.
comment clipped_copy_sprite_update Unused: until it leaves the screen (then the copy and the two clip blocks are freed and it goes): draw_depth = its floor's screen y; it queues the second clip entry, itself, the first clip entry with the screen y of the point 0x60 above its floor (point_to_screen) in its low 12 sprite_flags bits, and the copy at its position, all at draw_depth.
comment draw_eel_piece_parts Unused: an ending for eel_segment_move that also draws a piece's parts 2 and 3: every 3 frames each takes its next frame (eel_part_frames_2 by eel_part_steps, eel_part_frames), both at the piece's screen position and floor depth + 1; then the shadow at its floor. The live code skips parts 2 and 3, so they are never drawn.
comment draw_mine_with_bubbles Draws a mine under water: at its floor depth and touchable; its shadow, unless switched off (piece_value), every other frame at its floor; the three bubble parts at (0, -0xA), (5, -0x14) and (0xA, -0x14) from it, a new frame every other frame, queued at r14 + 1 and r14 - 1: the shadow's screen_y on its frames, else whatever earlier code left in r14.
comment draw_rope_bridge_ropes Draws a plank's two rope parts, if it has them, at its screen position plus their offsets (kept in projected_x and projected_y_fraction), slack (sprite 0x9D20) while it falls; on a plank that takes a group slot, the slot's mark hides one: 0xFF the first rope, 0x80 the second.
comment eel_anchor_draw_shadow Draws the eel anchor's shadow, its third part, at the anchor's floor in bucket 0xFF.
comment eel_anchor_stretch Stretches the eel: the anchor's speed changes by stretch_step every frame, turning back in (-0x18) past 0x1111 and out again (+0x28) under 0x480, with a 0x3C-frame pause at each end. Piece n down the chain gets the anchor's direction, n times its speed and stretch_step (as var_81); then the anchor's shadow is drawn.
comment eel_anchor_update An eel's anchor at its den: once the den has set its bit (den_number) of stage_trigger_bits it waits for the pieces to explode (eel_anchor_wait_explode). Otherwise removed off screen, drawn at its floor and touchable, and the eel stretches out and back (eel_anchor_stretch), resting pause_timer frames at each end.
comment eel_anchor_wait_explode An eel whose den has collapsed: removed with all its pieces once piece_count, the pieces still to finish exploding, reaches 0. Its chaser test (facing 0 only: chaser_object within 0x10 in y) sets the den's bit of stage_trigger_bits, which is already set when this runs.
comment eel_anchor_wait_explode_spare_return Unused: an empty function, a lone return the original leaves after eel_anchor_wait_explode.
comment eel_den_collapse A collapsing eel den: the next frame of eel_den_collapse_frames every 3 frames, then (after frame 0x2C) only eel_den_draw; drawn every frame.
comment eel_den_draw Draws an eel den: placed from its map position (unproject_object), removed off screen, else queued at screen_y - 0x18 and touchable.
comment eel_den_init Sets up an eel den: a stone arch (sprite 0xAC70) with a 0x47 x 0x42 box hitbox 0x28 high, kind 0xC and health 1; handler eel_den_wait. It ends with current_object back on object_cursor, which matters only when spawn_eel_den runs it.
comment eel_den_wait An eel den waiting: it collapses when chaser_object comes within 0x35 in y, when it is hit with no health left, or when its bit (spawn_arg_1) of stage_trigger_bits is already set: it sets that bit, which makes its eel explode, drops 3 rings from 0x18 up and crumbles (eel_den_collapse). Else drawn (eel_den_draw).
comment eel_head_explode_delay An eel's head waiting its turn to explode: counts explode_delay down, still drawn at its floor with its shadow; then eel_piece_start_explosion.
comment eel_head_move An eel's head, placed along the eel (place_eel_piece) and drawn at its floor with its shadow, but not touchable: every 4 frames its next frame (eel_head_frames, or with facing set the extend or retract frames as stretch_step goes out or in), resting 0x3C frames after each run. Once its den's bit is set it explodes after (speed / the anchor's speed) * 0xB frames.
comment eel_init Sets up an eel at its den: the anchor ball (kind 0x24, speed 0x480 its stretch, stretch_step 0x10, direction 0x34A, 0x14A with facing) with two parts and a shadow, then piece_count four-part pieces after it, wave amplitudes growing by wave_step, wave angles 0x30 apart (kept in explode_delay): the head last (eel_head_move), the rest eel_segment_move.
comment eel_piece_explode An exploding eel piece: the next frame of underwater_explosion_frames every 3 frames, from frame 2; after frame 0x15 it takes one off its anchor's piece count (spawn_arg_2_low), plays sound 0x24 and stays as eel_piece_gone. Queued at screen_y - 0x10.
comment eel_piece_gone An exploded eel piece: an empty function, so it is no longer drawn; its anchor removes it with the rest of the eel (eel_anchor_wait_explode).
comment eel_piece_start_explosion Starts an eel piece's explosion: 0x18 lower (projected_y), palette 0x1070, sprite_flags 4, from frame 2 of underwater_explosion_frames; sound 0x24, handler eel_piece_explode.
comment eel_segment_explode_delay A body piece of an eel waiting its turn to explode: counts explode_delay down, still drawn at its floor with its shadow; then eel_piece_start_explosion.
comment eel_segment_move A body piece of an eel: placed along the eel (place_eel_piece), drawn at its floor and touchable, its shadow (its fourth part) at its floor; parts 2 and 3 are not drawn. Once its den's bit is set it explodes after (speed / the anchor's speed) * 0xB frames (eel_segment_explode_delay).
comment event_12_stage_frame The stage frame of game modes 0x12 and 0x13: next_stage_slot_request 0 and update_player_summary; with no player left (player_status all 0) game_mode 7 runs at once, game_mode saved; with event_12_fast_scroll set scroll mode 7 becomes mode 0 at speed 0x20; then the scroll, tiles, floors, spawners and contacts.
comment event_19_check_rope_bridge Game mode 0x19's stage frame: while rope_bridge_collapsing is set the scroll runs at speed 0x1E (scroll_mode_low 0); before, from scroll_position 0xDA0 the scroll stops and, with a player in play, scene_timer counts to 0x78 and sets rope_bridge_collapsing. Then the stage frame; from scroll_position 0x1A00 game_mode 3 at scroll speed 5.
comment flash_mine_palette Flashes a mine: every 5 frames sprite_palette = mine_flash_palettes[mine_flash_steps[flash_step]], flash_step looping over 0xC; with sinking set, bit 15 of the palette is flipped and bit 14 set.
comment geyser_burst_erupt A geyser burst: the ground cracks and bursts (geyser_burst_animation, faster from frame 4, sound 0xA as frame 4 starts), then it sprays (geyser_burst_spray_animation, looping), touchable only while spraying; queued at screen_y - 0x10, removed once off screen.
comment geyser_burst_start Places a geyser burst on screen (project_object) and hands it to geyser_burst_erupt from the next frame.
comment geyser_erupt An erupting geyser: the ground cracks (geyser_animation, faster from frame 3, sound 0xA at frame 3), then the water jet (geyser_jet_animation, looping); touchable from frame 4, removed below screen y 0x180. Row geysers draw their jet at draw_depth (one back for each new row of their front), the others at screen_y.
comment geyser_front_init Sets up stage 0x10's invisible geyser front from geyser_fronts[sprite_frame] (the spawn's index): z, top_height, its run of geyser_lists, path and row_pattern, previous_floor_z_int 0x30 below (a row on the first step); clears geyser_front_start. Once an object is free it takes it as its hazard box (0x30 x 0xC0, kind 0x29) and waits (geyser_front_wait).
comment geyser_front_read_script The geyser front's path: with no wait left, once scroll_position reaches the entry's position or the front has risen to its z limit, it loads the entry's screen_threshold, catch_up_factor, base_speed and wait_timer, or at the path's end frees its hazard box and is removed; then geyser_front_rise_step.
comment geyser_front_rise The rising geyser front, the chasers' routine: its path (geyser_front_read_script) on the first frame and while play goes on; when a player mishap, a drop-in or no player in play begins it holds back (base_speed 0, screen_threshold 0x130, both saved, hold_timer 0xFF), then only steps (geyser_front_rise_step).
comment geyser_front_rise_sound The geyser front's rumble: while its screen_y is at most 0x130, sound 0x12 every 0x32 frames (sound_timer).
comment geyser_front_rise_step A step of the geyser front: chaser_speed by the chasers' rule (base_speed within screen_threshold, else camera speed x catch_up_factor, at least base_speed; holding back ends after 0x300 scroll units or hold_timer); past the wait it climbs that far up the floor's slope, its hazard box 0xA0 below; listed geysers, a row every 0x30 of rise, the line at top_height.
comment geyser_front_spawn_geyser_row A row of geysers from the front: sets flags bit 0 (the geysers of earlier rows then go one depth back) and previous_floor_z_int = z_int; with 5 free objects, five geysers at its x and z, 0x2E apart in y from 0x10 past it, each waiting its delay from the geyser_row_delays set its next row_pattern byte picks. Then geyser_front_rise_sound.
comment geyser_front_wait The geyser front before it rises: spawns its listed geysers as scroll_position reaches them (spawn_listed_geyser) and starts rising (geyser_front_rise) once geyser_front_start is set or scroll_position reaches 0x6500 (0x8400 for the second front).
comment geyser_line_move The invisible geyser line: once its wait is over it takes the next geyser_line_scripts entry (speed, wait_timer) when scroll_position reaches its position or y its y limit; it moves -y by speed, a geyser burst every 0x300 of travel, one more at the script's end, then goes. After a player mishap it stands still for up to 0xFF frames or 0x300 scroll units.
comment geyser_wait A geyser waiting to erupt (geyser_erupt): a listed one (flags bit 31) until scroll_position reaches erupt_delay, drawn meanwhile; a row one, not drawn, counts erupt_delay down, going a depth back whenever its front spawns a row. Neither counts while the front holds back; both keep anchor_x/anchor_y on their projected position.
comment lid_trap_init Sets up a lid trap: a pad (sprite 0x8840) with a 0x26 x 0x26 box hitbox, kind 0x23 (a player touching it gets stuck: state 0x2C), and its open lid as its child (sprite 0x8850); handler lid_trap_update.
comment lid_trap_update A lid trap, removed off screen. Once touched (flags bit 2) the pad turns kind 5 with sprite 0x8848, the lid takes palette 0x1110, sound 0x11 plays and sprung is set; then the lid snaps shut, a frame of lid_trap_close_steps every 4 frames. The pad is touchable and queued at screen_y - 0x58, the lid 0x10 above it.
comment mine_explode A mine exploding on the bottom: steps through underwater_explosion_frames, each held by its underwater_explosion_durations count, drawn with draw_mine_with_bubbles; after the table's 0x16 frames it plays sound 0x24 and is removed with its pieces (that last step reads one past both tables, never shown).
comment mine_fall A mine falling (and, run by mine_float, bobbing): moves, removed off screen, falls toward its floor. Above the water height spawn_arg_1 it flashes, is drawn at its floor depth and touchable, its shadow every other frame; at that height it splashes (spawn_mine_splash), plays sound 0x16, drops 4 and sinks (mine_sink).
comment mine_float A mine bobbing above the water: speed_z = sine_table[timer], timer + 0x10 a frame; once the nearest player is within 0x80 its handler becomes mine_fall. Runs mine_fall every frame.
comment mine_init Sets up a mine: sprite 0x8610, half size when spawn_arg_2 is set, a round hitbox of radius 8 and height 0x1E, kind 0x24; its pieces: a shadow at the mine's sprite_scale (the + 0x24 just before is overwritten) and three bubble parts from frames 0, 1 and 2, the last x-flipped; handler mine_float.
comment mine_sink A mine sinking through the water: speed_z - 0x5800 a frame against fall_to_floor's 0x6000, drawn by animate_sinking_mine; on the bottom it plays sound 0x20, takes palette 0x1070, switches its shadow off (piece_value 1) and explodes (mine_explode).
comment mine_splash_animate A mine's splash: the water column takes the next of mine_splash_frames every 3 frames, at its floor depth, and is removed with the spray after 0x1A; the spray, 7 below it, steps through mine_splash_spray_frames every 3 frames until 0x14, queued at r14 + 2, r14 being whatever earlier code left in it.
comment place_eel_piece Puts an eel piece on its anchor (projected position, z and floor_z, z raised by height_offset) with speed_x/speed_y from direction and speed, so the next move carries it out along the eel; wave_angle steps by 8 a frame and sine_table[wave_angle] x wave_amplitude is added to z and, twice, to speed_y.
comment propeller_machine_chase The propeller machine chasing the players (+y), the chasers' routine: its path (propeller_machine_read_script) on the first frame and while play goes on; when a player mishap, a drop-in or no player in play begins it holds back (base_speed 0, screen_threshold -0x50, both saved, hold_timer 0xFF), then only steps (propeller_machine_chase_step).
comment propeller_machine_chase_step A step of the propeller machine: past the wait it moves +y by chaser_speed (base_speed from screen_threshold on, else camera_slope_speed x catch_up_factor, at least base_speed); the propellers turn (propeller_machine_animation in three parts). From y 0x1B00 its box is 0x68 high, from 0x1BB0 geyser_front_start; sound 0xB every 0x3C frames on screen; zone list.
comment propeller_machine_init Sets up stage 0x10's propeller machine as chaser_object: multi-part, a 0xF2 x 0x80 box 0xF0 high of kind 0x18, propeller_machine_animation and propeller_machine_path. With a take_free_object block it copies propeller_machine_parts there as its part list, spawns its back zone (spawn_chaser_back_zone 0) and chases (propeller_machine_chase); else it retries next frame.
comment propeller_machine_read_script The propeller machine's path: once wait_timer has run out, when scroll_position reaches the entry's position or y its y limit it loads the entry's screen_threshold, catch_up_factor, base_speed and wait_timer, or at the path's end frees its part list block (sprite_frame is part_list's low half) and is removed; then propeller_machine_chase_step.
comment railing_segment_draw A railing segment: removed at scroll_position 0x6700 or once it has left the screen (margin 0x88); queued at screen_y + 0x40 while on screen.
comment railing_segment_start Places a railing segment on screen (project_object) and hands it to railing_segment_draw from the next frame.
comment railing_spawner_init Sets up stage 0x10's railing spawner at height 0x520, last_spawn_position 0; handler railing_spawner_update.
comment railing_spawner_update Stage 0x10's railing spawner, removed at scroll_position 0x5000: every 0x800 scroll units, with 0xA free objects, it lays 10 railing segments (multi-part railing_parts, handler railing_segment_start) at its x and z, 0xB8 apart in y, carrying on from where the last ten ended. The z of 0x528 each first gets is overwritten with the spawner's.
comment river_raft_foam_update A raft's foam: sits at its raft's (parent's) screen position and animates (river_raft_foam_animation); the raft queues it. Removed once flags bit 10 is set, which the raft does when it leaves the screen or tips over.
comment river_raft_spawner_init Sets up a raft spawner from river_raft_spawners[sprite_frame] (the spawn record's index): x, y, floor height, end_position, then its raft list (entry, list_start); last_raft_y = its y, last_raft_speed 0; handler river_raft_spawner_update.
comment river_raft_spawner_update A raft spawner, removed at end_position (from scroll_position 0x9C00 it sets goal_unlocked). When the last raft it launched (last_raft_y) has drifted down to it and the waterfall (group_leaders[0]) is downstream it launches its list (per raft: distance behind, speed, floor block; with foam) up to 0x8000, which loops, or 0xFFFF. It moves upstream with the camera.
comment river_raft_update A raft drifting down the river (+y by speed_y) on its floor, 2 lower while carrying something (flags bit 3), a platform with its foam; removed off screen. When the waterfall (group_leaders[0]) reaches its far end, then its middle, it drops the foam and tips over the edge (river_raft_fall_animation, river_raft_fall_palettes), falling till screen y 0xE8.
comment river_water_update A row of the river's water, its parent the waterfall: its frame follows the waterfall's animation_frame, drawn at screen_y + depth_offset, removed below screen y 0xE8. Once the waterfall reaches its y it takes two clip entries (given two second-pool blocks) cutting it at the waterfall's line; with the waterfall 0x5C past, it frees them and goes.
comment rope_bridge_plank_check_rider_and_draw Draws a plank on screen: platform list, its ropes, and queued at its floor's screen y + depth_offset (kept in draw_depth). A plank whose takes_slot is negative (none of stage 0x10's) first watches for a kind 0xF rider: the half it stands on marks the plank's group_slots byte, and a marked plank becomes record 0's normal plank on that half.
comment rope_bridge_plank_start_falling Starts a plank's fall: the next state, the tumbling animation from its start (2 frames a step), speed_z 0; the bridge's last plank clears its plank count (bridge_0_plank_count or bridge_1_plank_count: no more sag); sound 0x28 when on screen; then rope_bridge_plank_check_rider_and_draw.
comment rope_bridge_plank_update A rope bridge plank (its parent the group leader), every 4th with two rope parts. It sags: z eases a quarter of the way a frame (in speed_x) to its floor plus bridge_0_sag/bridge_1_sag[plank_index] (in previous_floor_z). Once the leader passes it along direction, or rope_bridge_collapsing, it falls tumbling; past screen y 0x1C0 it frees its ropes and goes.
comment scene_anchor_init Sets up stage 0x10's scene anchor from scene_anchor_positions[sprite_frame]: x, y, floor height and end_position; scene_object = it; handler scene_anchor_update.
comment scene_anchor_update Stage 0x10's scene anchor (scene_object), removed at end_position: every frame it sets its z so that at its x/y it sits at screen y 0xA0; the players caught in game mode 0x19 (state 0x30) ease their x and z toward it.
comment spawn_bubble_column Unused: spawns one bubble column (spawn_object_with_pieces) with palette 0x1060, spawn_arg_1 = r14 (its run of mine_bubble_frames) and handler bubble_column_animate, and plays sound 0x24, as bubble_column_init sets one up.
comment spawn_eel_den Unused: spawns one object (spawn_object_with_pieces) and sets it up as an eel den (eel_den_init, which puts current_object back on object_cursor).
comment spawn_geyser Makes a geyser at x, y and height r14: sprite 0x5DA0, a 0x20 x 0x30 box hitbox of kind 0x27 (it hurts), geyser_animation, draw_depth 0xEF, the front as its parent, erupt_delay = wait; handler geyser_wait. Hands back spawned (0xFFFFFFFF when made, 0 with no free object) and geyser.
comment spawn_geyser_burst Makes a geyser burst at the geyser line's position: palette 0x9130, a 0x30 x 0x30 box hitbox 0xA0 high of kind 0x29 (it hurts), geyser_burst_animation at 6 frames a step; handler geyser_burst_start.
comment spawn_geyser_line When the geyser front reaches its top: makes the invisible geyser line at geyser_line_starts[sprite_frame] (x, y, z, and its path in geyser_line_scripts); handler geyser_line_move.
comment spawn_listed_geyser Spawns the front's next geyser of geyser_lists once scroll_position reaches its position: at its x, y and z, waiting for scroll_position to reach its start position (flags bit 31, animation_speed 0x20). Hands back geyser, the new object (unchanged when none was made).
comment spawn_mine_splash As a mine hits the water: its palette becomes 0x1070, and a two-part splash is spawned at its position (projected_y + 1): the water column (palette 0x90A0, handler mine_splash_animate) and the spray, sprite 0x8570, palette 0x1080 | 0x4000.
comment spawn_waterfall Spawns the waterfall as a group leader (group_leaders[the record's leader & 3]) from the record: x, y, z, direction and its group_leader_data path; a multi-part sprite whose part list is a second-pool copy of waterfall_parts, waterfall_animation at 3 frames a step; handler waterfall_move. With no second-pool block free the object it allocated is left unset.
comment stone_block_draw A stone block: queued at screen_y + depth_offset until it has left the screen (check_left_screen), then removed.
comment stone_block_init Sets up a stone block from stone_block_positions[sprite_frame] (the spawn record's index): x, y, z and depth_offset; sprite 0x8A00, palette 0x9120; handler stone_block_draw.
comment turtle_drop_rings The surfaced turtle drops rings while it swims: one (drop_rings, angle 0x154, flip 2) whenever its countdown spawn_arg_1 runs out, then 0x30 again; spawn_arg_2 counts them, and past 4 it only swims (turtle_swim).
comment turtle_init Sets up a sea turtle: its shell (sprite 0x7F28) with a 0x26 x 0x26 box hitbox and kind 0xC, and as its pieces its head (sprite 0x7F70) and its shadow (sprite 0x5D88); handler turtle_wait.
comment turtle_surface A touched turtle rising: speed_z alternates between -0x8000 and 0 while it swims (turtle_swim); once 0x20 above its floor it stops rising and drops rings (turtle_drop_rings).
comment turtle_swim A swimming turtle: every 3 frames a stroke step (turtle_swim_frames, turtle_swim_speeds), with a 0x3C-frame glide after the last; it moves along direction 0x14C at speed + 0xE0, speed losing 3 a frame, and is removed off screen. Its head animates beside it; over a floor its shadow is its own frame, shrunk by its height. Drawn at its floor, touchable.
comment turtle_wait A turtle waiting under the water: drawn at screen_y - 0x20 and touchable until touched (flags bit 2), then stroke_step 1 and turtle_surface. The head and shadow code after the draw runs only while stroke_step is 4, which it never is here.
comment waterfall_move The waterfall, a group leader moving up the river (-y), the chasers' routine: its path (waterfall_read_script) on the first frame and while play goes on; when a player mishap, a drop-in or no player in play begins it holds back (base_speed 0, screen_threshold 0x160, both saved, hold_timer 0xFF), then only steps (waterfall_move_step).
comment waterfall_move_step A step of the waterfall: chaser_speed by the chasers' rule (base_speed while screen_x is within screen_threshold and screen_y within 0xE0, else camera speed x catch_up_factor, at least base_speed), and past the wait it moves -y by it; from y 0x1480 geyser_front_start. Sound 0x10 every 0x3C frames unless map_screen_active; animated, drawn.
comment waterfall_read_script The waterfall's path: once wait_timer has run out and scroll_position reaches the entry's position it loads the entry's screen_threshold, catch_up_factor, base_speed and wait_timer, or at the path's end frees its part list and is removed; then waterfall_move_step.
comment apply_stage_layer_blending Sets the stage's layer blending: clears bit 12 (blending) in the seven layer shadows from mixer_text_blend, then for each entry of stage_layer_blending[stage] (a count, then the entries) puts the entry's mask in the low 6 bits of the layer shadow it names and sets the entry's bit there, and its blend factor in mixer_blend with the enable bit 11.
comment banner_letter_spin A letter of a broken stage clear banner: spins through banner_letter_animation, queued at depth 0xFF, and is removed at its end; at step 0xC an odd letter (kind holds its number) spawns a sparkle (banner_sparkle_animation, y-flipped when kind bit 1 is set).
comment banner_sparkle_animate A banner sparkle: animates, queued at depth 0xFF, and is removed when its animation wraps.
comment blink_demo_prompt The demos' prompt: every 0x20 frames demo_prompt_shown flips, and on showing, the static sprite block at 0x209FC0 is set to PUSH START BUTTON (0x89B8), or INSERT COIN(S) (0x89A8) short of credits, at screen 0xA0, 0x64. It is queued on top while shown in a demo (current_screen 4 or 5); otherwise bits 2-3 of port D are cleared.
comment check_stage_clear Once scroll_position reaches goal_scroll_position with stage_exit_started set (and player_status_spare 0): players_finished is set for each player not in play (player_status 0, 2, 3, 4 or 0x82), and when players_finished and goal_unlocked, read as one 32-bit word, are all 0xFF the stage is cleared: game_mode 0x17, or 0x1E on stages 0xB-0xF.
comment check_stage_select_buttons Unused: while the service button is held (input_ports_active bit 0), player 1's button pressed sets game_mode 8, the debug stage select (written as the halfword game_mode_word).
comment clear_player_blocks Zeroes the three player blocks, 0x600 bytes from PLAYER_BLOCK(1); hands back 0.
comment cleared_banner_wait The CLEARED banner, not drawn yet: once stage_name_shown is set it is revealed (stage_clear_banner_reveal).
comment continue_to_next_stage_slot When next_stage_slot_request is set, which nothing does (it is only ever cleared): keeps the floor block where this stage links to the next (stage_link_points) and the one it reaches there, their x, y and height (stage_link_from_*, stage_link_to_*); then stage + 1, its map pointers, and stage_init_flags bits 0-3 cleared.
comment draw_game_over_banner Draws the GAME OVER banner (sprite 0x89A0, palette 0x8970: the words with Eggman behind them) with the static sprite block at 0x209FC0, at screen 0xA0, 0x6C, queued on top of everything.
comment game_mode_character_select Game mode 0x1B, choosing characters at a game's start: set up once (stage_init_flags bit 16: contact lists, camera and characters_taken cleared; the stage's view, map pointers, goal, spawns and palette; cabinet_events 0x15), then the spawners and contacts each frame; the intro (game_mode 1) after at least 3 frames, once character_select_timer is 0.
comment game_mode_continue Game mode 0xE, which nothing sets: CONTINUE ? (printed while continue_count is 9) and continue_count at text cell (24, 14), down 1 a second (a button press at once); after 1 the texts go and game_mode 7. A start pressed with credits (by cabinet_config) clears player_status and the contact lists, erases the texts and returns to saved_game_mode.
comment game_mode_event_09 Game mode 9, which no code or stage event sets: only the scroll, the tile layers, continue_to_next_stage_slot and the nearby floor blocks; nothing changes the mode.
comment game_mode_event_0A Game modes 0xA and 0xB (only 0xA is ever set: stage 5's event at scroll 0x100): with a player left, stage 5's switch event (update_stage_05_event); with none, game_mode 7 at once, game_mode saved.
comment game_mode_event_0C Game mode 0xC, stage 4's event at scroll 0: by scene_step event_0c_start (also for step 4), event_0c_first_quake, event_0c_second_quake, event_0c_finish; then the scroll, tiles, restart point and the hit, wall and platform checks (no spawners).
comment game_mode_event_0D Game mode 0xD, stage 8's event at scroll 0: the players frozen (players_frozen, event_0d_active) for 0xB4 frames (scene_timer), then the scroll runs at speed 0x1C; with the stage frame going on (no zones), at scroll_position 0xA00 game_mode 3, the players free and scroll mode 7.
comment game_mode_event_0F Game mode 0xF, which no code or stage event sets: by scene_step event_0f_start (also for step 3), event_0f_stop_scroll, event_0f_show_messages; then the scroll, tiles, restart point, spawn_walls and the hit, wall and platform checks.
comment game_mode_event_10 Game mode 0x10, stage 6's opening scene (the first of a game): by scene_step, the opening objects and update_stage_06_background for 0x1A4 frames (background step 3 at 0x168; the colour offsets down 1 every 8 frames from 0xF0 to 0x12B), then background step 5, the offsets reset and 0x12C frames of lightning (spawn_stage_06_lightning); then game_mode 2.
comment game_mode_event_12 Game mode 0x12, stage 9's event at scroll 0x4C00: event_12_stage_frame until scroll_position 0x80B1, where the scroll stops (scroll_mode 1) until every player is out of play or done drifting (drift_done); then a sign (kind 0xF at 0x644, 0x149E), event_12_fast_scroll cleared, scroll mode 7 and game_mode 3, run at once.
comment game_mode_event_13 Game mode 0x13, stage 9's event at scroll 0: the scroll held (scroll_mode 1) for 0x78 frames (scene_timer) while update_screen_wipe opens the screen, then scroll mode 0; event_12_stage_frame every frame, and from scroll_position 0x16AC game_mode 3, run at once.
comment game_mode_event_14 Game mode 0x14, stage 9's event at scroll 0x1800, the scroll stopped: once a player is in play and none in state 0x25 has yet to bounce (sequence_bounced), it spawns an icicle dropper and Eggman's speech, and 0x12C frames later scroll mode 7 and game_mode 3; a sign (kind 0xE) at scene_timer 0x3C. The stage frame; game over with no player left.
comment game_mode_event_15 Game mode 0x15, stage 3's event at scroll 0xA360: the stage frame with the scroll stopped (scroll_mode 1) until check_stage_clear changes game_mode (run at once); game over with no player left.
comment game_mode_event_16 Game mode 0x16, stage 3's event at scroll 0x1B80 and 0x8610: the stage frame without the scroll or tile updates until scroll_resume_request is set, then scroll mode 7 and game_mode 3; game over with no player left.
comment game_mode_event_18 Game mode 0x18, the last stage's (0x11) event at scroll 0: the ending (update_ending).
comment game_mode_event_19 Game mode 0x19, stage 0x10's event at scroll 0xC80: update_player_summary (game over with no player left); a player in play whose x passes 0x17C sets barred_gate_closing; then event_19_check_rope_bridge.
comment game_mode_event_1A Game mode 0x1A, stage 8's event at scroll 0x3100: set up on scene_step 0, then each event_1a_scroll_script entry gives the scroll its mode and speed once scroll_position reaches its position (an 0xFFFF one: once seesaw_launched is set and scene_timer runs out). The stage frame; game over with no player left; at scroll 0x6080 game_mode 3, scroll mode 7, speed 5.
comment game_mode_event_1C Game mode 0x1C, stage 8's riding event at scroll 0x7A8D: by scene_step event_1c_follow (steps 0, 1 and 4), event_1c_stop, event_1c_scroll; the stage frame; game over with no player left; at scroll_position 0x84A0 game_mode 3 at scroll speed 5, and bridge_0_plank_count 0.
comment game_mode_event_1F Game mode 0x1F, stage 0xB's escape at scroll 0xF00: start_escape_countdown once; the stage frame after check_stage_clear (game over with no player left); with escape_started the alarm (flash, shake, sounds 0xF and 0x7C, 0x7C again at 0x30C); the countdown runs until all have finished (the swap loop marks player 1's byte for player 3); at escape_seconds 0 YOU FAILED (0x20).
comment game_mode_game_over Game mode 7, game over: on its first frame sounds 0x94 and 0x73 (0x76 on stage 0x11) and cabinet_events 0x1F; then the GAME OVER banner (its text after the ending) with the contacts running; from 0x78 frames back to the attract screens (game_in_progress 0, blending and colour offsets off). A start with enough credits begins a new game.
comment game_mode_intro Game mode 1, the intro at a game's start (stage 7, how to play): set up once (stage_init_flags bit 13: contact lists and camera cleared, view at start section 6, map, goal, floors, spawns, palette, stage_start_sounds[stage]); then the stage frame without zones, until a player in play presses the button or game_mode_timer reaches 0x21C: game_mode 2.
comment game_mode_play_stage Game modes 3, 5 and 6, a frame of play: update_player_summary (on stage 0xB in a game it also closes joining, once); with no player left game_mode 7; then check_stage_clear, the grey screen, the scroll, tiles, floors, restart point, spawners and contacts, and run_stage_events. When game_mode changes it runs the new mode at once.
comment game_mode_screen_wipe Game mode 0x11, the first event of stages 3, 4, 0xA, 0xC and 0x10: the stage frame (the scroll held on stage 4) while update_screen_wipe opens the screen's windows, then game_mode 3 once edges_done shows all four edges in place (0xF).
comment game_mode_stage_change_fade Game mode 0x1E (check_stage_clear on stages 0xB-0xF): fades to black (update_fade) with the stage frame going on, then starts the next stage (start_stage) and fades it in from black (game_mode_timer -1) before game_mode 3; on stage 0x11 it goes to game_mode 3 straight after start_stage. The layers' colour offsets are on during the fades.
comment game_mode_stage_clear Game mode 0x17, a stage cleared: once, stage_clear_timer 0x14A and two banners, the stage's name (stage_name_banners[stage], stage_clear_banner_reveal) and CLEARED (cleared_banner_wait), with sounds 0x94 and 0x7B; banner_break from timer 0x78. The stage frame goes on; when the timer runs out, the results (game_mode 0x1D).
comment game_mode_stage_end_fade Game mode 4, between stages: on its first frame sounds 0x95 and 3; clears mixer_blend bit 11 (the blending), runs the island map screen (run_map_screen, map_screen_active set every frame) and starts the next stage (game_mode 2) once map_screen_step reaches 5. Hands back run_map_screen's bottom_speed.
comment game_mode_stage_results Game mode 0x1D, the stage's results: once, game_mode_timer 0x1E0 and the ring tally (start_ring_tally); the stage frame goes on until stage_results_done is set, then game_mode 4 (the map screen).
comment game_mode_stage_select Game mode 8, the debug stage select (only the unused check_stage_select_buttons sets it): once, stage 3, its texts, and video RAM from 0xE000 cleared; player 1's trackball steps stage through 0-0x11, drawn at text cell (24, 21); start 1 takes cleared_stages from stage_select_cleared_values[stage] and starts it (game_mode 2, stage_start_kind 1).
comment game_mode_start_game Game modes 0 and 0x21, starting a game or a demo: the screen and the player blocks cleared, player_status 0 (the spare byte 0xFF), the player objects started. A game (game_in_progress) clears cleared_stages and game_frames, current_screen 6, and goes to character select (game_mode 0x1B); a demo takes stage from demo_input and starts it (game_mode 2, stage_start_kind 0xFF).
comment game_mode_you_failed Game mode 0x20, YOU FAILED (from the stage 0xB escape): once, sound 0x94 and the colour offsets on; they rise to 0x1F (white) a step every 4 frames while the stage frame runs without the scroll; then game_mode_timer counts: sound 0x85 at 2, the YOU FAILED text from 0x78, you_failed_done at 0x12C; game_mode 7 once no player is left.
comment load_stage_map_pointers Points current_restart_point, floor_blocks and floor_block_bounds at stage's lists (stage_restart_points, stage_floor_blocks, stage_floor_block_bounds); hands back the bounds' table_offset and bounds_list.
comment read_trackball_step Adds player_number's trackball x motion to trackball_step_sums (restarting the sum when the direction changes) and hands back in player_number a step: 1 each 0x40 counts one way, -1 the other, else 0; trackball comes back as the address of the trackball's motion.
comment reset_objects_from_list Empties the object list and pools (reset_objects), then starts an object per handler of the list in object_handler_list (a count, then handlers), walking the player blocks (player_block_cursor): player_start_stage becomes player_join for a player who is_joining, or player_resume when its player_status is 1, 2 or 3. Stops when no object is free.
comment reset_trackball_step Clears trackball_step_sums[player_number] and reads the trackball once (read_trackball_step): the start of the debug stage select.
comment run_game_mode One frame of the game: runs the mode game_mode names (0x21, past the table, runs game_mode_start_game too). A mode that changes game_mode runs the new one in the same frame (run_game_mode again).
comment run_stage_events The stage's events: once scroll_position reaches the next (scroll position, game mode) record of stage_events[stage], it switches game_mode to it (scene_step and scene_timer 0), unless the scroll is already 0x100 or more past it (stage 0x11 takes it anyway); the cursor moves on either way.
comment spawn_banner_sparkle Spawns a ring sparkle (ring_sparkle_animation, palette 0x81B0, handler banner_sparkle_animate) offset_x along the banner from its screen position.
comment stage_clear_banner_hold A shown stage clear banner: queued at depth 0xFF until banner_break; then sound 0x1B and it splits into letters (banner_letter_spin): itself and new objects, as many as its animation's first word (banner_letters_left), 0x10 apart from 8 past its x, each with its number in kind, odd ones y-flipped (temp_1 is banner_letters_left).
comment stage_clear_banner_reveal Reveals a stage clear banner a step at a time (its animation), a sparkle 0x10 further along at each step (spawn_banner_sparkle, sparkle_offset); at the last step it holds (stage_clear_banner_hold) and sets stage_name_shown, or cleared_shown for the CLEARED banner (flags bit 0).
comment start_player_objects Starts the player objects afresh: reset_objects_from_list with player_objects (player_1_start to player_3_start and the three shadows). Hands back handler_cursor.
comment start_player_objects_alt Restarts the player objects for a stage: reset_objects_from_list with player_objects_alt (player_start_stage for each player, which it turns into player_resume or player_join as their blocks say, and the three shadows). Hands back player_number.
comment start_stage Game mode 2, a stage starts: cleared_stages + 1 (at most 0xE) if stage_start_kind is 0, player objects restarted unless it is negative, contact lists and stage state cleared; blending, view (a demo's start section from demo_input), map, goal, floors, spawns, stage_start_sounds[stage], palette; game_mode 3. In a game, cabinet_events 0x15 (0x14 when cleared_stages - 2 is odd).
comment update_grey_screen The grey screen while a player asks for it: with grey_screen_request set in any player block, grey_screen_active and mixer_offset_select bits 1-4 are set; when none asks any more they are cleared and grey_screen_ended is set for that one frame.
comment update_player_summary Sums up the players: player_dropping_in while a player_status is 0x81 (scroll_mode 0 meanwhile, then saved_scroll_mode back), no_player_in_play when no status has bit 7, player_mishap when a player still in has its mishap_flag (all three cleared), players_in_play (also in in_play_count). With skip_player_summary set it only clears the three flags.
comment update_restart_point Moves current_restart_point on through the stage's 8-byte restart records to the first whose scroll position is past scroll_position; hands back position, its low half now scroll_position.
comment advance_attract_screen Moves the attract mode to its next screen: attract_step + 1 (wrapping after 8), system_mode = attract_sequence[attract_step], system_mode_step 0.
comment attract_demo_1 System mode 4, the first demo: once, current_screen 4, game_mode 0, demo_input at attract_demo_1_inputs and attract_timer -0x240; then the game runs (run_game_mode) on the recorded inputs until attract_timer reaches 0, and the next attract screen follows.
comment attract_demo_2 System mode 5, the second demo: as attract_demo_1, with current_screen 5 and attract_demo_2_inputs.
comment attract_ranking System mode 2, the ranking: once, screen mode 0, the text layer, page 0 and the objects cleared, start_ranking_display and current_screen 2; the next attract screen once ranking_done is set.
comment attract_title System mode 3, the title: once, the display off for 2 frames, screen mode 0, the text layer and the objects cleared, the title's state reset and current_screen 3; then every frame the (C) SEGA 1992 line and run_title_screen, until title_done sends it on to the next attract screen.
comment attract_warning System mode 0, the warning screen after power-on: step 0 sets attract_timer -0x258, turns the display off for 3 frames, loads the font, video registers and sprites, screen mode 1 (wide), init_sound, and prints warning_screen_text; step 1 waits for attract_timer (wait_attract_timer); step 2 goes on to the next attract screen.
comment boot Power-on: the vector table base, eight byte pairs to 0xE00000 (unmapped on this board) and the interrupt controller's set-up to 0xD00000, the privileged stack pointers 0x20FE00, a delay; then for ever: init_system, frames (wait_for_frame, run_frame) until test_mode is set, test mode while it lasts, then over again from system mode 3, the title.
comment clear_work_ram Zeroes the work RAM from 0x208000 to 0x20FDFF (0xFC blocks of 0x80 bytes), keeping system_mode_and_step.
comment count_frames_and_check_test_switch From irq_vblank_start outside test mode: power_on_frames + 1 (carrying into power_on_frames_high), saved to the bookkeeping and its backup copy every 64 frames; game_frames + 1; test_mode set when the test switch or the board's test button (inputs_pressed bits 1, 13) is pressed without service (bit 0).
comment delay_after_reset An empty loop of 0x8000 passes that boot runs before the first init_system.
comment init_game_settings The game's settings at init_system: cabinet_config from setting_cabinet_config, game_in_progress, demo_prompt_shown and joining_closed 0, the ranking loaded (load_ranking, whose result it hands back) and stage_order copied to stage_order_copy (0x11 halfwords).
comment init_io_ports Sets the I/O chip up: port D 0x10, ports G and H 0, CNT 0x80, direction 0xC8 (D, G and H as outputs), then D, G and H again; cabinet_events 0x1F.
comment init_mixer Loads the mixer: 0x28 halfwords of defaults into the mixer registers, and their last 0x10 into the mixer shadows from mixer_text_blend, which irq_vblank_start copies out every frame.
comment init_system Sets the machine up, at power-on and after test mode: interrupts off, work RAM cleared, the I/O ports, the frame and system state, sound, settings and bookkeeping, mixer, palettes, sprites, font, video registers and objects, three read_controls, the interrupt controller acknowledged, check_moving_seat_dip and init_game_settings; interrupts back on.
comment int_default The handler of 247 of the 256 vector table entries: four halts, then on into int_vector_02 (four more halts, then run_vblank_copies).
comment int_vector_02 Vector 2, the NMI: four halts, then run_vblank_copies, which follows it in the program; int_default runs into it.
comment irq_sound The sound interrupt (vector 0x42): only acknowledges it (0xFB to irq_acknowledge).
comment irq_timer0 Timer 0's interrupt (vector 0x43): timer_0_ticks + 1 (nothing reads it), the timer reloaded with 0x32, acknowledged (0xF7 to irq_acknowledge).
comment irq_timer1 Timer 1's interrupt (vector 0x44): timer_1_ticks + 1 (nothing reads it), the timer reloaded with 0x23, acknowledged (0xEF to irq_acknowledge).
comment irq_vblank_end The vblank end interrupt (vector 0x41): runs vblank_hook (the sprite buffer swap) unless debug_pause is set, counts frames_to_wait down for wait_for_frame, and acknowledges (0xFD to irq_acknowledge).
comment irq_vblank_start The vblank interrupt (vector 0x40): the mixer shadows out to the mixer, the pending vblank copies, vblank_start_hook, a sound command (send_sound_command); outside test mode count_frames_and_check_test_switch and, unless free_play, update_coins; display_off_frames counts down and at 0 turns the display back on (io_cnt bit 1); acknowledged, vblank_counter + 1.
comment pause_on_start_3 Unused: a debug pause on start 3 (inputs_pressed bit 7): debug_pause + 1 (no vblank_hook), then frames go by (wait_for_frame, attract_timer held still) until start 3 is pressed again; debug_pause - 1.
comment play_demo_buttons In a demo (current_screen 4 or 5, current_game_mode 3, no game in progress): puts the 3 low bits of the byte at demo_input into bits 25-27 of inputs, the demo players' buttons, and moves demo_input on by one. Hands back inputs.
comment play_demo_trackballs In a demo (current_screen 4 or 5, current_game_mode 3, no game in progress): the next 6 signed bytes at demo_input become trackball_1_x to trackball_3_y, and demo_input moves past them.
comment read_controls Reads the controls: ports B, A, F, E as one word (B on top) through play_demo_buttons into input_ports and input_ports_active, inputs_pressed and inputs_released from the change (active low); the six trackball counts into trackball_1_x to trackball_3_y (one beyond +-0x60 keeps the last), the counters cleared, x negated; play_demo_trackballs.
comment read_service_inputs Unused: ports F and E (service, test, coins, starts and the board's switches) as one halfword in service_inputs, the last one in service_inputs_previous, with service_inputs_pressed and service_inputs_released from the change (active low): read_controls' edges on 16 bits.
comment reset The V60's reset vector: jumps to boot.
comment run_frame A frame of boot's loop: frame_counter + 1; outside system modes 0 and 6 the attract credits, and a start (a player's button with setting_no_start_buttons) pressed with enough credits for cabinet_config starts the game (run_frame_start_game; configs 2 and 3 through run_frame_check_p2_start); else system_mode's handler, run_objects, build_sprite_list, draw_queued_strings.
comment run_frame_check_p2_start The start check of run_frame for the individual coin chutes (cabinet_config 2, and 3 after player 3's start): player 2's start (bit 5; player 2's button, bit 0x18, with setting_no_start_buttons) with enough p2_credits, or player 1's (bit 4 or 0x10) with p1_credits, starts the game (run_frame_start_game); else the system mode's handler and the rest of the frame.
comment run_frame_start_game A start pressed with credits: system_mode 6 (the game), game_mode 0, system_mode_step 0, game_in_progress 0xFF; then the rest of the frame: run_game_mode, run_objects, build_sprite_list, draw_queued_strings.
comment run_vblank_copies Runs the copies queued for vblank: for each of the vblank_copy_count entries of vblank_copies (source, dest, halfwords) the source block is copied to dest and on after it as many times as dest's top byte says; vblank_copy_count and vblank_copies_pending are cleared.
comment send_sound_command Sends one queued sound a frame (irq_vblank_start): the next id of sound_queue (32 entries) is looked up in sound_commands and its two bytes written to the sound CPU's mailbox, sound_command_group and sound_command.
comment split_frame_count Splits a frame count (frames_high:frames, a bookkeeping time) into days (/ 0x4E9FA0), hours (/ 0x346A7), minutes (/ 0xDFB) and seconds (/ 0x3C), each from the last remainder: the first three divisors take 59.64 frames a second, the last 60.
comment system_mode_idle System mode 1, which attract_sequence never holds: only clears system_mode_step.
comment wait_attract_timer System mode 7, and step 1 of attract_warning: once attract_timer reaches 0, system_mode_step + 1 and frame_counter 0.
comment wait_for_frame Waits for the next frame: sets sprite_list_ready, polls until irq_vblank_end has counted frames_to_wait below 0, reloads it from frames_per_wait, points sprite_list_start_offset at the sprite back buffer, attract_timer + 1, and reads the controls (read_controls), handing back what that hands back.
comment wait_for_frames Sets frames_to_wait = frames_per_wait, then wait_for_frame; frames_per_wait is 0 (only the unused init_sprites_swapping_every_frame sets 1), so it waits one frame. Hands back wait_for_frame's count.
comment angle_of_vector The angle (0-0x3FF) of the vector dx, dy, handed back in dx: the smaller of |dx| and |dy| over the larger (times 0x100) picks a byte of angle_ratios, added to the base angle angle_octants gives for the signs and the larger axis (taken from ~entry when the entry is negative); octant gets the entry.
comment convert_64_to_bcd Unused: the 64-bit number number_high:number_low in BCD, 24 digits: two divisions by 10 make each byte, written backwards below stack_pointer and padded with zeros, then read back most significant first into bcd_high, number_high and number_low.
comment convert_from_bcd Unused: hands back the number the 8 BCD digits of value stand for, from the top digit down (times 10 plus each nibble).
comment convert_to_bcd value becomes its BCD, up to 8 digits: each division by 10 puts the remainder in the top nibble and shifts the rest down, and a shorter number is rotated into place. digit keeps the last remainder (past 8 digits, the low byte of what is left, in BCD).
comment find_floor The floor under the current object, from the nearby_floor_blocks block holding its x, y: floor_block, floor_type, floor_attribute, then find_floor_set_floor_z (none: find_floor_no_block); on a platform (flags bit 3) its block and z. The old real floor goes to previous_floor_block and previous_floor_z.
comment find_floor_block_at_point Unused: hands back the floor block of nearby_floor_blocks whose box holds the point x, y, or no_floor_block when none does (floor_blocks + 2 when no block is nearby).
comment find_floor_no_block find_floor when no nearby block holds the object: floor_block = -1 (a real floor is first kept in previous_floor_block and previous_floor_z), and floor_z and floor_slope from no_floor_block through find_floor_set_floor_z.
comment find_floor_set_floor_z floor_z = the height of block (whole pixels), floor_slope = its slope; on a slope floor_z adds (y - y_min, or x - x_min when the floor_slopes record has bit 1) >> 12, rounded, times the slope's factor >> 4.
comment find_nearby_floor_blocks Lists in nearby_floor_blocks (count in nearby_floor_block_count) the floor blocks with one of the four points of their floor_block_bounds in the area view_x - 0x80 .. + 0x1C0, view_y - 0x80 .. + 0x160 (the bounds' extremes are tested first). Nothing while nearby_floor_blocks_locked.
comment get_floor_z_at_point Unused: find_floor's floor z for any point: block = the nearby_floor_blocks block whose box holds x, y (no_floor_block when none), floor_z = its height << 16 plus, on a slope, (y - y_min, or x - x_min) >> 16 times the floor_slopes factor. With no block nearby floor_z is left alone.
comment point_to_screen The screen position of a projected point: screen_x = projected_x - view_x, screen_y = (projected_y + z) >> 16 - view_y, which projected_y also gets.
comment polar_to_xy angle becomes dx = sine_table[angle] * length >> 6 and length becomes dy = -(cosine_table[angle] * length >> 6); cos_part keeps the cosine product.
comment project_object The current object's isometric screen point: projected_x = ((x + y) >> 8) * 0xB5 >> 16, projected_y = ((y - x) >> 9) * 0xB5 (16.16; 0xB5 / 0x100 is cos 45 degrees), then update_screen_position.
comment project_object_2 project_object with update_screen_position written in, except that once scroll_position reaches 0xA5F0 screen_y takes current_player's own_view_y in place of view_y.
comment project_point The screen position of the map point point_x, point_y at height z: projected as in project_object, then point_to_screen; point_x and point_y become its screen x and y, projected_y the screen y too.
comment screen_point_to_ground The map x, y under the screen point point_x, point_y at height z: adds view_x and view_y, takes z off y, doubles it and unprojects (unproject_point) into point_x and point_y.
comment set_speed_from_direction speed_x = sine_table[direction] * speed_int >> 6 and speed_y = -(cosine_table[direction] * speed_int >> 6): speed (its whole part) along direction. Hands back the cosine product.
comment set_speed_from_player_speed set_speed_from_direction with current_player's ground_speed_int as the speed.
comment side_of_box sides = where the current object's x, y lies around the box of other: 8 before it in x, 4 past it, 2 before it in y, 1 past it (0 inside). hitbox_offset_y gets other's.
comment square_root value becomes its integer square root, two bits of value a round for 16 rounds (steps_left counts them down to 0).
comment unproject_point The inverse of project_object: from a projected x and twice the projected y (16.16), point_x = ((point_x - point_y) >> 8) * 0xB5 and point_y = ((point_y + point_x) >> 8) * 0xB5, the map x and y.
comment update_screen_position screen_x = projected_x - view_x + sprite_offset_x and screen_y = (projected_y + z) >> 16 - view_y + sprite_offset_y; screen_y comes back too.
comment add_step_screen_offset Adds the dx, dy step_offset points at (an animation step's, after its frame word) to screen_x and screen_y, negated by the x and y flips (sprite_flags bits 6 and 7).
comment animate Counts animation_timer down; when it runs out it reloads it from animation_speed and moves to the next step (animate_step). wrapped and at_last_step come back FF when the step wrapped round or is the last.
comment animate_all_parts animate for a multi-part sprite: on each new step (backwards with flags bit 7, wrapping) the step's frame is written into every part of part_list. at_last_step is FF on the last step; part ends past the last part.
comment animate_and_move animate with steps of frame, dx, dy: on each new step sprite_frame takes the frame and projected_x and projected_y_int move by dx, dy (negated by the flips). Hands back at_last_step FF on the last step.
comment animate_around_anchor The same steps of frame, dx, dy, but placing the object at anchor_x, anchor_y plus dx, dy (set_step_around_anchor). Hands back FFFFFFFF on the last step.
comment animate_part_list The animation timer of a sprite whose steps are part lists: when animation_timer runs out it reloads from animation_speed and animate_part_list_step moves on.
comment animate_part_list_step animate_step for multi-part sprites: animation_frame moves on (backwards with flags bit 7, wrapping) and part_list = the animation's address plus the step's word, the next part list.
comment animate_step animation_frame + 1 (- 1 with flags bit 7) and sprite_frame = that step's word. Past the last step (its number is the animation's first word) it wraps to 0 (going back, to the last) with wrapped FF; reaching the last step (0 going back) sets at_last_step FF. frame is the new step.
comment animate_step_with_flags The next step of an animation of two-word steps, always forwards: its flags word goes into sprite_flags in place of the flips (bits 6-7) and its frame into sprite_frame. at_last_step is FF on the last step.
comment animate_with_flags The animation timer (animation_timer, reloaded from animation_speed when it runs out), then animate_step_with_flags.
comment animate_with_screen_offset animate with steps of frame, dx, dy whose dx, dy are added to screen_x and screen_y (add_step_screen_offset); between steps the current step's offset is added again (apply_step_screen_offset). Hands back at_last_step FF on the last step.
comment apply_step_screen_offset Adds the current animation step's dx, dy to screen_x and screen_y again, without moving on (animate_with_screen_offset between steps).
comment build_sprite_list Writes the sprite list from sprite_list_start, emptying the depth buckets from 0xFF (drawn first) to 0: an entry per object, by sprite_flags bits 15/14/12: a scaled or unscaled frame, the object's own 16 bytes (clips), a jump, a sized frame, or one per part (over 16 parts: int_default).
comment cancel_sprite_buffer_swap Unused: sprite_list_ready = 0, so swap_sprite_buffers skips the next swap. init_sprites_without_swaps makes it the vblank_hook, and then the buffers never swap.
comment clear_sprite_buckets Empties the 256 depth buckets (each points at itself) and puts the end mark 0xFFFF in sprite_buckets_end; hands back the address past the last bucket.
comment cycle_part_palettes Every palette_delay frames (palette_timer) the next palette of palette_list (its first word is the last index; it wraps) goes into every part of the object's part_list.
comment depth_to_bucket Hands back, in bucket's low word, the bucket queue_sprite_at_depth would use for depth: 0xFF - clamp(depth + 0x10, 0, 0xFE). Nothing is queued.
comment get_previous_floor_bucket Unused: the first half of queue_sprite_at_previous_floor_2: floor_depth = (projected_y + previous_floor_z) >> 16 - view_y + 0x10 clamped to 0-0xFE and bucket = 0xFF - floor_depth. Nothing is queued.
comment init_sprite_hardware The end of init_sprites: sprite_flip from setting_monitor_flip, the sprite control registers from sprite_control_init, entries 0-1 from sprite_list_header, sprite_back_buffer and sprite_list_start after them, end marks (0xFFFF) in the buffers, sprite_frames at the frame records, clear_sprite_buckets.
comment init_sprites Sets the sprites up: vblank_hook = swap_sprite_buffers, frames_to_wait = frames_per_wait = 0, then init_sprite_hardware (through init_sprites_from_frame_wait).
comment init_sprites_from_frame_wait init_sprites from its frame wait on: frames_to_wait = frames_per_wait = 0, then init_sprite_hardware. init_sprites_without_swaps comes in here.
comment init_sprites_swapping_every_frame Unused: init_sprites with vblank_hook = swap_sprite_buffers_now, a swap at every VBLANK whether the list is ready or not, and frames_to_wait = frames_per_wait = 1.
comment init_sprites_without_swaps Unused: init_sprites with vblank_hook = cancel_sprite_buffer_swap: the VBLANK only clears sprite_list_ready, so the sprite buffers never swap.
comment load_frame_size Unused: sprite_width and sprite_height = the width and height of the frame's record (sprite_frames + sprite_frame), the size sized frames are drawn at.
comment load_sized_frame_address Unused: sprite_scale = the address word of the frame's record, which sized frames take as their graphics address.
comment load_sized_frame_address_at_row Unused: sprite_scale (a sized frame's graphics address) = the frame record's address plus row times its width >> 2 (the width halved first unless sprite_flags bit 9, 8 bits a pixel): the frame from row row down.
comment queue_sprite Queues the current object for drawing at the depth of its screen_y (queue_sprite_at_depth); hands back the clamped depth.
comment queue_sprite_at_base Queues the current object at the depth of screen_y - sprite_offset_y, its base before the sprite offset; hands back the clamped depth.
comment queue_sprite_at_depth Pushes the current object (through draw_next) onto depth bucket 0xFF - clamp(depth + 0x10, 0, 0xFE): the lower on screen, the later it is drawn. bucket_head gets the bucket, depth the clamped value.
comment queue_sprite_at_depth_offset Unused: queue_sprite_at_depth with bucket_offset added to the bucket (& 0xFF): that many buckets further back.
comment queue_sprite_at_floor Queues the current object at the depth of the floor under it, (projected_y + floor_z) >> 16 - view_y, for objects in the air; hands back the clamped depth.
comment queue_sprite_at_previous_floor queue_sprite_at_floor with previous_floor_z.
comment queue_sprite_at_previous_floor_2 A second copy of queue_sprite_at_previous_floor that hands nothing back.
comment queue_sprite_in_bucket Pushes the current object onto depth bucket bucket (0xFF is drawn first, under everything; 0 last).
comment queue_sprite_in_layer Pushes the current object onto the bucket in sprite_layer's low byte (the HUD and overlays).
comment queue_sprite_on_top Queues the current object in bucket 0, drawn last, over everything.
comment set_step_around_anchor Puts the current object on step frame of the steps at steps (a frame word, dx, dy): sprite_frame = the step's frame, projected_x = anchor_x + dx, projected_y_int = anchor_y + dy, dx and dy negated by the flips.
comment swap_sprite_buffers The VBLANK hook init_sprites sets: once sprite_list_ready (which it clears), sprite_buffer_flip + 2 and swap_sprite_buffers_now (+ 2 more): the finished list is shown and the other buffer is filled next.
comment swap_sprite_buffers_now sprite_buffer_flip + 2, then sprite_back_buffer_offset and the jump in sprite_list_jump from sprite_buffer_swaps[flip & 6]; reads sprite_status. As the hook of init_sprites_swapping_every_frame (+ 2 a VBLANK) it also reaches the two records the game's + 4 skips.
comment breakable_panel_break Plays breakable_panel_break_frames 0-0x10, one every 3 frames (frame_timer), as the panel cracks open to an arched doorway; then only draw_breakable_panel. Drawn every frame.
comment breakable_panel_init The blue stone panel (sprite 0x9278, palette 0x91F0): box 7 x 0x40, height 0x64, kind 5, breakable_panel_wait. Its piece is the arch outline (0x9308) whose palette draw_breakable_panel cycles.
comment breakable_panel_wait Once touched by an attacking object (flag 0x40 of touched_by): drop_rings spawn_arg_2 rings at angle spawn_arg_1, spread at random, and breakable_panel_break. Until then draw_breakable_panel.
comment draw_breakable_panel Drawn at screen_y - 0x20 and touchable, even open; removed off screen. Unbroken, its outline piece is drawn at screen_y - 0x19 and its palette steps through breakable_panel_glow_steps every 3 frames, pausing midway and at the end.
comment electric_pole_draw Drawn draw_offset_y lower unless removed off screen. Rising (frame_index under 0xA) it shows the charging spark and is touchable from frame 2 on; risen, it is touchable and the front spark and two side sparks step through their 7 frames every other frame.
comment electric_pole_init The electric pole, closed (sprite 0x67D8): hitbox_size_x 0x19, height 0x53, health 4 (against attacks), kind 5, electric_pole_wait; its three spark pieces get palette 0x83D0, the third a step ahead.
comment electric_pole_rise Every other frame the next of electric_pole_rise_frames (through electric_pole_sprites); at step 0xA it is up: projected_y_int - 0xB (draw_offset_y 0xB puts the sprite back) and electric_pole_update takes over. Runs electric_pole_update.
comment electric_pole_update Explodes (sprite_scale 0x200, explosion_animate, sound 7) when chaser_object comes within 0xF in x or it is touched with health under 4; touched with health 4 or more it becomes kind 0x15. Then electric_pole_draw.
comment electric_pole_wait Waits closed (electric_pole_update) until the nearest player (face_nearest_player) is within 0x70, then electric_pole_rise.
comment exit_gate_start Unused: a gate (sprite 0x6520) hung 0xE0 above its floor, exit_gate_update; clears sign_stop_flag and puts up spawn_sign_2 kind 8 (an IN sign with an arrow) at 0x942, 0x10E0, 0x50 above the floor.
comment exit_gate_update Unused: removed while map_screen_active. Once the three players_finished bytes are FF with goal_unlocked still 0 (one 32-bit test) it falls (speed_z + 0x6000) to its floor: sign_stop_flag = 0xFF, sound 0x20. Drawn with queue_sprite.
comment fire_vent_blast The vent blasts: blast_delay -1, burst_state 1, the dark grille (0x6A50, palette 0x9120), the glow stops (palette_index 0xFF); a fire_vent_burst_animate object anchored 0x3A, -0x31 off in projected x, y, and sound 0xA. Then fire_vent_draw.
comment fire_vent_burst_animate The burst of fire: fire_vent_blast_animation, with fire_vent_blast_palettes' palette for each new step, drawn at screen_y + 0x68. At its last step it sets its vent's burst_state to 0xFF and is removed.
comment fire_vent_draw Draws the vent at screen_y - 0x48; touchable once it has blasted (burst_state set), and when burst_state is 0xFF (the burst is over) it turns to 1 and the vent to kind 0xC.
comment fire_vent_spawner_start fire_vent_cursor = fire_vent_list, then fire_vent_spawner_update from now on.
comment fire_vent_spawner_update When scroll_position reaches the next fire_vent_list record, a vent at its x, y, z with its blast_delay (sprite 0x6A48, box 0x90 x 0x48, height 0x28, kind 0x22, glow palettes, fire_vent_start); a negative delay ends the list and removes the spawner.
comment fire_vent_start project_object, then fire_vent_update from now on.
comment fire_vent_update Removed once it has left the screen (margin 0x40). The glow cycles (animate_sign), faster once blast_delay is 0x1E or less; blast_delay counts down while screen_y is 0x18 or more and at 0 fire_vent_blast (-1 means blasted). Else fire_vent_draw.
comment fireball_fly While flight_timer runs: moves by speed_x, speed_y on an arc, z = floor_z - sine_table[arc_angle] * 0x140 (arc_angle + 9 a frame, 0xE in stage 0xC), drawn at its floor. Landed, it plays the rest of its animation, then next_handler takes over 0x28 higher.
comment fireball_start The fireballs machine_fire_update throws: sprite flags 0x1004, palette 0x9010, then init_fireball_flight.
comment flame_burn Animated and drawn at screen_y + 0x30 until it has left the screen (check_left_screen), then removed.
comment flame_start A big flame (flame_animation, palette 0x9010) set on its floor; flame_burn.
comment init_fireball_flight fireball_animation; flight_timer 0x36 frames (0x24 in stage 0xC), speed_x and speed_y to reach target_x, target_y in that time, the arc's base floor_z = z; sound 0xF, fireball_fly.
comment machine_fire_climb_step The climb: base_speed while the top's screen y (screen_y - hitbox_height) is screen_threshold or less, else catching up with the camera, in the hold-back limits; after wait_timer z climbs by it and projected x, y follow the floor attribute's x_per_rise (x, y from it). Sound 9 every 0x3C frames on screen.
comment machine_fire_read_script Until the fire's top (z - hitbox_height) reaches the height of the next machine_fire_script record, machine_fire_climb_step; then it loads the record's screen_threshold, catch_up_factor, base_speed and wait_timer and climbs on, or at a negative factor the climb ends (next state).
comment machine_fire_update The machine's fire: a small flame until the machine is wrecked; then an invisible box (kind 0x13, rising_fire_object): the spawns_with_next_handler fireballs, a climb (machine_fire_script), 0x80 frames +x with fireballs in three lanes (lane 3's timer is sound_timer), machine_fire_done.
comment machine_run The machine: animated until machine_wrecked, then stopped on the smashed frame (0x68E8); touchable, drawn at screen_y - 0x68; removed once it has left the screen.
comment machine_start The machinery (machine_animation, box 0xE8 x 0x20, height 0x50, kind 0xC, machine_run) on its floor; clears machine_wrecked. Spawns its fire (machine_fire_update, a small flame) and a GO! arrow sign (spawn_sign kind 5).
comment push_crate_break Every other frame the next of push_crate_break_sprites; at the last (2) push_crate_move takes over, after push_crate_release_all_rings when ring_count is above 0. Then push_crate_move.
comment push_crate_init The crate (0x6968, box hitbox, kind 0x20, push_crate_update) and its stop post with a top piece (spawn_object_with_pieces 2, push_crate_stop_post_update), set back post_distance * (0x22, 0x11) + (0x11, 0x1D). child, which held the crate's own piece, now points at the post.
comment push_crate_move Moves, removed off screen. Whole: touchable, drawn at screen_y - 0xD, and while speed_x is not 0 a ring (push_crate_release_rings) each time ring_timer runs out (reloaded with 0x10) while ring_count lasts. Broken: drawn in bucket 0xFF, under everything.
comment push_crate_release_all_rings rings_to_release = ring_count, then push_crate_release_rings: every ring the crate still holds.
comment push_crate_release_rings drop_rings rings_to_release rings behind the crate: released_ring_angle = push_crate_push_angles[(push_quadrant + 2) & 3], no flip, no random spread.
comment push_crate_stop_post_update The stop post: when its crate (parent) comes within 0x33 in y, the crate stops for good (stopped, speeds 0). Draws the post (queue_sprite) and its top piece 0xF higher, at screen_y + 0x11.
comment push_crate_update chaser_object within 3 in y, or a touch by an attacking object (flag 0x40): push_crate_break. Touched by an object whose (direction + 0x40) / 0x40 - 1 equals push_quadrant, unless stopped: speed along push_crate_push_angles[push_quadrant] at the toucher's speed. Then push_crate_move.
comment ram_target_burst Unused: the target bursts into ram_target_debris_count rock_debris_fly pieces, each ram_target_debris record giving its sprite flags, offset from the target's screen position, speeds and animation (in ram_target_debris_animations); palette 0x8240.
comment ram_target_check_hits Unused: ram_target_update never calls it. On a roller_ram_hit frame hit_count + 1 and the animation restarts; at each threshold of damage_script the next animation; at 4 hits ram_target_burst. Then ram_target_draw.
comment ram_target_draw Unused: once appear_state is set the animation plays once (then appear_state 1, animation_frame 0xFF); with appear_state 1-0x7F it is solid (add_to_zone_list). Drawn at screen_y - sprite_offset_y + 0x10, 0x30 less before it appears.
comment ram_target_part_start Unused: a multi-part sprite (flags 0xD006) of ram_target_part_list_2, or ram_target_part_list_3 when variant (the spawn's sprite_frame) is not 0, on its floor; ram_target_part_update.
comment ram_target_part_update Unused: on roller_ram_hit frames with chaser_object no more than 0x60 past its y, hit_count + 1; at 4 variant 0 becomes the sprite 0x6A88 and the others are removed. Drawn at screen_y - 0x28; removed once it has left the screen (margin 0x88).
comment ram_target_start Unused: the roller's ram target (sprite 0x6A58, palette 0x91A0): box 0x30 x 0x10, height 0x28, kind 0x21, direction 0x300, ram_target_animation, damage_script = ram_target_damage_script, on its floor; ram_target_update.
comment ram_target_update Unused: health 0x7F; removed once it has left the screen (margin 0x40); appear_state 0xFF once screen_x reaches 0x50; then ram_target_draw. It never calls ram_target_check_hits.
comment rising_flame_init A rising flame: flame_animation, palette 0x9010 (0x9260 in stage 8), its base floor_z = z; next state.
comment rising_flame_rise z - 1 a frame until 0x28 above its base (floor_z); then the next state.
comment rising_flame_spawn_next While rising_fire_object climbs (state 2): once the fire's top (z - hitbox_height) is level with this flame or above it, another rising flame at the fire's x and top and this flame's y, and the next state. Without the climb, the next state at once.
comment rising_flame_update The flames that rise along the machine's fire: rising_flame_init, rising_flame_rise, rising_flame_spawn_next or rising_flame_wait_offscreen by state; removed when rising_fire_object reaches state 7 or off_screen is set. Animated, drawn at screen_y + 0x20 up to screen y 0x108.
comment rising_flame_wait_offscreen off_screen = 0xFF once the flame is below screen y 0x110 or left of screen x -0x40.
comment roller_fixed_zone_update The hazard box spiked_roller_spawn_fixed_zone leaves: touchable until scroll_position reaches 0x7600, then removed.
comment spawn_stage_03_end_pillar A brick pillar sprite (0x6600, palette 0x8740) fixed at screen 0x146, 0x75, shown by stage_03_end_pillar_show; hands back the object (allocate_object_outside_loop).
comment spike_panel_base_draw Part 4, the plate with the holes: drawn in bucket 0xFF, behind everything, until it is removed off screen.
comment spike_panel_hitbox_update Part 3, touchable only while the panel's frame_index is 4 or more: at part 1's place 0xD8 back in projected x, kind 0x12, or 0xC from the panel's step 5.
comment spike_panel_hold Holds the panel at start_step while no player is within 0x40 in x (find_nearest_player_x); with one there, or from the start when start_step is 0, trigger_delay counts down, then start_step = 0 and spike_panel_update takes over. Runs spike_panel_update.
comment spike_panel_init The spiked panel and four spawned parts: 1-2 (kind 0xC, spike_panel_part_idle), 3 a hitbox 0x8D x 0xBC (kind 0x12, spike_panel_hitbox_update), 4 the plate 0x6B58 (spike_panel_base_draw), cut off the chain. The head: step = start_step, step_timer = spawn_arg_2, trigger_delay 0x1E, spike_panel_hold.
comment spike_panel_part_idle The handler of parts 1 and 2: does nothing, as spike_panel_update moves and draws them.
comment spike_panel_update With chaser_object at a direction between 0x100 and 0x378 the panel is removed in 0xF debris; else it steps spike_panel_frame_sequence (spike_panel_step_durations) and places, sizes and draws the head and parts 1-2 by the frame. Sound 0x25 sits where frame_index is under 3: it never plays.
comment spiked_roller_burst_out State 9: hidden for wait_timer frames, then the roller bursts out: state 0, it becomes chaser_object, scroll_resume_request, sound 7, and roller_wall_fragment_count pieces of wall (roller_wall_fragments records) fly off as rock_debris_fly objects.
comment spiked_roller_chase State 0, the leader: the first frame and, unless holding back, every frame with no player mishap (no_player_in_play, player_mishap, player_dropping_in) read the script; a mishap starts holding back (base_speed 0, screen_threshold 0x160 or -0x20, hold_timer 0xFF). Then spiked_roller_chase_move_step.
comment spiked_roller_chase_check_event Once scroll_position reaches the next spiked_roller_events record (event_offset) with substate 0, its test (y at most, x at least, y at least or x at most a coordinate) passing sets state from the record and moves to the next record. roller_ram_hit = 0.
comment spiked_roller_chase_move_step Keeps to its floor (not kinds 2 and 5; a drop over 8: state 1, chaser_object 0); chaser_speed = base_speed past screen_threshold, else camera ground speed x catch_up_factor >> 8 (at least base), in the hold-back limits; after wait_timer it moves along path_direction unless grey_screen_active.
comment spiked_roller_chase_read_script With wait_timer out and scroll_position at the next path record: loads screen_threshold, catch_up_factor, base_speed and wait_timer (no wait: kind 0x18 and players_frozen 0); a negative factor ends the path (speeds 0, state 5 once off screen). Then spiked_roller_chase_move_step.
comment spiked_roller_chase_rumble The rumble on screen: back from off screen (rumble_off_screen) rumble_timer restarts; while it moves, sound 0xB every spiked_roller_rumble_delays[chaser_speed >> 4] frames. Then spiked_roller_chase_check_event.
comment spiked_roller_crash_into_machine State 6: rolls -y at chaser_speed 0x10 (0x50 once scroll_position reaches 0x8600) with its rumble until y is 0xAE0 or less: stops with camera_shake_active, sound 6, machine_wrecked and scroll_resume_request; state 5 and chaser_object 0 once below screen y 0x158.
comment spiked_roller_drop_in State 3: leaps and comes down (speed_z + 0x8000 a frame, moving by speed_x and speed_y) onto z 0: camera_shake_active, sound 0x20, state 0.
comment spiked_roller_fall States 1 and 8: falls (speed_z + 0x1000 a frame), turning fast (animation_speed 1); player = 0xFF (removal) once below screen y 0x130.
comment spiked_roller_fall_off_ledge State 1: its palette word gets 0x4000 (another priority group), then spiked_roller_fall.
comment spiked_roller_follow_leader State 2, the four other parts: the leader's y and speed_y (x and speed_x for path_direction 1 and 3), kind, box, z and floors, its states 1 and 8, its hidden and backwards bits; animated at the chase speed. Removed (player = 0xFF) when the leader is in state 5.
comment spiked_roller_ram State 4, the ram: spiked_roller_ram_charge, _pause, _back_off or _wait by substate, with its own rumble (sound 0xB). No script or event sets state 4, so it never runs.
comment spiked_roller_ram_back_off Ram substate 2: rolls back +y one a frame (chaser_speed 0x10) until y reaches the coordinate of the event that fired; then substate 3 with the animation backwards (flags bit 7).
comment spiked_roller_ram_charge Ram substates 0 and 4: rolls -y 4 a frame (chaser_speed 0x40) until y is at most the event's coordinate - 0x50: camera_shake_active, roller_ram_hit, sound 7, next substate. Off screen, or after 4 charges (roller_ram_count), back to state 0.
comment spiked_roller_ram_pause Ram substate 1: stopped (chaser_speed 0, roller_ram_hit 0) for 0x3C frames (pause_timer); then substate 2 with the animation forwards.
comment spiked_roller_ram_wait Ram substate 3: turns in place (chaser_speed 0x40) for 0x3C frames, counted only while a player is in play, then charges again (substate 0).
comment spiked_roller_remove State 5: player = 0xFF, the mark spiked_roller_update removes the part by (clearing chaser_object if it is the leader).
comment spiked_roller_shift_box State 0xD: hitbox_offset_y = -0x26, then back to the chase (state 0, spiked_roller_chase).
comment spiked_roller_sink_and_jump State 7: sets stage_03_floor_block_falls[0] (sound 0xD the first time) and sinks 0x40 into its floor, then jumps (speed_z -6, speed_x 3, gravity 0x3000) and lands on it: state 8, stage_03_floor_block_falls_1, camera_shake_active, goal_unlocked, sound 0xD.
comment spiked_roller_smash_wall State 0xA: stops as a solid kind 0xC (chaser_speed 0); in stage 0xE it sets chaser_stopped. State 5 once off screen (margins 0x68, 0x70).
comment spiked_roller_spawn_fixed_zone State 0xF: leaves an invisible hazard box (0x100 x 0xF3, height 0xA0, kind 0xC, roller_fixed_zone_update) at x -0x8AD, y 0x114A, z 0x470, then back to the chase (state 0).
comment spiked_roller_start Builds a spiked roller of kind sprite_frame (spiked_roller_kinds): its parts side by side (spacing and part record from its layout) in state 2 behind the leader, which takes the first state (0 sets players_frozen, 0xE hides it at z 0x50), the script, the back zone and chaser_object if free.
comment spiked_roller_update Every roller part's handler: the state's routine (0-0x10); then a part marked in player is removed (clearing chaser_object if it is it), a hidden one (flags bit 0) is not drawn, and the rest join the zone list and are drawn (queue_sprite_at_base, at the previous floor in state 1).
comment spiked_roller_wait_for_turn State 0xB: waits as a still kind 0xC until chaser_object is free, then takes it: state 0, hitbox_height 0x40.
comment spiked_roller_wait_hidden State 0xE: hidden (flags bit 0) until scroll_position reaches 0x9B00 or machine_fire_done is set; then shown, state 3 (spiked_roller_drop_in).
comment spiked_roller_wait_to_burst_out State 0xC: hidden until chaser_object is free, then state 9 with wait_timer 0x3C (spiked_roller_burst_out).
comment stage_03_end_pillar_show The pillar is drawn at screen_y + 0x20 until map_screen_active is set, then removed.
comment stage_03_floor_block_start A stone floor block of the stage 3 finale: its kind (sprite_frame, kept in speed_x_int) gives from stage_03_floor_block_kinds its sprite offsets, a depth offset (speed_y_fraction), the animation and which stage_03_floor_block_falls byte it waits for (speed_x_fraction). Clears both bytes; z 0.
comment stage_03_floor_block_update Waits until its stage_03_floor_block_falls byte is set, then falls (speed_z + 0x1000 a frame), kinds 0-3 dropping their sprite offsets, and plays its crumble animation once. Drawn at screen_y - the depth offset - sprite_offset_y; removed below screen y 0x158.
comment start_fireball_flipped A fireball flipped in x (sprite flags 0x1044, palette 0x10C0), then init_fireball_flight.
comment drop_icicle_row Three icicles (icicle_update, kind 0x28) 0xE0 above the dropper, 0x48 apart across its direction, falling after 0x3C-0x5B frames, each with an icicle_shadow_follow shadow. Within 0x40 of the pending path record's coordinate they line up 0x20 short of it (flags bit 0 on them and the dropper).
comment handle_rod_handle_init Gives the handle a shadow piece (sprite 0x5D80, sprite_scale 0x80), then handle_rod_handle_update.
comment handle_rod_handle_update The handle sits index * 0x12 from the pivot (its parent) along the rod's angle, through the speed fields; removed with the rod (end_position 0xFFFF). The grip is touchable and rideable. Drawn by the pivot's depth, its shadow on the floor unless the rod drops_players.
comment handle_rod_hang The rod hangs straight down, falling when there is no floor under it; moved, drawn, rideable; past end_position, handle_rod_remove.
comment handle_rod_init The rod's pivot (0x73B8) raised 0x12 per handle and handle_count handles (handle_rod_handle_init) 0x12 apart. The first becomes the near post (handle_rod_post_init) a rod's length away along angle 0x300, the second the grip (kind 0x1A, spawn_arg_2 = the pivot's x); one more object is the far post.
comment handle_rod_post_fall The post falls to its floor and is drawn at it until removed off screen.
comment handle_rod_post_init The near post (0x74C8) with its arm piece (0x74D0), or the far post (0x74D8) when fallen is already set; rideable; handle_rod_post_update.
comment handle_rod_post_update With no floor under it (floor_z 0x800 or more, not on a platform) the post sets fallen and handle_rod_post_fall takes over. Rideable; drawn, a fallen one at the pivot's depth; the near post's arm piece is drawn on top.
comment handle_rod_remove Removes the rod's pivot and its children (delete_object_and_children).
comment handle_rod_start_swing Clears handle_rod_drop_players and players_pulled (they were set for one frame), then handle_rod_swing.
comment handle_rod_swing The pendulum: angle_speed + swing_accel and angle + angle_speed_int a frame; at the bottom swing_accel flips and angle_speed loses 0x100 (with drops_players the rod bounces back from angle 0 at half speed). Hangs at angle 0 once slow (handle_rod_hang); removed past end_position.
comment handle_rod_wait Drawn and rideable until the near post (its parent) falls; then with drops_players handle_rod_drop_players = 1, else players_pulled = 1 and event_12_fast_scroll = 1, and handle_rod_start_swing. hitbox_size_x keeps the pivot's screen_y, its handles' depth.
comment ice_block_row_start Once 0xE objects are free: 0xE ice blocks (sprite 0x7498, box 0x20 x 0x5C, height 0x50, kind 0xC, ice_block_update) at x -0x2B2, z 0x78, 0x5C apart along y from its y; then removed.
comment ice_block_update By state: projected; touchable until touched, then it slides towards -x at 1.0 a frame (pushed by an object that is not a player, at its speed_fraction << 8); it falls past x -0x2BA and is removed below screen y 0x120.
comment ice_cube_slide The launched ice cube slides at direction 0x2B6 with its speed, falling to its floor; drawn at screen_y - 0x10 with its shadow 0x1E below in bucket 0xFF; on the hitter list until removed off screen.
comment ice_floor_block_update An ice cube of the floor, by state: a platform until a player stands on it (sound 0x14); it shakes (z +/- 1) for 0x1E frames, 0x12C while that player's invulnerable_timer runs; then palette | 0x2000, sound 0x2A, and it falls until off screen.
comment ice_floor_start With 9 objects free (it takes 35): 5 rows of 7 ice cubes (ice_floor_block_update, sprite 0x7490, the first of each row palette | 0x2000) 0x2E apart from its x, y, on the floor block ice_floor_floors[sprite_frame] at its height; then removed.
comment ice_launcher_init The ice launcher (sprite 0x7420): box hitbox, height 0x28, kind 5, last_step 0xF, ice_launcher_update.
comment ice_launcher_update Removed once scroll_position reaches spawn_arg_2. Steps through ice_launcher_frames (each step lasts ice_launcher_frame_durations[step_duration_index]) with each frame's hitbox; at step 0xC it launches an ice cube (ice_cube_slide, kind 0x19) at ice_cube_speeds[cube_speed_index].
comment ice_platform_leader_move The invisible leader of the moving ice platform (moving_floor_object): x + speed_x a frame, turning back (sound 0xF) when travel leaves -0x87..0x87; removed once scroll_position reaches end_position.
comment ice_platform_piece_update A piece of the ice platform, by state: at its leader's (parent's) x - member * 0x2D and y, on the platform list, until an icicle lands on it (icicle_landed): ice_platform_piece_parts_2 and it falls until off screen. The last piece (member 3) starts with those parts.
comment icicle_buried_part_draw The part of a stabbed icicle below the floor: a copy of it (palette | 0x2000) drawn over it after a clip entry whose top is the floor's screen y (project_point) and before one that resets the clip. Removed with them (free_object_2) off screen or once map_screen_active.
comment icicle_cracks_spread The cracks round a stabbed icicle (icicle_crack_animation around its anchor): one step each time the icicle sinks another 0x1C into the floor; drawn over it; removed off screen or once map_screen_active.
comment icicle_dropper_catch_up_speed The catching-up speed: camera_slope_speed_int (compute_camera_slope_speed with the floor slope's floor_slope_factors entry) x catch_up_factor >> 8, at least base_speed; then icicle_dropper_move_at_speed.
comment icicle_dropper_check_scroll_position The pending path record loads (icicle_dropper_load_record) once scroll_position reaches its scroll position; until then icicle_dropper_move_step.
comment icicle_dropper_choose_speed base_speed once the dropper is past screen_threshold in screen_x (the way it moves); else wait_timer is dropped, a hold-back ends and it catches up (icicle_dropper_catch_up_speed). Then icicle_dropper_move_at_speed.
comment icicle_dropper_coordinate_reached The dropper reached the pending record's coordinate: icicle_dropper_load_record, skipping the record's scroll position.
comment icicle_dropper_finish The last dropper's end: once its icicles have counted goal_unlocked up to 3, after finish_timer (3 frames) goal_unlocked = 0xFF and it is removed.
comment icicle_dropper_load_record Loads the pending path record's screen_threshold, catch_up_factor, base_speed and wait_timer and moves path to the next; a negative factor ends the path: icicle_dropper_ready = 0xFF and removed (the last dropper: icicle_dropper_finish).
comment icicle_dropper_move An invisible chaser that drops rows of icicles: holds back on a player mishap; after wait_timer the pending path record loads once its coordinate or scroll position is reached (riding the ice platform, platform_offset reaching the coordinate ends the path). Then icicle_dropper_move_step.
comment icicle_dropper_move_at_speed chaser_speed = move_speed (0x400 for the last dropper once every player finished); after the wait it moves that far along its direction (only platform_offset while riding) and counts travel; unless the row lined up (flags bit 0), drop_icicle_row every 0x400 of travel (0x300 riding).
comment icicle_dropper_move_step On its floor, or riding at moving_floor_object's x - platform_offset + 0x2D; projected. A hold-back ends after 0x300 of scroll, when hold_timer runs out, or for the last dropper once every player finished (catching up); else icicle_dropper_choose_speed.
comment icicle_dropper_start Waits for icicle_dropper_ready (and clears it); then from icicle_dropper_records[sprite_frame]: x, y, z, direction, rides_platform, last_dropper and the path (icicle_dropper_scripts); travel 0x180, wait_timer 1; icicle_dropper_move. current_icicle_dropper = it.
comment icicle_shadow_follow An icicle's shadow on the floor (sprite 0x5D90): grows (sprite_scale + 9 up to 0x21C) once the icicle's timer is 0x32 or less; removed off screen or when the icicle has sunk. On the ice platform it moves with it, blinks, and falls with a falling piece.
comment icicle_update The icicle by state: waits its timer and falls; stabs in (sound 0x29, icicle_buried_part_draw, clips, icicle_cracks_spread), sinks 0x70 and set_icicle_wall_box. On the ice platform it aims where the platform will be and brings its piece down. Lined-up ones with counts_goal count goal_unlocked up.
comment object_type_17_idle The handler object_types gives type 17 (five objects): does nothing.
comment padded_wall_init The padded wall (0x77D0, padded_wall_update) and four pads (spawn_objects 4: sprite 0x77A8, box 0x17 x 0x35, height 0x20, kind 0xB, wall_pad_update) along it, 0x26, 0x13 apart in projected x, y, each unlinked from the next; all go at spawn_arg_1.
comment padded_wall_update The wall is drawn until scroll_position passes spawn_arg_1, then removed with its children.
comment set_icicle_wall_box The sunk icicle becomes a wall: a box hitbox (flags bit 1) 0x70 x 0x80 or 0x80 x 0x70 across its direction, with the offsets for that direction.
comment snow_mound_burst The burst into snow: every second frame the mound takes the next of snow_mound_burst_frames and part 2 the frame two ahead; every other step, part 3 its own next frame (from part_3_frame 4). Past frame 0xE the mound and its parts are removed. Drawn by snow_mound_draw.
comment snow_mound_draw Draws the mound at screen_y + 0x12. Whole, it is touchable and rideable and part 4, its reflection, copies its frame 0x15 lower in bucket 0xFF; bursting, part 2 at its place, part 3 0x16 higher and part 4 with part 2's frame.
comment snow_mound_init The snow mound (0x7B30, palette 0x91B0) 0x20 lower in projected y: box 0x22 x 0x21, height 0x20, kind 5, rideable; part 3 flipped in x, part 4 flipped in y (palette 0x31C0, its reflection on the ice); snow_mound_update.
comment snow_mound_update The mound moves and falls to its floor; removed off screen. At the first touch it bursts (snow_mound_burst), dropping its ring_count rings first (drop_rings at angle spawn_arg_2, random_scatter for the spread); untouched, snow_mound_draw.
comment spawn_ice_platform_leader The ice platform's invisible leader from the spawn record: group_leaders[its leader & 3], end_position, travel = x - reference_x, x, y, z, speed_x 1.0, ice_platform_leader_move; it becomes moving_floor_object. spawned = -1, or 0 with no free object.
comment spear_trap_draw Moves the spear with z from spear_z, then draws its pieces by the floor's screen y, the clip entries (bottoms floor - 5 and floor + 0x20) cutting the spears at the floor line. Keeps z in spear_z and leaves z = floor_z.
comment spear_trap_draw_touchable add_to_touchable_list, then spear_trap_draw.
comment spear_trap_init The spear trap of ten objects: spear sprites (0x73A0, 0x7390, 0x7388, 0x7398, 0x73A8) and clip entries (sprite_flags 0x5000, their words a clip rectangle) spear_trap_draw sets at the floor line; box 0x18 x 0x18, height 0x100; first wait spawn_arg_2; spear_trap_wait.
comment spear_trap_init_100 Object type 15: direction 0x100, then spear_trap_init.
comment spear_trap_init_300 Object type 28: direction 0x300, then spear_trap_init.
comment spear_trap_retract The spear falls back to its floor (fall_to_floor from spear_z), touchable; landed, phase_timer = spawn_arg_1 (the period) and spear_trap_wait.
comment spear_trap_rise The spear shoots up (speed_z - 0x10F00 a frame) for 0xC frames, touchable as kind 4; then sound 0x1B, kind 0xC, and 0x3C frames of spear_trap_stay_up.
comment spear_trap_stay_up The spear stays up and touchable for phase_timer (0x3C) frames, then spear_trap_retract.
comment spear_trap_wait The spear rests (drawn, not touchable) for phase_timer frames, then kind 4, phase_timer 0xC and spear_trap_rise; removed off screen.
comment wall_pad_update A pad of the padded wall: once touched it plays one round of wall_pad_frames (wall_pad_sprites, wall_pad_frame_durations); drawn and touchable; removed past spawn_arg_1.
comment build_ring_tally_panel A player's panel: the portrait of character (ring_tally_portraits, with its result_frames) at 0x40, 0xA0, and second-pool pieces: the bonus label (0x9EF0, 0x9EF8 without gets_bonus), the backdrop, the ring icon and three digits.
comment cycle_ring_tally_backdrop_palette Every 2 frames the panel's backdrop (the concentric squares, the piece z points at) takes the next of the 7 ring_tally_backdrop_palettes.
comment pick_ring_bonus_players The three ring counts come with the bonus bit 0x100; it stays only on the players with the most rings (ties included). The counts compare as signed bytes, so 0x80 rings or more count as fewer.
comment ring_tally_end_sound_wait Waits for ring_tally_counted; then sound 0x73 if ring_tally_percent is under the stage's bonus percentage, and it is removed.
comment ring_tally_percent_draw_digits Queues the percentage's ones and tens digits in their layers, the hundreds only when it shows a 1 (frame 0x9DF0).
comment ring_tally_percent_set_digits Sets the three digit sprites (percent_digit_sprites) to shown_percent in BCD (the hundreds 0 or 1), then ring_tally_percent_draw_digits.
comment ring_tally_percent_update The percentage: the first frame builds "TOTAL RINGS" (0x9E38), the sprite behind the digits (speed_y) and the three digits; then the digits roll at random every frame until ring_tally_counted, then show the percentage (update_ring_tally_percent). Removed when stage_results_done.
comment ring_tally_player_1_update Player 1's panel: the first frame builds it at the left (build_ring_tally_panel) and starts the count; then update_ring_tally_panel.
comment ring_tally_player_2_update Player 2's panel: built in the middle (screen x 0xA0), or in player 3's place on the right (ring_tally_player_3_update) when cabinet_config bit 0 is clear; then update_ring_tally_panel.
comment ring_tally_player_3_update Player 3's panel: built on the right (screen x 0x100); then update_ring_tally_panel.
comment ring_tally_player_3_update_spare_return Unused: a lone return left after ring_tally_player_3_update; nothing calls it.
comment ring_tally_ticker_update The counting sound: 0x83 every 6 frames until ring_tally_counted, then removed.
comment start_ring_tally Game mode 0x1D's start: ring_tally_percent = the players' stage rings * 100 / stage_ring_counts[stage]; at stage_ring_bonus_percents[stage] or more the players with the most rings get the bonus (pick_ring_bonus_players). A panel per player in the game, the percentage, the ticker and the end sound.
comment update_ring_tally_panel The panel by step: counting up (update_ring_tally_panel_count_up); the bonus (give_result_bonus, the character's sound and 0x88), then the bonus result with the backdrop cycling; or, without the bonus, sound 0x89 and the other result frame. Removed when stage_results_done.
comment update_ring_tally_panel_count_up Draws the panel (leading zeros hidden) and every 2 frames shows one more ring up to ring_total (rings_counted and ring_total compare as signed bytes); then it counts out of ring_tally_panels_counting (the last sets ring_tally_counted) and goes to the bonus step, or past it.
comment update_ring_tally_panel_tail A lone return that update_ring_tally_panel's bonus step calls last; it does nothing.
comment update_ring_tally_percent After results_timer (0x12C frames) stage_results_done; while the percentage is at or above the stage's bonus percentage, the sprite behind the digits cycles ring_tally_blink_palettes every 2 frames.
comment call_00F8B2 What call_code runs for metal_wall_rise's address, an object handler: metal_wall_rise with octant 0.
comment call_00F8E1 What call_code runs for metal_wall_update's address, an object handler: metal_wall_update with octant 0.
comment call_01130E What call_code runs for push_crate_move's address, the broken crate's handler: push_crate_move with cursor, random_value and ring 0.
comment call_080000 What call_code runs for spawn_objects_by_handler's address: spawn_objects_by_handler with list_offset, record and object 0, its results dropped.
comment call_08E0CE What call_code runs for delete_object_and_children's address, the handler every object starts with: delete_object_and_children, its results dropped.
comment call_090161 What call_code runs for join_character_chooser_update's address, the join chooser's handler: join_character_chooser_update with text_x 0.
comment game_entry The code at the reset vector: reset.
comment button_box_init Sets up the button box Eggman spawns in his stage B scene: sprite 0x8928, palette 0x11D0, step_timer 0xB4 (its wait before it rises), then button_box_wait.
comment button_box_press Plays the box's steps 0x13 to 0x22 (button_box_steps, each frame of button_box_frames held its button_box_durations): the button held down, the lid closing and the box sinking back into the floor. At step 0x23 it leaves the box to draw_button_box.
comment button_box_rise Waits out step_timer, then plays the box's steps 0 to 0x12 (button_box_steps, button_box_durations, button_box_frames): it rises out of the floor and its lid opens on a lit button. Then it waits for Eggman's eggman_scene.phase to be 2, when he presses it, and goes on to button_box_press.
comment button_box_wait Draws the box until its parent, Eggman, has eggman_scene.step 1, which his first talking frame sets; then button_box_rise.
comment draw_button_box Places the box on the screen from its projected position (unproject_object), deletes it once it is off screen and queues it in bucket 0xFF, under everything, even on the frame it is deleted.
comment draw_eggman_booth Draws the booth's two parts at screen_y = slide_y in bucket 0, on top: Eggman with the frame eggman_booth_steps gives for step (eggman_booth_frames), then the backdrop. Plays sound 0x41 on every frame that shows frame 0xB, except in stages 0 and 5.
comment eggman_booth_animate Plays the booth's 0x1D steps, each frame of eggman_booth_steps held for its eggman_booth_frame_durations (step_timer), then slides the booth back up 4 lines a frame and deletes it with its parts once slide_y goes below 0. Drawn by draw_eggman_booth.
comment eggman_booth_slide_in Slides the booth down 4 lines a frame (slide_y, both parts' screen_y) until slide_y reaches 0x2C, then sets it to 0x2F and starts eggman_booth_animate at step 0.
comment eggman_scene_init Sets up Eggman's scene in stage B: Eggman (sprite 0x88A8, palette 0x81F0, step_timer 0x3C) with eggman_scene_wait, and his shadow part (sprite 0xAA50; the palette 0x250 it writes is at once replaced by 0x9FFE). Spawns the button box at his position, projected_x - 0x1E and projected_y + 0x12, with button_box_init.
comment eggman_scene_move Moves Eggman (move_object_projected), lets him fall to his floor and queues him in bucket 0. Once delete_if_off_screen removes him it sets scroll_mode 7 (follow the players) and clears stage_trigger_bits; until then his shadow is drawn every other frame at his floor, 4 lower, in bucket 0xFF.
comment eggman_scene_pause Draws Eggman for step_timer frames (0x3C from eggman_scene_init), then sets eggman_scene.phase to 1, a value nothing tests, and starts eggman_scene_talk.
comment eggman_scene_press_button Plays Eggman's 7 button-pressing frames (eggman_press_frames, each held its eggman_press_durations). When the fifth shows, eggman_scene.phase = 2 (the box's button goes down) and escape_started = 1; after the last, eggman_scene_run_off with a 0x3C-frame wait.
comment eggman_scene_run_off Waits step_timer frames, then runs: every 3 frames the next of the 8 eggman_run_frames, with direction_to_speed(0x20, angle 0x2A0) added to speed_x and speed_y each time, so he speeds up as he goes.
comment eggman_scene_talk Eggman talks: each time step_timer runs out, the next of three message boxes at (0xA0, 0x80), kind eggman_scene_texts[direction] for eggman_scene_text_durations[direction] frames, with sound eggman_scene_sounds[direction] (direction counts them); a new talking frame every 7 frames. After the third, eggman_scene_press_button.
comment eggman_scene_wait Draws Eggman until scroll_position passes 0xFC0, then sets scroll_mode 2 (wait) and stage_trigger_bits to 1, which the players' stage B sequence waits for, and starts eggman_scene_pause.
comment eggman_speech_run Runs a speech script, except while current_game_mode is 0x11 (the screen wipe): when frame_count reaches the record's delay it runs its command: 1 Eggman's booth (spawn_eggman_booth), 2 a message box (x, y, kind, frames), 3 a sound, each moving script on and restarting frame_count; 7 waits there for good; 0 and 4 delete the object.
comment eggman_speech_start Starts one of Eggman's speeches: script = the eggman_speech_scripts record for the object's sprite_frame (stage 3 uses 1, stage 8 0, stage A 3), frame_count 0, then eggman_speech_run.
comment eggman_teleport_animate Steps Eggman through eggman_teleport_steps, each frame of eggman_teleport_frames held its eggman_teleport_durations: he builds up in stripes, stands, works a remote and fades out; step 0xFF deletes him and the beam. The beam part is drawn every other frame at his floor, 8 to the right; Eggman 3 frames out of 4.
comment eggman_teleport_init Sets up Eggman's teleport: sprite_scale 0x180, palette 0x8220, its part a tall beam (sprite 0x79A8), then eggman_teleport_animate. Only spawn_eggman_teleport, which nothing calls, spawns it.
comment sliding_door_draw Moves the door (move_object_projected) and deletes it once off screen; else adds it to the touchable list and queues its three parts at screen_y - 0x50: the clip entry, the door sprite at its screen position, and the second clip entry, whose edges (sprite_palette, sprite_scale) become screen_x and 0x3FF - projected_x + screen_x.
comment sliding_door_init Sets up stage B's sliding door: the main object is a sprite clip entry (sprite_flags 0x5000) with a box 0xE6 x 0x21, height 0x100, kind 0xC; part 2 is the door (sprite 0x7830, palette 0x9070), part 3 a second clip entry. open_delay 0xA, then sliding_door_wait.
comment sliding_door_open Slides the door along angle 0xB4 at speed 0x100 until its projected_x reaches 0x3EF, then stops it, sets sliding_door_opened to 0xFF and leaves it to sliding_door_draw.
comment sliding_door_wait Waits until every playing player (player_status negative) has stage_b_path_done set, with at least one playing; then counts down open_delay (0xA) while that holds and starts sliding_door_open. Drawn by sliding_door_draw.
comment spawn_eggman_booth Spawns Eggman's booth, a two-part object at screen x 0x9F that slides in at the top of the screen (eggman_booth_slide_in): Eggman (palette 0x8200, his first step held 0x2F frames) and the backdrop (sprite 0x5718, palette 0x8210). Does nothing more when no object is free; hands back free_count and block.
comment spawn_eggman_teleport Unused: spawns a two-part object (spawn_object_with_pieces) and gives it eggman_teleport_init, without testing whether one was spawned.
comment arrow_board_ride Rides its platform (z and floor_z = the platform's z), queued at screen_y - 0x20 until check_left_screen finds it has left the screen, then deleted. Grey (palette 0x8400) while grey_screen_active is set, palette 0x9190 again once grey_screen_ended is set.
comment arrow_board_start An arrow board of stage 4 (sprite 0x6EB8, a wooden board with a red arrow, palette 0x9190), projected from its x and y; then arrow_board_wait_for_platform, this frame too.
comment arrow_board_wait_for_platform Stays in the rider list until it stands on a platform (flags 8), then arrow_board_ride.
comment board_prop_start Projects the prop from its x and y and goes on to board_prop_update.
comment board_prop_update A prop under the propped board: deleted once it has left the screen; falls with the board (its z moves by propped_board_object's speed_z), keeps draw_depth = screen_y - 0xB while the board is still, shakes with it (shake_dx), is touchable and queued at draw_depth.
comment boulder_roll Rolls just ahead of the bridge collapse: x = group_leaders[0]'s x - 0x30, speed_x = -chaser_speed << 12, health 0x7F, group_leader_0_held cleared; the first time x_int is 0x180 or less, camera shake and sound 0xF. Falls where there is no floor block (floor height 0x7FF becomes 0x230); touchable, a rider, deleted below screen y 0x180.
comment boulder_start A boulder of stage 4 (boulder_animation, sprite_scale 0x200, kind 9, box 0x80 x 0xC0, height 0x60), direction 0x300, speed 0x30000, wait_timer 0x78; sets group_leader_0_held, so new group leaders wait, joins the rider list and goes on to boulder_wait.
comment boulder_wait Waits in the rider list until scroll_position reaches 0x3B00, or for wait_timer (0x78) frames once it is past 0x3800, then boulder_roll.
comment bouncing_fireball_knocked_away Unused: knocks the object flying: speed_z -0x80000, the direction and speed of the object that touched it (touched_by), the direction turned 0x80 one way or the other by bit 0 of speed_int, set_speed_from_direction; then knocked_object_fly.
comment bouncing_fireball_update A fireball: moves by speed_x and speed_y, falls 0x6000 a frame and bounces off its floor at half speed (sound 0x21 when it goes back up at 2 or more). Deleted with its shadow once it has left the screen; else animated, touchable if it has a hitbox_size_x, a rider, queued, its shadow at its floor, blinking while on a platform.
comment bouncing_fireball_update_spare_return Unused: an empty function, a lone return the original left after bouncing_fireball_update.
comment catapult_base_update The catapult's base part: takes its parent's z (and floor_z) and follows its frame (the parent's animation_frame - 1, stepped once by animate_around_anchor); queued when it has a sprite_frame.
comment catapult_dummy_update The handler of the dummy part catapult_wait_for_platform_setup adds to a catapult with a launch delay: an empty function.
comment catapult_hold_up Holds the spoon up for animation_speed (0x32) frames, wobbling between frames 4 and 5 every 3 frames, the last 0x1E frames on frame 4; then state 3 from frame 4 at speed 4, animating backwards (flags 0x80). The 4 it stores in those last frames goes to current_player, the base part (set by catapult_update), and catapult_base_update overwrites it.
comment catapult_launch Throws: state 1 from frame 1 at animation speed 3, forwards (flags 0x80 cleared), sound 0x1D. A player touching it in state 1 is launched (check_player_contacts).
comment catapult_swing_down Swings the spoon back down around its anchor; at frame 1 or below it shows frame 0 and goes back to state 0, its kind set back to launch_kind.
comment catapult_swing_up Swings the spoon up around its anchor (animate_around_anchor); after the last step, state 2 with animation_speed 0x32 and animation_timer 3 for catapult_hold_up.
comment catapult_update Deletes the catapult and its base part once it has left the screen. Else runs catapult.state (0 and 5 wait for a rider, 1 swing up, 2 hold, 3 swing down, 4 wait for the scroll) with current_player on the base part, takes its platform's z when rides_platform, is touchable and queued at screen_y - 0x14 (+ 0x14 in states 1-3).
comment catapult_wait_for_platform Stays in the rider list until it stands on a platform (flags 8), then sets rides_platform to 0xFF and sets itself up from the template offset kept in catapult.state (catapult_wait_for_platform_setup).
comment catapult_wait_for_platform_setup Sets the catapult up from object_pair_templates + template_offset (catapult_pair_template): handler, animation, launch_kind, anchored on its floor; with a catapult_launch_delays[sprite_frame] delay, state 4 as kind 5 until launch_position, else state 0 as launch_kind. Adds its base part (deleting itself when none is free) and, with a delay, a dummy part.
comment catapult_wait_for_rider Shows frame 1 (pressed) while touched (flags 4), else frame 0; touched with touch_distance at most 0x211, it throws (catapult_launch).
comment catapult_wait_for_scroll State 4 of a catapult with a launch delay: throws by itself (catapult_launch) once scroll_position reaches launch_position. No stage list uses a kind with a delay.
comment catapult_x_start A catapult of stage 4 that throws players along +x: keeps template offset 0 (spoon sprites 0x6BE0-0x6C08, launch kind 6) in catapult.state and waits in the rider list for a platform (catapult_wait_for_platform).
comment catapult_x_start_on_floor Unused: sets up catapult_x_start's catapult at once, on the floor: rides_platform 0, flags 0, catapult_wait_for_platform_setup with template offset 0 and object.
comment catapult_y_start A catapult of stage 4 that throws players along +y: keeps template offset 0x1A (sprites 0x6C10-0x6C58 with a separate base, launch kind 7) in catapult.state and waits in the rider list for a platform (catapult_wait_for_platform).
comment catapult_y_start_on_floor Unused: sets up catapult_y_start's catapult at once, on the floor: rides_platform 0, flags 0, catapult_wait_for_platform_setup with template offset 0x1A and object.
comment door_roller_drop Health 0x7F; flies out of the doorway (speed_z starts at -0x80000) and falls 0x6000 a frame, moving 5 a frame along y, animated, in the rider list. On its floor: state 2 and a camera shake, or deleted when there is no floor there (z_int 0x800 or more).
comment door_roller_ride Rides its platform, the bridge planks (z and floor_z = the platform's z), in the rider list until it is at screen y 0x130 or below; then sets off_screen, so door_roller_update deletes it.
comment door_roller_roll_out Rolls 1 along y a frame for roll_out_timer (8) frames, then sets flags 0x400000, which drops the crumbling floor under it at once, and goes to state 3.
comment door_roller_update A roller behind the door: runs its state (door_roller.state, var_80: 0 and 4 door_roller_wait, 1 door_roller_drop, 2 door_roller_roll_out, 3 door_roller_ride); deleted once off_screen (var_83) is set; animated, projected and, from state 1 on, queued (queue_sprite_at_base).
comment door_roller_wait Health 0x7F; waits for scene_step 3 (the door bursts), then state 1.
comment door_wall_start The brick wall with a stone doorway at the start of stage 4 (sprite 0x6EB0, palette 0x9110) at x -0xE2E, y 0xF31 on its floor, with 0x10 pieces to burst into (door_fragments); then door_wall_wait_to_burst.
comment door_wall_wait_to_burst Drawn until scene_step reaches 3; then it bursts into pieces_left (0x10) fragments (rock_debris_fly, palette 0x8240) at random spots up to 0x3F pixels from its screen position, with random speeds and the animation of each door_fragments record, and deletes itself.
comment fence_post_draw Places the post from its projected position, lets it fall to its floor and deletes it once off screen; else queues it, touchable (health 0x7F) and in the rider list.
comment fence_post_init Builds the electric fence (electric_fence_state = 1): this post and extra_posts more, each a new two-part object fence_post_offsets[row_direction] from the one before (kept in current_player). A post: sprite 0x6CA8, kind 0xE, height 0x64, box by row_direction, fence_post_start; its part the arc (sprite 0x6C88, fence_arc_flags, arc_phase).
comment fence_post_start Joins the rider list, places the post from its projected position (unproject_object) and goes on to fence_post_update.
comment fence_post_update While electric_fence_state is 1 it draws the post's arc toward the next post (fence_arc_offsets, the next of fence_arc_frames every other frame, the arc's handler_low the toggle) and the post; at 0xFF it deletes the post; any other value makes it a plain post (kind 5, box 0x1B x 0x19) left to fence_post_draw.
comment fireball_emitter_start The fireball emitter near the end of stage 4: from the list fireball_emitter_lists gives for its sprite_frame it takes its x, y and floor height and keeps the rest, its shots, in shot; then fireball_emitter_update. It has no sprite.
comment fireball_emitter_update Moves with the camera's ground speed for its floor's slope (compute_camera_slope_speed) while screen_y is -0x40 or more, on its floor. When scroll_position reaches the next shot it spawns a fireball (bouncing_fireball_update, kind 0x17) at the shot's x and dy with its speeds, fireball_types looks and a shadow; deleted after the last shot.
comment flame_machine_arm_draw Places the arm at the back wall's projected position plus (spawn_arg_1, spawn_arg_2) and its sway offset (flame_machine_arm_sway_offsets[sway_step]) times side; queues it at the depth of the second arm's screen_y - 1, and its part at its screen position in bucket arm_index * 4.
comment flame_machine_arm_rest Holds the arm for rest_timer (0x1E) frames, then clears its sway bit in flame_machine_flags (0x40 << arm_index) and goes back to flame_machine_arm_update.
comment flame_machine_arm_take_disc Hands the arm a spiked disc: a two-part object at the arm (sprite 0x6278, sprite_scale 0xC0, kind 0x24, which hurts players; spawn_arg_1 = arm_index) with spiked_disc_hold, its part the shadow (0x6988); clears the arm's take-disc bit (0x400 << arm_index).
comment flame_machine_arm_update Arm arm_index, not run once the machine is destroyed: bit 0x400 << arm_index gives it a disc; with bit 0x10 << arm_index (active) it steps flame_machine_arm_frames every other frame; frame 0xC sets bit 0x40 << arm_index and it sways (sway_step) until sway_count passes 0x26, then 0x200 (discs dropped) and flame_machine_arm_rest; frame 0xD ends.
comment flame_machine_back_wall_update The back wall: unless the machine is destroyed or an arm is active (0x10, 0x20), it steps flame_machine_back_wall_frames every 3 frames, placed by flame_machine_back_wall_offsets from the box, rods by flame_machine_rod_sprites. Frame 5 starts both arms; the cycle's end sets 4, 8 (fire), 0x400, 0x800 (take discs), clears 0x200, pauses 0x1E frames.
comment flame_machine_cannon_update A cannon (spawn_arg_1 0 or 1): drawn, touchable (health 0x7F), flashing with the box (flame_machine_cannon_hit_palettes[flame_machine_flash_step]). When its fire bit (4 << spawn_arg_1) is set it spawns a flame (sprite 0x6910, kind 0x17) at flame_machine_flame_offsets[spawn_arg_1] with flame_machine_flame_burn and clears the bit.
comment flame_machine_destroyed The machine breaks: goal_unlocked = 0xFF, flame_machine_flags 0x100000 (destroyed), health 0. Every 8th of its destroyed_frames an explosion (flame_machine_explosion_start) at a random spot over the box; at 0x130, flame_machine_remove and the wreck (flame_machine_wreck_draw) at projected 0x1B2F, 0x14F.
comment flame_machine_draw Draws the box, touchable. A hit (health no longer previous_health) starts the flash: flame_machine_flags 0x1000, flame_machine_flash_step 4, sound 0x21, then per step a flame_machine_hit_palettes palette and sound 0x1E. Then its floor piece, the capsule (animated every other frame), its second piece and the shadow; at health 0, flame_machine_destroyed.
comment flame_machine_explosion_start One of the breaking machine's explosions: speed 0x20, direction 0x80, 8 debris chunks (spawn_debris), then explode_object.
comment flame_machine_flame_burn A cannon's flame: every other frame the next of flame_machine_flame_frames (kept in spawn_arg_2; sprites from flame_machine_flame_sprites), touchable on those frames from frame 2, deleted at frame 0xB. Placed 0x20 above its z, its shadow on the floor at frames 2-4; explodes when the machine is destroyed.
comment flame_machine_init Builds the machine at the end of stage 4 as one chain from the box (sprite 0x6258, kind 0xC, health 0x28, time_left 0xE10, flame_machine_update): its floor piece, the capsule's two pieces and shadow, the back wall with two rods, two arms (arm_index 0 and 1, side 1 and -1) and two cannons (spawn_arg_1 0 and 1). flame_machine_flags = 2.
comment flame_machine_remove The broken machine's last frame: electric_fence_state = 0xFF, so the electric fence's posts delete themselves, and the machine is deleted with all its parts.
comment flame_machine_scroll_stop_hold Sets scroll_mode 7 (follow the players) every frame once flame_machine_flags has 0x100000 (the machine destroyed). Object type 8, which no stage list spawns.
comment flame_machine_scroll_stop_wait Stops the scroll (scroll_mode 1) once scroll_position reaches spawn_arg_1, then flame_machine_scroll_stop_hold. Object type 8, which no stage list spawns, so it never runs.
comment flame_machine_update The box: time_left counts down while active_player_count is non-zero, and when it runs out the machine breaks (flame_machine_destroyed); otherwise flame_machine_draw.
comment flame_machine_update_spare_return Unused: an empty function, a lone return the original left between flame_machine_destroyed and flame_machine_remove.
comment flame_machine_wreck_draw The machine's wreck: the scorched ground (sprite 0x8360) in bucket 0xFE and two wrecked pieces at (0x6C, -0x66) and (-0x21, -0x27) from it in buckets 0xFD and 0xFC; deleted once map_screen_active is set.
comment goal_unlocker_start Sets goal_unlocked to 0xFF and deletes itself (near the ends of stages B to F).
comment knocked_object_fly Unused: flies with gravity (speed_z + 0x6000 a frame) by speed_x, speed_y and speed_z with no floor test, animated and queued until is_off_screen; then frees its pool 2 child and deletes itself. Only bouncing_fireball_knocked_away sets it.
comment object_type_9_idle The handler object_types gives type 9: an empty function. No stage list spawns the type.
comment pebble_start A pebble of stage 4 (sprite 0x6C60, a small grey rock, palette 0x9020) set on its floor and projected; then pebble_update.
comment pebble_update Queued at screen_y - 0x10 until check_left_screen finds it has left the screen, then deleted.
comment propped_board_start The big board propped up in stage 4 (sprite 0x6EA8, kind 0x11, box 0x10 x 0x12B, height 0xE0, z 0x230), propped_board_object = it; with two objects free it adds the two props (sprite 0x6EC0, kind 0xC, board_prop_start) 0x20 lower in x and 0x70 either way in y.
comment propped_board_update Health 0xA; deleted once it has been on screen and left it, or below screen y 0x180. When group_leader_1 (the bridge collapse) reaches its x it falls (speed_z + 0x2000 a frame). Touched, it shakes (shake_strength 0x3C, screen_x moved +-shake_strength >> 4) and the first time releases its rings; touchable, queued at draw_depth.
comment release_board_rings Scatters the board's rings: scatter_rings_fixed 8 times, from x - 0x40, y - 0x40, z - 0x90 and 0x20 further in y each time; the board's x, y and z are put back after.
comment spawn_door_rollers Called by event_0c_start: places four spiked rollers behind the door from door_roller_layout (x, y, first frame; spiked_roller_animation, palette 0x82F0, kind 4, speed_z -0x80000, roll_out_timer 8) on their floors, with door_roller_update; current_object is left on the last one.
comment spiked_disc_draw_held Moves the disc (move_object_projected) and queues it in bucket (spawn_arg_1 + 1) * 2, then its shadow and spiked_disc_spin. The test for arm frame 8 reads arm_frame as a halfword with spin_toggle's low byte, 1 after the disc's first frame, so from then on the shadow is drawn whatever the frame.
comment spiked_disc_draw_shadow Draws the disc's shadow part at its floor (place_sprite_at_cursor_floor), 0x28 lower, then spiked_disc_spin.
comment spiked_disc_drop Lets the dropped disc fall to its floor (fall_to_floor), then goes on to spiked_disc_roll; drawn by spiked_disc_draw_held.
comment spiked_disc_hold Holds the disc on its arm (its parent): the arm's position (copy_position_to_cursor) plus spiked_disc_hold_offsets[the arm's frame_index] in projected x and z, arm_frame keeping that frame. Once flame_machine_flags has 0x200 (discs dropped) it goes on to spiked_disc_drop.
comment spiked_disc_roll Rolls toward the players: moves, falls to its floor and is deleted once off screen; else its speed is set along angle 0x154 at 0x300 (direction_to_speed) and it is queued at screen_y + 0x18, with its shadow, then spiked_disc_spin.
comment spiked_disc_spin Explodes the disc once the machine is destroyed. Else steps its spin through the four spiked_disc_sprites and makes it touchable (health 0x7F) and a rider; spin_toggle, set back to 1 each time, lets that happen every frame after the first instead of every other.
comment vine_bar_update The bar at the vine's tip: the vine's x, y, z and holding, moved to match where vine_swing drew it; touchable and in the rider list (players jumping into it are held until holding is 0), its height reaching its floor (up to 0x50). Its shadow while on a platform; "CATCH!!" (spawn_sign kind 7) while holding is 1 over a floor.
comment vine_init Sets up a vine: segment_count - 1 pieces (sprite 0x6E68), then the bar, a two-part object (sprite 0x6E70, kind 0xA, vine_bar_update, its part the shadow 0x5D90) chained after them, current_player serving as the chain's last link; speed = phase_step << 16, base_x = x, depth_offset = segment_count * 8, floor_z = z; then vine_swing.
comment vine_scroll_stop_end Once scroll_position passes spawn_arg_2: scroll_mode and saved_scroll_mode 7 (follow the players), vines_hold_players = 0, and the stop object deletes itself.
comment vine_scroll_stop_hold Restarts the scroll (scroll_mode and saved_scroll_mode 0, scroll_speed 8) and goes on to vine_scroll_stop_end once every player's object (vine_scroll_stop_player_blocks) is out (flags 0x400), held on a bar (flags 0x10000) or at x_int -0x120 or more; until then it runs vine_scroll_stop_end's check.
comment vine_scroll_stop_init The invisible scroll stop at the vines: sets vines_hold_players, so held players cannot let go of the bars, and starts vine_scroll_stop_wait.
comment vine_scroll_stop_wait Stops the scroll (scroll_mode and saved_scroll_mode 1) once scroll_position reaches spawn_arg_1 and goes on to vine_scroll_stop_hold. Before that position it already runs vine_scroll_stop_hold every frame, which can restart the scroll and skip the stop.
comment vine_swing Swings the vine: its phase (speed_z) grows by speed, the curl speed_y = sine_table[phase] * curl_scale; each segment steps 0x18 * the sine and cosine of an angle growing by the curl in x and z, leaving the vine's x and z at the tip. holding is 0 only at phase 0x9F-0x110 without vines_hold_players. Deleted at screen_x +-0x200 (vines_hold_players = 0xFF).
comment add_coin Counts a coin: bookkeeping_chute + 1, a pulse more pending at pending_offset for the coin counter. Unless credit_block has max_credits: changed flag, coin_sounds + 1, and the chute's multiplier (chute_offset) of coins, plus a bonus coin every bonus_adder, become credits at credit_coin_cost each, up to max_credits. Hands back coin_credits plus those.
comment add_service_credit Gives credit_block a service credit unless it already has max_credits: changed flag, credits + 1, service_coin_sounds + 1, and its coins and bonus_coins cleared when it reaches the maximum. Hands back service_credits + 1, or unchanged when full.
comment add_to_credit_pool Unused: unless free_play, adds credits_to_add to credit_pool, at most 9 (credit_pool_coins cleared at the cap), and copies it to bookkeeping_coin_credits and bookkeeping_backup around update_bookkeeping_checksum. Hands back the credits added in its low byte (0 with free play).
comment apply_settings From init_system: the settings into credit_coin_cost, credits_per_start, the chute multipliers, bonus_adder (1 is free_play), health_setting and starting_health; coin counters and sounds cleared; shared_coin_counter_word 0xFC when both multipliers match. The credit blocks get back their credits from saved_credits (no coins, at most 9).
comment blink_start_prompt Every 16th frame redraws credit_block's start prompt at text_position: erased (io_port_d bits 2-3 cleared) while bit 5 - frames_per_wait of frame_counter is set; else "Push start button" with free_play or enough credits, "Insert coin" with nothing in, or "Insert more coin" ("2 credits start" by turns when 2 are needed). Its cabinet_config test changes nothing.
comment build_fraction_glyph Unused: hands back the pixel rows of a small fraction: numerator_top_rows and numerator_bottom_rows from fraction_digit_font for numerator, 3 rows higher, and denominator_top_rows and denominator_bottom_rows for denominator ORed with the slash (fraction_slash_top_rows, fraction_slash_bottom_rows).
comment copy_credit_counts Copies the counts halfword (coins and credits) of three credit blocks, 4 bytes apart from credit_block, to dest.
comment draw_attract_credits Draws the credits outside a game, by cabinet_config: with common chutes (0, 1, 4) block 1's count, the big prompt over the demos (blink_demo_prompt) and "2 credits 1 play" when credits_per_start is 2 (not in free play); with individual chutes each player's block: its count (draw_credit_count) and start prompt (blink_start_prompt).
comment draw_credit_count Redraws credit_block's count at text_position (draw_credit_count_now) the first time, while its bit of stage_init_flags (0xF, 0x12, 0x13 for blocks 1 to 3) is clear, or when the block's changed flag is set.
comment draw_credit_count_now Draws credit_block's count at text_position (kept in credit_text_position): "Free play" with free_play; else "credit" ("credits" from 2), the count as two BCD digits (one below 10), then the coins toward the next credit as a small fraction over credit_coin_cost (draw_fraction), cleared when none or when a credit costs 1 coin.
comment erase_attract_credits Unused: the undo of draw_attract_credits by cabinet_config: erase_credit_count and erase_start_prompt at the common or per-player positions. It takes the positions without the 0x310000 base, so its writes would miss the text map, and with individual chutes they are not all draw_attract_credits' ones.
comment erase_credit_count Unused: blanks the credit line at text_position: erase_text_here over credits_label_text ("credit  "), then over the record after it, the 4 cells of the count and fraction. Only erase_attract_credits calls it.
comment erase_start_prompt Unused: clears io_port_d bits 2-3 and erases prompt 1 of prompt_texts ("Insert more coin") at text_position (erase_text_lines).
comment pulse_coin_counter Pulses a coin counter (io_port_d bit counter_bit) from its state at counter_state: on for 6 frames, off for 6, once per pending coin. Then saves the credits (save_credits) when service_credits or coin_credits were added this frame.
comment save_credits Copies the coins and credits of the three credit blocks to saved_credits, then update_bookkeeping_checksum, then copies them to saved_credits_backup.
comment take_from_credit_pool Unused: unless free_play, takes credits_to_take from credit_pool and copies it to bookkeeping_coin_credits and bookkeeping_backup around update_bookkeeping_checksum.
comment update_coins At each VBLANK: clears the blocks' changed flags; unless free_play, plays one pending coin and one service sound (0x7F), gives every block in use a credit on a service press (inputs_pressed 0x1001), adds the coins of the chutes released (bits 2, 3, 6) to the blocks by cabinet_config (add_coin), with the bookkeeping, and pulses the coin counters.
comment init_sound Starts the sound: sound_queue_read 0, sound_queue_write 1 with sound 0x96 queued first, io_cnt bit 2 set (the sound CPU runs), io_port_d bit 3 cleared and sound_cpu_hold 0 (the Z80 program goes on).
comment play_sound Asks for sound sound_id: during a game (game_in_progress) always through queue_sound; outside one only when setting_advertise_sound is on and sound_id's bit is set in attract_sound_mask.
comment queue_coin_sound Unused: sets io_port_d bit 3 and queues sound 0x7F, the coin sound update_coins plays.
comment queue_sound Puts sound_id in sound_queue at sound_queue_write and moves it on, wrapping at 32, with no filter (the test mode and the coins use it directly).
comment set_port_d_bit_3_if_sound_allowed Unused: sets io_port_d bit 3 when unused_coin_byte, game_in_progress or setting_advertise_sound is set; init_sound clears that bit.
comment clear_text_layer Zeroes the text map: 0xE0 blocks of 16 bytes from its first cell (clear_blocks).
comment draw_bcd_4 Unused: draw_bcd_digits with the 4 digits of value's low halfword.
comment draw_bcd_6 Unused: draw_bcd_digits with the 6 digits of value's low 24 bits.
comment draw_bcd_8 Unused: draw_bcd_digits with all 8 digits of value.
comment draw_bcd_digits Writes digit_count BCD digits of value right to left into the cells from text_position, each nibble + 1 as the character with attribute's high byte; once the rest of value is 0 the leading cells are cleared to 0 (the last digit is always drawn). Hands back text_position at the first cell.
comment draw_credit_digits Draws value's low byte as two BCD digits at text_position (draw_bcd_digits), a leading 0 cleared: the credit count of draw_credit_count_now.
comment draw_fraction Writes a small fraction in two cells at text_position with attribute: character 0x80 + numerator, then 0x90 + denominator (each & 0xF): the coins toward a credit over credit_coin_cost.
comment draw_hex_2 Draws value's low byte as 2 hex digits at text_position (draw_hex_digits), a leading 0 cleared.
comment draw_hex_4 Draws value's low halfword as 4 hex digits at text_position (draw_hex_digits), leading zeros cleared.
comment draw_hex_6 Unused: draws value's low 24 bits as 6 hex digits at text_position (draw_hex_digits).
comment draw_hex_8 Draws value as 8 hex digits at text_position (draw_hex_digits), leading zeros cleared.
comment draw_hex_digits Writes digit_count hex digits of value ('0'-'9', 'A'-'F') right to left into the cells from text_position, with attribute's high byte; once the rest of value is 0 the leading cells are cleared to 0 (the last digit is always drawn). Hands back text_position at the first cell.
comment draw_queued_strings After build_sprite_list: turns string_queue, last string first, into sprite entries at sprite_entry, one per character through text_frames, all sized by the string's scale and its first visible character's frame (blanks only move x); then sprite_list_end_command, and string_count 0.
comment erase_formatted_text Unused: erases what print_formatted_text prints for text, writing cell with its low half cleared over the cells, the attribute bytes skipped. Its code tables have no entry for code 0x24.
comment erase_formatted_text_attribute_double Unused: codes 0xC (at width 2) and 0x18 erased: skips the attribute byte, then erase_formatted_text_double.
comment erase_formatted_text_attribute_single Unused: codes 0xC (at width 1) and 0x12 erased: skips the attribute byte, then erase_formatted_text_single.
comment erase_formatted_text_attribute_triple Unused: codes 0xC (at width 3) and 0x1E erased: skips the attribute byte, then erase_formatted_text_triple.
comment erase_formatted_text_double Unused: erases a run at 2 cells a character, writing cell as a long, so the second cell gets cell's high half, which erase_formatted_text does not clear; then the code byte: 6 another run at this width, 0 ends, the others go to their routine.
comment erase_formatted_text_position_double Unused: code 0x15 erased: a new position halfword, then erase_formatted_text_attribute_double.
comment erase_formatted_text_position_single Unused: code 0xF erased (and the text's start): the next halfword replaces text_position's low half, then erase_formatted_text_attribute_single.
comment erase_formatted_text_position_triple Unused: code 0x1B erased: a new position halfword, then erase_formatted_text_attribute_triple.
comment erase_formatted_text_single Unused: writes cell over a run of characters, one cell each, up to its 0, then acts on the code byte: 3 another run at this width, 0 ends, the others go to their routine (0x24 has none).
comment erase_formatted_text_skip_double Unused: code 0x21 erased at width 2: text_position moves by a signed byte of cells.
comment erase_formatted_text_skip_single Unused: code 0x21 erased at width 1: text_position moves by a signed byte of cells.
comment erase_formatted_text_skip_triple Unused: code 0x21 erased at width 3: text_position moves by a signed byte of cells.
comment erase_formatted_text_triple Unused: erases a run at 3 cells a character, writing cell as a long and a halfword, so the second cell gets cell's high half; then the code byte: 9 another run at this width, 0 ends, the others go to their routine.
comment erase_text Writes 0 over the cells the print_text record at text covers (erase_text_here at 0x310000 | its first halfword); hands back text past the record.
comment erase_text_here Writes cell over as many cells from text_position as text has characters (after its attribute byte, up to a 0); hands back text past the 0 and text_position after the last cell.
comment erase_text_lines Writes 0 over the cells print_text_lines writes for text at text_position.
comment erase_text_list Unused: erase_text for each record from text until a 0 halfword.
comment expand_font Expands count 1-bit characters, 8 rows of a byte, from source into 4-bit characters at dest in color & 0xF; each row's blank pixels take the row above's pixels moved one pixel right, in pen color + 1.
comment expand_font_2bpp Unused: as expand_font for 2-bit characters: count characters of 8 halfword rows from source, each pixel (0-3) plus color & 0xF making a 4-bit pixel of the character at dest.
comment expand_font_2bpp_transparent Unused: the same, but pixels that are 0 stay 0 (transparent): color is added only where a pixel is set.
comment formatted_text_attribute_double Unused: codes 0xC (at width 2) and 0x18: the next byte is the attribute (the cells' high byte), then formatted_text_double.
comment formatted_text_attribute_single Unused: codes 0xC (at width 1) and 0x12: the next byte is the attribute (the cells' high byte), then formatted_text_single.
comment formatted_text_attribute_triple Unused: codes 0xC (at width 3) and 0x1E: the next byte is the attribute (the cells' high byte), then formatted_text_triple.
comment formatted_text_double Unused: writes a run at 2 cells a character (cell, cell + 1) up to its 0, then acts on the code byte: 6 another run at this width, 0 ends, the others go to their routine.
comment formatted_text_position_double Unused: code 0x15: the next halfword is the new position (text_position's low half), then formatted_text_attribute_double.
comment formatted_text_position_single Unused: code 0xF and a formatted text's start: the next halfword is the new position (text_position's low half), then formatted_text_attribute_single.
comment formatted_text_position_triple Unused: code 0x1B: the next halfword is the new position (text_position's low half), then formatted_text_attribute_triple.
comment formatted_text_single Unused: writes a run of characters at text_position, one cell each (attribute << 8 | character), up to its 0, then acts on the code byte after it: 3 another run at this width, 0 ends, the others go to their routine.
comment formatted_text_skip_double Unused: code 0x21 at width 2: text_position moves by a signed byte of cells, then the next run.
comment formatted_text_skip_long_double Unused: code 0x24 at width 2: text_position moves by a signed halfword of cells, then the next run.
comment formatted_text_skip_long_single Unused: code 0x24 at width 1: text_position moves by a signed halfword of cells, then the next run.
comment formatted_text_skip_long_triple Unused: code 0x24 at width 3: text_position moves by a signed halfword of cells, then the next run.
comment formatted_text_skip_single Unused: code 0x21 at width 1: text_position moves by a signed byte of cells, then the next run.
comment formatted_text_skip_triple Unused: code 0x21 at width 3: text_position moves by a signed byte of cells, then the next run.
comment formatted_text_triple Unused: writes a run at 3 cells a character (cell, cell + 1, cell + 2) up to its 0, then acts on the code byte: 9 another run at this width, 0 ends, the others go to their routine.
comment load_text_font Sets up the text layer: video_text_control 0x1F7 (map at 0x31F000, characters at 0x31C000), copies 0x9A0 halfwords of text_font into the characters, expands the 0x5B 1-bit characters that follow it in color 1 (expand_font) and clears the text map.
comment print_formatted_text Unused: prints a formatted text: a position halfword (0x310000 | it), an attribute byte, then runs of characters ending in 0, each followed by a code: 0 ends, 3/6/9 go on at 1, 2 or 3 cells a character, 0xC a new attribute, 0xF/0x15/0x1B a new position and attribute and 0x12/0x18/0x1E a new attribute at width 1, 2 or 3, 0x21/0x24 skip cells.
comment print_text Prints a print_text record at text: its first halfword is the text map offset (0x310000 | it), then print_text_here writes its attribute byte and characters; hands back text past the record.
comment print_text_here Writes text, an attribute byte then characters up to a 0, at text_position, a cell (attribute << 8 | character) each; hands back the last cell in cell, text past the 0 and text_position after the last cell.
comment print_text_lines Writes text like print_text_here over several lines: a byte with bit 7 set starts the next line, one text row (0x80 bytes) below the start of the line before, instead of a character.
comment print_text_list Prints each print_text record from text until a 0 halfword.
comment queue_string Adds a string to string_queue (x, y, palette, scale, text) for draw_queued_strings to draw with sprites; mode 0 instead takes x, y, palette and scale from the record at x and the text after them. Ignored once 0x2B strings are queued.
comment queue_string_at Queues the string text at screen x, y with palette and scale (queue_string, mode 1).
comment queue_string_record Queues the string of a record at record: x, y, palette, scale, then the characters (queue_string, mode 0), as the GAME OVER banner does; the y and scale passed in are not used.
comment apply_video_registers The VBLANK hook: writes video_registers_latched to the chip's registers 0x31FF10-0x31FF4F and video_layer_opaque_latched to video_layer_opaque_flags, then latches the shadows from nbg0_scroll_x on (scroll, centres, pages) for the next VBLANK: the chip gets them a frame later.
comment clear_blocks Writes count blocks of 16 zero bytes from dest; hands back dest past them.
comment clear_page_0_and_scroll Zeroes the first 0x400 bytes of video RAM (0x300000) and the 0x80 bytes of scroll and register shadows from nbg0_scroll_x.
comment copy_blocks Copies count blocks of 16 bytes from source to dest; hands back both past them.
comment copy_palette_block Copies size bytes of colors from source to dest in palette RAM (move_string, by halfwords); hands back both past them.
comment copy_palettes_with_pen_0 Writes 16-color palettes at dest from size bytes of source colors: pen 0 is pen_0_color, pens 1-15 the next 15 colors of the source.
comment empty_vblank_hook Unused: an empty function, the vblank_start_hook init_video_registers_layers_off installs.
comment erase_vram_script Unused: walks a VRAM script like run_vram_script but writes 0 (cell) to every cell it would draw, skipping the data; the address, skip and page-clear commands work as there, and 0xAA ends.
comment erase_vram_script_bytes Unused: command 0x8F erased: a count byte, then the high byte and count bytes skipped; cell count times.
comment erase_vram_script_end Unused: command 0xAA's routine, which ends an erase_vram_script; erase_vram_script returns by itself on it, so this is an empty function.
comment erase_vram_script_fill Unused: commands 0x86, 0x89 and 0x8C erased: a count byte, the word skipped; cell count times.
comment erase_vram_script_fill_down Unused: command 0x9B erased: a count byte, the word skipped; cell count times down a column, 0x40 bytes apart.
comment erase_vram_script_fill_down_wide Unused: command 0x9E erased: cell count times down a column of a 64-cell map, 0x80 bytes apart.
comment erase_vram_script_fill_up Unused: command 0xA1 erased: cell count times up a column, 0x40 bytes apart.
comment erase_vram_script_repeat Unused: command 0xA4 erased: cell count times to the same cell; hands back script past the command.
comment erase_vram_script_words Unused: commands 0x92 and 0x95 erased: a count byte, the count words skipped (0x95's dictionary is left alone); cell count times.
comment fill_halfwords Writes value into count halfwords from dest; hands back dest past them.
comment init_video_registers Sets up the video chip: vblank_start_hook = apply_video_registers; from video_control, 0x00-0x0F from video_register_defaults, 0x10-0x4F cleared, the zoom steps 0x50-0x5B 0x200, 0x5C-0x5F from the defaults, five clip rectangles 0,0-0x13F,0xDF, 0x88-0x8F from the defaults; then set_screen_mode(0) and clear_page_0_and_scroll.
comment init_video_registers_layers_off Unused: init_video_registers, then the four tile layers off (video_layer_disable | 0xF) and empty_vblank_hook as vblank_start_hook, so the register shadows would never reach the chip.
comment load_default_palettes Empties the VBLANK copies (vblank_copies_pending and vblank_copy_count 0) and loads default_palette_list into palette RAM (load_palette_list); hands back where it stopped writing.
comment load_palette_list Loads palette RAM (0x600000) from a list: a count, then entries of a source, a palette RAM offset and a copy mode, each source running to the next entry's: 0 copy_palettes_with_pen_0, 2 copy_palette_block, 4 repeat_palette_block (6 would be copy_palettes_with_pen_0 with pen 0 = 6; no list has it).
comment queue_vblank_copy Adds a copy to vblank_copies for the next VBLANK: source, dest (its top byte is how many times it is copied) and halfwords; vblank_copies_pending is cleared while the entry is written, then set.
comment repeat_palette_block Writes the 16-color blocks of the palette the list's next long points at, each as many times as its count, the counts being the size / 2 halfwords at source; moves palette_list past that long.
comment run_vram_script Runs a VRAM script from script, writing tilemap cells at dest: a byte 0x00-0x7F writes that word of vram_script_dictionary; 0x80-0xA7 are the commands of the vram_script_ functions (address, skips, fills, counts, raw bytes and words, page clears, columns, repeats); 0xAA ends. Hands back script and dest after it.
comment set_screen_mode Sets the screen: mode bit 0 wide (416 pixels: video_control bit 15, sprite_wide_screen); bit 1, flipped once more by setting_monitor_flip, flips the screen (video_control bit 9, sprite_flip 3); screen_mode keeps the result. Hands back video_control's address.
comment vram_script_bytes Command 0x8F: a count byte and a high byte, then count low bytes, each written as a cell with that high byte.
comment vram_script_clear_pages Command 0x98: an address halfword (dest = 0x300000 + it * 2) and a page count byte: clears count * 0x200 halfwords from there (fill_value 0).
comment vram_script_count_down Command 0x8C: a count byte and a word: count cells of word, word - 1, word - 2 and so on.
comment vram_script_count_up Command 0x89: a count byte and a word: count cells of word, word + 1, word + 2 and so on.
comment vram_script_end Command 0xAA's routine, which ends a script; run_vram_script returns by itself on it, so nothing calls this empty function.
comment vram_script_fill Command 0x86: a count byte and a word: the word in count cells.
comment vram_script_fill_down Command 0x9B: a count byte and a word: the word in count cells down a column, 0x40 bytes apart.
comment vram_script_fill_down_wide Command 0x9E: a count byte and a word: the word in count cells down a column of a 64-cell map, 0x80 bytes apart.
comment vram_script_fill_up Command 0xA1: a count byte and a word: the word in count cells up a column, 0x40 bytes apart.
comment vram_script_repeat Command 0xA4: a count byte and a word: writes the word count times to the same cell; dest does not move.
comment vram_script_set_address Command 0x80: dest = 0x300000 (video RAM) + the next halfword * 2.
comment vram_script_skip Command 0x83: dest moves by a signed byte of cells.
comment vram_script_skip_long Command 0xA7: dest moves by a signed halfword of cells.
comment vram_script_words Command 0x92: a count byte, then count words, one per cell.
comment vram_script_words_to_dictionary Command 0x95: as 0x92, each word also stored in vram_script_dictionary at dictionary_cursor, which wraps within 0x100 bytes.
comment animate_sign Cycles the current object's palette: every palette_delay frames palette_index steps on through the list at animate_sign.palette_list (a word with the last index, then the palettes), back to 0 after the last, and sprite_palette takes that entry.
comment build_wall_parts Sets the wall's box and palette from its wall_kinds record (a negative size counts per part: times the part count in speed_x_fraction) and chains its parts from pool 2: per step one with the part sprite and x/y step, and a second (palette 0x9FFE) when the kind has a second sprite. A negative part sprite makes the wall invisible.
comment chaser_back_zone_check_parent The back zone of a stage 8 boulder: kind 0x14; removed once its parent's burning_boulder.state is 3 (the boulder falling away), else chaser_back_zone_update.
comment chaser_back_zone_update Removed once scroll_position reaches end_position; until then it takes its parent's box, position, height and direction, turns the box into one 0x200 long right behind the parent (against its direction, direction >> 8) and joins the zone list.
comment check_wall_left_screen Follows the wall's two ends in speed_y: bit 0 once its start (screen position less the sprite offsets) is on screen, bit 1 once it has left again, bits 2 and 3 the same for its far corner (at the screen offset spawn_walls keeps in speed_z). Hands back 0xFFFFFFFF once both ends have come and gone, else 0.
comment crawler_head_update The crawler's head: moves, falls to its floor and is removed off screen. Once a player's x comes within 0x28 of it (0x35 for variant 1) it sets crawler_head.triggered, which starts the crawl. Touchable, a rider, drawn with its shadow (over a pit with on_platform: at its platform's draw_depth - 0xF, no shadow); then crawler_step.
comment crawler_init Object type 21, which no stage spawns: eight 2-part pieces stepped from the object by crawler_segment_offsets (kind 4, box 0x29 x 0x2F, crawler_segment_animate, staggered step timers and walk steps); the last becomes the tail (kind 0xD, crawler_segment_update), the object the head (kind 0xD, crawler_head_update). All have shadows.
comment crawler_segment_animate Every 4 frames the next of the variant's six walking frames (crawler_walk_steps into crawler_segment_frames, walk_step back to 0 after 5), then crawler_segment_update.
comment crawler_segment_update A segment or the tail: moves, touchable, a rider, falls to its floor (removed off screen while the head is over a pit) and is drawn with its shadow, variant 1's tail sorted 8 lower. Over a pit, while the head is not, it stops falling and is not drawn; with on_platform it sorts at its platform's draw_depth - 0xF. Then crawler_step.
comment crawler_step Once the head has crawler_head.triggered set: every 5 frames (step_timer) flips moving; while moving the piece goes at speed 0x200 along the direction in spawn_arg_2, otherwise it stands still.
comment floor_sign_start Unused: a stage list object made a sign on the floor: sprite floor_sign_sprites[sprite_frame], palette 0x8450, sprite_flags 0x1004, z at its floor (find_floor); projected, then floor_sign_update.
comment floor_sign_update Unused: drawn (update_screen_position, queue_sprite) until check_left_screen says it has left the screen, then removed.
comment get_pacing_axis From the travel direction of the floor block under the object of player (a player block): hands back in axis_field the word index of the object coordinate it runs along (0x29 y_int, 0x27 x_int) and in decreasing 0xFFFF when it runs toward smaller values (0 and 4 along y, 3 along x), else 0.
comment group_leader_hold_start Stage list object: loads group_leader_hold_records[sprite_frame] (leader number, direction, coordinate, end scroll position, which_flag) and sets group_leader_0_held, or group_leaders_1_3_held when which_flag is set, so the leaders spawned meanwhile wait; then group_leader_hold_wait.
comment group_leader_hold_wait Clears its held flag and is removed once scroll_position reaches end_position, or once group_leaders[leader] reaches coordinate along direction (0 and 4: y down to it, 1: x up, 2: y up, 3: x down).
comment init_sign_from_kind Sets up the object sign from the object_kinds record at record (just past its z): sprite_frame, how long it shows (speed_x_fraction), its palette cycle (an offset into sign_palette_lists, a step every 4 frames) and handler; sprite_flags 0x1000, parent the current object. Then projects it and hands back its depth.
comment object_type_4_idle Object type 4's handler (5 blocks): does nothing. No stage list uses type 4.
comment place_screen_sign Unused: sets up sign at screen_x, screen_y from screen_sign_kinds[kind]: sprite, how long it shows (speed_x_fraction) and palette cycle from sign_palette_lists (a step every 4 frames); sprite_flags 0x1000, handler screen_sign_show. It is never projected: the position stays a screen position.
comment place_sign Puts the object sign at x, y, z and sets it up from record kind of object_kinds, without the record's own z (init_sign_from_kind); hands back sign and its depth.
comment player_pacer_start Stage list object (stages 3, 4 and 8): keeps pacer_end_positions[sprite_frame] in speed_x_fraction as its end, then player_pacer_update now and every frame after.
comment player_pacer_update Every frame clears the three players' speed_scale; until scroll_position reaches its end (speed_x_fraction, then removed) it ranks the players in play along get_pacing_axis: with two, the one ahead gets speed_scale 0xD; with three, the first 0xD and the second 0xE.
comment roller_back_zone_update Takes the leader's box, position, height and direction and turns the box into one 0x200 deep behind it and 0x1A2 across, in the zone list; removed once the leader reaches roller_back_zone.limit along its direction (direction >> 8).
comment scenery_sprite_cycle_palette Until map_screen_active is set (then removed): animate_sign, then drawn at depth screen_y + depth_offset like scenery_sprite_draw.
comment scenery_sprite_cycle_palette_start The handler of scenery kind 0xA (stage 0xF): its palette cycles through scenery_sprite_palettes every 6 frames (animate_sign), the sprite 0x10 higher; then scenery_sprite_cycle_palette from the next frame.
comment scenery_sprite_draw Drawn at depth screen_y + depth_offset until map_screen_active is set (the map screen), then removed.
comment scenery_sprite_draw_until_offscreen Drawn at depth screen_y + depth_offset until check_left_screen says it has left the screen, then removed.
comment scenery_sprite_draw_until_you_failed Removed once game_mode_timer reaches 0x3C in game mode 0x20 (YOU FAILED); until then scenery_sprite_draw.
comment scenery_sprite_draw_wide_margin scenery_sprite_draw_until_offscreen with a margin of 0x50 (check_left_screen_margin).
comment scenery_sprite_start Stage list object, one of the stages' static scenery sprites: loads scenery_sprite_kinds[sprite_frame] (sprite flags, sprite, palette, depth_offset, handler) and stands on its floor (kind 0xD at z_int 0x8D instead); projected.
comment screen_sign_show Unused: sign_show without update_screen_position: animate_sign, counts speed_x_fraction down and is drawn on top (bucket 0); removed when it runs out.
comment screen_sign_start Unused: a stage list object made a screen sign: its x_int/y_int become the screen position and sprite_frame the kind, then place_screen_sign on itself.
comment sign_follow_parent animate_sign, then drawn on top 0x38 above the floor point under the parent, at the parent's screen x, while the parent's flags have bit 0x40; removed once it is clear.
comment sign_follow_player animate_sign, then drawn on top 0x28 above the floor point under the parent, at the parent's screen x, while the parent's player is in state 0x22 (in a zone); removed once it is not.
comment sign_show Each frame: update_screen_position, animate_sign, counts speed_x_fraction down and is drawn on top (bucket 0); removed when it runs out.
comment sign_show_unless_mode_32 sign_show, except in game mode 0x20 (YOU FAILED), where the sign is removed.
comment sign_show_unless_stage_0 sign_show, except in stage 0, where the sign is removed.
comment sign_show_until_cage_bar_hit The arrow over "BREAK!!" in stage 5: sign_show_unless_stage_0 until cage_bar_hit is set, then removed.
comment sign_show_until_gate_closed sign_show until sign_stop_flag is set, then removed (only the unused exit gate sets the flag).
comment sign_show_until_goal_unlocked The "BREAK!!" sign: sign_show until goal_unlocked is set, then removed.
comment sign_show_while_parent_still sign_show while the parent's speed_z_int is 0; removed once it is not.
comment sign_show_while_player_busy sign_show while the parent's player is stuck or trapped (state 0x1D, 0x2C or 0x32); removed once it is not.
comment sign_start Stage list object made a sign: its z from record sprite_frame of object_kinds, the rest of the record set up on itself by init_sign_from_kind.
comment sign_start_from_type Object type 13: unprojects itself, then place_sign makes it sign kind spawn_arg_1 at its own x, y, z, shown for spawn_arg_2 frames (stage 4's "BREAK!!", stage 9's "CATCH!!").
comment spawn_chaser_back_zone Gives the current chaser its back zone: a new object of kind 9 (flags 0x40000002) whose end_position and handler come from object_steps record zone_index, parent the chaser. Hands back the zone (zone unchanged when no object is free).
comment spawn_roller_back_zone spiked_roller_start's back zone: a new object of kind 0x18 (flags 0x40000002, handler roller_back_zone_update) whose limit is the word of object_steps record zone_index, parent the leader (object_cursor).
comment spawn_screen_sign Unused: takes a free object (allocate_object_outside_loop) and makes it a screen sign of kind at screen_x, screen_y (place_screen_sign); nothing when none is free.
comment spawn_screen_sign_2 Unused: spawn_screen_sign with allocate_object.
comment spawn_sign Takes a free object (allocate_object_outside_loop) and makes it sign kind of object_kinds at x, y, z (place_sign); hands back the sign and its depth. Nothing when no object is free.
comment spawn_sign_2 spawn_sign with allocate_object; hands back the sign.
comment spawn_walls Once scroll_position reaches the stage's next wall_spawns record (wall_spawn_cursor; records 0x100 or more behind are skipped) spawns that wall: position, end_position, kind, part count, handler wall_update, parts from build_wall_parts and its far corner's screen offset in speed_z. Short of pool 2 blocks it tries again next call.
comment wall_editor_draw_arrow Unused: draws one more arrow (sprite 0x6388, palette 0x8230, sprite_flags 0x1004) in the next 0x80-byte slot after wall_editor_arrow_count, up to 7 (nothing resets the count): at marker_x, marker_y on the floor there, on object_cursor's platform when it rides one, projected and drawn on top.
comment wall_editor_draw_arrow_at_z Unused: wall_editor_draw_arrow with z = marker_z instead of the floor's.
comment wall_editor_draw_pattern_number Unused: prints the wall's kind (its wall_kinds offset in sprite_frame / 0x14) with draw_hex_2 at text column 12, row 18, after the wall editor menu's "Pattern No.".
comment wall_editor_rebuild_parts Unused: frees the wall's chain of pool 2 parts and builds it again with build_wall_parts.
comment wall_editor_set_side_label Unused: copies 12 characters into wall_editor_side_text: wall_editor_far_label ("Oku", far) when the wall's depth_from_next is set, else wall_editor_near_label ("Temae", near).
comment wall_update spawn_walls' handler: until scroll_position reaches end_position (or, with end_position 0, until check_wall_left_screen says both ends have gone) wall_update_list_and_draw; then, or as soon as map_screen_active is set, frees its pool 2 parts and is removed.
comment wall_update_list_and_draw Joins the wall list and, unless invisible, draws its parts as a strip from its screen position, each a step on from the last (those off screen skipped): each sorted at its own screen y + 0x10, or at the next part's - 0x10 with depth_from_next, and with a second sprite its second part under everything (bucket 0xFF).
comment wall_update_spare_return Unused: does nothing; a second return right after wall_update_list_and_draw's own.
comment bubble_near_parent_update A lava column turned bubble (lava_column_become_bubble): bubble_pop at draw_depth 0xFFF0 (under everything) while its y_int is greater than its parent's y_int + 0xB0; removed once it is not.
comment bubble_pop Removed when map_screen_active is set or it has left the screen; otherwise animated and drawn at its draw_depth. Each time its animation wraps (a pop) it takes a new animation speed 1-8: variant 6 at random; the others first move to base_x and base_y plus one random offset (-0xF to 0xF) on both, the speed from the new x_int's low bits.
comment bubble_start Stage list object (stages 5, 8, 0xB, 0xC): a bubble that swells and pops (bubble_animation, speed 4), z from bubble_heights[sprite_frame] (its variant), base_x/base_y its position, palette 0x9000 (0xD000 in stage 8, 0x10B0 outside stages 5 and 8); projected, then bubble_update.
comment bubble_update In stages 5, 8, 0xB and 0xC outside game mode 0x20 (YOU FAILED): draw_depth = screen_y, then bubble_pop; anywhere else removed.
comment cage_bar_start Projects the bar, then cage_bar_update from this frame on.
comment cage_bar_update A bar of the stage 5 cage, kept at health 5. Once the spiked press has counted 0xC8 frames, a hit by an attacking player bends it (the next frame of cage_bar_animation, cage_bar_hit set, sound 0x21) and the next breaks it: 8 frames later the broken frame 1 pixel right, no longer touchable. Removed outside stage 5 or off screen.
comment cage_start Stage list object of stage 5: once ten objects are free, ten bars 0x18 apart in x from its position (sprite 0x7058, kind 0xC, box 0x20 x 0x10, height 0xE0, cage_bar_animation, handler cage_bar_start), cage_bar_hit cleared; then removes itself.
comment exit_door_show Drawn (queue_sprite) until its screen_y reaches 0x130, then removed.
comment exit_door_start Stage list object of stage 5: sprite 0x7348, palette 0x91A0, sprite_flags 0x1004, z 0; projected, then exit_door_wait_for_switch.
comment exit_door_wait_for_switch exit_door_show until stage_05_switch_step is 2; then it becomes sprite 0x71A0, palette 0x9140, moved to x 0x1E2, y 0x154C and projected, with exit_door_show from then on.
comment spiked_press_start Stage list object of stage 5: the spiked press (sprite 0x7070), kind 9, box 0xE0 x 0x20, height 0xE0, speed_y 0x2000, on its floor; it becomes spiked_press_object, stops the scroll (scroll_mode 1), freezes the players and spawns Eggman's booth (spawn_eggman_booth); then spiked_press_update.
comment spiked_press_update Counts frames in game modes 3 and 0xA: at 0x14 a message box and at 0x50 a voice by caged_character, at 0xC8 the "BREAK!!" sign and arrow, players unfrozen, at 0x168 sign 3. From 0xC8 it creeps along y (sound 0xD every 0x3C frames), pausing 0x100 frames when no one is in play or one drops in, to y 0x3D0: scroll speed 5.
comment stage_05_floor_patch_show Drawn (queue_sprite) while stage is 5, else removed.
comment stage_05_floor_patch_start Stage list object of stage 5: a patch of stone floor tiles (sprite 0x7050, palette 0xB0E0, sprite_flags 0x1008), z 0; projected, then stage_05_floor_patch_show.
comment cactus_init Object type 19: a cactus (sprite 0x77D8, palette 0x9070), flipped (sprite_flags 0x40) when spawn_arg_1 is set, with a small shadow part (sprite 0x5D80, scale 0x60); then cactus_update.
comment cactus_update Rests on its floor until the tornado (tornado_object) comes within 0x60; then, as sprite 0x77E0, it spirals up (angle - 0x10, spiral_speed + 0x18 and speed_z 0xC00 more upward each frame) until removed off screen. Drawn with its shadow. The z_int - 3 after fall_to_floor is never reached: its distance test has already failed.
comment centipede_chase The centipede's head: until scroll_position passes spawn_arg_1, centipede_command 0 and centipede_move (touchable and aiming with face_nearest_player only while not falling). With no active players it holds its last path point and centipede_command 1 holds the body. Then centipede_leave. Its anchor_y stores are never read.
comment centipede_follow_path Puts the piece at centipede_path[path_index] (projected_x and the whole of projected_y), then path_index steps on, back to 0 after 0x32. Hands back the address after that entry.
comment centipede_hop The hop of a centipede piece: while hop_timer runs it counts down with speed_z 0x600 more upward each frame; at 0 the piece falls (fall_to_floor with 0x4000 of its gravity taken back) and, once z (all 32 bits) is 8 or more, hop_timer restarts at 0x5A with speed_z 0. Hands back fall_to_floor's height and z.
comment centipede_init Object type 20: the centipede's head (sprite 0x7940, kind 0x24, height 0x32, centipede_chase) and ten 2-part pieces at its position that wait 5, 0xA ... 0x32 frames (path_index) before they follow: nine segments (0x7980, centipede_segment_wait) and the tail (0x7A08, centipede_tail_wait, segment_ahead the last segment). All have shadows.
comment centipede_leave The head once scroll_position has passed spawn_arg_1: heads along spawn_arg_2 with centipede_move at the speed of a player 0x1F away (nearest_player_distance forced); centipede_command 0, and 9 once it is removed off screen, which removes the body.
comment centipede_move The head's step: sprite centipede_head_frames[(direction / 0x92 + 1) & 7]; speed ((nearest_player_distance x 2) & 0x3FF) + 0x100, twice that along direction plus once along wobble_angle (- 0x10 a frame), so it weaves. Unless centipede_command is 1 it writes its projected x/y to centipede_path. Then hop, sand puff, draw.
comment centipede_segment_follow A body segment: removed with its shadow when centipede_command is 9; otherwise takes the next centipede_path point (centipede_follow_path) unless centipede_command is 1, then hops (a sand puff on landing) and is drawn like the head.
comment centipede_segment_wait A body segment's start: counts its delay in path_index down while hopping in place, then centipede_segment_follow.
comment centipede_tail_follow The tail: centipede_segment_follow, and while following it turns toward segment_ahead (direction_to_object) for its sprite, centipede_tail_frames[(direction / 0x92 + 5) & 7].
comment centipede_tail_wait The tail's start: counts its delay in path_index (0x32 frames) down while hopping in place, then centipede_tail_follow.
comment draw_centipede_piece Draws a centipede piece sorted by the floor point under it (queue_sprite_at_floor), puts it in the rider list, and draws its shadow part at its floor at depth screen_y - 0xA.
comment quicksand_start Stage list object of stage 0xA: quicksand of kind 0x1C (it sinks the player), touch size 0x60, height 0x50, 0x30 below its floor, sprite 0x74E0 cycling quicksand_palettes, quicksand_animation; with two pool 2 blocks free, a multi-part copy of quicksand_parts over it. A non-zero sprite_frame sets flag 1: never drawn.
comment quicksand_swirl Touchable while within 0x70 of the screen (then removed with its two part blocks); unless flag 1 is set it cycles its palette (animate_sign) and every animation_speed frames puts the next quicksand_animation frame in every part of its multi-part sprite, both drawn at depth screen_y - 0x58.
comment rimmed_sinkhole_edge_zone_update The hole's edge zone: copies the hole's sprite_scale and draw_depth and stays touchable (kind 0x1D) until flag 0x400 is set, then removed.
comment rimmed_sinkhole_open Like sinkhole_open, growing to scale 0x100 with its rim; touchable, drawn at draw_depth = screen_y - 0x30 (no less than -0xF) with the rim 8 behind it. Off screen (margins 0x58, 0x20) it frees the rim, sets flag 0x400 on its edge zone (which then goes) and is removed.
comment rimmed_sinkhole_start Stage list object of stage 0xA: stays as the hole's edge zone (kind 0x1D, size 0x70, height 0x40, rimmed_sinkhole_edge_zone_update) and spawns the hole: like sinkhole_start (sinkhole_growth_records, kind 0x1B, sprite 0x7518) with a pool 2 rim part (sprite 0x7538), handler rimmed_sinkhole_open.
comment sand_arch_ball_bob bob_phase + 0xC (& 0x3FF) and speed_z = -sine_table[bob_phase] x 0xC: the ball bobs up and down.
comment sand_arch_ball_delay In the rider list, not drawn: adds 2 to bob_phase until it passes 0x3FF (so the balls start one after another), then sand_arch_ball_move with the phase wrapped.
comment sand_arch_ball_gone Does nothing: the handler of a ball that has fallen out of the stage, until its leader removes the row; the other ball code also ends in it.
comment sand_arch_ball_init Moves the ball once by 0x200 x row_position along its direction (each ball 0xC pixels further than the one before), then sand_arch_ball_delay.
comment sand_arch_ball_move Moves, touchable, a rider. Over a floor it bobs (sand_arch_ball_bob): hidden from 0x10 below the surface, its sprite drawn only more than 8 above it, its part showing sand spray at the surface while within 0x10 of it, else its shadow. Over a pit it falls, and at z_int 0x800 counts in the leader's balls_gone and stops.
comment sand_arch_init Object type 41: a row of sand balls, direction row_direction x 0x40; ball_count 2-part balls at its position (sprite 0xA1C0, palette 0x9200, box 0x10, height 0x10, kind 0x24, with shadows), their row_position 6, 0xC, 0x12 ... and bob_phase spread by spawn_arg_2 / (ball_count + 1); then sand_arch_update.
comment sand_arch_update The row's leader: its own sprite is never drawn, only its shadow part at its floor under everything; removed with its balls once balls_gone reaches ball_count. The is_on_screen toggle it keeps in row_position is never read.
comment sand_block_leader_move The sand blocks' leader, an invisible chaser on its group_leader_data script: on its first frame and while all is well sand_block_leader_read_script; when no player is in play, one has a mishap or drops in, it starts holding back (base speed 0, screen_threshold -0x20, or 0x160 in the odd directions) with sand_block_leader_move_step.
comment sand_block_leader_move_step The leader's step: chaser_speed is base_speed on its side of screen_threshold, else the camera's ground speed x catch_up_factor >> 8 (at least base_speed); a hold ends after 0x300 of scroll or 0x100 frames with players in play. After the wait it moves along its direction, centred on its floor block; y_int 0xA2B or less clears sand_block_leader_active.
comment sand_block_leader_read_script While wait_timer runs it only moves; then, once scroll_position reaches the next record's position, it loads screen_threshold, catch_up_factor, base_speed and wait_timer from it (a negative factor word ends the script: removed). Then sand_block_leader_move_step.
comment sand_block_leader_wait Waits until scroll_position reaches 0x5100 or group_leader_0_held is cleared, then sand_block_leader_move from the next frame.
comment sand_block_update A sand block, a platform eaten by its leader: once the leader's y is inside it (y + hitbox_size_y - 0x10), objects allowing, out come its cut face (sand_face_update) with sand streams, two clip entries and sound 0x11; then hitbox_size_y shrinks to the leader's y - y, the clip line at the leader, and at 0 the face falls (flag 0x400) and the block goes.
comment sand_face_update The moving cut face of a sand block (a 2-part sprite): follows the leader's y and z, animated, at depth screen_y - 0x70. With flag 0x400 it falls (speed_z + 0x6000 a frame, sand_face_fall_animation) until screen y 0x150, then frees its part list, sets flag 0x400 on its streams and is removed.
comment sand_patch_draw Drawn at depth screen_y - 0x38 until it leaves the screen (check_left_screen_margins: 0x38 once seen, 0x10 before), then removed.
comment sand_patch_start Stage list object of stage 0xA: a triangle of sand (sprite 0x7530, palette 0x9020), flipped when bit 15 of sprite_frame is set, on its floor; then sand_patch_draw.
comment sand_puff_animate Removed over a pit (floor_z_int 0x800 or more) or after its lifetime frames; meanwhile cycles the three sand_puff_frames every frame, drawn and in the rider list.
comment sand_puff_start unproject_object and the rider list, then sand_puff_animate from the next frame.
comment sand_rock_crumble Every second frame shows the next of the 0xE sand_rock_crumble_frames on the rock and the one after it on its part, still drawn; past the last the rock and its part are deleted.
comment sand_rock_draw Drawn at depth screen_y + 0x14; touchable while crumble_frame is 0.
comment sand_rock_draw_piece Unused: puts the rock's part at the rock's screen position and draws it at depth screen_y + 0x1F.
comment sand_rock_init Object type 24: a sandstone boulder (sprite 0x7BA8, palette 0x9010), box 0x22 x 0x21, height 0x20, kind 5, its part in the same palette; then sand_rock_update.
comment sand_rock_update Falls to its floor; removed off screen. The first touch (flag 4) starts it crumbling (sand_rock_crumble), dropping its ring_count rings at angle spawn_arg_2 first when it has any; until then sand_rock_draw.
comment sand_streams_update Falling sand under a sand block's face: removed with flag 0x400; else at the face's screen position (less its sprite offsets), every part of its multi-part sprite showing sand_stream_frames[the face's animation_frame], at the face's depth.
comment shaking_block_animate Every 3 frames the next of its three shaking_block_frames (by spawn_arg_1); removed off screen. With spawn_arg_1 0 it is drawn under everything (bucket 0xFF) with its shadow part 0x10 left and 7 down from its floor point; with 1 on top of everything (bucket 0), its part at -0x28, +0x20.
comment shaking_block_init Object type 23: palette 0x9050, a box from shaking_block_boxes[spawn_arg_1] it never uses, and one more part: sprite 0x7AA0 with spawn_arg_1 set, else its shadow strip 0x7570 (palette 0x1060); then shaking_block_animate (tried again next frame without a free part).
comment sink_swirl_animate Animates the swirl's streak and, at the same screen position, its sand puff part, both at depth screen_y + depth_offset; once the streak's animation reaches its last step it frees the puff and is removed.
comment sink_swirl_start project_object, then sink_swirl_animate from this frame on.
comment sinkhole_open Removed off screen (margin 0x68). From open_position on: sound 0xF on its first step, then sprite_scale + zoom_step a frame up to 0x180 with its sprite offsets and hitbox_size_x scaled to match, animated; touchable and drawn (draw_depth = screen_y) from then on.
comment sinkhole_start Stage list object of stage 0xA: a sinkhole that opens once scroll_position reaches open_position, its delay past now; zoom_step and delay from sinkhole_growth_records[sprite_frame & 0x7FFF], bit 15 flips it; scale 1, kind 0x1B, sinkhole_animation, on its floor; then sinkhole_open.
comment spawn_centipede_sand_puff On the frame a centipede piece lands: a 1-block sand puff at its position, 6 lower (projected_y + 6): sprite 0x75A8, palette 0x1020, scale 0x180, lifetime 0x2D frames, handler sand_puff_start.
comment spawn_sand_block_leader spawn_object_groups' leader of the stage 0xA sand blocks: a new group_leaders entry from the leader_spawn record at record (x, y, z, direction, group_leader_data script), handler sand_block_leader_move (sand_block_leader_wait while group_leader_0_held is set); sets sand_block_leader_active. spawned is 0xFFFFFFFF, or 0 with no object free.
comment spawn_sink_swirl When a player starts sinking in quicksand, within 8 of its centre (check_player_contacts), with 4 objects free: two swirls at the player, 8 lower, from sink_swirl_effects: a streak (palette 0x1210, its depth offset, handler sink_swirl_start) with a sand puff part (palette 0x1230) each.
comment spawn_tornado_hazard An invisible box the tornado leaves at x, y, z: kind 0xC, height 0x50, 0xF0 by 0x80 turned with the tornado's direction, handler tornado_hazard_update; nothing when no object is free.
comment spawn_tornado_rear_zone The rear zone of tornado kind 1: a new object of kind 9 (flags 0x40000002, height 0x160) with handler tornado_rear_zone_follow; hands back zone (unchanged when none is free).
comment spawn_whirlwind Called by tornado_throw_whirlwinds: a 1-block whirlwind at the tornado's position (palette 0x90F0, box 8, height 0x28, kind 0x1F), its spin_step the shared whirlwind_spin_step + 0xB (mod 0x40); handler whirlwind_aim. The self_copied_byte line copies a byte onto itself.
comment stepped_wall_draw Unprojects; unless removed off screen, drawn under everything (bucket 0xFF) and touchable.
comment stepped_wall_init Object type 44: a long green wall of five stepped panels (sprite 0xB670, palette 0x8D60 | 0x2000, sprite_flags 6), box 0x1D x 0xDC, height 0x80, kind 0xC; then stepped_wall_draw.
comment tornado_catch_up_speed The tornado's catching-up speed: camera_slope_speed (with floor_slope_factors' factor where the floor slopes or has a type) x catch_up_factor >> 8, at least base_speed; then tornado_move_at_speed.
comment tornado_choose_speed base_speed while screen_x is at or below screen_threshold (directions 0 and 3) or at or above it (1, 2 and 4); otherwise, or while screen_y is negative, it ends any hold (threshold and base speed back) and catches up with tornado_catch_up_speed.
comment tornado_column_circle One of the tornado's five twisters: removed once it has flag 0x400; circles the tornado at its rotation angle (the tornado's spawn_arg_2) plus its own offset (speed_z_int), the radius (speed_z_fraction) swinging between 0x1000 and 0x6800 at a random rate (hitbox_size_y); animated on the floor, its top part 0xE0 higher when it has one.
comment tornado_hazard_update Touchable (and never drawn) while within 0xF0 of the screen, else removed.
comment tornado_load_record Loads the script record at path_cursor: screen_threshold, catch_up_factor, base_speed, wait_timer, direction (box 0xF0 across, 0x80 along); direction 3 with no wait unfreezes the players, the record ending at tornado_kind_0_end_record frees group leader 0. A negative second word ends it: removed with its columns, goal_unlocked if the third is too.
comment tornado_move The tornado, a chaser: from its second frame it holds back whenever no player is in play, one has a mishap or drops in. After its wait (not counted in game mode 0x11) it loads the next script record once scroll_position or its x/y along its direction reaches the record's; then tornado_move_step. Removed with its columns on map_screen_active.
comment tornado_move_at_speed chaser_speed = move_speed (x4 once every player has finished); after the wait it moves chaser_speed << 12 along its direction (0 y-, 1 x+, 2 and 4 y+, 3 x-). Past screen x 0x180 or -0x40 (by direction) it is off screen; on screen, outside game mode 7, sound 0x12 every 0x29 frames, at once when it comes back.
comment tornado_move_step The tornado's step: 8 below its floor, rotation_angle - 4, zone list, tornado_throw_whirlwinds, a hazard from the edge of its floor block at each point of its hazard list it passes. A hold ends after 0x300 of scroll, or 0x100 frames past the last mishap, drop-in or empty play; then tornado_choose_speed or tornado_catch_up_speed.
comment tornado_rear_zone_follow Removed once scroll_position reaches 0xE500; until then takes chaser_object's box, position and direction, 0x60 lower (z_int + 0x60), makes the box 0x200 long starting 0x40 behind the tornado's position (against its direction) and joins the zone list.
comment tornado_remove_with_columns Sets flag 0x400 on its five columns (the slots from 0x96, which then delete themselves) and removes the tornado.
comment tornado_start Stage list object: waits while sand_block_leader_active is set (kind 1 only before scroll 0x9C00), then takes x, y, hazard and whirlwind lists and script from tornado_records[sprite_frame], becomes chaser_object and tornado_object, holds group leader 0, adds its back zone (kind 0, also freezing the players) or rear zone and five columns.
comment tornado_throw_whirlwinds Once scroll_position reaches the next position in its whirlwind list (part_list), spawn_whirlwind, and the list steps on.
comment wall_piece_draw Unprojects and is removed off screen; drawn at depth screen_y + 0x14 (at most 0xEE) while its screen_y is 0xE0 or less, lower down at its screen_y.
comment wall_piece_init Object type 22: a piece of green stone wall, sprite wall_piece_frames[spawn_arg_1], palette 0x9080; a box from wall_piece_boxes[spawn_arg_1] and height 0x28 it never uses (wall_piece_draw joins no list); then wall_piece_draw.
comment whirlwind_aim face_nearest_player, then heads for the farthest player (each distance_to_player raised by 1 first) at speed 3 x nearest_player_distance, for 0xF0 frames of whirlwind_move, starting now. When player 3 is the farthest it takes direction_to_player_2.
comment whirlwind_move For whirlwind.timer frames, or until touched: moves along its direction at its speed plus a circle of speed 0x100 (spin_angle + spin_step a frame), cycling the four whirlwind_sprites, touchable; removed off screen. Then whirlwind_vanish.
comment whirlwind_vanish Every other frame shows the next of the eight whirlwind_vanish_sprites, then deletes itself; still drawn and touchable meanwhile.
comment blown_away_effect_update The effect over a blown-away player (player_state_blown_away): at its parent's screen position, removed once that reaches screen y 0x100; cycles steps 0-2 of its animation at depth screen_y + 0x10.
comment player_in_zone_steer Steering in a zone: speed follows the square of the trackball's speed (at most 0x5000000; at once up, 1/32 of the gap a frame down) and stops with no input below speed_int 0x20; state 0, 1, 0x13 or 0x14 by it for player_animate, then state 0x22 again while flags bit 0x8000000 (in a zone) is set.
comment player_state_blown_away State 0x1F, blown away: on its first frame the character's tumble animation, speed_z -0x80000 and speed_x -0x10000, mishap_flag, its voice (sound 0x3A, 0x61 or 0x6E) and a blown_away_effect_update object over it. It flies under gravity to screen y 0x108, then loses 8 health: player_respawn with health left, else state 0x16.
comment player_state_choosing_character State 0x34, choosing a character: on its first frame keeps game_frames in join_time and, in game mode 0x1B, opens its chooser (start_character_chooser). Once the game mode is not 0x1B it starts the stage (handler player_start_stage, is_joining 0), its character becoming intro_character if none is set yet.
comment player_state_continue State 0x23, the continue countdown: on entry (continue_countdown 0x900) out of play (player_status 2, flag 0x400), CONTINUE in its HUD, record_player_game, its character freed. Each frame it shows the credits and continue_seconds; a credit (try_continue) joins again, start below 7 seconds skips one, and past 0 or with joining_closed state 0x2E.
comment player_state_entering_stage State 0x2A, walking in on the stage's entry path (stages 3, 4, 0xA-0x10; others return at once): requests stage_player_entries[stage] with flag 0x4000000 (stage 4 adds sound 0x64, 0x6B or 0x51 by character), clears it and enter_on_respawn once the path ends, moving as previous_state (player_move). Its state 0 store is overwritten at once.
comment player_state_held State 0x15, held by its holder (an object of kind 8 or 0xA): while substate is 0 or the holder's vine_bar.holding is set it hangs at the holder's x, y - 8 and z + 0x20, a rider, drawn at depth 0xFF; when it lets go, state 0x17 (hopping) at speed 0, run at once.
comment player_state_in_zone State 0x22, in a kind 0x16 zone: on entry it stands on its floor facing direction 0x100 with its normal palette and controls, the first time with sign kind 0xB over it (sign_follow_player); then each frame check_player_contacts and, unless that hurt it (state 0xD or 0x20, run at once), player_in_zone_steer.
comment player_state_intro_turn State 0x1E, the game intro: waits until intro_character is its character, then requests path 0x14 once and moves as previous_state (player_move).
comment player_state_opening_scene State 0x1C, the opening scene (game mode 0x10), by scene_step: in steps 1 and 4 the player runs toward the screen from its place in opening_scene_player_positions, growing until scene_timer 0x12C, shaken with the stage 6 quake, its voice at 0x140; in step 3 a still frame on opening_machine_object, scaled with it; hidden in steps 0 and 2.
comment player_state_opening_scene_spare_return Unused: does nothing; a lone return after player_state_opening_scene's last jump.
comment player_state_out_of_game State 0x24, out of the game: on its first frame clears its player_status and stage_rings, erases its name and prints GAME OVER in its HUD. A start with credits (try_join, unless joining_closed) joins again (player_join, rings 0, stages_reached 1); else after 0x78 frames state 0xB. GAME OVER is erased either way.
comment player_state_stage_b_sequence State 0x38, the stage 0xB sequence by ending_step: walks path 0x4D, then stands facing direction 0 (stage_b_path_done); when sliding_door_opened, restarts the scroll and walks path 0x50 (the first one plays sound 0x94 and its voice); once stage_trigger_bits is set and cleared it runs off on path 0x53 (state 0x13) with escape_countdown_running.
comment player_state_stage_exit State 0x21, the stage exit: walks its goal's exit path (stage_goal_exit_paths, stage_exit_started; stages_reached + 1 except after stages 5 and 0xB-0xF), then sets its players_finished entry, no shadow (player flags 8). In stage 4: stands 0x168 frames, runs to (0x1225, 0x1439), jumps within 0xA0 of it (sound 0x16), falls to screen y 0xB8.
comment player_state_stage_scene State 0x18, the stage 0 and 5 scenes by scene_step: 0 moves as usual; 1 and 8 walk scene_path, then its players_finished entry; 2 waits at (0x1C0, 0x154D) for stage_05_switch_step to finish, then path 0xE; 3 stands; 4 path 5; 5-6 hop on stage_00_geyser and rise; 7 shrinks away to the screen centre after a delay by character, sound 0x16, voice.
comment player_state_stuck State 0x1D, stuck in an object of kind 0x11: runs its substate (player_stuck_begin, player_stuck_wait_release, player_stuck_shake, player_stuck_jump_out), then moves with that object (touched_by): its speed_z, and the word at its var_80 added to screen_x; drawn at draw_depth, which follows only while the object is still.
comment player_state_stunned State 0x2F, stunned: unless contacts hurt it (state 0xD), it loses 4 health and shows its stunned frame (Ray animated) for 0x3C frames on its floor, then dies (state 0x16) with no health left, or plays its recovery animation and stands (state 0) facing direction 0x300 with control_lock 4.
comment player_state_tumbling State 0x20, hurt and tumbling: substate 0 costs 4 health (not in stage 8 before scroll 0x4000) with invulnerable_timer 0x258, substate 1 nothing; knocked flying (player_tumbling_start), it lands, bounces twice and lies until it walks off after 0x1E frames, or at once on the trackball or a jump. Out of health: knocked-out animation, then state 0x16.
comment player_state_you_failed State 0x39, game mode 0x20 (YOU FAILED): once you_failed_done is set it starts the continue countdown (state 0x23 with 9 seconds, player_status 0xFF, in_game 0); drawn until game_mode_timer reaches 0x3C.
comment player_stuck_begin Substate 0 of state 0x1D: the character's stuck frame with its normal palette, control_lock and escape_progress 0, a stuck_effect_update object over the player, sign kind 0xD 0x28 above it (sign_show_while_player_busy) and sound 0x23.
comment player_stuck_jump_out Once the escape animation ends, bounces out: the character's bounce record 1 (direction, flip, animation), speed 0x4000000, speed_z -0x34000, state 0xE (bouncing) substate 1 for 0x14 frames.
comment player_stuck_shake Shaking free: escape_progress grows by the trackball's speed_int while it pushes at speed 0x100 or more toward headings 0x180-0x380, back to 0 otherwise; the stuck animation at 0, else one of three struggle animations by progress. At 0x10000 the escape animation, sound 0x2B and substate 3.
comment player_stuck_wait_release Waits until the object it is stuck in (touched_by) has its word at var_80 back to 0, then substate 2: its animation restarted at speed 4 and the character's voice (sound 0x35, 0x31 or 0x3D).
comment player_tumbling_fly The tumble's flight: gravity 0x6000 a frame, moves, and steps back where the floor is 8 or more above it; tumble frames while hurt_contact_delay is above 0x50 or it is in the air (frames 5 and 6 when falling fast). On landing substate 3, frame 7, sound 0x20. Sorted at its floor (the previous one where there is none).
comment player_tumbling_knock_with_toucher_speed Unused: takes twice the toucher's speed_x and speed_y in place of player_tumbling_start's fixed push, then player_tumbling_set_launch_speed.
comment player_tumbling_launch Turns the player to the toucher's direction (0x100 on floor type 0xD) and starts the character's tumble animation for its facing, its normal palette with a hurt flash every 3 frames, its voice (sound 0x3A, 0x61 or 0x6E) and spawn_tumble_effect(effect); then the first frame of player_tumbling_fly.
comment player_tumbling_set_launch_speed speed = 0x4000000 for a knocked-back tumble, then player_tumbling_launch.
comment player_tumbling_start Starts the tumble (substate 2): mishap_flag, hurt_contact_delay 0x78, speed_z -0x80000, player_status 1 when out of health. A toucher of kind 0x17 gives no push (player_tumbling_launch); others knock it 0x40000 along their direction (0x100 on floor type 0xD): 0 y-, 0x100 x+, 0x200 y+, else x-; then player_tumbling_set_launch_speed.
comment shadow_update_spare_return Unused: does nothing; a lone return after the opening scene's shadow code, where shadow_update jumps in game mode 0x10.
comment stuck_effect_update The object over a stuck player: follows propped_board_object down and its shake, drawn at its draw_depth (screen_y - 0x20 while the board is still); removed off screen. Animated while its player struggles (substates 0, 1, 4), sprite 0x1AA8 in substate 2, sprite 0x1AB0 with the animation over once the player bounces out (state 0xE).
comment character_chooser_chosen After the choice: until character_select_timer runs out (then removed with its parts) draws the chosen portrait, the chosen character's name, the player label and the panel; the side portraits are no longer drawn.
comment character_chooser_confirm Takes the first free character from the one shown into the kind of its player object (the parent) and characters_taken; free_character_count and players_choosing_character drop (none left choosing: character_select_timer 0x3C). The portrait goes back in place in the chosen pose; a random voice of the character (chooser_voice_sounds), sound 0x20.
comment character_chooser_idle No turn this frame: a taken character turns the chooser on (character_chooser_turn); otherwise the portrait steps through chooser_idle_steps every 3 frames and is drawn with the side portraits (taken ones dimmed, palette 0x87A0) and the labels.
comment character_chooser_turn A turn of 0x18 frames: the middle portrait slides by turn_direction and shrinks while the side ones slide and scale toward that side; then the next character that way shows (taken side ones dimmed), turning on again while it is taken. Confirms at once with one free character, and keeps character_select_timer at 0x1D or more meanwhile.
comment character_chooser_wait The chooser's handler: confirms (character_chooser_confirm) on its player's button, with one free character left or with character_select_timer below 0x1E. Trackball x adds 8 a frame to right_count or left_count; when their high bytes (taken and cleared each frame) differ it turns that way (character_chooser_turn), else character_chooser_idle.
comment character_select_countdown_update The PLAYER SELECT countdown: counts character_select_timer down, never below 0, its seconds shown as a digit (ranking_letter_sprites 0x20 + n) growing from scale 0x10 to 0x140; the heading's palette bounces through player_select_title_palettes. Removed with the heading once nobody is choosing and the timer is out.
comment count_free_characters free_character_count = how many of the three characters have their characters_taken bit (character_taken_bits) clear.
comment draw_character_chooser_labels Draws the chooser's three label parts after the portraits: the name, refreshed to character_name_sprites[character] when name_changed is set (then cleared), the player label and the panel.
comment join_character_chooser_take_character Ends a join chooser: the first free character from the one shown becomes the kind of the player object (object_cursor) and gets its characters_taken bit; the player's bit in players_in_join_chooser is cleared.
comment join_character_chooser_update A joining player's chooser, while its bit in players_in_join_chooser is set: shows the countdown's seconds on both sides of the portrait; the trackball steps through the free characters (taken ones skipped), and the button, joining_closed or the end of the countdown takes the one shown (join_character_chooser_take_character). Drawn on top.
comment reset_character_select Unused: clears characters_taken, players_in_join_chooser, free_character_count and character_select_timer (the original's overlapping stores clear the timer twice).
comment start_character_chooser Opens a player's chooser in game mode 0x1B (the first one also shows the background picture and the PLAYER SELECT heading with its countdown), character_select_timer 0x258: the first free character from the player's number, the next two smaller at its sides, a name, the player label, a panel. right_count and left_count start at its screen x and y.
comment start_join_character_chooser A joining player's chooser (player_select_character), when a character is free: in the player's block of join_chooser_blocks a small portrait of the first free character from the player object's kind, countdown 0x21C, handler join_character_chooser_update, a name block after it; the player's bit set in players_in_join_chooser.
comment animate_palette_stage_04 Stage 4's palette animation: palette_cycle_0 every 4 frames through the 8 frames of stage_04_cycle_palettes, each copied to 2 rows at palette RAM + 0x8500.
comment animate_palette_stage_09 Stage 9's palette animation: palette_cycle_0 every 3 frames through the 7 frames of stage_09_cycle_palettes, 2 rows at palette RAM + 0x88A0.
comment animate_palette_stage_0a Stage 0xA's palette animation: palette_cycle_0 every 5 frames through the 6 frames of stage_0a_cycle_palettes_0 (1 row at palette RAM + 0x8A40), palette_cycle_1 every 5 frames through the 0xA of stage_0a_cycle_palettes_1 (9 rows at + 0x8A60).
comment animate_palette_stage_0c Stage 0xC's palette animation: palette_cycle_0 every 6 frames through the 8 frames of stage_0c_cycle_palettes, 1 row at palette RAM + 0x8800.
comment animate_palette_stage_0d_0e The palette animation of stages 0xD and 0xE: palette_cycle_0 every 6 frames through the 8 frames of stage_0d_0e_cycle_palettes, 1 row at palette RAM + 0x88C0.
comment animate_palette_stage_0f Stage 0xF's palette animation: palette_cycle_0 every 0xC frames through the 6 frames of stage_0f_cycle_palettes_0 (3 rows at palette RAM + 0x8920), palette_cycle_1 every 6 frames through the 6 of stage_0f_cycle_palettes_1 (1 row at + 0x8900).
comment animate_palette_stage_10 Stage 0x10's palette animation: palette_cycle_0 every 6 frames through the 8 frames of stage_10_cycle_palettes_0 (6 rows at palette RAM + 0x8B40), palette_cycle_1 every 4 frames through the 8 of stage_10_cycle_palettes_1 (1 row at + 0x8940).
comment animate_stage_palette Runs the stage's palette animation: animate_palette_stage_04, _09, _0a, _0c, _0d_0e (stages 0xD and 0xE), _0f or _10; the other stages have none (skip_stage_palette_animation).
comment cycle_palette Steps the palette cycle whose state (delay, frame_count, frame, timer) is at cycle: every delay frames it queues the next 16-colour frame from source (halfwords long, the frames 0x20 bytes apart) to the palette_count rows at dest, 0x20 apart, back to the first after frame_count.
comment cycle_palette_set Steps the palette cycle record at palette_cycle_set (frames, dest, timer, delay, rows, frame, frame_count): every delay frames it queues the next 16-colour frame of frames to each of its rows at dest, 0x20 apart, back to the first after frame_count; hands back the last source.
comment cycle_palette_set_0 cycle_palette_set on the first record (palette_cycle_set = 0x209090), for update_stage_06_background.
comment cycle_palette_set_1 cycle_palette_set on the second record (palette_cycle_set = 0x2090A0), for update_stage_06_background.
comment get_nbg3_block The NBG3 block under its scroll: nbg3_block_column = nbg3_scroll_x >> 9 (blocks of 0x200 pixels), nbg3_block_row = (nbg3_scroll_y + 0x400) >> 8 (blocks of 0x100).
comment init_nbg0_picture_screen Prepares a full-screen picture on NBG0: display off for 3 frames, the tile bank bit 0x400 set, the tile_bank_1_palette_list palettes, only NBG0 and the text layer on (video_layer_disable 0x2E), NBG0 scroll 0 and pages 0. Hands back load_palette_list's dest.
comment init_nbg1_layer Puts NBG1 on pages 0xF-0x12 and clears them (four vram scripts), then sets the stage's mixer blend bits: one setting for stages 9 and 0x13, one for stage 0x10, one for the rest. Hands back dest.
comment init_nbg2_layer Puts NBG2 on pages 0xA-0xD and clears them, then the stage's mixer blend bits; in stages 1 and 2 it also fills the two row scroll tables (video RAM + 0xA800 and + 0xA000, 0x100 entries each) with a sine wave and blends NBG2 (mixer_blend 0xB00), as stage 4 does without the wave.
comment init_nbg3_layer Draws NBG3 for the stage and sets nbg3_block_map_width (stage_nbg3_widths): the default script with set_nbg3_page_5_flags in stages 0-3, 5-7, 0x11 and 0x12, the stages' own scripts in 4, 8 (also 0x13), 0xA and 0xC-0x10, cleared pages 5-8 in 9 and 0xB; then its nbg3_pages, mostly with the scroll at 0.
comment init_nbg3_layer_cleared_pages Unused: a case of init_nbg3_layer no stage uses: clears tile pages 0xA-0xD (as init_nbg2_layer does) and puts NBG3 on them, scroll 0, then set_nbg3_page_5_flags.
comment load_palette_set_and_backdrop Loads stage_index's palettes: video_control's tile bank bit 0x400 from stage_tile_bank_bits, the palette list stage_palette_sets[stage_index] (load_palette_list), backdrop control bit 0x200, and backdrop_color by the current stage, not stage_index: 0x200 in stages 3 and 7, 0x6212 in 9 and 0x13, else 0. Hands back the tile bank bits.
comment load_stage_palette Copies the stage's own palette: stage_palettes[stage] gives a source and a halfword count, copied to palette RAM + 0xB800.
comment load_stage_palette_set load_palette_set_and_backdrop for the current stage; hands back its tile bank bits and dest.
comment select_nbg3_pages Sets nbg3_pages from the stage's stage_nbg3_block_maps: the pages of the 2 x 2 blocks at NBG3's scroll (get_nbg3_block), each placed by nbg3_quadrant_grid at the parity of the block's column and of its row x (nbg3_block_map_width - 1); the scroll is wrapped to 0x3FF, 0x1FF.
comment set_backdrop_color_19aa Unused: backdrop_color = 0x19AA, a case of load_palette_set_and_backdrop's stage switch that no stage uses.
comment set_nbg1_scroll_to_camera NBG1's scroll = the camera's position in whole pixels (camera_x_pixels, camera_y_pixels).
comment set_nbg2_scroll_stage_04 Stage 4: NBG2 on pages 0xA-0xD, scrolled with the camera (the original adds 0x20 to x and takes it off again).
comment set_nbg2_scroll_stages_03_07 Stages 3 and 7: NBG2 on pages 0xA-0xD, scrolled with the camera, 0x10 further in x and y once scroll_position reaches 0x9159; hands back scroll_position.
comment set_nbg2_scroll_to_camera NBG2's scroll = the camera's position in whole pixels (camera_x_pixels, camera_y_pixels).
comment set_nbg2_scroll_to_camera_2 set_nbg2_scroll_to_camera again: a second copy, for stages 1 and 2.
comment set_nbg3_page_5_flags video_layer_opaque_shadow = 0xA, palette_color_4000 = 0xF70, and bit 13 (0x2000) set in the 0x400 cells from video RAM + 0x1400, tile pages 5 and 6.
comment set_nbg3_pages_5_6 Unused: NBG3 on pages 5, 5, 6, 6 (0x06060505) with scroll 0: the end of init_nbg3_layer's stage 4 case again, without its scripts.
comment set_stage_layer_mixing The stage's layer mixing: the four NBG mixer registers from mixer_nbg0 on = stage_layer_priorities[stage] bytes + 0x200, the low four bits of video_layer_disable from stage_layer_disables, video_rowscroll_control from stage_rowscroll_settings.
comment show_nbg0_picture init_nbg0_picture_screen, then NBG0 on page 1 with a repeating pattern of Sonic heads (sonic_head_pattern_script) and its 16 colours (nbg0_picture_palette) queued to palette RAM + 0x8BE0: the background of the ranking and PLAYER SELECT screens.
comment show_recycle_it_screen Unused: init_nbg0_picture_screen, then recycle_it_script on page 0: the "RECYCLE IT, DON'T TRASH IT!" screen with the EPA seal.
comment show_winners_dont_use_drugs_screen Unused: init_nbg0_picture_screen, then winners_dont_use_drugs_script on page 0: the "WINNERS DON'T USE DRUGS" screen with the FBI seal.
comment skip_nbg1_scroll Does nothing: update_nbg1_scroll's case for the stages whose NBG1 stays put, and its common end.
comment skip_nbg2_scroll Does nothing: update_nbg2_scroll's case for the stages whose NBG2 stays put, and its common end.
comment skip_stage_palette_animation Does nothing: animate_stage_palette's case for the stages without a palette animation.
comment update_nbg1_scroll NBG1 follows the camera (set_nbg1_scroll_to_camera) in stages 3, 7, 8, 9, 0xA, 0xB and 0x10; in the other stages it stays put.
comment update_nbg2_scroll Scrolls NBG2 by stage: with the camera in stages 1, 2, 9, 0xA, 0x10 and 0x13, set_nbg2_scroll_stages_03_07 in stages 3 and 7, set_nbg2_scroll_stage_04 in stage 4; elsewhere it stays put. Hands back position, scroll_position after the stage 3 and 7 case.
comment update_nbg3_scroll Per-stage NBG3 parallax: half the view (y + 0x80) with select_nbg3_pages in stages 3, 5, 6, 7 and 0xA, and in stage 0 until scroll_ended (then a quarter); three quarters in stages 0xC and 0xE, a quarter in 0x10, the camera itself in 9; the other stages leave it alone.
comment update_stage_06_background Game mode 0x10's stage 6 background, by stage_06_background_step: 0-2 the cloudy horizon and the forest (palette set 1), drifting sideways; 3-4 the upper and lower jaws drawn and closed over the screen, sound 0x1E, then 7 a shake dying away (stage_06_shake); 5-6 the horizon and the island tower, two palette cycles started at random.
comment bridge_fire_climb_step The bridge fire's chase: speed_fraction is base_speed while its top (screen_y - hitbox_height) is at or above screen_threshold, else camera_ground_speed * catch_up_factor >> 8, at least base_speed; z climbs by it after wait_timer. Sound 9 every 0x3C frames on screen; its top reaching z 0x534 sets stage_trigger_bits, once (player marks it done).
comment bridge_fire_read_script Steps through bridge_fire_script: a record sets screen_threshold, catch_up_factor, base_speed and wait_timer once the fire's top reaches its height or scroll_position its position less hitbox_height (0: height only); a negative factor removes the fire, a negative threshold sets state 7. Then bridge_fire_climb_step.
comment bridge_fire_rise The bridge fire's handler: bridge_fire_read_script, unless holding back. When no_player_in_play, player_mishap or player_dropping_in is set it holds back (base_speed 0, screen_threshold 0xE0, the old ones saved, hold_timer 0xFF): bridge_fire_climb_step only, which ends the hold after 0x300 of scroll or hold_timer.
comment bridge_fire_start Sets up the invisible fire under the hanging bridge: box 0x40 x 0x80, height 0x70, kind 0x13, z 0x868, rising_fire_object, path bridge_fire_script. With 3 free objects it makes three rising flames (rising_flame_update) 0x20, 0x40 and 0x60 along y at its top and becomes bridge_fire_rise; else it tries again next frame.
comment burning_boulder_flame_flicker A flame on a burning boulder: while the boulder rolls (its speed_fraction above 0) it plays its animation at the boulder's screen position + offset_x/offset_y, sorted just above it (its draw_depth + 1); at the end of the animation it waits a random 0-0x3F frames. Removed once it has flag 0x400.
comment burning_boulder_flame_start Sets up a boulder's flame from burning_boulder_flame_layout[kind] (kind is the flame's number, 0-3): offset_x and offset_y from the boulder and its animation (an offset into burning_boulder_flame_animations); then burning_boulder_flame_flicker.
comment burning_boulder_start Sets up a burning boulder from burning_boulder_records[sprite_frame]: x, start_delay and path (0xFFFFFFFF: the follower, 0x120 wide, spawn_chaser_back_zone 2; else lead_burning_boulder); y 0x3E0 - 0x60 * sprite_frame, box 0x90 x 0x90, kind 0x14, 0x70 above its floor; four flames chained through child, then burning_boulder_update.
comment burning_boulder_update State 0 waits start_delay and falls to its floor (shake, sound 7); 1 the leader rolls along y as a chaser (path records, sound 0xD) until it has no floor past scroll 0x3300; 2 the follower rolls at the leader's speed; 3 falls away, flames and follower removed; 4 waits for burning_boulder_released; 5 drops onto the seesaw, seesaw_launched from z 0x9E0.
comment collapsing_bridge_plank_update State 1: a sagging plank, a platform, until event_1c_riding (plank 0: sound 9); 2: it hangs from the previous plank at its angle + bridge_swing_angle (not below 0x200). Plank 0 falls from 0x300 to 0x200 and swings with decay (sound 0x20 a swing); after 6 it stops, clears event_1c_ride_phase and shows sign 0x16. Removed at scroll 0x9000.
comment ember_emitter_move An invisible ember source, removed at end_position. It moves at camera_slope_speed >> 7 along byte 0x15 of its floor block (0 and 4 -y, 1 +x, 2 +y, 3 -x; 0 and 3 while screen_x >= 0x40, the others while <= 0x40), on its floor unless sprite_frame is 3; every random 0-0x3F frames a falling ember 0-0x7F from it.
comment ember_emitter_start Sets up an ember source: end_position from ember_emitter_end_positions[sprite_frame], z on its floor; then ember_emitter_move.
comment extend_lava_flow Adds a trailing sheet of lava behind the object (lava_flow_segment_follow, linked as its child, with its own copy of lava_flow_parts in a second-pool block) from scroll_position 0x6180, while it has no child yet, its screen_x is -0x50 or more and both pools have a free block. block is only scratch.
comment falling_ember_update A falling ember: state 0 raises it 0x70 at a time to above the screen; 1 falls (speed_y, speed_z, falling_ember_animation) to its floor (0x530 where none), then bursts (falling_ember_burst_animation, or the slope one on a sloped or typed floor); 2 plays the burst, then is removed. Removed when it leaves the screen.
comment fire_trench_flame_burn A flame column of a fire trench: plays its animation once, its hitbox_height following fire_trench_flame_heights[animation_frame], touchable; removed at the last step.
comment fire_trench_start Sets up a fire trench (sprite 0xB200, palette 0x51F0) at z 0x530 with flame_timer 0, so the first flames come at once; then fire_trench_update.
comment fire_trench_update Draws the fire trench and every 0x12C frames spawn_fire_trench_flames; removed once it has left the screen (check_left_screen_margin 0x70).
comment fire_wall_rise The fire wall rises until z 0x550 (thrown up at speed_z -0x100000, slowed by 0x6000 a frame), animating its parts, touchable. Removed with its part list at scroll 0x6800.
comment fire_wall_start Sets up the fire wall beside the metal platform: a multi-part sprite (fire_wall_parts in a second-pool block) at (0x100, 0xE47, 0x610), box 0x5B x 0xF0, height 0x100, kind 0x14, fire_wall_animation, speed_z -0x100000; sound 7, then fire_wall_rise (without a free block it tries again next frame).
comment flame_jet_burn The flame of a flame jet: its front sits at the nozzle + speed along angle (speed grows by growth_rate), flickering through flame_jet_flame_sprites; with spawn_arg_1 set it is the touchable part (hitbox_size_y = its screen distance from the nozzle). Once stage_trigger_bits is set its 9 pieces fill the line back to the nozzle.
comment flame_jet_ignite The jet fires: flame_jet_burn while speed grows (growth_rate 0x380) up to 0xF000, then growth_rate 0 and flame_jet_burn as the handler.
comment flame_jet_init Sets up a flame jet: the nozzle (flame_jet_nozzle_sprites[spawn_arg_1], box 0x4C x 6, kind 0x14, direction spawn_arg_2) with a shadow piece, and a flame (0x6910, kind 0x14, angle 0x158, 0x350 with spawn_arg_1, flame_jet_pilot) with 9 pieces scaled 0x168 down by 0x18. The flame's start offset tests the shadow's spawn_arg_1.
comment flame_jet_nozzle_update Draws a flame jet's nozzle, removed off screen, and its shadow piece under everything (bucket 0xFF). With spawn_arg_1 0 the nozzle is the touchable part, its hitbox_size_y 1.5 times the flame front's screen distance; with spawn_arg_1 set it is drawn on top (bucket 0) and the flame carries the touch box.
comment flame_jet_pilot The pilot flame: until stage_trigger_bits is set, speed grows to about 0x666 and shrinks to 0 again (growth_rate 0x80 and -0x80) through flame_jet_burn. Then sprite_scale = spawn_arg_2 (0x180), growth_rate 0x380 and flame_jet_ignite.
comment group_slot_trigger_update Steps through group_slot_triggers (scroll position, slot, value): once scroll_position reaches the entry and group_slots[slot] is taken (not 0) it becomes the value | 0x8000, which makes that row of lava rocks erupt after the delay, and the next entry follows. Slot 0x8000 sets goal_unlocked; any negative slot removes the object.
comment lava_column_become_bubble Turns the landed lava column into a lava bubble: base_x += x_int, base_y = y_int, z 0x540, palette 0xD000, bubble_animation, handler bubble_near_parent_update.
comment lava_column_land Turns the tossed lava column into a lava bubble 0x40 along x from it (base_x 0x40, lava_column_become_bubble).
comment lava_column_land_in_place Unused: lava_column_land with base_x 0, the bubble at the column's own x.
comment lava_column_rise A lava column erupting under a rock: rises (speed_z - 0x6000 a frame) to z 0x570, then stands as lava_columns[side]. Removed off the left of the screen, while map_screen_active is set or when another column has taken its side; animates its parts, touchable.
comment lava_column_start Projects the new lava column, then lava_column_rise.
comment lava_column_toss A lava column thrown up from a slot-triggered rock (speed_z -0xE0000, 0x6000 a frame back down), animating its parts, touchable, sorted by its floor; removed off the left of the screen. Back at z 0x660 it frees its part list and lands (lava_column_land).
comment lava_column_toss_start Projects the tossed lava column, then lava_column_toss.
comment lava_flame_burn A flame on the lava: follows its parent at anchor_x/anchor_y, plays its animation once and is removed.
comment lava_flow_advance The lava flow's handler: lava_flow_read_script, unless holding back. When no_player_in_play, player_mishap or player_dropping_in is set it holds back (base_speed 0, screen_threshold -0x50, the old ones saved, hold_timer 0xFF): lava_flow_move_step only, which ends the hold after 0x300 of scroll or hold_timer.
comment lava_flow_move_step The lava flow's chase along +x: chaser_speed is base_speed while screen_x is at or past screen_threshold, else camera_ground_speed * catch_up_factor >> 8, at least base_speed; x moves by it after wait_timer; spawn_lava_flame, extend_lava_flow. sound_timer is also its off-screen mark, cleared every frame on screen: sound 0x10 plays every frame.
comment lava_flow_read_script Steps through lava_flow_script: the next record (scroll position, x, screen_threshold, catch_up_factor, base_speed, wait_timer) is taken once scroll_position reaches its position or x_int its x (0: position only); a negative factor removes the flow and its part list. Then lava_flow_move_step.
comment lava_flow_segment_follow A sheet of lava trailing the one before it (its parent): 0x40 left and 0x20 down on screen, map x 0x50 less, the parent's frame in both its parts; also spawn_lava_flame and extend_lava_flow. Removed with its part list at scroll 0x7E00, or past screen x -0xA0 (clearing the parent's child).
comment lava_flow_start Sets up the lava flow (lava_flow_parts in a second-pool block): box 0x60 x 0xE7, height 0x50, kind 0x14, z 0x530, lava_flow_animation, path lava_flow_script, wait_timer 0x78; clears metal_platform_lifted and waits in lava_flow_wait (without a free block it tries again next frame).
comment lava_flow_wait Waits for metal_platform_lifted, then wait_timer (0x78) frames; then spawn_lava_steam and lava_flow_advance as the handler.
comment lava_rock_draw_at_previous_floor Draws a lava rock when on screen (screen_x -0xA0 to 0x1E0): a platform, draw_depth the screen y of previous_floor_z + depth_offset.
comment lava_rock_erupt A lava rock erupts: state 3, its break animation from the start, and spawn_lava_column under it (a second 0x71 along x for a multi-part rock), with sound 7 when screen_x is -0x90 or more. With takes_slot 1 only member 0 does that, and member 2 without the sound. block is only scratch.
comment lava_rock_update A rock slab over the lava (a platform, its glow cycling): 1 waits for the group leader within 0x5A along direction, or its group_slots entry; 2 bobs, rises to rise_limit and erupts (lava_rock_erupt) once the leader is past it in y or erupt_delay runs out; 3 flies up breaking, removed above the screen. Removed while map_screen_active is set.
comment lava_steam_puff_update A steam puff on the lava flow: plays lava_steam_animation at its parent's position + anchor_x/anchor_y, waits a random 0-0x3F frames and plays it again. Removed at scroll 0x7E00.
comment metal_platform_base_draw Draws the metal platform's frame until it leaves the screen (check_left_screen_margins 0x40/0x10), then removes it; it stays when the deck flies off.
comment metal_platform_base_start Sets up the metal platform's frame (sprite 0xAFB8, palette 0x1230) at z 0x530, then metal_platform_base_draw.
comment metal_platform_start Sets up the metal platform's deck (sprite 0xAFC0) at z 0x530 with timer 0x12C, and a second object, the far railing (sprite 0xAFC8 at 0x131, 0xF24, state 3); both run metal_platform_update (without a free object it tries again next frame).
comment metal_platform_update State 0 waits timer (0x12C) frames; 1 sets metal_platform_lifted, starts the fire wall (fire_wall_start) and the deck flies up (speed_z 0x100000, 0x6000 more a frame after 0xA frames); 3 the railing waits for the deck, then flies up with it (state 2). From screen y 0x28 up it shows metal_platform.frame; removed at screen y -0x58.
comment ridden_object_fall A player's ride object: in the rider list until it stands on a platform (flags 8), then ridden_object_follow.
comment ridden_object_follow Keeps the ride object at its platform's (a bridge plank's) x and z every frame; removed at scroll 0x8D00.
comment ridden_object_start Sets up a player's ride object (made by spawn_player_ride): box 0x10 x 0x10, it becomes scene_object; then ridden_object_fall.
comment rock_wall_init Sets up a rock wall (sprite 0x7C18, palette 0x87C0): box 0x18 x 0x4D, height 0xB4, kind 0xC, its two sparkle parts in palette 0x8A10; then rock_wall_update.
comment rock_wall_update A rock wall: touched by an attacking player (the toucher's flag 0x40) it crumbles open, a rock_wall_break_frames frame every 3 frames, dropping spawn_arg_1 rings at angle spawn_arg_2 at the fifth. While whole its two parts sparkle and glint, pausing twinkle_delay (0x1E) frames after each glint.
comment scene_carrier_move An invisible carrier: adds its speeds to its position every frame (sprite_frame 1-3 only once seesaw_launched is set); removed at end_position.
comment scene_carrier_start Sets up a carrier from scene_carrier_records[sprite_frame]: position, 32-bit speed_x/speed_y/speed_z and end_position. Number 0 becomes scene_object, 1-3 player_carriers[sprite_frame - 1]; then scene_carrier_move.
comment seesaw_start Sets up the seesaw: a multi-part sprite (seesaw_parts in a second-pool block) at (0x70, 0xF68, 0x9E0) with seesaw_animation; then seesaw_tilt (without a free block it tries again next frame).
comment seesaw_tilt Draws the seesaw; once seesaw_launched is set it plays seesaw_animation once into its part list (the word at +0xA). Frees its part list and is removed at scroll 0x4D00.
comment spawn_fire_trench_flames Spawns three flame columns along the fire trench (y 0xE47, 0xE97, 0xEE7, z 0x530): box 0x40 x 0x80, kind 0x14, fire_trench_flame_animation, fire_trench_flame_burn; sound 7 when the trench's screen_x is 0x140 or less. Nothing without 3 free objects.
comment spawn_group_slot_trigger Spawns the object that fires the lava rocks' group slots: next_trigger = group_slot_triggers + the record's path, handler group_slot_trigger_update; clears group_slots. Hands back spawned 0xFFFFFFFF (0 when no object was free), count 0 and the object in trigger.
comment spawn_lava_column Spawns a lava column 0xE0 below the rock: lava_column_parts, box 0x74 x 0x30, height 0xE0, kind 0x14, lava_column_animation, side 1 from x 0xC97, lava_column_start. For a rock with takes_slot 1 it is tossed instead (lava_column_toss_start, speed_z -0xE0000, box 0x70 x 0x60). Hands back the part list block; block is only scratch.
comment spawn_lava_flame Every random 0-0x1F frames (counted in spawn_arg_2) a flame on the lava: lava_flame_animation or lava_flame_animation_2 at random, at a random anchor from the object (x -0x30 to -0x12F, y 0x10 to 0xD0), lava_flame_burn. Hands back the flame (flame as it came when none is made); random_value is only scratch.
comment spawn_lava_steam Spawns lava_steam_puff_count steam puffs (lava_steam_puff_update) at lava_steam_puff_offsets around the lava flow, while objects are free.
comment treadmill_gate_half_update The gate's other half: takes the first half's z, with its chain-top piece 0x63 above drawn on top (bucket 0); touchable, kept at full health.
comment treadmill_gate_update The gate's first half: falls (fall_to_floor) with the treadmill's speed_z taken off (its lift) and stops 0x28 above its floor, where it sets the treadmill's flags bit 0 (sound 0x1E once); while moving, sounds 0x1E and 0xC. Its chain-top piece 0x63 above is drawn on top; touchable, kept at full health.
comment treadmill_init Sets up the treadmill: the belt (sprite 0x72B0, kind 0x16, box 0x10 x 0xE9; a first 0x61 x 0xE9 box is overwritten at once), the roller (+0x27,+0x18, treadmill_roller_update), the gate's first half (+0x73,+0x31, treadmill_gate_update) and its other half (+0xC3,+0x59, treadmill_gate_half_update), each with a piece.
comment treadmill_roller_update The treadmill's roller: its frame and its piece's (0x10 right, 8 up) from treadmill_roller_sprites and treadmill_roller_part_sprites by the treadmill's belt_phase >> 5; touchable, kept at full health.
comment treadmill_update The treadmill: the summed speed of the players in state 0x22 (treadmill_player_blocks), in whole pixels less spawn_arg_1, picks speed_z from treadmill_lift_forces (the gate's lift) and turns the belt (belt_phase, treadmill_belt_sprites). Drawn under everything (bucket 0xFF); in the zone list until the gate is up (flags bit 0).
comment climb_hand_update A hand left on the climbed object: drawn just over its player (the player's draw_depth + 5) until the player leaves state 0x27 or its step animation starts step 4; then removed.
comment player_effect_start Projects the effect object, then player_effect_update.
comment player_effect_update Plays the effect's animation once, sorted at screen_y + depth_offset, then removes it.
comment player_floor_8_sequence_struggle State 0x25 step 12, caught: the trackball's speed >> 4 adds to escape_progress_low, which drops to 0 when the ball stops or rolls between 0x280 and 0x380; escape frames by it (idle animation at 0). At 0x400 the player jumps out (step 13, speed_z -0x80000, speed_y 0x20000); when state_timer runs out, step 14.
comment player_knocked_off_fly The knocked-off player flies (gravity 0x6000, a quarter on floor type 4), the second hurt frame once falling; below screen y 0x120 it loses 8 health: player_respawn, or at 8 or less dying (state 0x16).
comment player_knocked_off_start Knocked off (state 0x36): 0x258 frames invulnerable, mishap_flag, hurt_contact_delay 0xA, a hop (speed_z -0x80000) and a push of 4 a frame along the toucher's direction (both halved on floor type 4), sound 0x1D and the character's cry, the hurt animation and flash; then player_knocked_off_fly.
comment player_move_with_scroll States 0x25 and 0x26: the player steers as usual (player_steer) on its floor and is carried by the scroll (compute_scroll_drift of its floor block; in 0x26 towards 0x300 once drift_turned_back, nudged 0xA along x when the drift stops); drawn with the drift animations (drift_anim_step: turning to +x or -x and back).
comment player_object_climb_begin Starts a climb along the touched object: at its x and y, z 0x78, no speed, object_climb_steps 1, screen_x its projected x, and the character's climb animation (Ray: object_climb_animation_ray with climb_hand -1, the others climb_hand 0).
comment player_object_climb_move When a climb step's animation ends: back to substate 1, climb_hand flips (Ray alternates object_climb_animation_ray and object_climb_animation_ray_2) and the player moves one hold on: screen_x + 0x12, projected_y - 9, x and y from unproject_point.
comment player_object_climb_push Climbing: the trackball's speed (set_climb_speed_from_trackball) aimed between 0x380 and 0x17F adds to step_accumulator; at 0x400 a step (substate 2, object_climb_steps + 1, spawn_climb_hand, sound 0x64, 0x6B or 0x51 by character) if the next hold is on screen, else the push is lost.
comment player_state_carried_off State 0x31 (stage 8): runs as previous_state while it has a floor; off the edge it falls, pulled toward scene_object, to z 0x9D0 and lands hurt. Step 4 sets burning_boulder_released and stands until seesaw_launched throws it up off the screen; it flies to its player_carriers object and at z 0x530 is trapped (state 0x32).
comment player_state_caught State 0x30: the caught player plays the near-edge fall animation, then cries, pulled toward scene_object in game mode 0x19 (else sinking 0x84000 a frame); at z 0x4C8 it leaves a player effect (depth_offset 0x70) and goes hurt (state 0xD, substate 2) with sound 0x87.
comment player_state_climbing_object State 0x27: climbs hand over hand along the touched object (begin, push, move by substate) until x_int reaches climb_remaining, then stands. With handle_rod_drop_players set and x_int 0x450 or less it lets go and falls (state 6; a cry at 8 health or less). Sorted by the object's parent's spawn_arg_1_low and object_climb_steps.
comment player_state_final_stage State 0x2B: once ending_finished is set the player gets 0xC8 rings and one more stages_reached, leaves the game (in_game 0) and goes to the continue countdown (state 0x23, 9 seconds).
comment player_state_floor_8_sequence State 0x25, by sequence_step: from scene_step 2 the player drops hurt from above with a cry, hops, rides the scroll to y 0x4F2, sinks 0x20 into the floor and drifts on to y 0x9B0, floats to y 0xA1C and is caught: shaking free (step 12) within state_timer (0x258) jumps it out to land walking, else it falls (step 14).
comment player_state_held_still State 0x2D: does nothing.
comment player_state_knocked_off State 0x36: with health left, player_knocked_off_start and then player_knocked_off_fly; with none, dying (state 0x16).
comment player_state_pulled State 0x26: pulled after scene_object (an eighth of the way a frame in x and z) until scene_object's direction is 0x1D0 or less, thrown along +x (speed_x 0xA0000) to the floor, carried with the scroll (player_move_with_scroll, flailing) past x 0xCD0 and back to x 0x630, then it skids (state 0xC) with a cry.
comment player_state_riding State 0x35 (game mode 0x1C): the player hangs from its ride object (ridden_object; its platform plank instead when it started at x 0xA60-0xAE0), 0x20 lower and 0x10 along x, in the character's hanging frame; once that plank's flags bit 0 is set (the bridge has stopped) it starts climbing (start_climb).
comment player_state_scene_thrown State 0x19 (game mode 0x0D), by scene_step: at each new step 0, 1 or 3 the player is lifted 0xA0 + 0x20 * kind and falls hurt (state 0xD, substate 2); at step 2 it is thrown (speed_z -0xE0000, speed_y 0x80000) with a cry. Between steps it stands on its floor, or stays hurt.
comment player_state_sinking State 0x33: sinks into the floor (floor_z and z 0x1D8 on block type 0x22, else 0x20; first moved 0xC toward its edge unless previous_state is 9) with its animation, an effect and sound 0x87; y + 1.5 a frame and after 0x1E frames z + 0xA000, cut at ground_clip_bottom. 0x20 under its floor: -8 health, respawn or dying.
comment player_state_stuck_on_object State 0x2C, stuck on an object, by substate: begin, wait, shake, jump out. Touched in a zone (flags 0x8000000) it goes back through check_player_contacts and player_dispatch_state.
comment player_state_swept_away State 0x28: caught in a swirl, the player circles chaser_object (turning toward it by swirl_turn_rate) while rising ever faster (speed up to 0xA00, speed_z + 0x280 a frame), flailing, with a cry; above the screen (-0x40) it loses 8 health: player_respawn, or dying (state 0x16).
comment player_state_trapped State 0x32: two trap objects (trap_flash_start, trap_start) and sound 0x21, the trapped animation, then a sign (kind 0x14) and the struggle: trackball speed above 0x100 fills escape_progress (else it drains), struggle frames by it; at 4 the player jumps out, bounces once and stands (state 0). Tumbling (state 0x20) leaves at once.
comment player_state_tripped State 0x29: tripped, the player hops (speed_z -0x80000) flailing with a cry, still touching zones and players; when the hop starts falling it goes hurt (state 0xD, substate 2) with the hurt animation.
comment player_stuck_on_object_begin Puts the player on the touched object, 0x20 back along y, in its stuck frame (0x1A40, 0x5180 or 0x35C8 by character), escape_progress 0, state_timer 0x1E, a sign (kind 0xD) above it and sound 0x23. sign is only scratch.
comment player_stuck_on_object_jump_out When the escape animation ends the player bounces off (state 0xE, substate 1) by its character's bounce record 1 (direction, flip, animation): speed 0x2000000, speed_z -0x1A000.
comment player_stuck_on_object_shake Shaking free: the trackball's speed, aimed between 0x180 and 0x380, adds to escape_progress (a speed below 0x100 clears it); struggle frames by escape_progress / 0x4000, the stuck animation at 0. At 0xC000 the escape animation and sound 0x2B (substate 3).
comment player_stuck_on_object_wait Waits state_timer frames, then substate 2 with the animation restarted at speed 4 and the character's voice (0x35, 0x31 or 0x3D).
comment scene_object_start Sets up scene_object at (0x4D7, 0x145B), z 0x78, end_position 0x5C00, direction 0x300; then scene_object_update.
comment scene_object_update Between scroll 0x4FF0 and end_position the object circles in x and z around (0x5D0, z 0x78): direction - 0xF a frame, radius scene_object_view.radius_x4 >> 2, which grows by 1 a frame. Removed at end_position.
comment sinking_effect_update The effect of a sinking player: moves 1.5 along y a frame with the player, plays its animation once (sorted at screen_y + depth_offset), then is removed.
comment spawn_climb_hand Leaves a hand on the climbed object at the player's projected position and z (sprite 0x8300 for Sonic or 0x8258 with climb_hand 0, 0x82C8 below 0, 0x82C0 above), in the player's palette and offsets; handler climb_hand_update. hand is only scratch.
comment spawn_player_effect Spawns an effect object 0x20 along x, 0x10 back along y and 0x10 lower than the player (palette 0xD190, player_effect_animation, depth_offset 0x28, player_effect_start) and hands it back in effect.
comment spawn_struggle_marker Spawns the marker that follows a caught player (sprite 0x77A0 in the player's palette with priority bits 0x8000, offsets 2,-0xF, struggle_marker_update) and hands it back in marker.
comment struggle_marker_update Follows its player (its parent) while that player is in state 0x25 step 12, sorted just above the player's floor; otherwise removed.
comment trap_flash_start Projects the trap's flash, then trap_flash_update.
comment trap_flash_update The trap's flash: counts animation_speed down from 8 as a timer, drawn two frames out of four, then removed.
comment trap_start Projects the trap, then trap_update.
comment trap_update The trap: sprite 0xB500 once its player's substate is 3 or more (broken free); removed off screen.
comment animate_ending_debris Moves a piece of debris along path: the first record whose end_time is above the object's timer gives its screen x, y and sprite (palette 0x10C0, layer 6); removed at the 0xFFFF record. Only timer_low (timer's low byte) counts up, so timer wraps at 0x100.
comment animate_ending_runner Draws a runner of the first scene with the frame at frame_entry: it grows by 1 every 4 frames and takes the next of its 12 frames every 2; removed once ending_first_scene_done is 1.
comment animate_ending_runner_across Draws a runner crossing the screen: the body frame from body_frames by timer >> 2 (timer 0-7) and the legs part from leg_frames by cycle_count (0-3); at screen x 0x300 both go back to 0.
comment animate_horizon_blast Draws the next ending_horizon_blast_frames frame (draw_ending_blast_frame); removed at the 0xFFFF end.
comment animate_tower_blast Draws the next ending_tower_blast_frames frame (draw_ending_blast_frame); removed at the 0xFFFF end.
comment build_ending_scene Builds the ending by ending_scene_step: 0 sets up the screen (display off for 0x10 frames, layers and palettes, sound 0x94, colour offsets 0x20, pages and scrolls, clip rectangle 1 cleared, six vram scripts) and goes to 1; 1 spawns the tower, the horizon, the three runners and the road (then 2); 2 and 3 do nothing.
comment draw_ending_blast_frame Draws the blast frame at frame_entry (layer 6, palette 0x10B0, scale 0x100) and steps frame_index every 6 frames.
comment draw_ending_blast_frame_copy Unused: the same code as draw_ending_blast_frame.
comment ending_big_explosion The big explosion at the tower (from its timed list): at (0xA1, 0x62) in layer 9 with a second-pool part kept in speed_x, sound 7; frames from ending_big_explosion_frames and the part's from ending_big_explosion_frames_2, the next every 6 frames; at the 0xFFFF end the part is freed and it is removed.
comment ending_close_up_fade_in Fades the close-up in from black (update_fade, fade_kind 2), the second colour offsets following; removed when the fade is done.
comment ending_credits_design_scroll The DESIGN. credit block: header 0x97E8 and five credits_design_parts kept in speed_y, z, ending.part_3, animation and x, scrolling up 1 pixel every 2 frames until the last is at screen y -0x14; then the parts are freed and it is removed.
comment ending_credits_director_scroll The DIRECTOR. credit block: header 0x97C8 and its credits_director_parts (kept in speed_y and z); scroll_credits_block does the rest.
comment ending_credits_fade_in Back from white for the credits: the first frame loads the credits palettes, sets the second colour offsets to 0x1F, starts ending_credits_spawner_run and sets up the layers, the backdrop colour (0x5FD2) and the scrolls; after 0x78 frames it starts ending_music_play once and lowers the offsets by 1 every 3 frames, removed at 0.
comment ending_credits_fade_out Fades the credits to black (update_fade, fade_kind 0), the second colour offsets following color_offset_red/green/blue; when done it starts ending_eggman_scene_fade_in and is removed.
comment ending_credits_program_scroll The PROGRAM. credit block: header 0x97C0 and four credits_program_parts kept in speed_y, z, ending.part_3 and animation, scrolling up 1 pixel every 2 frames until the last is at screen y -0x14; then the parts are freed and it is removed.
comment ending_credits_sound_scroll The SOUND. credit block: header 0x97E0 and four credits_sound_parts kept in speed_y, z, ending.part_3 and animation, scrolled like ending_credits_program_scroll.
comment ending_credits_spawner_run The credits' timer object: counts its timer and runs run_timed_spawns_3, which starts the portrait, the name plate and the seven credit blocks.
comment ending_credits_special_thanks_scroll The SPECIAL THANKS. credit block, the last: header 0x97D0 and eight credits_special_thanks_parts kept in speed_y, z, ending.part_3, animation, x, y, floor_z and previous_floor_z, scrolled like the other blocks.
comment ending_credits_team_logo_scroll The first credit block: the team logo (header 0x9850 at screen y 0xF4) and its credits_team_logo_parts part, kept in speed_y, scroll up 1 pixel every 2 frames until the part is at screen y -0x14; then the part is freed and it is removed.
comment ending_credits_voice_actor_scroll The VOICE ACTOR. credit block: header 0x97F0 and five credits_voice_actor_parts kept in speed_y, z, ending.part_3, animation and x, scrolled like the other blocks.
comment ending_debris_1_fly A burning chunk flying off the tower top (from the tower's timed list): sound 0xB on its first frame, then animate_ending_debris along ending_debris_1_path with sprite flags 9.
comment ending_debris_2_fly The second chunk: sound 0xB, then animate_ending_debris along ending_debris_2_path with sprite flags 0xA.
comment ending_debris_3_fly The third chunk: sound 0xB, then animate_ending_debris along ending_debris_3_path with sprite flags 9.
comment ending_dust_cloud_rise The dust cloud along the horizon after the collapse: at x 0x9E (palette 0x10D0, layer 6), every 7 frames the next (screen y, sprite) of ending_dust_cloud_frames, whose place it keeps in z; removed once ending_first_scene_done is 1.
comment ending_eggman_fly_away Eggman's machine in close-up flying off (scale 0x200 down by 3, by 2 below 0x100), palette cycling; below scale 0x20: sound 9, a twinkle growing to 0x110 and shrinking, the parts freed, then a circle opening around (0xA0, 0x70) in the per-line window tables (radius + 3 every other frame) past 0xE0; then the three runners across.
comment ending_eggman_fly_away_spare_return Unused: does nothing (a stray return the original has after ending_eggman_fly_away).
comment ending_eggman_fly_in Eggman's machine flies in along ending_eggman_flight_path for 0x1E0 frames, a jet flame 0x20 behind it until x 0xA6 and eight smoke puffs handed along every 0xA frames (drawn while timer is above 0x32); sound 0x75 at 0x104. Then ending_eggman_turn with its sprite; it frees its parts, and its path pointer too as if a part.
comment ending_eggman_scene_fade_in Sets up Eggman's scene on its first frame (ending_stripes_script, the page shadows, all layers on) and fades it in from black (fade_kind 2), the second colour offsets following; when done it starts ending_eggman_fly_in and is removed.
comment ending_eggman_scene_fade_out Fades Eggman's scene to black (fade_kind 0), the second colour offsets following; when done it starts ending_close_up_fade_in and ending_eggman_fly_away and is removed.
comment ending_eggman_turn Eggman turns to the viewer: the sprite from ending_eggman_turn_frames by its timer (the other sprite fields come from the fly-in); at the 0xFFFF end it starts ending_eggman_scene_fade_out and is removed.
comment ending_flash_to_white The flash to white (from the tower's timed list): mixer_offset_select 0x80FF and the second colour offsets up from 0 by 1 every 3 frames; at 0x1F it sets ending_first_scene_done, starts ending_credits_fade_in and is removed.
comment ending_horizon_blast_1 A blast along the horizon (from the tower's timed list): sound 9 and start_ending_blast on its first frame, at (0x35, 0xAA), animate_horizon_blast.
comment ending_horizon_blast_2 The second horizon blast, at (0x52, 0xA8): as ending_horizon_blast_1.
comment ending_horizon_blast_3 The third horizon blast, at (0x73, 0xA5): as ending_horizon_blast_1.
comment ending_horizon_blast_4 The fourth horizon blast, at (0x93, 0xA0): as ending_horizon_blast_1.
comment ending_horizon_blast_5 The fifth horizon blast, at (0xAD, 0x9E): as ending_horizon_blast_1.
comment ending_horizon_blast_6 The sixth horizon blast, at (0xC6, 0xA2): as ending_horizon_blast_1.
comment ending_horizon_blast_7 The seventh horizon blast, at (0xCF, 0xAF): as ending_horizon_blast_1.
comment ending_horizon_blast_8 The eighth horizon blast, at (0xE4, 0xAC): as ending_horizon_blast_1.
comment ending_horizon_blast_9 The ninth horizon blast: as ending_horizon_blast_1, but only its first frame is at (0xF8, 0xB0); later frames are at (0xE4, 0xAC), the eighth blast's spot.
comment ending_horizon_show The dark hills under the tower (sprite 0x9AC0, palette 0x10F0, layer 5) at (0x9E, 0xBE); after 0x258 frames it starts ending_dust_cloud_rise once; removed once ending_first_scene_done is 1.
comment ending_mighty_run_across Mighty crossing the screen last: body 0xA7B0 and legs 0xA7C0 (scale 0xE0, layers 4 and 3, ending_mighty_body_frames and ending_mighty_leg_frames kept in x and y), 3 pixels a frame.
comment ending_mighty_run_forward Mighty, the right runner of the first scene: from (0xB0, 0xC0) at scale 0x40, palette 0x150, 2 pixels right and 1 up every 0x20 frames; animate_ending_runner with ending_mighty_run_frames.
comment ending_music_play Queues sounds 0x94 and 0x82 (the ending music) on its first frame, then the ending_music_cues sounds at their times; removed at the 0xFFFF end.
comment ending_name_plate_show The name plate under the portrait: the first frame sets clip rectangle 1 to 0xB1,0x21-0x120,0x78 and takes a second-pool part (kept in z: sprite 0x9800 above, at 0xE9,0x18); the plate by its timer from ending_name_plate_frames at (0xE8, 0xA3), NBG0 scrolling 1 a frame; at the end ending_window_open.
comment ending_portrait_show The credits' character portrait at (0xE8, 0x4C): sprite and palette from ending_portrait_frames and ending_portrait_palettes by its timer, over a backdrop part (sprite 0x9880, kept in speed_y, palettes from ending_portrait_backdrop_palettes); at the end the part is freed and it is removed.
comment ending_ray_run_across Ray crossing the screen behind Sonic: body 0xA7E0 and legs 0xA7F0 (scale 0xF0, layers 3 and 2, ending_ray_body_frames and ending_ray_leg_frames kept in x and y), 4 pixels a frame.
comment ending_ray_run_forward Ray, the left runner of the first scene: from (0x90, 0xC0) at scale 0x40, palette 0x140, 2 pixels left and 1 up every 0x20 frames; animate_ending_runner with ending_ray_run_frames.
comment ending_road_scroll The road under the runners (palette 0x12B0, layer 2) at (0x9F, 0xD4): the next of the 5 ending_road_frames every frame; removed once ending_first_scene_done is 1.
comment ending_sonic_run_across Sonic crossing the screen first: body 0xA810 and legs 0xA820 (layers 1 and 0, ending_sonic_body_frames and ending_sonic_leg_frames kept in x and y), 5 pixels a frame; after 0x78 frames, counted in hit_list_next, it sets ending_finished.
comment ending_sonic_run_forward Sonic, the centre runner of the first scene: at x 0xA0 from y 0xC0, scale 0x40, palette 0x130, 1 pixel up every 0x20 frames; animate_ending_runner with ending_sonic_run_frames.
comment ending_tower_animate The tower behind the runners: the first frame runs cloudy_horizon_script and turns NBG3 on; sprite and palette from ending_tower_frames by its timer (standing, then collapsing), layer 7, and every frame run_timed_spawns_2 starts the debris, blasts, explosion and flash on time. Removed at the end of the list.
comment ending_tower_blast_1 A blast at the tower's base (from the tower's timed list): sound 0xA and start_ending_blast on its first frame, at (0x90, 0xA0), animate_tower_blast.
comment ending_tower_blast_2 The second tower blast, at (0xA1, 0x98): as ending_tower_blast_1.
comment ending_tower_blast_3 The third tower blast, at (0xAE, 0xA0): as ending_tower_blast_1.
comment ending_window_open Opens the portrait window: clip rectangle 1 grows by 1 pixel a frame on each side up to 0,0-0x140,0xE0 (step collects a bit per finished edge) while NBG0 scrolls 1 a frame; then it starts ending_credits_fade_out and is removed.
comment init_credits_sprite Puts the current credit sprite at screen x 0x58, layer 0, no flags, scale 0x100; current_object goes back to object_cursor.
comment init_eggman_smoke_puff Sets up one of Eggman's smoke puffs, piece: sprite 0x9B88, palette 0x1280, layer 0, at screen (-0x10, 0), scale 0x100.
comment init_ending_runner_across Starts a runner across: the object's started set, timer and cycle_count 0; the current sprite (body or legs) at (0, 0x68), palette | 0x2000, sprite flags 0x140.
comment init_ending_scene_object Clears the flags_low_byte (started), step and timer of object, a new scene object, and sets ending_scene_step to 2.
comment load_credits_palettes Sets bit 0x400 of video_control (the tile bank bit), drops the pending vblank copies and loads tile_bank_1_palette_list.
comment run_timed_spawns_2 One step of timed_spawns_2 for the tower: once the next record's time is reached (the object's timer) it starts an object whose handler is the record's offset from ending_debris_1_fly, and moves on (list_index); the 0xFFFF end does nothing. object is only scratch.
comment run_timed_spawns_3 One step of timed_spawns_3 for the credits: once the next record's time is reached it starts an object whose handler is the record's offset from ending_credits_fade_in, and moves on; at the 0xFFFF end the timer object is removed.
comment scroll_credits_block Scrolls a credit block: the header and its parts in speed_y and z move up 1 pixel every 2 frames until the part in z is at screen y -0x14 (scrolled_out); then the parts are freed and the block removed. The longer blocks use it only on their first frame.
comment scroll_credits_sprite Draws a credit sprite in its layer, 1 pixel higher when the block's (object_cursor's) timer has reached 2; current_object goes back to object_cursor.
comment start_ending_blast Starts a blast: started 1, frame_index 0, timer 0.
comment wrap_credits_scroll_timer Puts the block's (object_cursor's) timer back to 0 once it reaches 2: the credits move 1 pixel every 2 frames.
comment ascii_to_ranking_letter Turns an initial of default_ranking into a ranking letter code: 'a'-'z' to 0-0x19, ' ' to 0x2B, '3' to 0x23; any other character hangs in an endless loop. Hands back letter with its low byte replaced.
comment build_ranking_display Builds the TOP15 screen once: for each of the 15 records (lowest first, rows 15 up to 1) a row of 12 sprites (rank, initials, portrait, name, four score digits, stage) run by ranking_row_update, current_player counting the rows; the five title letters (ranking_title_spin_in), ranking_title_layout toggled; then ranking_display_scroll_in.
comment copy_ranking_records Copies count 8-byte ranking records from source to dest, then ranking_checksum = compute_crc16 of ranking (0x78 bytes); hands back compute_crc16's polynomial.
comment draw_ranking_title Draws the five TOP15 letters at the first one's scale: the centre one, two at +-(speed_x, speed_y), and the outer two at +-2 speed_x with +-2 speed_y added along x (ranking_title_layout 0) or y (1).
comment drift_ranking_background Drifts the TOP15 background: the direction (speed_z's low half) swings between 0x180 and 0x280 by 2 a frame, speed_x/speed_y gain direction_to_speed(0x100) of it and NBG0 scrolls to their whole parts. speed's low half swings 0-0x300 as well, but the step always uses 0x100.
comment enter_ranking_initials A player's ranking entry (player state 0x2E): OUT OF RANK! blinks for out_of_rank_timer, then game_over_done; else the button goes to the next letter (index spawn_arg_1_high, up to 3) or on the end mark saves (enter_ranking_initials_save_record); enter_ranking_initials_count_down does the rest. jump_offset is only scratch.
comment enter_ranking_initials_count_down The ranking entry's frame: time_left counts down (at 0 the record is saved), with the seconds and the rank drawn above; trackball x moves between the letters (spawn_arg_1's high byte), trackball y changes the chosen one (codes 0-0x2B, into initials); the letters and the end mark are drawn, the chosen one blinking. button_bit is only scratch.
comment enter_ranking_initials_save_record Ends the entry (game_over_done): if the rings beat the lowest record, the lower records move down one and the new one goes in (initials 'sex', 'xxx', 'x x' and 'xx ' become 'tak'), ranking is copied to saved_ranking and ranking_checksum recomputed. Clears the player's bit in ranking_entry_players.
comment load_ranking Loads ranking from saved_ranking when compute_crc16 of it (0x78 bytes) matches ranking_checksum, else reset_ranking; hands back what copy_ranking_records does.
comment ranking_display_end Sets ranking_done and removes the TOP15 display object (delete_object_and_children).
comment ranking_display_hold Holds the TOP15 screen for 0x12C frames (hold_timer), the background drifting, then tells the rows to remove themselves (rows_remove) and ends with ranking_display_end.
comment ranking_display_scroll_in Brings the TOP15 screen down 1 pixel a frame (screen_y up to 0x24) while the background drifts; then the portraits animate (rows_animate) and ranking_display_hold.
comment ranking_row_update A TOP15 row: at the display's screen_y + row * 0x17, its 12 sprites on top (bucket 0); once rows_animate is set the portrait plays its ranking_portrait_frames (the second set of 0x14 for row 1), one every 2 frames; removed with its sprites when rows_remove is set.
comment ranking_title_pause Holds the TOP15 title for pause_timer (0x1C) frames, then ranking_title_rise.
comment ranking_title_rise Moves the TOP15 title up 1 pixel a frame to screen y 0x23, then leaves it to draw_ranking_title.
comment ranking_title_spin_in The TOP15 letters spin in: scale + 2 up to 0x200, direction + 8 a frame, speed_x/speed_y = direction_to_speed(0x2000) of it (their spread); at full scale with direction 0x300 they pause (ranking_title_pause, 0x1C frames).
comment reset_ranking Fills ranking with default_ranking's 15 records (their ASCII initials through ascii_to_ranking_letter), then copies it to saved_ranking with ranking_checksum (copy_ranking_records).
comment start_ranking_display Starts the TOP15 screen: the display object (allocate_object_or_start_list, display) at (0xA0, -0x1A4) with the NBG0 picture shown (show_nbg0_picture, whose rows and source it hands back); handler build_ranking_display.
comment start_ranking_entry Starts a player's ranking entry at game over: rings below every record give OUT OF RANK! for 0x78 frames (the block's handler field 1); else the player's ranking_entry_blocks block (three letters, an end mark) with rank, rings, character, stages_reached, time_left 0x4B0 (handler 0). Sets the player's bit in ranking_entry_players.
comment add_play_time Adds game_frames to bookkeeping_play_time, carrying into bookkeeping_play_time_high.
comment compute_bookkeeping_crc compute_crc16 of the 0x40 bookkeeping bytes at data: hands back crc, and data moved past them.
comment compute_crc16 CRC-16 (polynomial 0x1021, the register seeded with DE BD EB) of count bytes from data, with 8 more shifts at the end: hands back crc, data past the bytes and polynomial (0x10210000). The EEPROM, bookkeeping, ranking and ROM checks use it.
comment compute_crc16_alternate_bytes compute_crc16's CRC of count ROM bytes from data, every other byte: each step adds 1 to the address, then 1 more to its low byte only, so from an odd address it reads the odd bytes across pages, from an even one it circles one 256-byte page. rom_checksums holds what it gives.
comment compute_crc16_strided Unused: compute_crc16's division by 0x11021 over count bytes from data, byte_gap + 1 apart, except that the first three bytes take the place of the DE BD EB seed (crc is shifted out unused); hands back the 16-bit remainder.
comment compute_eeprom_backup_checksum compute_eeprom_crc of the EEPROM backup copy from its write count on (0x36 bytes, its address handed over in current_object); hands back the CRC, for eeprom_backup_checksum.
comment compute_eeprom_checksum compute_eeprom_crc of the EEPROM block from eeprom_write_count to its end (0x36 bytes, the address handed over in current_object): hands back crc and polynomial.
comment compute_eeprom_crc compute_crc16's CRC of the 0x36 bytes at current_object, counted down in free_objects (both are left changed): hands back crc and polynomial (0x10210000).
comment eeprom_deselect Clears the EEPROM's chip select, clock and data lines (bits 5-7 of lines) on port_d and hands back lines.
comment eeprom_lock Sends the EEPROM the write-disable command (0), deselects it and turns interrupts back on.
comment eeprom_read_all Reads the EEPROM's 0x40 words (read command 0x80 + address) into eeprom_magic onward; interrupts stay off (eeprom_select). data is only scratch.
comment eeprom_read_all_and_enable_interrupts Unused: eeprom_read_all, then interrupts on.
comment eeprom_read_word Reads one EEPROM word: eeprom_send_command(command), then 0x12 clocks reading port_f bit 7, then eeprom_deselect; hands back the 16-bit word in data and the port D lines in lines.
comment eeprom_read_word_at Unused: reads the EEPROM word at address command (read command command | 0x80) and hands it back; interrupts on after.
comment eeprom_save Saves the settings: eeprom_write_count + 1, eeprom_checksum recomputed, the 0x40-byte block copied to eeprom_backup, then write enable (command 0x30) and the 0x40 words written (command 0x40 + address). Hands back the last command and words_left 0.
comment eeprom_save_and_lock eeprom_save, then eeprom_lock (write disable, interrupts on); command and words_left as eeprom_save hands them back.
comment eeprom_select Gets ready to talk to the EEPROM: hands back port D's value in lines and the port addresses (io_port_f, io_port_d) in port_f and port_d; turns interrupts off.
comment eeprom_send_command Starts an EEPROM command on port_d: lines low, a clock, chip select up, then the start bit and command's 8 bits, high bit first, each on the data line (bit 7) with a clock (bit 6); hands back lines.
comment eeprom_set_lines Writes lines to port_d (the EEPROM lines) and hands back data untouched.
comment eeprom_unlock Unused: sends the EEPROM the write-enable command (0x30), deselects it and turns interrupts back on.
comment eeprom_write_word Writes one EEPROM word: eeprom_send_command(command), then data's 16 bits, high bit first; chip select down for two clocks, then up, clocking until port_f bit 7 goes high (the chip is ready); deselects and hands back lines.
comment eeprom_write_word_at Unused: writes data to the EEPROM word at address command (write command command | 0x40); interrupts on after.
comment load_bookkeeping Loads the bookkeeping at power-on: reset when bookkeeping_rom_stamp is not this ROM's (rom_checksums + rom_checksum_ic8); else the block if its CRC matches bookkeeping_checksum (then copied to the backup), or the backup copied back, or a reset. Then power_on_time and power_on_frames_top from the bookkeeping.
comment load_settings Reads the EEPROM and checks it: a wrong eeprom_magic loads eeprom_defaults ('SEGA' also sets eeprom_write_count 0x64), another ROM's stamp or two bad CRCs reset the settings (default_settings), a bad CRC takes the backup, a bad backup is rewritten. Rebuilds also set moving_seat_dip from DIP 1, clear bookkeeping_rom_stamp, then save. crc is only scratch.
comment record_player_game Books a player's game at its end: bookkeeping_games + 1, its time (game_frames - join_time) added to bookkeeping_player_time and checked against the longest and shortest; when nobody is still playing (no player_status negative, 1 or 4) add_play_time. Then the checksum and the backup copy.
comment reset_bookkeeping Fills the bookkeeping block and its backup with bookkeeping_defaults, stamps both with this ROM's stamp (rom_checksums + rom_checksum_ic8) and updates bookkeeping_checksum.
comment update_bookkeeping_checksum Sets bookkeeping_checksum to compute_bookkeeping_crc of the bookkeeping block and hands the CRC back.
comment carried_ring_keep_spinning The frame of a ring riding a platform while it is not taken: flags bit 4 copies bit 3; the ring floats hover_height above its platform's z on the platform's floor_block, animates (a glint at step 0x15, glint_timer 0x78), is touchable and drawn with its shadow; once off the screen it was on, removed with its shadow.
comment carried_ring_spin Handler of a ring that rides a platform: removed with its shadow while map_screen_active is set; touched (flags bit 2), it is taken (sound 0x13, spawn_ring_sparkle, removed with its shadow) unless the toucher is a player in state 0xD or 0x20 whose hurt_contact_delay is running. Otherwise carried_ring_keep_spinning.
comment carried_ring_spin_spare_return Unused: an empty return left after carried_ring_keep_spinning's code; it does nothing.
comment carried_ring_wait Handler of the carried rings (stage_object_templates 1 and 3) until they stand on a platform (flags bit 3), then carried_ring_spin; meanwhile on the rider list and not drawn, its shadow's palette set to 0x87A0.
comment gauge_ring_fall_into_gauge Unused: the gauge ring's step 1: moves on by speed_x and drops along a sine arc from base_y by drop_height onto its gauge while it shrinks (sprite_scale - 8 a frame); when it has shrunk away the parent's health goes up by 1 and the ring is removed.
comment gauge_ring_rise Unused: the gauge ring's step 0: moves on by speed_x while it rises along a sine arc up to 0x40 above base_y (direction + turn_speed a frame); after hop_timer frames step 1, with base_y at its gauge's y (hud_gauge_positions_3p[kind].y) and drop_height from the distance down to it.
comment gauge_ring_update Unused: the gauge ring's handler: step 0 gauge_ring_rise, step 1 gauge_ring_fall_into_gauge (nothing sets step 2), then animates it and queues its sprite.
comment init_sparkle_object Sets up the new sparkle or glint sparkle at the current object's x, y, z and floor_z: sprite_flags 0x1000, palette 0x1B0, flags 0, handler sparkle_start, its animation to start from the first step at 2 frames a step.
comment move_scattered_ring Moves a scattered ring by speed_x/speed_y, stepping back and turning round when the floor ahead is more than 8 above it; gravity 0x6000, bouncing with 3/4 of speed_z; animates with a glint. On screen it joins the touchable and rider lists and is drawn with its shadow; off it (margins 0x20, 0x58) scattered_ring_vanish.
comment ring_spin Handler of the spinning ring: removed with its shadow while map_screen_active is set or in game mode 0x20; touched (flags bit 2), it is taken (sound 0x13, spawn_ring_sparkle, removed with its shadow) unless the toucher is a player in state 0xD or 0x20 whose hurt_contact_delay is running. Otherwise ring_spin_keep_spinning.
comment ring_spin_keep_spinning The spinning ring's frame while not taken: animates (a glint at step 0x15, unused_glint_timer 0x78), removed with its shadow once off the screen it was on, else touchable, drawn with its shadow. On floor type 7 the shadow block shows the ring again as far below the floor as it is above, unless bit 4 is set in step_accumulator of whatever block current_player holds.
comment scatter_rings Throws count rings (at most 10) from the current object, kind 3 with a pool 2 shadow, handler scattered_ring_wait, until a pool runs out. A ring at an odd count takes a random direction, kept in scatter_fixed, the next its mirror; speed and jump are random only while scatter_fixed's bytes are 0, so the kept direction leaves later rings without them.
comment scatter_rings_fixed scatter_rings with scatter_fixed 0xFFFF: the rings get no speed and no jump and fall from the current object where they are (an odd count still draws a direction).
comment scatter_rings_random scatter_rings with scatter_fixed cleared first, so the rings get a random speed and jump (until scatter_rings' kept direction turns them off for the rings after the first odd count).
comment scattered_ring_live Handler of a scattered ring that can be taken: after lifetime frames it shrinks away (scattered_ring_vanish); touched (flags bit 2), it is taken (sound 0x13, spawn_ring_sparkle, removed with its shadow) unless the toucher is a player in state 0xD or 0x20 whose hurt_contact_delay is running; otherwise move_scattered_ring.
comment scattered_ring_vanish Handler of a scattered ring going away: drifts on by speed_x/speed_y while it and its shadow shrink by 0x10 of sprite_scale a frame; removed with its shadow once it has shrunk away.
comment scattered_ring_wait Handler of a ring just thrown: moves it (move_scattered_ring) while pickup_delay counts down with flags bit 9 set (players ignore its touches); then clears the bit and goes on to scattered_ring_live.
comment sparkle_animate Handler of a sparkle or glint: animates it and draws it in front of its floor point (depth: the floor's screen y + 0x10); removed at the end of its animation.
comment sparkle_start First frame of a sparkle or glint: projects it to the screen, then becomes sparkle_animate.
comment spawn_gauge_ring Unused: spawns a ring that flies from the current object (its parent, 0x20 above it on the screen, kind copied) to its player's health gauge: ring_animation, sound 0x7F, speed_x to reach the gauge's x + 0x10 (hud_gauge_positions_3p) in 0x3C frames, handler gauge_ring_update.
comment spawn_ring_glint Spawns a glint on the current ring: ring_glint_animation, drawn 0x10 right of it and 8 up (init_sparkle_object); nothing when no block is free.
comment spawn_ring_rows Spawns the stage's ring rows (stage_template_spawns, cursor set on the stage's first call): once scroll_position reaches a record, count objects of its stage_object_templates record from x, y, dx, dy apart, each var_82 above its floor with a pool 2 shadow. A record passed by 0x100 is skipped; waits while a pool has fewer than count blocks.
comment spawn_ring_sparkle Spawns the sparkle of a taken ring at the current object: ring_sparkle_animation, drawn 0x10 up (init_sparkle_object); nothing when no block is free.
comment spawn_stage_template Unused: spawn_stage_template_0 with record template_index of stage_object_templates (kept in template_spawn_index) in place of the first.
comment spawn_stage_template_0 Unused: when pool 2 has a block, spawns stage_object_templates' first record (the spinning ring) at x, y, z (kept in template_spawn_x/_y/_z) with scroll_position in its health halfword, projected, find_floor, and a pool 2 shadow child; current_object is kept, current_player is left on the new object.
comment still_ring_wait Handler of a ring that does not spin (stage_object_templates 2): touched, sound 0x13 and removed with its shadow; removed too once off the screen; else on the touchable list and drawn with its shadow.
comment player_animate Second switch on the player's state, after player_move: the animation and drawing of states 0 (standing), 1 (walking), 2 (falling), 4 (jumping), 0xC (skidding), 0x13 and 0x14 (running) and 0x17 (hopping); state 3 does nothing and every other state goes back to player_dispatch_state.
comment player_animate_falling State 2, falling to a lower floor: a hop on the first frame (speed_z -0x10000, +0x10000 if player flags bit 7 was set), gravity 0x6000, the running animation; on the floor state 1. No floor below (0x7FF) drops it into the pit: state 8, ledge_side, player_status, mishap_flag, the edge hang animation. Drawn at its floor or the previous one.
comment player_animate_hopping State 0x17, a hop: on the first frame speed_z -0x80000 (by speed from hop_speeds_by_speed on floor type 6) and the normal palette; then bounce_off_wall_in_air, jump_delay 2, the running animation and gravity 0x6000. Landing: state 1, z on the floor, jump_delay 0, flags bits 13, 20 and 21 cleared.
comment player_animate_jumping State 4, the jump: speed_z -0x90000 on the first frame, then attack_power 4 (sound 0x1F as it is set, flags bit 6), the attack jump animation and gravity 0x9000 once past its first frame (both less on floor type 4); in stage 5 before cage_bar_hit a sign and "ATTACK!" above. Lands after 0x50 frames, or on the floor once state_timer is out.
comment player_animate_jumping_land The jump's end: state 1, z on the floor, jump_delay 0, flags bits 6, 13, 20 and 21 cleared, the normal palette (palette_list + 2); then player_draw.
comment player_animate_run_cycle Picks the run animation: the alternate run (player_animate_run_cycle_alternate) with flags bit 31, or on floor type 1 with floor_attribute 1-4 while facing is 0-4 past floor_1_facing_bases[floor_attribute] (for 1 and 4 only while alternate_run_toggle, + 2 a frame, is 0-0x7F); else player_animate_run_from_table.
comment player_animate_run_cycle_alternate The alternate run animations of the player's character (sonic/ray/mighty_alternate_run_animations) through player_animate_run_from_table.
comment player_animate_run_from_table The run cycles' common part: animation_table's animation for facing (restarted at step 2 after a non-running one), stepped the frame after walk_cycle (+ animation_speed_int) reaches 0x4F0 in state 0x14, else 0x600 (0x1800 on floor type 4's floor_4_slow_run_steps), walk_cycle keeping its low byte; drawn like the standing player.
comment player_animate_running State 0x13's animation: the character's running table (sonic/ray/mighty_running_animations) through player_animate_run_cycle.
comment player_animate_running_fast State 0x14's animation: the character's running fast table (sonic/ray/mighty_running_fast_animations) through player_animate_run_cycle.
comment player_animate_skidding State 0xC, skidding, trackball locked; the jump button turns it into a jump at once. First frame: the skid animation for the opposite facing, skid_slowdown and skid_speed_slowdown (ground_speed and speed / 8, / 32 on floor type 7), sound 0x27; then player_animate_skidding_slow_down; stopped, the animation ends in walking (state 1).
comment player_animate_skidding_slow_down The skid's slowing down (substate 1): a dust puff every 4 frames (spawn_skid_dust; not on floor type 7 or while drawn clipped), the skid animation up to its step 2, ground_speed and speed less skid_slowdown and skid_speed_slowdown; when speed runs out both are 0 and substate 2. Then drawn.
comment player_animate_standing State 0's animation: the character's standing table by facing, restarted when it was not standing (first step kind * 8 + 0x2C frames, standing_started), later steps 3 frames but 2 and 6. Drawn unless hidden (flags bit 19): at its base, or with player flags bit 0 clipped at the ground (clip entries at +0x102 and +0xF0) at draw_depth.
comment player_animate_walking State 1's animation: the character's walking table by facing, stepped the frame after walk_cycle (+ animation_speed_int) reaches 0x200, which also sets flags bit 15 for player_move to apply walk_pending_x/_y; walk_pending_x/_y and walk_cycle cleared when it was not walking. Drawn like the standing player.
comment player_climbing_animate_vertical_step State 0xA, a vertical step (none while animation_speed is negative): speed 4 - min(|speed_y_int|, 3), * 4 with fast_climb; each new step moves z and climb_remaining 2 (down with flags bit 7) and the screen position by the wall record's +4 slope, keeping the old ones; it ends at frame 6 or 0. Then player_climbing_update_position.
comment player_climbing_begin_climb State 0xA: step_accumulator 0, substate 3, then player_climbing_move.
comment player_climbing_check_vertical_step State 0xA: step_accumulator_int at 4 or more starts a step down (flags bit 7 set), at -4 or less a step up (bit cleared), through player_climbing_start_vertical_step; in between player_climbing_animate_vertical_step.
comment player_climbing_descend_over_edge State 0xA substate 2, getting over the top edge to climb down: animate_around_anchor (drawn meanwhile) until the climb-over animation ends, then the climb animation from step 6 at the anchor's screen position and player_climbing_begin_climb.
comment player_climbing_grab_wall State 0xA substate 1: keeps y (x with the wall record's byte 0) within the record's span (+0xA, +0xC long). From below: climb_remaining = record +6, the climb animation, snapped to the block's edge, z and climb_base_z at the floor, player_climbing_begin_climb. From the top: at the block's x_max, the climb-over animation round an anchor, substate 2.
comment player_climbing_move State 0xA substate 3: while a step's animation runs, player_climbing_check_vertical_step; else the trackball's speed (pushed_out pushes up) adds to step_accumulator when mostly vertical, to side_step_accumulator when sideways (each cleared when the input turns round), a side step at +-2 (+-8 with fast_climb: block type 0x20 above z 0x4DE).
comment player_climbing_pull_up State 0xA substate 4, the pull-up over the top: animate_around_anchor (drawn meanwhile) until it ends; then z and floor_z at the wall record's +8 (the top), state 0 with speeds 0, placed 0xA past the floor block's edge and turned toward heading 0x80 (0x380 when the record's byte 0 is set).
comment player_climbing_side_step State 0xA, a sideways step: both accumulators 0, the climb-sideways animation; screen_x moves 2 (back with flags bit 7) and projected_y 1, unless the new spot, unprojected, leaves the wall record's span or the screen rows 0x10-0xCF; the old position kept in saved_screen_x/_y. Then player_climbing_update_position.
comment player_climbing_start State 0xA substate 0: flags bits 6 and 13 cleared, the normal palette, speed 0, control_lock 0, substate 1; then player_climbing_grab_wall.
comment player_climbing_start_vertical_step State 0xA, a new vertical step: both accumulators 0, the climb animation at speed 4. Refused (animation_speed 0xFF, step_accumulator 0) when it would go below climb_base_z or past the screen's edge (screen y 0xD0 going down, 0x10 going up). Then player_climbing_animate_vertical_step.
comment player_climbing_update_position State 0xA's frame end: climb_remaining at 0 starts the pull-up (substate 4); at the wall record's +6 or more (the foot) the player stands (state 0, free_turn_timer 3, 2 off the wall). Then x/y unprojected from the screen, the lists, on floor block type 0x20 the priority and splash_zone (z 0x4C7-0x4E9, spawn_climb_zone_effect on entering); drawn.
comment player_draw Draws the player unless hidden (flags bit 19): at its floor, or the previous floor when it has no floor block; with player flags bit 0, clipped at the ground: the clip entries at +0x102 and +0xF0 and the player at draw_depth (its floor's screen y).
comment player_hurt_fly State 0xD substate 2, knocked back: gravity 0x6000 (/4 on floor type 4 unless the toucher is kind 0x27), moves, find_floor, check_fall_into_pit (state 9 dispatched); a floor 8 or more above sends it back to its previous x, y and floor. Landing: substate 3, state_timer 2, sound 0x20, the animation's next step; drawn at its floor.
comment player_hurt_get_up State 0xD substate 5: the get-up animation (speed 3); at its end substate 6, mishap_flag 0, the normal palette, state_timer 0x1E and, unless flags bit 24, invulnerable_timer 0x78 with the blink (flags bit 29); state 0x16 at health 0. Each frame the hurt flash, z on the floor, drawn at its base.
comment player_hurt_knockback State 0xD substate 0 or 1: flags bits 2, 6 and 13 cleared, mishap_flag, hurt_contact_delay 0xA, substate 2, speed_z -0x80000 and a push of 0x40000 along the toucher's direction (none from kind 0x24; both halved on floor type 4); the player faces it, sound 0x21 and the character's voice, then player_hurt_set_animation.
comment player_hurt_land_after_hop State 0xD substate 4 lands: substate 5, z on the floor, animation_timer 0, then player_hurt_get_up.
comment player_hurt_set_animation State 0xD: the character's hurt animation by facing from its first step (flips and frame), the hurt flash every 3 frames (none, 0xFF, with flags bit 24), then player_hurt_fly.
comment player_launch Launches the player along direction: speed_z -0x100000, speed 0x5000000, facing it, the attack jump animation's first step, flags bits 6 and 13 cleared and 12 set, the normal palette, control_lock 0, the next substate; then player_launched_fly.
comment player_launch_angle_100 Substate 0 of states 0xF-0x12: direction 0x100, then player_launch (which goes on to substate 2).
comment player_launch_angle_200 Substate 1 of states 0xF-0x12: direction 0x200, then player_launch.
comment player_launched_fly The launch's flight (substates 2 and 3): every speed turned round at screen_x 0x120 or more, gravity 0xA000, moves (previous_x/_y kept), find_floor; landing on a floor block: the next substate, z on the floor, speed_x/_y halved and speed_z turned round and halved.
comment player_launched_land The launch's end (substate 4): state 1, every speed and animation_speed 0, flags bit 12 cleared.
comment player_move Common frame of states 0, 1, 2, 4, 0xC, 0x13, 0x14 and 0x17: counts down hit_stop, control_lock and free_turn_timer, checks contacts and walls, steers by the trackball and moves (in state 1 by the walk animation's steps), find_floor, then player_move_check_edge; a state set on the way, or by floor_type_player_states, goes to player_dispatch_state.
comment player_move_check_edge The rest of player_move: set_floor_sprite_priority and check_player_floor_edge (states 5-9 dispatched), apply_moving_floor, projection (sprite_offset_y 0x12 for it in a jump with attack_power above 1), keep_player_on_screen (state 0x16 dispatched, player_respawn stops), the jump check, blink, platform and lists, then player_animate.
comment player_over_edge_check_pull_up State 7's hang: ledge_pullback at 0x14 or more moves on to climbing back (substate + 1); state_timer counts down and when out the player drops (player_over_edge_drop), but with player flags bit 1 or flags bit 17 only once the touched object's sprite_scale is 0x100 or more; else player_over_edge_hang_finish.
comment player_over_edge_drop State 7 ends in a drop: state 8, player_status 0xFF (1 with flags bit 10 at health 8 or less), mishap_flag 0xFF; then player_over_edge_hang_finish.
comment player_over_edge_hang_finish State 7's frame end while hanging (and on the drop): animate, update_player_blink, project, contacts unless flags bit 17, the lists; drawn at the previous floor (ledge_side 2 or more), just in front of touched_by (its draw_depth + 1) with player flags bit 1 or flags bit 17, else at fall_draw_depth.
comment player_state_bouncing State 0xE, bouncing off a wall or an object: the bounce record for bounce_direction, speed 0x1000000, speed_z -0x68000, sound 0x25; a hop and a rebound at half speed_z (gravity 0x8000), the animation stepping as speed_z passes bounce_speed_z; then up to 0x14 frames on the floor (the button or trackball end them) and the headings turned round.
comment player_state_climbing State 0xA, climbing a wall (attribute_record, its floor_types record by floor_attribute): contacts first (states 0xD, 0x1F, 0x20, 0x36 dispatched); touched by a kind 5 object it only updates its position; else by substate: 0 start (from the top after a jump: state 9), 1 grab the wall, 2 over the top edge, 3 climb, 4 pull up.
comment player_state_dropping State 8, dropping into the pit: holds the hang's last frames 0x1E frames, lets go (a slip effect, characters 1 and 2's slip pieces, sound 0x16, the voice at health 8 or less) and falls down the screen. Below screen y 0x110: 8 health off and player_respawn (state 0x16 at 8 or less); no loss in stage 5 and stage 8 before scroll_position 0x4000.
comment player_state_dropping_spare_return Unused: an empty return left after player_state_dropping's code; it does nothing.
comment player_state_dying State 0x16: the character's dying frame flies up off the screen from where it was (screen x kept within 0x20-0x100), swaying (direction +-7 a frame, kept in the wall field) and speeding up after 0x40 frames, blinking. Past screen y -0x28: player_status 0xFF, in_game 0 and the continue countdown (state 0x23, continue_seconds 9).
comment player_state_falling_into_pit State 9, falling into a pit: fall_draw_depth below the edge, mishap_flag, then gravity 0x6000, drawn at that depth. Over floor block type 0x22 or 0x26 with y_int under group_leaders[0]'s it sinks (state 0x33; the depth test reads 0 for both); floor type 0xD from z_int 0x530: state 0x20; below screen y 0x130: 8 health off, player_respawn or state 0x16.
comment player_state_falling_into_pit_spare_return Unused: an empty return left after player_state_falling_into_pit's code; it does nothing.
comment player_state_falling_off_edge State 6, falling off the edge into the pit: fall_draw_depth below the edge, the fall animation shifted up and left, the voice at health 8 or less, then gravity 0x3000 (sinking and floor type 0xD as in state 9). Below screen y 0x100: 8 health off and player_respawn (state 0x16 at 8 or less); no loss in stage 5 and stage 8 before scroll_position 0x4000.
comment player_state_falling_off_edge_spare_return Unused: an empty return left after player_state_falling_off_edge's code; it does nothing.
comment player_state_game_over State 0x2E, game over: on its first frame start_ranking_entry and player_status 3; enter_ranking_initials every frame until game_over_done is set, then state 0x24 with continue_countdown and state_timer 0.
comment player_state_hurt State 0xD, hurt (contacts once hurt_contact_delay is out), by substate: 0 takes 4 health (not in stage 5; state 0x16 at 0), 1 (flags bit 24) none, then player_hurt_knockback; 2 flies back; 3 lies state_timer frames, 4 hops, 5 gets up; 6 waits state_timer frames (the button or the trackball cut it short), then walks; 7 and 8 the shock, then flying back.
comment player_state_hurt_spare_return Unused: an empty return left after player_state_hurt's code; it does nothing.
comment player_state_inactive State 3: returns at once.
comment player_state_inactive_2 State 0x1A: returns at once.
comment player_state_launched States 0xF-0x12, launched: by substate 0 a launch toward direction 0x100, 1 toward 0x200 (player_launch), 2 and 3 the flight (player_launched_fly), 4 the landing; then animated, projected with sprite_offset_y 0x12, kept on screen, on the lists and drawn at its floor (or the previous one).
comment player_state_launched_spare_return Unused: an empty return left after player_state_launched's code; it does nothing.
comment player_state_not_playing States 0xB and 0x37, not playing: out of the game (flags bit 10, in_game and health 0, player_status 0). Unless joining_closed, a start with credits (try_join) gives handler player_join, in_game 1, rings 0, stages_reached 1 and is_joining, and erases the player's HUD message (hud_text_positions_3p, or _2p with cabinet_config bit 0 clear).
comment player_state_over_edge State 7, over an edge: slides on until slip_distance reaches 0x20000000 (/4 on floor type 9 or with flags bit 17), then hangs by its ledge_side; pulled back (ledge_pullback 0x14) it climbs back onto the floor, else it drops (state 8; on a moving edge once edge_object has passed it). Off the screen while climbing back: player_respawn, or state 0x16.
comment player_state_short_animation State 0x1B: the character's one-off animation (speed 5) with the normal palette for 0x41 frames, then grey_screen_request cleared and standing (state 0). Projected and drawn at its base.
comment player_state_teetering State 5, teetering at a pit's edge with the teeter animation for its ledge_side. Pulled back (ledge_pullback 0x10) it stands on the previous floor, headings from ledge_recover_headings; else it falls off (state 6, player_status, mishap_flag): at once on a kind 0x19 object, else at the animation's step 7 once state_timer (0x3C; 1 for ledge_side 4) is out.
comment advance_camera_scroll_x_y advance_scroll_position, advance_camera_x and advance_camera_y by speed: the scroll and the camera both go on.
comment advance_camera_x camera_x += section_direction_x * speed.
comment advance_camera_x_scroll advance_camera_x and advance_scroll_position by speed (the camera's y at its limit).
comment advance_camera_x_y advance_camera_x and advance_camera_y by speed (scroll_position at the section's end).
comment advance_camera_y camera_y += section_direction_y * speed.
comment advance_camera_y_scroll advance_camera_y and advance_scroll_position by speed (the camera's x at its limit).
comment advance_scroll move_camera_along_path by speed and update_camera_view; once scroll_position reaches the section's end the next section starts: scroll_speed, follow_base_speed, the x and y limits, the next (mode, duration) of scroll_mode_script, the direction and the end, back_edge_frames 0. Then set_camera_velocity.
comment advance_scroll_position scroll_position += speed.
comment advance_scroll_unless_ended advance_scroll, unless scroll_ended is set.
comment call_run_map_screen Unused: calls run_map_screen with 0 and object.
comment clamp_camera_pixels Keeps camera_x_pixels within 0-0x1FFF and camera_y_pixels within 0-0xFFF.
comment clamp_colour_offsets Masks color_offset_red, _blue and _green to 0-0x1F.
comment clear_bitmap_lower_half Unused: clears the lower half of the 8 bpp bitmap layer (rows 0x70-0xDF: 0x7000 halfwords from video RAM 0xE000, also bitmap_address) and turns the bitmap layer on with bitmap_8bpp 1 and bitmap_window 0,0-0x13F,0xDF.
comment compute_camera_ground_speed When camera_velocity_x/_y changed (camera_ground_speed_vx/_vy): camera_ground_speed = sqrt(vx * vx + vy * vy) << 13 with vx, vy the velocities >> 5 and >> 4, camera_ground_direction the angle of (>> 9, >> 8) + 0x80, camera_map_direction the angle of (>> 8, >> 8); hands back that angle's octant (else octant as it came).
comment compute_camera_slope_speed The same on a floor slope (slope_factor, 0 flat), recomputed only when the velocity or slope_factor changed: camera_slope_speed, camera_slope_direction (+ 0x80), camera_scroll_direction (angle of the velocities >> 5). On a slope camera_velocity_y is taken >> 5 and divided by 1 + |slope_factor| (16.16), not >> 4 as when flat.
comment debug_sprite_mover_update Unused: the debug sprite's handler: sprite 0x8DB8 (palette 0x130, layer 0x32) at debug_sprite_x, debug_sprite_y; held 1P jump and 1P start move it right and left, 2P jump and input bit 25 down and up, a pixel a frame; the two are shown in hex at text cells (3,22) and (8,22).
comment get_tile_set_list Hands back in script_list the vram script list of tile set tile_set from the stage's table at script_list: 16-bit offsets from blank_tile_set_lists for stages 0-2 and 0x11-0x13, 32-bit pointers for the others; stage_offset gets stage * 2.
comment get_tile_set_list_bank Hands back script_list + 0x100000 for stages 3-0x10 and 0x13, unchanged for stages 0-2, 0x11 and 0x12.
comment get_tile_set_script_bank Hands back script + 0x100000 for stages 3-0x10 and 0x13, unchanged for stages 0-2, 0x11 and 0x12.
comment index_stage_list_pair Unused: reads two per-stage tables at once: first_entry and second_entry each become the long at their address + stage_offset, plus section_offset.
comment init_stage_view Sets up a stage's view: display off 8 frames, default palettes, the tile sets of stage_initial_tile_sets, zoom, blending, the NBG1-3 layers; the camera at the stage's origin, view offsets, scroll_end and the first section's speeds, limits, scroll mode and direction from the per-stage tables; scene steps, shake and scroll skip panel reset.
comment is_direction_in_quadrant Looks through count entries of sine_table from sine_entry for the pair section_direction_y lies between: found (1 in its low byte) with sine_entry left there, or 0 after count entries; a direction straight along an axis (0x4000 or 0xC000, the other 0) is found at once.
comment is_player_at_bottom_edge Hands back 1 (at_edge's low byte) when object's screen_y is at or below push_edge_bottom, else 0; 0xA past it also sets player_past_leading_edge.
comment is_player_at_diagonal_edge_30 Hands back 1 (at_edge's low byte) when object is on or above the line screen_y = screen_x / 2 + edge_offset; a second pass at edge_offset - 0xA sets player_past_leading_edge, which it clears first. Its inner test, screen_x against 2 * (screen_y - edge_offset), is the same line and never passes.
comment is_player_at_diagonal_edge_60 Hands back 1 (at_edge's low byte) when object is on or below the line screen_y = screen_x / 2 + edge_offset. It clears player_past_leading_edge, and its second pass, at edge_offset - 0xA, takes the line 0xA back, so both count from 0xA before the line; its inner test is the same line and never passes.
comment is_player_at_diagonal_edge_c0 Hands back 1 (at_edge's low byte) when object is on or above the line screen_y = edge_offset - screen_x / 2; a second pass at edge_offset - 0xA sets player_past_leading_edge, which it clears first. Its inner test is the same line and never passes.
comment is_player_at_diagonal_edge_f0 Hands back 1 (at_edge's low byte) when object is on or below the line screen_y = edge_offset - screen_x / 2. It clears player_past_leading_edge, and its second pass, at edge_offset - 0xA, takes the line 0xA back, so both count from 0xA before the line; its inner test is the same line and never passes.
comment is_player_at_left_edge Hands back 1 (at_edge's low byte) when object's screen_x is at or left of push_edge_left, else 0; 0xA past it also sets player_past_leading_edge.
comment is_player_at_right_edge Hands back 1 (at_edge's low byte) when object's screen_x is at or right of push_edge_right, else 0; 0xA past it also sets player_past_leading_edge.
comment is_player_at_top_edge Hands back 1 (at_edge's low byte) when object's screen_y is at or above push_edge_top, else 0; 0xA past it also sets player_past_leading_edge.
comment is_player_facing_scroll Hands back 1 (facing's low byte) when heading, a target_heading, is within a quarter turn either side of the middle of the scroll's quadrant (sine_entry's quarter of sine_table: 0x180, 0x280, 0x380 or 0x80), else 0.
comment is_player_pushing_edge Hands back 1 (pushing's low byte) when the player object object is at a leading screen edge for push_edge_case (the push_edge_* lines and, in cases 2, 4, 6 and 8, a diagonal); at a trailing edge it sets player_at_trailing_edge and hands back 0. In stage 3 between scroll_position 0x87A9 and 0x96F1 case 8 counts only the top edge.
comment load_camera_start_section Puts the camera and the scroll at the start of section stage_start_section of the stage's tables, as init_stage_view does for the first: camera_x/_y at the section's start (+ camera_origin_x/_y), its x and y limits and direction, scroll_position at its start and its end next, scroll_speed, follow_base_speed and its scroll mode.
comment load_tile_set Loads tile set tile_set of the stage: every vram script of its list in stage_tile_sets (up to a negative entry) through run_vram_script, list and scripts moved for the stage (get_tile_set_list_bank, _script_bank); then tile_sets_in_progress = wanted_tile_sets, which loaded_tile_sets takes after the fourth slot (tile_set_slot 0 again).
comment move_camera_along_path Moves the camera along the section by speed: camera_x/_y by section_direction_x/_y * speed unless that passes section_limit_x/_y (from camera_origin, the way the limits run), scroll_position unless it passes the section end; camera_limits_reached keeps which stopped (all three: snap_camera_to_section_end). Hands back scroll_position + speed.
comment move_camera_to_start_section load_camera_start_section, then update_camera_view; y gets view_y.
comment reload_changed_tile_set Checks one slot of wanted_tile_sets a frame (tile_set_slot, from 0 again when the wanted sets changed): a set not among the four of loaded_tile_sets is loaded (load_tile_set). Meanwhile tile_sets_in_progress keeps the wanted sets; after the fourth slot loaded_tile_sets takes them and tile_set_slot is 0 again.
comment run_scroll_skip_mode Unused: the scroll skip panel's frame. Closed (scroll_skip_open 0), it opens while service and 2P start are held (input_ports_active bits 0 and 5): scroll_skip_last_point for the stage, stage_start_section 0xD, armed flags cleared, the panel printed. Open: scroll_skip_draw_values, _next_point, _previous_point, _check_call, _check_exit.
comment run_stage_05_switch Stage 5's switch to stage 0's view by stage_05_switch_step: every layer blended, a fade to black, stage 0's tile layers (coast, horizon, sky, city, tower top) with the camera at 0xF80,0x670 and NBG1-3 scrolled as stage 0's, a fade in from black; then scroll_position 0x5E5A, stage 0, blending off and stage_05_switch_step -1.
comment scroll_at_scroll_speed Scroll mode 0: advance_scroll_unless_ended at scroll_speed.
comment scroll_skip_check_call Unused: "CALL:1P START": 1P start pressed after a release (scroll_skip_call_armed) moves the camera to stage_start_section (move_camera_to_start_section), every frame while it stays held, as the flag is not cleared; nothing while both starts are held.
comment scroll_skip_check_exit Unused: "EXIT:2P START": 2P start pressed after a release (scroll_skip_exit_armed) closes the panel (scroll_skip_open 0, scroll_skip_erase_panel); nothing while both starts are held.
comment scroll_skip_draw_values Unused: one value a frame (scroll_skip_value_index 0-2): the stage's entry for stage_start_section in scroll_skip_value_lists[index] (x position, y position, scroll total) in hex at its cell (attribute 0x200); after the third, stage_start_section in hex at (24,15) (attribute 0x600).
comment scroll_skip_erase_panel Unused: prints scroll_skip_blank_text over the panel (print_text_list).
comment scroll_skip_next_point Unused: "NEXT:1P JUMP": 1P jump pressed after a release (scroll_skip_next_armed) adds 1 to stage_start_section, which is kept at most scroll_skip_last_point; nothing while both jumps are held.
comment scroll_skip_previous_point Unused: "BACK:2P JUMP": 2P jump pressed after a release (scroll_skip_back_armed) takes 1 off stage_start_section, not below 0; nothing while both jumps are held.
comment scroll_skip_print_panel Unused: prints the scroll skip panel (scroll_skip_panel_text) with print_text_list.
comment scroll_skip_total_cell Unused: hands back text cell (34,17), where the SCROLL TOTAL value goes.
comment scroll_skip_x_position_cell Unused: hands back text cell (34,18), where the X POSITION value goes.
comment scroll_skip_y_position_cell Unused: hands back text cell (34,19), where the Y POSITION value goes.
comment scroll_slowing_down Scroll mode 3: scroll_speed - 1 each time scroll_mode_timer runs out (reloaded from scroll_mode_duration), down to 1; advances at scroll_speed (scroll_at_scroll_speed).
comment scroll_speeding_up Scroll mode 4: advances at scroll_ramp_speed, + 1 each time scroll_mode_timer runs out (reloaded from scroll_mode_duration); once it has reached scroll_speed, scroll mode 0 and scroll_ramp_speed 1, and that frame only set_camera_velocity with the speed its caller passed.
comment set_camera_velocity camera_velocity_x/_y = section_direction_x/_y * speed's low half; camera_shake_length and camera_shake_wave 0x200, update_camera_with_shake, then (unless scroll_ended) update_background_scroll.
comment set_nbg0_scroll_from_camera NBG0's scroll from the camera's pixels (x & 0x3FF, y & 0x1FF) and the camera's place in the stage's block map: tile_block_column = camera_x_pixels / 0x200, tile_block_row = camera_y_pixels / 0x100 * stage_block_map_width. Hands back scroll_x with camera_x_pixels in its low half.
comment set_screen_push_edges From section_direction_x/_y's signs: the quadrant (sine_entry at its quarter of sine_table, count 0x100 for is_direction_in_quadrant) and the screen edges players push in it (push_edge_left, _right, _top, _bottom; 0xFFFF none), with push_edge_case 1, 2, 4, 6, 7 or 8 (1 and 7 for straight +x and -y).
comment snap_camera_to_section_end Puts the camera at the section's x and y limits (+ camera_origin_x/_y) and scroll_position at the section's end.
comment spawn_debug_sprite_mover Unused: once per stage view (debug_sprite_spawned), an object with handler debug_sprite_mover_update (allocate_object_outside_loop).
comment start_stage_view init_stage_view for every stage but 0x11 (unused_scroll_flag set first, which init_stage_view clears); stage 0x11 only clears ending_scene_step.
comment update_background_scroll update_nbg1_scroll, update_nbg3_scroll and update_nbg2_scroll; then follow_base_speed is forced to 0x10 once scroll_position reaches the first position of the stage's stage_fast_scroll_ranges list past the current section's start.
comment update_camera_view camera_x_pixels and camera_y_pixels from camera_x/_y's whole parts, view_x/_y from them minus view_offset_x/_y; hands back view_y.
comment update_camera_view_y camera_y_pixels from y's whole part (16.16) and view_y = it minus view_offset_y, which it hands back.
comment update_camera_with_shake update_camera_view, then while camera_shake_active every other frame camera y moves by sine_table at camera_shake_wave + camera_shake_step (* 0x20: up to 8 pixels), the step going up 0xA a frame until camera_shake_length ends the shake; NBG0's scroll follows. Hands back view_y.
comment update_fade One step of a fade of the mixer's colour offsets (color_offset_red, _blue, _green) by fade_kind: 0 to black (0x3F down to 0x20), 1 from white (0x1F down to 0), 2 from black (0x20 up to 0x3F), 3 to white (0 up to 0x1F), fade_speed a frame; fade_step 0 sets the start, and fade_kind is -1 the frame after the end.
comment update_scroll The scroll's frame by scroll_mode (update_scroll_at_end from scroll_end on): 0 at scroll_speed, 1 stopped, 2 paused, then the next mode of scroll_mode_script, 3 slowing, 4 speeding up, 5/6 nearby_floor_blocks_locked set/cleared; 7 and 8 follow_base_speed plus the speed of the fastest player pushing a leading screen edge facing the scroll.
comment update_scroll_at_end scroll_ended = -1; only stage 3 goes on, its end by stage_03_end_step: two end scripts and NBG0's pages, the camera climbing straight up, sped up to run at 0x25 for stage_03_end_timer (0x78) frames; then camera_y 1, a third script, a slowdown to a stop, all_players_path_request 0x23 and spawn_stage_03_end_pillar; the camera then stays still.
comment update_stage_00_exit_scene Stage 0's exit by stage_00_exit_step: the camera climbs straight up at scroll_speed (0x35, later down to 2), the tower wall and beam scripts run as NBG0's y scroll passes 0x20 and 0xF6, pages changing with stage_00_exit_timer; a fade to white; then NBG0 and NBG1 zoom in on new pages (0x2000000 / stage_00_exit_zoom >> 8) as colours come back.
comment update_stage_tile_layers A stage frame's tile layers: animate_stage_palette, then (until scroll_ended) clamp_camera_pixels, NBG0's scroll and wanted_tile_sets from the 2 x 2 blocks of stage_block_maps around the camera, a set a byte; when they differ from loaded_tile_sets reload_changed_tile_set (a reload past its second slot keeps its sets), else tile_set_slot 0.
comment clip_line_at_x Moves the line's start dx along x: start_x += dx and start_y += (end_y - start_y) * dx / (end_x - start_x), start_y kept when the line is vertical; unclipped_y gets start_y before the move.
comment clip_line_at_y Moves the line's start dy along y: start_y += dy and start_x += (end_x - start_x) * dy / (end_y - start_y), start_x kept when the line is level; unclipped_y gets start_y before the move.
comment clip_line_end_inside Nothing to move: the end is inside bitmap_window (outcode 0), or its outcode has both left and right (3, 7, 11).
comment clip_line_to_bottom_edge Moves the line's start onto bitmap_window_bottom (clip_line_at_y by bitmap_window_bottom - start_y).
comment clip_line_to_left_edge Moves the line's start onto bitmap_window_left (clip_line_at_x by bitmap_window_left - start_x).
comment clip_line_to_right_edge Moves the line's start onto bitmap_window_right (clip_line_at_x by bitmap_window_right - start_x).
comment clip_line_to_top_edge Moves the line's start onto bitmap_window_top (clip_line_at_y by bitmap_window_top - start_y).
comment clip_line_to_window Clips a line to bitmap_window: moves its start onto the edge outcode names (top or bottom when a corner code also has left or right), then swaps the ends and does the same with end_outcode, so the line comes back with its ends swapped; unclipped_y gets the last moved end's y from before the move.
comment clip_rect_to_window Sorts the rectangle left, top, right, bottom and clips it to bitmap_window: unclipped_edges comes back 0 when it lies wholly outside, else 0xF less a bit for each edge moved (1 left, 2 right, 4 top, 8 bottom). The outside tests take left and top unsigned, so a rectangle starting at a negative x or y counts as outside.
comment draw_circle Unused: a midpoint circle of radius |radius| around x, y in color, eight points a step (plot_circle_point_8bpp or _4bpp), none when it is wholly outside bitmap_window. current_player holds 2 bits a point (plot, or plot if inside), but points 4, 6 and 7 are plotted out of the order they assume; at 4 bpp odd pixels get color << 3, not << 4.
comment draw_circle_polygon Unused: meant to draw a circle of radius |radius| around center_x, center_y in circle_color: 64 points (sine_table * radius >> 14 mirrored into eight octants) in bitmap_draw_buffer joined by draw_line. It cannot: two mirrored stores are off (mirror_cursor + 0x7E, point_cursor + 0x7F) and the line loop steps 2 bytes, joining (x, y) to (y, next x).
comment draw_saved_bitmap_rect Unused: meant to copy a save_bitmap_rect buffer back onto the bitmap row by row, from its saved_bitmap_rect header (row_bits, rows); the row copy is an instruction the V60 cannot run, so the C stops at invalid_operand on the first row.
comment fill_flat_top_triangle Unused: the triangle with its top edge from left_x to right_x on row top_y and its apex at bottom_x, height rows lower: both edges stepped in 16.16, one polygon_span a row at span_cursor (addresses from current_player, 0x200 a row), then fill_span_list.
comment fill_quad Unused: fills the convex quadrilateral left_x, top_y / second_x, second_y / third_x, third_y / bottom_x, bottom_y (in order round it) with fill_color at 8 bpp: edges walked in 16.16 from the top vertex into polygon_span records at bitmap_draw_buffer, then filled; flat rows go to fill_span_8bpp or the triangle code. Keeps the vertices on the stack.
comment fill_span_at_point Unused: fills the one run through point_x, point_y up to border_color (nothing outside bitmap_window or on fill_color or border_color). At 8 bpp flood_fill_span does it; the 4 bpp nibble scans start from outcode (0) instead of point_x and read through right_cursor, which nothing sets: that path cannot work as written.
comment fill_span_at_point_fill_run Unused: fill_span_at_point's 4 bpp fill of x left_x to right_x from r14 with fill_color's low nibble: an odd first pixel (high nibble), an aligning byte, longs, a halfword, a byte, an even last pixel (low nibble). With one pixel left after the halfwords (or none after the aligning byte) it still writes the byte: 2 pixels too many.
comment fill_span_at_point_left_byte Unused: fill_span_at_point's 4 bpp left scan: the byte to the left (two pixels), its high nibble, the nearer odd pixel, first: fill_color or border_color there ends the run after it, as does the count's end; else its low nibble (fill_span_at_point_left_even_pixel).
comment fill_span_at_point_left_even_pixel Unused: fill_span_at_point's 4 bpp left scan: the low nibble, the farther even pixel: fill_color or border_color ends the run after it; else the next byte to the left (fill_span_at_point_left_byte) while the count lasts; then fill_span_at_point_scan_right.
comment fill_span_at_point_right_byte Unused: fill_span_at_point's 4 bpp right scan: the next byte to the right, its low nibble, the nearer even pixel, first: fill_color or border_color there ends the run before it, as does the count's end; else its high nibble (fill_span_at_point_right_odd_pixel).
comment fill_span_at_point_right_odd_pixel Unused: fill_span_at_point's 4 bpp right scan: the high nibble, the farther odd pixel: fill_color or border_color ends the run before it; else the next byte (fill_span_at_point_right_byte) while the count lasts; then fill_span_at_point_fill_run.
comment fill_span_at_point_scan_right Unused: fill_span_at_point's 4 bpp right scan from x right_x + 1 to bitmap_window_right, a byte (two nibbles) at a time through right_cursor (which nothing sets); with right_x already at the window's right edge straight to fill_span_at_point_fill_run.
comment fill_span_list Unused: fills span_count polygon_span records from bitmap_draw_buffer (bitmap address and length, a negative length skipping the row) with color at 8 bpp: an odd first byte, 16-byte blocks, then 8, 4, 2 and 1.
comment fill_span_list_from_r8 Unused: fill_span_list with polygon_color, the colour fill_quad keeps.
comment fill_triangle Unused: fills the triangle top_x, top_y / middle_x, middle_y / bottom_x, bottom_y with color at 8 bpp: sorts the points by y, sets current_player to bitmap_address (the base of the span addresses) and the polygon_span list at bitmap_draw_buffer, then fill_triangle_sorted.
comment fill_triangle_sorted Unused: the points sorted top to bottom: one row (fill_span_8bpp from the leftmost to the rightmost x) when all share y, fill_flat_top_triangle when the top two do; else both edges stepped in 16.16, a polygon_span (bitmap address, length) a row at span_cursor, turning at the middle point; then fill_span_list.
comment flood_fill Unused: scanline flood fill from seed_x, seed_y with fill_color up to border_color in bitmap_window (none when the seed is outside or either colour): the seed's span, then a ring queue of flood_fill_record spans in bitmap_draw_buffer (object_cursor in, last_object out) worked off a row above and below each; the queue reaches over string_queue.
comment flood_fill_push_span Unused: fills the run at r14 (x left_x) in the scanned row (flood_fill_span) and queues its flood_fill_record at object_cursor: direction and row y + direction, its right and left ends, the flood_fill_push_parent_* range; object_cursor wraps from 0xCCC4 to bitmap_draw_buffer. right_x and right_address come back at the run's right end.
comment flood_fill_scan_row Unused: scans row y + direction under or over the current span, from flood_fill_left_x to flood_fill_right_x (copied into the flood_fill_push_parent_* range): every run not in the fill or border colour goes through flood_fill_push_span, the scan going on 2 past its right end; hands back the x the scan reached.
comment flood_fill_scan_row_skip_parent Unused: a queued span's row in direction: toward its own flood_fill_direction the whole row (flood_fill_scan_row); back toward its parent only the parts left of flood_fill_parent_left_x and right of flood_fill_parent_right_x + 1, each run there through flood_fill_push_span.
comment flood_fill_skip_parent_right Unused: flood_fill_scan_row_skip_parent's right part: from flood_fill_parent_right_x + 1 to flood_fill_right_x, two pixels at a time, each run of fillable pixels pushed through flood_fill_push_span; hands back scan_x.
comment flood_fill_scan_seed_rows Unused: the seed span's neighbour rows: flood_fill_scan_row above (direction -1) when y is below window_top, and below (1) when y is above window_bottom; hands back the x the last scan reached.
comment flood_fill_scan_span_rows Unused: the same as flood_fill_scan_seed_rows for a span taken from the queue, through flood_fill_scan_row_skip_parent.
comment flood_fill_span Unused: the run through the pixel at r14 (x left_x) at 8 bpp: scans left to window_left and right to window_right up to the fill colour (fill_pattern) or border_color, then fills it (flood_fill_span_fill_run); left_x and r14 come back at its left end, right_x and right_address at its right end. Also fill_span_at_point's 8 bpp path.
comment flood_fill_span_fill_run Unused: fills the run from left_x to right_x with fill_pattern from r14: an odd first byte, 16-byte blocks, then 8, 4, 2 and 1; hands back the address of the last byte written.
comment flood_fill_span_left_pair Unused: flood_fill_span's left scan: the next two pixels to the left (a halfword before r14), the nearer one first: the fill or border colour there ends the run after it (flood_fill_span_set_left_x); else flood_fill_span_left_pixel for the farther one.
comment flood_fill_span_left_pixel Unused: flood_fill_span's left scan: the farther pixel of the pair (or an odd first one): the fill colour ends the run after it, the border colour or the count's end too (an odd last pixel checked first when pixel_count is odd); else the next pair (flood_fill_span_left_pair).
comment flood_fill_span_right_pair Unused: flood_fill_span's right scan: the next two pixels (a halfword at right_cursor), the nearer one first: the fill or border colour there ends the run before it (flood_fill_span_set_right_x); else flood_fill_span_right_pixel for the farther one.
comment flood_fill_span_right_pixel Unused: flood_fill_span's right scan: the farther pixel of the pair (or an odd first one): the fill colour ends the run before it, the border colour or the count's end too (an odd last pixel checked first when pixel_count is odd); else the next pair (flood_fill_span_right_pair).
comment flood_fill_span_scan_right Unused: flood_fill_span's right scan from the pixel after the seed (seed_address + 1) toward window_right, a pair at a time (flood_fill_span_right_pair, _right_pixel); with right_x already at window_right straight to flood_fill_span_fill_run.
comment flood_fill_span_set_left_x Unused: left_x -= seed_address - r14, the run's left end x (r14 on its first pixel), then flood_fill_span_scan_right.
comment flood_fill_span_set_right_x Unused: right_x += right_cursor - seed_address, the run's right end x (right_cursor on its last pixel), then flood_fill_span_fill_run; right_cursor comes back as the last byte written.
comment get_point_outcode Where point_x, point_y is outside bitmap_window, in outcode: 1 left, 2 right, 4 above, 8 below (0 inside); both come back sign-extended.
comment get_window_outcode Swaps the line's two points (start_x/_y with end_x/_y) and hands back in outcode where the new start is outside bitmap_window (get_point_outcode: 1 left, 2 right, 4 above, 8 below).
comment plot_circle_point_4bpp Unused: one of draw_circle's points at 4 bpp: the top two bits of current_player's mask, shifted out: the first plots, the second plots only inside the window (r14, window_top, _right, _bottom); the byte at (point_y << 8) + (point_x >> 1) + bitmap_base gets high_color in its high nibble at odd x, low_color in its low one at even x.
comment plot_circle_point_8bpp Unused: one of draw_circle's points at 8 bpp: the same test of current_player's top two bits, then color at (point_y << 9) + point_x + bitmap_base.
comment save_bitmap_rect Unused: meant to save the rectangle left, top to right, bottom of the bitmap (corners sorted) into buffer: a saved_bitmap_rect header (width in bits, * 8 at 8 bpp, * 4 kept to a byte at 4 bpp, and rows), then each row. The row copy is an instruction the V60 cannot run, so the C stops at invalid_operand on the first row.
