move_data reads a row of Moves from the cartridge image ($0E:$4000, each row
checked against its own id); move_without_effect answers the refusals the
effect routines make on bytes already in WRAM: a stat stage at its limit or a
stat at 1/999, Mist or a substitute against a stat-lowering move, a status
move against a statused, Poison-type or (Electric) Ground-type target.
MacroState::move_without_effect defaults to false.
resolve_wram.py now follows the decomp's const and _RS counters and a struct
macro's field labels, so battle_struct fields resolve and 77 of 81 pinned
addresses are re-derived with no disagreement.
The first cartridge run found what the synthetic trace could not. In the
Viridian Forest north gate the font bit rose on one frame and DisplayTextID's
argument reached wSpriteIndex twenty frames later: DisplayTextIDInit loads the
font's tiles into VRAM first. Until then the byte still names the previous
text's subject, which may be the person in front of the fly from a conversation
that did not pay. So the watch now remembers the byte from the last ready
frame and reads it once, when it changes or after 45 samples, and only while
the bottom dialogue box is drawn (the start menu draws its own elsewhere); a
box that closes first is read on the closing sample if the dialogue box was up.
tests/rom_engage.rs, gated on FLY_ROM and FLY_ENGAGE_CHECKPOINT: from the rung-10
Pewter checkpoint, with the checkpoint's own v6 ledger restored (the migration,
on real game state), a scripted walker goes down Route 2 into the forest's north
gate, talks to the old man three times (one payout, 0.10) and picks up the
Antidote ball at (25, 11) (one payout, 0.15, "FOUND ITEM #11"); pressing A at
the empty tile pays nothing, and after rolling the emulator back to before the
pickup, taking it again pays nothing. No boundary payout lands while an indoor
header is loaded.
It also measured the warp transition: for about thirty frames of
PlayMapChangeSound wCurMap already names the destination while the tileset and
warp table are still the map being left, so the exit the fly stands on is
classified by the map it belongs to -- a town door still pays its on-exit half,
keyed under the building's id as before, and a building's door does not. A unit
test pins both directions.
The operator's decision of 2026-09-23: pay the fly for engaging with what is
inside a building, and stop paying it for walking back out of one. Chosen over
a pad rule and over weighting the choice, and like v0.5.0's catch reward it is
a catalog change, not a loop-review fix.
Indoors. engage::indoor is two of the cartridge's own tables: not outside by
CheckIfInOutsideMap (tileset OVERWORLD or PLATEAU; WarpFound2 labels the other
branch .indoorMaps) and not a place BikeRidingTilesets lets the bike be ridden
(OVERWORLD, FOREST, UNDERGROUND, SHIP_PORT, CAVERN). That is every house, mart,
center, gym, gate, lab, museum, ship, tower, mansion and hideout, and not
Viridian Forest, a cave, the Underground Path or the dock.
`talk`, +0.10, 100 ms, the catalog's tenth kind. Paid when the text box closes
on a conversation that (1) opened on the sample after one where the fly had the
joypad (state::controllable) and was standing still (wWalkCounter zero, the only
state the overworld reads A in) on the same tile; (2) is with the thing in
front: DisplayTextID copies its argument into wSpriteIndex, a sprite slot up to
wNumSprites whose sprite stands on the tile the player faces (or one further
across a counter, on a tileset with counter tiles), or a text id matching the
sign on that tile; an item ball is not a person; (3) opened indoors; (4) closed
on the same map. Keyed talk:<map>:sprite:<slot> / talk:<map>:sign:<id> in the
lifetime `seen` ledger, which is checkpointed and survives a rollback -- not the
macros' session `talked` ledger.
`item`, +0.15, 120 ms, the eleventh. An item ball is a toggleable sprite of this
map (wToggleableObjectList) whose wMapSpriteExtraData is (item id, 0), the shape
LoadMapHeader writes for an ITEM object_event and nothing else; PickUpItem sets
its global bit in wToggleableObjectFlags after GiveItem succeeds. A hidden item
is a bit of wObtainedHiddenItemsFlags, set by FoundHiddenItemText after GiveItem
and by nothing else. Either pays when its bit rises between two playable
samples, once per item (item:<global> / hidden:<index>) for the ledger's life.
`boundary` still writes every key indoors, so exit_visited answers what it did
and the macros see no change, but emits nothing on an indoor map. Outdoor,
forest and cave exits pay as before.
v6 -> v7. STATE_VERSION stays 4 and no field is added: the new ledgers are keys
in `seen`. A v6 state restores with no talk: keys, and the first playable sample
that finds `items:seeded` absent writes one key per item the game already shows
as taken, pays for none, and marks the seed -- so a rollback that un-takes a v6
pickup cannot pay for it. The two item balls a script reveals (the Rocket
Hideout's Silph Scope and Lift Key, toggles $87 and $88, the only ITEM entries
toggleable_objects.asm starts OFF) are left out of the seed. MIGRATES_FROM is
["pokered-unique8-v6"]; v5 is no longer migrated.
Feed kinds: both publish on `explore`, the family of new ground and a door found,
at the same quiet scale; not `area` (maps, notable), `story` or `wildwin`. No
feed-protocol change.
The compatibility string differs from main's in exactly one segment:
pokered-unique8-v6 -> pokered-unique8-v7.
Tests: catalog values and order; indoor over all 24 tilesets; a talk pays once
per (map, object), not while the box is open, not re-talked, not outdoors/in the
forest/in a cave, not for text opened with the joypad taken, simulated, scripted,
mid-step, about someone not in front, or the start menu; not across a warp or a
rollback; counter reach only with counter tiles; item balls, people, trainers,
hidden items, the seed and the script-shown balls; boundary indoors records and
pays nothing; a v6 state and a v6 FLYSIM01 envelope migrate with the new ledgers
empty and the items seeded. rom.rs's bedroom walk now proves the stairs are
recorded and unpaid on the cartridge.
wSpriteIndex, wWalkCounter, wMapSpriteExtraData, wToggleableObjectFlags,
wToggleableObjectList and wObtainedHiddenItemsFlags, resolved by
tools/resolve_wram.py from ram/wram.asm at the pinned commit and bracketed by
addresses symbols.rs already carries. Two of the brackets needed the tool to
count NUM_STATS and NUM_CITY_MAPS, which the decomp defines as const_value over
an enumeration; both are now counted up to the line that defines them.
Section 12.4 and row 37 read a macro the cartridge ended by taking the
joypad as a refusal and wrote the target blocked and the tile pushed on
the spot. A trainer who sees the fly takes the joypad the same way. In
the Pewter Gym the walk toward the leader crossed the Jr. Trainer's
sight line, and BROCK went into the blocked ledger for ten brain minutes
while the fly lost, blacked out and walked back to a room whose way out
was the pad again.
The entries now wait for the cartridge to give the joypad back: back in
the overworld is a refusal, written as before; a battle is a battle and
teaches the ledgers nothing. Tests for the rung's people off the screen,
facing one of them, and the challenge; the two push-back tests now hand
the joypad back before they read the ledgers.
wCurMap names the new map thirty-two frames before the header, the
coordinates and the warp table follow it, while the screen fades, and
nothing sets the joypad bits until the fade ends. The seam read
"map 54 at (16, 17)" -- Pewter City's doormat under the gym's id -- as
an overworld, dealt a pad, and a walk started there planned over the
wrong map; what it aimed at and the tile it left went into the ledgers
under the new map's id. Live, GO OUT started and finished in 0.05 s.
The driver reads a tear as the map byte having changed while the fly
still stands on a warp of the loaded table that leads to the map the
byte names (a doormat's LAST_MAP under a town's id included), deals it
as Unknown with an empty pad, and records no ground from it. Teleport
pads do not change the map byte, so they are never a tear; a tear is
bounded at TEAR_FRAMES all the same.
Between a trainer's challenge closing and the battle screen the
transition runs 219 frames with every joypad and script bit clear, so
the scene read overworld and a pad was dealt: a walk toward the leader
pressed into the animation, gave up after three refused steps and put
him in the blocked ledger, and the trainer's conversation read as over.
wCurOpponent is set when a battle is decided and cleared by EndOfBattle
with wIsInBattle. It is not in the generated table; it is the byte
between wIsInBattle's flag byte and wBattleType, both neighbours
checked against the table, and controllable() reads it.
CheckSpriteAvailability writes $ff into the image index of a sprite
outside its window, and state::npcs reports what is drawn. From the
Pewter Gym's doormat that is the guide alone, already talked to, so the
rung's list was empty: GO OBJECTIVE had nothing to aim at and GO OUT,
withheld only while the rung's person is in the room, was the pad.
Outside, GO OBJECTIVE walked back in. BROCK was twelve rows up.
state::offscreen_npcs reports the sprites the cartridge hides only for
being outside the window, read from bytes the seam already has, and
objective_targets reads them for a person. Nothing else does: a sprite
out of the window may be a toggleable object switched off, and GO NPC,
TALK and objects keep what is drawn.
Facing any of the rung's people is the arrival: with three in a gym,
leaving out only the one ahead walked GO OBJECTIVE between the leader
and the trainer.
Measured with the real brain on the seeded pocket: it pressed GO ROUTE
first, while the gym's door was still the second tier's answer. That
refusal wrote the door to the blocked ledger and also withheld GO ROUTE
on the tile, so the next deal -- the last resort, whose walk now goes
where it can -- was never made, and the fly waited out the window.
Only a last resort deals goals the ledger is already resting, so the
refusal worth remembering where the fly stands is the one that taught
the ledger nothing new. A refusal that writes a new exclusion changes
the next deal by itself.
Row 57's pocket had a way out: the road east, three tiles from the fly
and resting in the blocked window. The last resort ignores that window
but narrows to the ways toward the objective, and the only one was the
gym's door beyond the fence, so the route search refused and the road
was never tried. With the refusal withheld the pad went empty and the
fly waited out the road's window, ten brain minutes.
The narrowing is a preference and the dealer cannot search. When start's
route search cannot reach the preferred ways, it tries the rest of the
last resort, nearest reachable first, exactly as every walk chooses. The
pad is unchanged; only where the pressed macro walks.
The ROM proof now asserts the fly leaves the pocket inside a brain
minute: frame 517 (0.14 minutes), against 36,325 (10.1) on the base.
Row 57, live on rung 10 for two hours: the Pewter City pad was GO ROUTE
and nothing else, refused `no route` every 800 brain ms, no button
pressed. The dealer asks the cheap question and start asks the real
one; the blocked ledger closes the gap for every list except a last
resort, which ignores that ledger by design. So GO ROUTE's refusal,
which wrote the gym door to the ledger, could not take the button off
the pad, and re-stamped the door's window every hold, which kept GO
OBJECTIVE's only goal excluded for ever.
A `no route` or `precondition` refusal is now recorded with the tile
the fly stood on, and the dealer does not deal that button from that
tile for the blocked window. It is dealt again the moment the fly
stands anywhere else or the window closes. Nothing presses; a pad with
nothing runnable is empty and the fly waits.
Row 57. The pushed ledger (row 37, no window) recorded the tile a macro
set out from when the cartridge moved the fly. For a TALK that is the
tile the script fired on; for a walk it can be the far side of the map.
Pewter City's youngster takes the joypad on four tiles by the road east,
GO ROUTE aims east at Route 3 every time, and one walk from the town's
south entrance walled the south entrance, twenty-six tiles from the
script. Walks start wherever the last one ended, so the walls fenced the
fly into a pocket no route could leave.
A walk now records the tile it last stood the fly on, which is where the
cartridge took over. Every other macro keeps the tile it started on.
FLY_PROBE_CATCH=route drives the real PokemonPalette from a checkpoint,
one uniform choice per hold, prints every pad it deals and every refusal
with its reason, and reads the frame the pad comes down to one refusing
button on: which list emptied why, the no-window ledgers, and whether the
route search reaches each way out. FLY_PROBE_SEED_* rebuild the session
ledgers a restore starts empty; FLY_PROBE_RATCHET starts from the
ratchet's rollback snapshot; FLY_PROBE_HOLD presses raw directions first.
PokemonPalette gains inspect(), fences() and a doc(hidden) ledgers_mut()
for the survey. The loop never calls them.
The other half of row 56. The Pewter Gym guide's conversation is fifty-two boxes
long and a third of the presses that walk it are `NO`, whose B advances a plain
box exactly as `NEXT`'s A does. Every one of them cleared the pending `TALK`, so
the talked ledger never learned the conversation had happened, `TALK` stayed on
the overworld pad, and an A press at him reopened the whole ring: thirty brain
minutes of scene `dialog` with no walk macro dealt.
12.4's rule -- "the fly said no, so the thing is still on offer" -- is about a
declined *offer*, and which of the two a `NO` was is decided where it can be
seen: by whether the box closes on it. So the decision moves to the frame the
text goes away, which is where the talked entry is written anyway, and the
reading is `pending_answer`: armed only by an answer to a prompt this crate can
read, alive for one hold. A declining `NO` still standing there is a `NO` the
box closed on; anything else is a conversation walked through to its end.
Row 41 read the border at (11, 6)-(19, 11) because that is where the Pokemon
Center's script puts it, and named the limit in its own residual: "Red places a
two-option menu where the script asking for it says, so a prompt drawn elsewhere
reads false and keeps the pad it had". The Pewter Gym guide draws the same menu
at (14, 7)-(19, 11) with the cursor at column 15. Surveyed over 260 presses from
the live checkpoint: the box is drawn on 10 frames, `yes_no_prompt` answered
false on all 260, and `wTextBoxID` read `TWO_OPTION_MENU` on exactly the 10.
So the pad was `NEXT, YES, NO` on a box that was a choice -- two channels for
one A press, which is 12.10's forbidden pair -- and the reopened-prompt
exclusion never armed, because it only judges an answer to a prompt this crate
can read.
The screen half is now the border drawn **around the cursor the game parked in
it**. One fact about `DisplayTwoOptionMenu` rather than about any script: the
cursor goes in the box's first interior column, so the left edge is one column
to its left, in both boxes surveyed. The top is not fixed -- the nurse's box
begins two rows above the first item and the guide's one -- so the top is found
and the figure is then read whole, as `waiting` and the move list are.
Row 56 is a gym's dialog for thirty brain minutes with YES, NO and NEXT dealt
in equal thirds, so the question is which box each press is answering and
whether the seam can see it at all. `state::drawn_boxes` asks the screen
instead of a pinned rectangle: every complete `TextBoxBorder` on the frame.
`FLY_PROBE_CATCH=dialog` walks the conversation one raw pulse at a time and
prints both readings of every frame side by side, with the pad the palette
deals for it, and tallies how the candidate readings separate the frames.
`FLY_PROBE_ANSWER` picks the button a frame with a box on it is answered with,
so the two arms of a choice can be walked separately.
Three conflicts, all of them two reviews appending in the same place, all
resolved by keeping both sides: `macros.md` (row 50 keeps section 12.18, row 55
becomes 12.19), `macros-traps.md` (both trap rows, both residual pairs, both arm
sections) and `scene_probe.rs` (both survey modes, `accept` and `shop`). Every
source file auto-merged.
The two rows compose on the cartridge: from the mart checkpoint the fly leaves
the counter, walks the town, and no `MOVE n` blocks any more, so the longest
chain of one macro blocked on an unchanged frame is 1 in any scene.
A mart's buy list scrolls. The cursor walks rows 0, 1, 2 and then the window
moves under it, so an item's position in the counter's stock is its cursor index
only for the first three entries; the offset that would name the rest is not a
pinned address. Pewter's counter carries seven items and the Antidote is its
fourth, so `BUY ANTIDOTE` there aimed a cursor step above the list's own max and
reported `blocked` on its first frame, having pressed nothing -- and a purchase
has no walk target, so nothing was recorded and the button came back on the next
hold.
One accessor answers "which index, if any" for both the pad and the plan, and the
clerk talking is refused through the seam rather than here.
`wListMenuID` keeps PRICEDITEMLISTMENU for a whole mart visit: the clerk's text
is printed from inside the mart's own routine and never goes through the display
that clears it. So every frame of the Pewter mart read "the priced buy list",
including the "Here you are! Thank you!" box the stream was looking at, whose
leftover cursor bytes belong to a two-option box.
Which screen is up is now read from the figure the game draws, the same
construction the dialogue box's `waiting` test and the YES/NO prompt already
make: a full-width box waiting for a press is the clerk (`ShopScreen::Talking`),
the item window drawn is the buy list, and the item window blank is the counter
menu.
The first pass of the press survey counted 187 refusals that were its own held
button: JoypadLowSensitivity acts on a key's edge, so a direction the fly was
already holding produced no press. With the pulse releasing first, the reading
is 264 honoured of 3,102 by the cursor bytes alone and 231 of 231 by the bytes
and the box.
Row 50: MOVE n reported blocked 890 times in 1,431 macros on the cartridge,
every one of them on a frame the seam read as an open move list with a
placeable cursor.
MoveSelectionMenu writes wTopMenuItemY 12 and wTopMenuItemX 5 and nothing in
the game clears them, exactly as the two-option box's geometry outlives its
box. SelectMenuItem then decrements wCurrentMenuItem back to the 0-based slot
on its way out, which lands straight back inside the one-based range the
accessor reads. So the whole of a turn -- the text, the animation, the
enemy's reply -- read as the fly's own turn on an open list, the pad dealt
the four move buttons on it, and the cursor step pressed at a list nobody was
reading until its budget ran out.
The reading is the figure the menu draws, the same construction text_box's
waiting and yes_no_prompt already make: a box at (4, 12) fourteen wide with a
horizontal run over its top-left corner and the junction tile at (10, 12).
Surveyed with one rollback pulse per battle frame over 3,102 frames at the
rung-9 forest checkpoint: by the cursor bytes alone a real directional press
was honoured on 264 of them, and by the cursor bytes and the box on 231 of
231.
A frame whose list is not on screen is between turns, whose pad is the one
NEXT that advances text.
The operator's decision of 2026-09-22: pay the fly for keeping a wild Pokemon,
bump the adapter properly, and restart the live run from an early checkpoint
rather than from scratch.
The rule. `catch` is the catalog's ninth kind, appended so the key order
`counts` serializes in does not move. 0.30 for a species this run had never
owned, 0.10 for a repeat, three payouts per species for the lifetime of the
ledger; the `species` rule is untouched, so a first catch of a new species pays
0.80 across two kinds. The catch is read from `wCapturedMonSpecies` ($d11c),
whose comment in ram/wram.asm is "0 if no mon was captured": ItemUseBall zeroes
it before every throw and writes wEnemyMonSpecies into it only on the branch
that keeps the Pokemon, and UseBagItem's `.returnAfterCapturingMon` zeroes it
again and sets wBattleResult to 2 -- a value written on exactly two paths in
the game, that one and a link battle whose opponent ran. Both are required, so
a byte read out of a half-initialised battle cannot pay. Not wPartyCount: a
catch with a full party raises wBoxCount instead, and wPartyCount also rises
for a gift, a trade and a PC withdrawal.
"Never owned this run" is the `species` payout inside the same battle, because
nothing else can set a Pokedex bit during one. It is not read off the captured
species byte: that is the cartridge's internal index while the owned bitset is
by Pokedex number, and nothing in WRAM converts between them.
The address was resolved by tools/resolve_wram.py, not written by hand. The
tool needed NUM_TMS and NUM_HMS, which the decomp defines through its `const`
enumeration, so it now counts them from the file's own add_tm/add_hm
definitions and cross-checks NUM_TMS against the literal the same file
declares.
The feed's kinds are closed, so `catch` publishes on `wildwin` and nothing in
packages/feed or apps/stage changed. Deliberately not `pokedex`: the `species`
rule already pays for the bit the same catch sets. The stage's ticker copy is
keyed on the feed kind, so a catch row reads "wild win" -- stated in
docs/rewards-learning.md rather than left to be discovered.
v5 -> v6. STATE_VERSION stays 4: the rule adds one counter, `catchCounts`, and
changes nothing else, so a v5 state restores with it empty. That migration is
opt-in and needs all three of: the adapter segment being the only difference
between the two compatibility strings, the running adapter listing the
checkpoint's adapter in `migrates_from()`, and the deploy naming it in
FLY_ACCEPT_ADAPTERS. flysim applies the rule at restore and 05-deploy's gate
applies the same rule before it flips the symlink, writing the variable into
fly.env so the two cannot disagree.
The restart. infra/bin/fly-reset-to-milestone <N> archives both stores to a
dated directory, rewrites milestone-<N>.checkpoint with the ratchet's attempts
and recoveries at zero, installs it as the newest generation of both stores,
clears the milestone archives above N and the event log, and prints what it
did. It refuses while flysim is running and refuses a rung the run never
reached. The envelope work is in flysim::reset (`flysim
--reset-to-milestone N`); the shell script is the operator's wrapper.
Tests: catalog values and order; a synthetic WRAM trace of a catch (new,
repeat, cap, already-owned species, trainer/Safari/old-man/missed-ball
negatives, rollback replay); a v5 state restoring with the counter at zero; a
v5 checkpoint fixture accepted with the opt-in and refused without it; the
reset tool against copies of a state dir in temp directories; and a ROM-gated
catch from a rung-9 forest checkpoint, driven by the shipping THROW BALL macro.
The compatibility string differs from main's in exactly one segment, checked by
splitting both on `/`: pokered-unique8-v5 -> pokered-unique8-v6.
The rung-11 reading of row 54. Route 3's connections read north and west off the cartridge
(`wCurMapConnections`, and it has no warps at all) while the geography table carries west and east,
so the seven walkable tiles of its north edge had an unnameable destination -- and an unnameable
destination counted as unvisited, which made them first-tier for `GO ROUTE` on every hold for ever,
with `GO OBJECTIVE` off the pad beside them because nothing on that map leads to the objective.
A warp's destination is a byte the cartridge publishes, so `None` there is the `LAST_MAP` case
already handled. An edge's comes only from `geography::connected`, so `None` there means the table
cannot name the map on the other side and never will, and the only record left is the adapter's own
boundary ledger. It narrows: a genuinely new edge is still first-tier until the fly reaches it.
Which map is north of Route 3 is not guessed at here. That is a survey -- walk the fly off the edge
with real presses and read the map id back -- and it is recorded as a residual instead.
Two rules for section 13's errands, both from row 54's `GO HEAL`: 204 starts at a mean net of 0.0
tiles and a mean reach of 0.0, cycling with `GO ROUTE` and `GO FRONTIER` over five tiles for seven
and a half brain minutes.
An errand's aim at a door carries no press -- the warp fires when it is stepped on -- so an aim on
the tile the fly is already standing on settles for `SETTLE_FRAMES` and reports `done` with the
world exactly as it was. A completed errand walk writes the reached ledger, which `goals_toward`
does not filter, so the same button was dealt on the next hold and the same nothing happened again.
`exit_goals` has excluded a settled goal underfoot since row 13; this is the one walk that did not
have the rule.
And the errand ledger is session state, so a restore re-armed every errand in the town and walked
the fly back to a counter it had already used. `map_visited` is the adapter's lifetime answer to
the same question and it does survive, so both are asked and either pays the errand.
The other half of the same measurement. `wXCoord` and `wYCoord` are the tile the step began on
until the frame it ends, so the stood ledger recorded ground the fly had already left and the tile
under it stayed unstood: `path::frontier` kept offering it, `GO FRONTIER` was dealt aiming one tile
away, and `Arrival::Step` reported `done` the instant the step it did not make landed. A macro that
completes without changing anything, which is section 12.2's trap.
A step that has begun always finishes -- the cartridge owns the animation and no press stops it --
and the screen has already centred on the tile, so it is ground this run has covered. It clears the
map's frontier mark on the same rule the coordinates do: only ground never stood on before.
Measured on the cartridge from the rung-10 checkpoint: `wXCoord` and `wYCoord` change at the *end*
of a sixteen-frame step while the background scrolls throughout it, so for fifteen frames of every
sixteen the screen buffer is centred one tile ahead of the coordinates. The cross-check compared
the decode of the fly's own tile with the screen's reading of the tile ahead and refused. Pewter
City decoded on 118 of 120 standing frames and on none of the moving ones, so every walk the fly
actually took was re-planned over the ten-by-nine window, which is the oscillation of row 23.
Nothing in the pinned symbol table says "a step is in progress" and a new address cannot be pinned
without the disassembly `gen_symbols.py` reads, so the anchor is measured rather than named: the
screen is centred on the fly's tile or on one of its four neighbours, and the one it is centred on
is the one whose whole neighbourhood agrees with the decode. A decode with a wrong stride, a wrong
quadrant or a half-loaded map agrees with none of the five, and neither does the mid-warp tear the
per-serve check was added for, so both refusals stand.
`state::step_destination` names the tile the step is landing on, for the ledger the next commit
writes. The town fixture gains two landmarks beside the fly, because a neighbourhood that is the
same tile id in every direction cannot tell one anchor from another.
The reproduction, the three surveys and the before/after for the rung-10 Pewter stall, with the
ROM-gated run's table. Rows 51 (UNKNOWN's two states), 52 (a building with no row on the map
graph) and 53 (a frontier excluded by a window that lapses) each with their tests.
From the live rung-10 checkpoint with the stub rotation: map 0x34 to map 0x36 on frame 2423, on
15 macros, against never in five and a half live hours. TALK is on the pad inside the gym -- the
fly standing in front of one of the people rung 11's place names -- and it presses it 15 times
there. BACK on a dialog or unknown frame is 0 against the 189 the hunt measured on Pewter City
alone, no frame deals a pad on an unknown with nothing drawn on it, and GO FRONTIER starts 0
times on the museum's two floors.
The unit test beside it is the other half of "the objective reaches the leader": on the gym's
own map the rung's place resolves to the person standing in it, the walk aims at the four tiles
around them, the door is not a candidate while they are there, and talking to them empties the
list. It also pins what the ROM run showed: the town's errands come first for every map in the
area, the gym included, so both have to be paid before the objective is the leader.
Two Stuck rollbacks fired on rung 10 inside half an hour, both on a fly that was walking, and
both were the ratchet working to contract: the stall window is reset by exploration -- one tile
the run has never stood on -- and a fly crossing a town it has already covered to reach the
rung's own door earns none of it. Entering a map for the first time already counts, because a
new map is ground nobody has stood on; re-entering one does not, which is what the museum was.
So the window gains a second signal, passed in by the caller and meaningless to the ratchet
itself, exactly as coverage is: the Pokemon loop answers with "nearer the objective, in map
hops, than this run has ever been", over the same map graph GO OBJECTIVE walks. It can fire at
most once per step of the road, it spends no budget, it captures nothing and it skips no
trigger. Nothing in the macro layer reads it back and no button is bound on it.
The checkpointed ratchet state is untouched: the signal is a level on one sample, not a counter.
GO FRONTIER was 1,235 of the rung-10 run's macro starts in 47 minutes, over two museum floors
and a town whose ground the fly had already covered. The tiles it aimed at were real and
unreachable: 98 walkable tiles on the museum's ground floor, 62 of them reachable from the door,
39 never stood on and almost all of those behind the admission desk. A walk that can reach none
of its goals refuses no route and writes every goal to the blocked ledger -- which is a ten
brain minute window, so all of it came back and the refusal happened again, once per hold.
A window is right for a target somebody is standing in front of and wrong for ground the map has
fenced off. So the refusal is remembered per map instead, with no window, and it is cleared by
the one event that can change the answer: the fly standing somewhere on that map it had not
stood on before -- a door opened, a script carried it through, somebody moved out of a doorway.
Re-entering the map clears nothing, which is the loop the window made.
BACK was 678 macro starts in 47 minutes on rung 10, 189 of them on Pewter City with no text box
on screen at all. BACK is on no overworld and no dialog pad, so every one of them was dealt by
Scene::Unknown -- which holds two states under one name. One is a screen this crate cannot name,
the Pokedex or the trainer card or OPTION, where A and B are what leave it. The other is a frame
of the overworld where the cartridge is driving -- a warp in flight, a scripted push-back, the
museum guide walking the fly through the door -- which the detector calls Unknown because the
buttons are not reaching the player.
On the second, NEXT and BACK are an A and a B pressed into somebody else's script: they change
nothing and they complete where the fly stands, which is section 12.2's trap with no box to
advance. So Unknown's pad is dealt on whether a box is drawn, and a scripted overworld frame is
an empty pad the fly waits out -- the cartridge gives the buttons back by itself.
Rung 10 spent five and a half hours in Pewter City with the objective two doors away, and the
fly kept ending up inside the museum, where GO OBJECTIVE, GO SHOP and GO HEAL were all off the
pad: a map with no row in the graph has no neighbours, so next_hop answers nothing and area_of
answers nothing. Named as a residual by the previous review and worked here.
Both ids come from the cartridge's own warp table, surveyed from the rung-10 checkpoint rather
than counted: Pewter City names 0x34 at (14, 7) and at (19, 5), which are the museum's two
doors, and 0x34 names 0x35 at (7, 7), which is the staircase. The upper floor has no area, for
the same reason a bedroom has none -- no front door of its own -- and the road out of it is the
staircase, which is what the fly needs there.
The rung-10 review needed to tell "the whole-map grid is wrong on this map" from "this frame
was mid-step", and the refusal label cannot: both read "screen disagrees". So the decode is
split from its own cross-check, the disagreement is readable tile by tile, and the map survey
stands still for a while and counts how many of those frames decode.
On Pewter City, measured from the rung-10 checkpoint: standing still it decodes on 118 of 120
frames, and the frame the survey caught disagrees on three tiles by exactly one tile row in the
direction the fly was walking. The map is fine; the reading is refused while the fly is moving.
Unit: HEAL is off a full party's pad, including the rung-10 party's own
numbers, which is the assertion that the loop was never the heal's; TALK
is off a rested nurse's pad and on a hurt one's, and an ordinary
villager is unaffected; her prompt deals one answer and a plain box still
deals three; a readable prompt that is not hers deals both answers and no
NEXT; an answer whose prompt reopens is excluded and one that settled the
box is not; a completed heal and a declined prompt both retire her.
ROM-gated from the live checkpoint (FLY_CENTER_CHECKPOINT): the fly
leaves map 0x3a on a bounded number of macros, YES starts stay under
five, NEXT is on no pad while a prompt is readable, and TALK is on no pad
at a rested nurse. The run also counts dialog and prompt frames, which is
what makes the forty-six-to-one ratio visible.
TALK's precondition reaches over a counter because the cartridge does;
its talked-ledger entry was read one tile ahead. So TALK was bound at the
counter and recorded nothing, 107 times, and the nurse was never retired.
The entry now comes from the same reading the precondition makes.
A completed HEAL writes her into the talked ledger: the macro has had the
conversation with its own presses, and the reached window would otherwise
expire in ten brain minutes and offer the ring again. A declined prompt
writes it too -- 12.4 inverted for one person, because the pad only ever
offers NO at her prompt when the party is already full.
And the general rule: a YES or NO whose prompt is up again on the same
map and tile within one hold did nothing, so that answer joins the
blocked ledger for its window.
On a box that is a readable YES/NO prompt, NEXT is YES under another
name -- an A press at a two-option menu confirms the option the cursor is
on -- so it is off that pad and the pad is the box's two answers. That is
12.10's pair rule in a dialog.
At the nurse's own prompt only the answer that changes something is
bound: YES with a hurt or statused party, NO with a full one, read from
the same byte HEAL's precondition has read since section 13.
And TALK is off the pad at a nurse the party has no use for. Her
conversation is a service whose need the cartridge publishes, and a ring
of text that ends where it began is section 12.2's trap. Nobody else is
narrowed.
An answer the reopened-prompt ledger excludes leaves the pad, and the
exclusion never empties one: a box nothing can answer cannot be left.
Row 41 is the Pokemon Center nurse: 2,142 YES starts on one tile of map
0x3a with a party at 70/70. The survey that names it is a new probe mode
(FLY_PROBE_CATCH=nurse) that leaves the box with B and then pulses A,
printing every state the conversation passes through with the two-option
menu's own geometry beside it. It is a ring of forty-six presses and the
box is a choice on exactly one of them.
So the reading: wFontLoaded plus the border DisplayTwoOptionMenu draws,
plus the cursor it parks inside it. Both halves are needed -- the cursor
bytes survive the box closing, so all forty-six frames carry the
geometry. It claims that box and not "a choice is open" in general.
TargetKey gains Answer { at, yes } for the reopened-prompt exclusion.
The doc comment on Cursor::want credited THROW BALL's 63 blocked starts to the
bag drawing slowly, which is true of the frames and not the cause. What the rule
did was make the step report which list it had been left looking at, and the
answer was never the bag - it was the party list, because battle_entry's ITEM and
PKMN were the other way round. The rule stands on its own: a step that reads the
list it has already answered takes its length and its direction from entries it
is not walking.
The rung-10 loop whole: which building the fly was in, why every candidate list
on that map emptied, the byte dump that named the battle menu's swapped entries,
the ROM-gated before and after, and the two arms of the trap hunt being identical
because a restore clears the ledgers the loop was built out of.
Section 13.1's tables follow: MENU off both overworld rows and off the narrow
counter row, the move list's conditional BACK, the start menu reachable only by
the raw START button, and two new rows in the pad-empty audit - the room whose
one door the ledger rests, and the map with no way out at all, which is the one
empty pad left and is asserted rather than covered.
THROW BALL was 63 starts and 63 blocked on v0.4.3 and SWITCH 15 of them, and the
reason is neither macro's own script. The screen reads FIGHT PKMN over ITEM RUN
and the game's index does not: wCurrentMenuItem is the row inside the column the
cursor is in, and selection adds two for the right column, so the order is
FIGHT, ITEM, PKMN, RUN. battle_entry had PKMN 1 and ITEM 2 -- the row-major
reading of the picture -- so every macro that meant to open the bag opened the
party list and every macro that meant to open the party list opened the bag.
Surveyed on the cartridge from the rung-9 checkpoint: a THROW BALL aiming at 2
walked the cursor to wTopMenuItemX 15 / wCurrentMenuItem 0, pressed A, and the
party list opened -- wTopMenuItemY 1, wTopMenuItemX 0, wListMenuID $02 -- with
the game writing wCurrentMenuItem 2 on the frame after the press.
The fake had the same mistake in its own geometry, so it could not have caught
it: its two-by-two moved row-major. It is column-major now, like the cartridge.
THROW BALL on the cartridge was 63 starts and 63 `blocked`, mean sixty-nine
frames: the cursor to ITEM, the A that confirms it, the twenty settle frames,
and then a refusal on the very next frame. Red keeps one cursor for every menu
in the game, the bag takes longer than twenty frames to draw, and `listing` was
still answering for the battle *menu* -- four entries, max 3. A ball whose bag
index was above that read as off the end of the list and the step gave up at
once; one inside it was walked for by pressing UP and LEFT at the battle menu,
which is the blind pressing section 4 forbids.
So a Listing now says which list it is (ListKind) and a cursor step says which
list its target indexes into. A step whose list is not up waits exactly as it
waits for a list that reports no cursor at all, and takes its press order and
its budget from that list on the first frame it accepts input rather than from
whatever was up when the script was built. A confirming press that has already
begun still finishes: the press is what answers the list, so the cartridge is
already drawing the next one while it is issued.
The crossings are named: FIGHT and ITEM and PKMN and RUN over the battle menu,
a move slot over the move list, a bag index over the bag, a party slot over the
party list. A shop's own screens are one list and stay as they were.
The fake gained the one thing it could not express -- a list that takes frames
to draw -- and two of its fixtures had FIGHT and a potion opening nothing at
all, which is how they passed while the cartridge could not.
The v0.4.3 residual: BACK was 263 of 797 macro starts and every one of them was
over an open move list. A move list whose battler the seam cannot place binds no
MOVE n at all -- move_slot_bound needs wBattleMon* -- so its pad was BACK alone,
and the only thing that button does is close the list MOVE 1 on the menu
underneath had just opened. That is section 12.10's pair with MOVE 1 standing
where NEXT used to.
So BACK is dealt on the move list only while battle.own reads, and MOVE 1 keeps
the backstop it has over the top-level menu: with nothing readable the pad is
MOVE 1 alone and its script confirms wherever the cursor stands, which is the
press that ends a turn. With the moves readable the pad is unchanged.
Rung 10, thirty brain minutes inside the Pewter museum's upper floor (map 0x35):
macro starts MENU 82, BACK 82, GO FRONTIER 8, the event log alternating
`MENU start/done, BACK start/done`. MENU pressed START, the start menu opened,
and that scene's pad is CLOSE / CONFIRM / BACK -- so BACK pressed B and closed it
again. Two buttons that undo each other with nothing else changing, which is
section 12.10's rule one scene wider than a battle, and section 12.2's trap by
definition: a precondition satisfied wherever the fly stands and a macro that
completes without moving.
Nothing in the macro vocabulary uses the start menu for anything, so MENU comes
off scene_set's overworld row entirely rather than being narrowed. It stays a
type, a population, a tag and a script, so the roles, the channel order and
--print-compatibility are untouched, and the fly's raw START still opens the
start menu in macros mode.
MENU was also what made an empty overworld pad impossible. That guarantee moves
to the way out: `ways` gains one last resort for a *room*, the one GO ROUTE has
had outdoors since 13.1, so a map that offers nothing else at all still offers
its door or its staircase -- ignoring the blocked window, because a target the
ledger is resting is still the only place to go. The museum's staircase is a
passage and not an exit, which is why the old tier 3 could not answer for it.
A map with no way out at all is now a genuinely empty pad. No map in Red is
that, it is asserted as the named residual, and game.padEmptyMs reports it.