Commit graph

15 commits

Author SHA1 Message Date
dev
9501a5a17c macros: the survey numbers in the accessor are the corrected pulse's
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.
2026-09-22 19:09:42 +00:00
dev
e2bf4c0305 macros: the move list is the box on screen, not the cursor bytes it left behind
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.
2026-09-22 18:37:36 +00:00
acamilo
ad632621c1 macros: say when the step reading narrows to None 2026-09-22 17:44:53 +00:00
acamilo
31f35c6f2f macros: the whole-map grid is read from the tile the screen is centred on
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.
2026-09-22 14:57:34 +00:00
acamilo
26f957e084 macros: a frontier no walk can reach is a fact about the map, not a window
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.
2026-09-22 12:48:09 +00:00
acamilo
84ed0410bf macros: an UNKNOWN frame with nothing drawn on it deals no buttons
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.
2026-09-22 12:43:53 +00:00
acamilo
99a6a08bad probes: say which tile the map decode and the screen disagree about
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.
2026-09-22 12:43:41 +00:00
acamilo
86f0e7018d macros: read the nurse's YES/NO box, and survey her conversation
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.
2026-09-22 10:38:52 +00:00
acamilo
57d9bdff5a macros: Red's battle menu is two columns, so ITEM is under FIGHT and PKMN beside it
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.
2026-09-22 08:50:40 +00:00
acamilo
4bd0a4b3fa Merge fix/loop-20260922T0459: NEXT never on a pad with an input-accepting cursor; MOVE 1 is the main menu backstop; the bag is the fly's turn 2026-09-22 06:18:34 +00:00
acamilo
05c2552d78 macros: NEXT off every own-turn pad, and the bag is the turn its cursor says it is
Live on rung 9 thirty-five minutes after v0.4.2: NEXT 1264 macro starts, BACK
1241, the log alternating NEXT start/done, BACK start/done every hold, on map
51. NEXT on the top-level battle menu is an A press on the cursor, and the
cursor sits on FIGHT, so it opened the move list; BACK on the move list closed
it again. Each button was legitimate where it stood, and the pair undid itself
with nothing else changing, so the turn never resolved.

NEXT is now on no pad with a cursor accepting input. It keeps the between-turns
row alone and the forced switch, which has no BACK beside it to undo it. MOVE 1
is the top-level menu's backstop instead: the question over that menu is 12.8's
"is there a move list to open", FIGHT always opens, and the script there is
confirm FIGHT and stop, which reads no move at all.

The bag is the fly's own turn, because a cursor accepting input is one, and its
pad is the bag's own three answers: ITEM, THROW BALL, BACK. Section 12.10.
2026-09-22 06:14:48 +00:00
claude
abd3e563a8 gb: refuse a map header that does not fit wOverworldMap
The buffer is ds 1300 and every real map plus its three-block border fits in it.
A header that says otherwise is one read mid-load, and decoding it would read
past the buffer into somebody else s WRAM, so it is a refusal rather than a
clamp.
2026-09-22 04:15:29 +00:00
claude
bcae388d99 gb: re-check a cached grid against the screen when it is served
A warp writes wCurMap before the map header and the block data: on Oak s lab
doormat wCurMap already reads PALLET_TOWN while the header still reads the lab s
ten-by-twelve. The decode agrees with the screen on such a frame -- both are the
old map -- so only the id is wrong, and a grid filed under it would stay wrong
for as long as it was cached. One byte answers it: does the cached grid still
agree with the screen about the tile the fly is standing on.
2026-09-22 04:10:28 +00:00
claude
68228ef2a1 gb: decode the whole current map into a walkability grid
The walkable predicate answered for the ten-by-nine screen window and Unknown
everywhere else. MapGrid is the same rule over every tile of the loaded map:
block ids out of wOverworldMap, a blocks-to-tiles read of the tileset header s
blockset through the new bank-aware ROM read, the tileset s collision list as
before, and the TilePairCollisionsLand values as directed walls both ways.

The reader checks itself before it answers: the decode is compared against the
window predicate over the player s own neighbourhood, and a frame where the
window can answer for none of it -- a battle, a text box, a frame mid-warp -- is
refused, because wOverworldMap shares its bytes with the picture buffer. Every
refusal is named (GridRefusal) and leaves the window predicate in charge.

Cached per map id and size, so a map is decoded once on arrival rather than once
per question, and owned beside the session ledgers: never checkpointed.
2026-09-22 03:32:35 +00:00
acamilo
660c3cf00d flybrain v0.4.0: public tree (history retained privately)
Some checks failed
ci / node 22 (test + typecheck) (push) Has been cancelled
ci / rust stable (cargo test --workspace --release) (push) Has been cancelled
ci / infra/tests/lint.sh (push) Has been cancelled
ci / playwright apps/stage (allowed to fail) (push) Has been cancelled
2026-09-21 15:09:46 +00:00