From f0cebd25746806d5ec2248277a9eba523b20513b Mon Sep 17 00:00:00 2001 From: acamilo Date: Tue, 22 Sep 2026 06:15:09 +0000 Subject: [PATCH] docs: section 12.10, and rows 42 and 43 of the trap audit The contract: no pair of buttons on any battle pad may undo each other with nothing else changing, which is section 12.2's rule stated at the pad instead of at one macro. Section 13.1's table takes NEXT off the own-turn main menu, gives the bag a row of its own on the own turn, and records MOVE 1 as the backstop that row keeps. The audit carries the reproduction -- 71,673 frames in one battle, 73 of 73 windows flagged, BACK 739 starts on the move list against NEXT 739 on the main menu -- the ROM-gated run before and after, the trap hunt before and after (1 distinct tile to 260, 73 of 73 flagged windows to 68 of 73, 0 overworld frames to 20,394), and the residuals: NEXT on a move list whose cursor the seam cannot place is now the largest source of it, which is row 30b and wants a WRAM reading rather than a pad change. --- docs/design/macros.md | 64 ++++++++++++++- infra/docs/macros-traps.md | 163 +++++++++++++++++++++++++++++++++++++ 2 files changed, 223 insertions(+), 4 deletions(-) diff --git a/docs/design/macros.md b/docs/design/macros.md index ef0d405..b246c1a 100644 --- a/docs/design/macros.md +++ b/docs/design/macros.md @@ -859,6 +859,61 @@ reproduction and the numbers; two macros, and section 12.2's one rule between th The decoder, the reward catalog, the adapter version and the compatibility string are untouched. +### 12.10 No two buttons on a battle pad undo each other (2026-09-22, rung 9, seventy-one hours) + +Thirty-five minutes after v0.4.2 deployed, the watchdog flagged the same rung again. Rank 9 +(VIRIDIAN FOREST, next PEWTER CITY), seventy-one hours on it, and since the restart the macro +starts were `NEXT` **1264**, `BACK` **1241**, `THROW BALL` 5 and `GO WARP` 3 -- the event log +alternating `NEXT start/done, BACK start/done` every hold, on map 51. + +Reproduced from the live checkpoint with the real cartridge and the real brain +(`infra/docs/macros-traps.md` has the numbers): twenty brain minutes, **71,673 frames of them all +in one battle**, 73 of 73 windows flagged, one distinct tile, and the two counts that name the +mechanism -- `BACK` **739 starts on the move list** and `NEXT` **739 on the top-level menu**. + +- **The pair was split across two sub-states of one turn, which is why 12.9 did not catch it.** + `NEXT`'s script is one press of A. On a frame of battle text that A advances the text, which is + what the button is for. On the *top-level battle menu* the same A press **confirms whatever the + cursor is sitting on**, and the cursor sits on FIGHT, so `NEXT` opened the move list -- whose + `BACK` closed it again. Each button was legitimate where it stood: 12.9's rule is about `BACK` + and a list, and there *was* a list to leave. What is a trap is the **pair**: two buttons that + undo each other with nothing else changing, so the roll lands on one of them nearly every hold + and the turn never resolves. Section 12.2's rule, stated at the pad instead of at one macro: + **no pair of buttons on any battle pad may undo each other with nothing else changing.** +- **`NEXT` is the A that advances text, so it belongs only on a frame with no cursor accepting + input.** It is off every own-turn pad: the top-level menu, the move list, the party list and the + bag. The between-turns row keeps it alone, and the forced switch keeps it as row 8's backstop -- + the one arm with a cursor and no `BACK` at all, because a forced switch cannot be cancelled. +- **`MOVE 1` is the backstop the top-level menu keeps.** `NEXT` was there for the turn where every + other button drops (row 7), and it was the wrong button for the job twice over: it does not end + a turn, and what it does instead is reopen the list `BACK` had just closed. `MOVE 1`'s + precondition over that menu is 12.8's -- "is there a move list to open", and FIGHT always opens, + because `CheckPlayerHasUsableMoves` sets Struggle without opening it -- so it is bound there + whatever the seam makes of `wBattleMon*`, and its script over that menu is "confirm FIGHT and + stop", which reads no move at all. A battler the seam cannot read is not a reason to take the + turn's one ending button away. +- **The bag is the fly's turn, and its pad is the bag's own answers.** `Battle::own_turn` answered + `false` for it, which was the last frame in the game with a cursor accepting input and no own + turn -- so it landed on the between-turns row, and that row's `NEXT` on an open bag is the A + press that *uses* whatever the cursor holds. The invariant is now whole: **a battle frame with a + cursor accepting input is the fly's turn**, the forced switch excepted because it has a pad of + its own. The bag's row is `ITEM` (use the thing) / `THROW BALL` / `BACK`; `CONFIRM` is gone from + it, because it was the same blind A press under another name, and both scripts already navigated + this list by reading its cursor (7.1 of `macros-wram.md`). +- **`wListMenuID` is not stale, which is why the bag reading stands.** It is zeroed by + `DisplayTextIDInit` at the start of every text display (`macros-wram.md`), so a battle's text + frames cannot inherit an `ITEMLISTMENU` from a bag the fly closed. Checked because a stale byte + there would have put the bag's pad on every frame of battle text. +- **What the harness now holds, rather than the pads alone.** The ROM-gated run from this + checkpoint asserts that no battle pad deals both buttons, that `NEXT` is on no frame with a + cursor accepting input, that the longest `NEXT`/`BACK` alternation is under four, and that every + battle it enters *ends* -- 25 of 25, worst 275 macros -- where v0.4.2 spent a whole hunt inside + one that never did. The battle rules are asked of the **scene the pad was dealt for** and not of + `wIsInBattle`: the `$ff` frame a lost battle passes through reads `Unknown`, whose pad is + `NEXT, BACK` by contract (row 9), and it is not under a battle's rules. + +The decoder, the reward catalog, the adapter version and the compatibility string are untouched. + ## 13. Shops and Pokémon Centers (the operator, 2026-09-17: "refactor the shop macros. make it a ## priority to visit the shop at least once per area; make shop macros item purchases. same ## for the Pokécenter. heal should be a macro.") @@ -936,12 +991,12 @@ observe is not a precondition, it is a guess. | Menu (the start menu) | CLOSE, CONFIRM, BACK | unchanged. The cursor is the fly's to move with the **raw** D-pad, which still reaches the cartridge in macros mode; a SAVE or a POKéDEX button would be a macro per start-menu entry and is not asked for | | Menu (the bag, an elevator, the party list outside a battle) | CLOSE, CONFIRM, BACK | unchanged | | Unknown (the Pokédex, the trainer card, OPTION, a naming screen, a mid-warp frame) | NEXT, **BACK** | **BACK added** (row 9): B is what leaves the first three, and A leaves none of them | -| Battle, own turn, main menu | MOVE 1..4, SWITCH, ITEM, THROW BALL, RUN, NEXT | four move buttons for `ATTACK` (section 14); THROW BALL added, and gated on the species since 12.9; **RUN gated**, below. No `BACK`: the four entries are the answers to this menu | +| Battle, own turn, main menu | MOVE 1..4, SWITCH, ITEM, THROW BALL, RUN | four move buttons for `ATTACK` (section 14); THROW BALL added, and gated on the species since 12.9; **RUN gated**, below. No `BACK`: the four entries are the answers to this menu. **`NEXT` removed by 12.10** — an A press here confirms FIGHT and reopens the list the move list's `BACK` just closed, and `MOVE 1` is the backstop instead, bound here whatever the battler reads as | | Battle, own turn, move list | MOVE 1..4, BACK | as above | | Battle, own turn, party list | SWITCH, BACK | unchanged | -| Battle, the bag | NEXT, **BACK** | **BACK added**, and the list now reports a *cursor* (`macros-wram.md` 7.1) — before this `ITEM` could open the bag and had nothing to read | -| Battle, forced switch | SWITCH, NEXT | unchanged (row 8) | -| Battle, between turns | NEXT | `BACK` was added here for the bag (the row above reads as nobody’s turn) and **taken back out by section 12.9**: on a frame of battle text there is no list to leave, and a `BACK` that changes nothing is the trap of section 12.2 | +| Battle, own turn, the bag | ITEM, THROW BALL, **BACK** | the bag reports a *cursor* (`macros-wram.md` 7.1), and since **12.10** it is the own turn, because a cursor accepting input is one. Its pad is the list's own answers; `NEXT` and `CONFIRM` are both off it, being the same blind A press that *uses* whatever the cursor holds | +| Battle, forced switch | SWITCH, NEXT | unchanged (row 8). The one arm that keeps `NEXT` with a cursor up, because it cannot be cancelled and has no `BACK` to undo it | +| Battle, between turns | NEXT | `BACK` was added here for the bag and **taken back out by 12.9**: on a frame of battle text there is no list to leave, and a `BACK` that changes nothing is the trap of section 12.2. Since **12.10** the bag is not on this row at all, so `NEXT` here is only ever the A that advances text | | Shop | BUY POTION, BUY BALL, BUY ANTIDOTE, BUY REPEL, CONFIRM, LEAVE | two purchases to four; CONFIRM added | | PC | **CONFIRM**, LEAVE | **CONFIRM added**: a list the fly opened is one it can answer rather than only close. Depositing and withdrawing are still not in the vocabulary (row 17) | | Title | nothing | unchanged, by contract: the readout's boot variant applies | @@ -983,6 +1038,7 @@ measured where it cannot. | a restore, before the first `observe` | not a cause: the loop calls `observe` once at the end of boot and once after a ratchet recovery, so the first frame is decided on a real palette | | the title screen, and raw mode | not an empty pad by contract: the readout's boot variant applies and no palette is dealt | | a scene the detector cannot name | `Unknown` deals `NEXT` and `BACK`; a screen neither press leaves (the naming screen, row 14) is a genuine stall and still needs START, which is a contract change | +| the fly's own turn where the seam cannot read the battler, now that `NEXT` is off that row (12.10) | **fixed**: `MOVE 1` is bound over the top-level menu whatever `wBattleMon*` reads as, because FIGHT is one of that menu's four entries and always opens | And because "closed where a macro can close it" is not "closed": `game.padEmptyMs` publishes how long a **playable** scene has had nothing on the pad, in brain diff --git a/infra/docs/macros-traps.md b/infra/docs/macros-traps.md index 9e5918e..984cbb2 100644 --- a/infra/docs/macros-traps.md +++ b/infra/docs/macros-traps.md @@ -1359,3 +1359,166 @@ Four things about it that are not improvements, recorded rather than buried. - `infra/tests/lint.sh`: all checks passed, de-PII guard included. - `--print-compatibility`: byte-identical to v0.4.1, 648 bytes, decoder / reward catalog / adapter version / roles untouched. + +## 2026-09-22, rows 42 and 43: the pair that undid itself + +Thirty-five minutes after v0.4.2 deployed, the release watchdog flagged the same rung. Rank 9 +(VIRIDIAN FOREST, next PEWTER CITY), seventy-one hours on it, and since the restart the macro +starts were `NEXT` **1264**, `BACK` **1241**, `THROW BALL` 5 and `GO WARP` 3, with the event log +alternating + +``` +NEXT start, NEXT done, BACK start, BACK done +``` + +every hold on map 51. It is the residual the previous review named and left -- "a `NEXT, BACK` +2-cycle x75 closes the run inside one battle... worth watching" -- at full scale, and the reason +that review's rule did not catch it is that **each of the two buttons was legitimate where it +stood**. `docs/design/macros.md` section 12.10 is the contract this closed against. + +### The mechanism, measured + +Twenty brain minutes from the release container's own rung-9 checkpoint, real cartridge, real +brain as the readout. The whole run -- **71,673 frames, all of them `battle`** -- was one battle, +on one tile, with 73 of 73 windows flagged and every window reading `NEXT, BACK` x74 or x75. The +two rows of the hunt's own sub-state table are the mechanism: + +| macro start | battle sub-state | n | +| --- | --- | ---: | +| `BACK` | move list | **739** | +| `NEXT` | **main menu** | **739** | +| `NEXT` | move list, no cursor | 8 | +| `BACK` | party list | 1 | +| `MOVE 1` | main menu | 1 | +| `THROW BALL` | main menu | 1 | + +1. **`NEXT` on the top-level battle menu is not the press that advances text.** `NEXT`'s script is + one press of A. Between turns that A advances the text box, which is the button's whole + purpose. On the top-level menu the same A press **confirms whatever the cursor sits on**, and + the cursor sits on FIGHT -- so `NEXT` *opened the move list*. It was put there as row 7's + backstop for a turn where every other button drops out, and it is the wrong button for that + job twice over: it does not end a turn, and what it does instead is reopen a list. +2. **`BACK` on the move list closed it again**, which is 12.9's contract and correct: backing out + of a list is one of exactly two answers to one. So neither rule was broken and the loop was a + *pair*: main menu -> `NEXT` -> move list -> `BACK` -> main menu, 739 times each, at 2.4 macros + per brain second, with the turn never resolving and the battle never ending. Section 12.2's + rule restated at the pad: **no pair of buttons on any battle pad may undo each other with + nothing else changing.** +3. **The bag was the last frame in the game with a cursor accepting input and no own turn.** + `Battle::own_turn` answered `false` for it (12.6 had no observable for the bag at all; 13.1 gave + it a cursor), so it landed on the between-turns row -- whose `NEXT` on an open bag is the A + press that *uses* whatever the cursor holds. Not what the live loop was made of, and the same + defect: an A press dealt where A does not advance text. + +### What changed, all of it inside the macros + +- **`NEXT` is off every own-turn pad**: the top-level menu, the move list, the party list and the + bag. It stays alone on the between-turns row and stays as row 8's backstop on the forced switch, + which is the one arm with a cursor up and no `BACK` at all, because it cannot be cancelled. +- **`MOVE 1` is the top-level menu's backstop instead**, and it is bound there whatever the seam + makes of `wBattleMon*`: the question over that menu is 12.8's "is there a move list to open", + FIGHT always opens, and `MOVE 1`'s script over that menu is "confirm FIGHT and stop", which reads + no move at all. A battler the seam cannot read is not a reason to take the turn's one ending + button away. Over an **open list** the per-slot PP rule is unchanged (row 30a, row 34). +- **The bag is the fly's turn** (`state::battle`), and its pad is the bag's own three answers: + `ITEM` (use what the cursor is on), `THROW BALL`, `BACK`. `CONFIRM` is gone from it -- the same + blind A press under another name -- and both scripts already navigated this list by reading its + cursor (`macros-wram.md` 7.1). The invariant is now whole: **a battle frame with a cursor + accepting input is the fly's turn**, the forced switch excepted because it has a pad of its own. +- **`wListMenuID` was checked for staleness rather than assumed sound**: it is zeroed by + `DisplayTextIDInit` at the start of every text display (`macros-wram.md`), so a battle's text + frames cannot inherit an `ITEMLISTMENU` from a bag the fly closed, and the bag reading stands. +- **The harness asks the scene the pad was dealt for, not `wIsInBattle`.** The `$ff` frame a lost + battle passes through reads `Unknown`, whose pad is `NEXT, BACK` by contract (row 9); the first + version of the new assertion accused that row of a battle rule it is not under. + +| # | trap | trigger | test | fix, or why it is left | +| ---: | --- | --- | --- | --- | +| 42 | two buttons on a battle pad undo each other with nothing else changing: `NEXT` on the top-level menu opens the move list, `BACK` on the move list closes it | every wild battle, every turn -- 739 starts each in twenty brain minutes, one battle, 73 of 73 windows flagged | `no_battle_pad_holds_both_next_and_back`, `the_battle_row_is_the_move_buttons_switch_item_and_never_next`, `each_battle_menu_deals_its_own_pad`, `the_battles_turns_advance_from_the_rung_nine_forest_checkpoint` (ROM-gated: it **fails on v0.4.2** with `NEXT` dealt on `battle/main`) | **fixed**: `NEXT` is off every pad with a cursor accepting input, and `MOVE 1` is the top-level menu's backstop, bound there whatever the battler reads as. `NEXT` keeps the between-turns row and the forced switch | +| 43 | the battle bag reads as nobody's turn, so its pad is the between-turns `NEXT` -- an A press that *uses* what the cursor holds | choosing ITEM in a battle; rare, because `ITEM` needs a potion and a hurt Pokemon | `a_battle_frame_with_a_cursor_accepting_input_is_the_flys_turn`, `the_battle_bag_is_the_flys_turn_and_deals_its_own_two_uses`, `back_is_on_a_battle_pad_only_where_a_list_is_open` | **fixed**: `own_turn` is true for the bag, and its pad is `ITEM` / `THROW BALL` / `BACK`. This is row 30c closed -- it was left in 12.6 for want of an observable, given a cursor in 13.1, and given the right *turn* here | + +### The ROM-gated run, before and after + +Same test, same checkpoint, sixty-seven brain minutes of the game-blind rotation, `v0.4.2` +(`592c264`) against this branch. + +| measure | before (v0.4.2) | after | +| --- | --- | --- | +| verdict | **FAILED**: "`NEXT` was on the pad while a battle menu was accepting input" | **passed** | +| `NEXT` dealt on | `battle/between-turns`, **`battle/main`**, `battle/moves-unplaceable` | `battle/between-turns`, `battle/moves-unplaceable` | +| `BACK` dealt on | `battle/moves`, `battle/party` | `battle/bag`, `battle/moves`, `battle/party` | +| maps visited | **[51]** -- never left the forest | 51, 0, 12, 1, 41, 44, 42, 13, 50 (nine) | +| battles entered / ended | 14 / 15 | 26 / 25 | +| worst battle, in macros | not measurable: the run never left the fly's own turn | **275** | +| longest `NEXT`/`BACK` alternation | (the assertion fires first) | **1** | +| `MOVE 1..4` starts | 96 | **718** | +| `NEXT` / `BACK` starts | 1235 / 1315 of 3944 | 526 / 313 of 2090 | +| own-turn frames | 20,914 | 143,180 | +| `GO OBJECTIVE` on an overworld pad | map 51 only | nine maps | + +### The trap hunt, before and after + +Twenty brain minutes, seed 20260917, 4 sweep threads, the same connectome and the same cartridge, +from the release container's rung-9 checkpoint, **driven by the brain** rather than by +`FLY_TRAP_STUB`: this branch adds no macro type, so the thirty-one `macro_` populations are +the same function in both arms and `--print-compatibility` is byte-identical at 648 bytes. The two +arms differ in the macro code and in nothing else. + +| measure | before (v0.4.2) | after | +| --- | ---: | ---: | +| distinct (map, tile) | **1** | **260** | +| windows flagged | 73/73 | **68/73** | +| windows under 4 tiles | **73** | **2** | +| longest repeated sequence in a window | **`NEXT, BACK` x75** | `NEXT` x20 | +| macros started | 1489 | 797 | +| frames in `battle` | **71,673** (one battle, the whole run) | 50,600 | +| frames in `overworld` | **0** | **20,394** | +| `NEXT` starts on the main menu | **739** | **0** | +| `BACK` starts on the move list | 739 | 263 | +| `MOVE n` starts | **1** | **113** | +| `THROW BALL` starts | 1 | 63 | +| `RUN` starts | 0 | 7 | + +The before arm never left the battle it resumed in and never left the tile it stood on. The after +arm fought fifty thousand frames of battle *and* walked twenty thousand frames of overworld across +260 tiles. + +Raw reports: `hunt-before-20260922T0459.md` and `hunt-after-20260922T0459.md` in the coordination +state's `runs/` directory. + +### Residuals, named rather than worked around + +- **`NEXT` on a move list whose cursor the seam cannot place is now the largest source of it** -- + 142 of the after arm's 248 `NEXT` starts, over 8,687 frames. That frame is *correctly* not the + fly's turn (row 30b: `MoveSelectionMenu`'s coordinates appear before the engine has copied the + active Pokemon into `wBattleMon*`), so `NEXT` there is the between-turns press and it advances. + It is not a 2-cycle -- the ROM-gated run measures the longest `NEXT`/`BACK` alternation at + **1** -- but it is the same unplaceable cursor 12.6 named, and the honest fix is still a WRAM + reading rather than a pad change. +- **`BACK` is 326 of 797 starts**, all of them over an open list: 263 on the move list and 63 on a + party list. Row 34's contract, and where it leads is a menu with `MOVE 1` on it. +- **The 68 windows still flagged are the sequence rule's dominance arm, not a cycle.** They read + `NEXT` x11 to x20 or `BACK` x11 to x14 over windows holding **5 to 86** distinct tiles, where + every window of the before arm read `NEXT, BACK` x74 on **one** tile. Only two windows of the + after arm are under four tiles, against seventy-three before. A window spent in a long battle is + one tile by construction, which is the known false-positive shape of the tile rule and why this + row is proved on the pad and sub-state tables as well. +- **Row 41 is not reproduced here and is not closed.** The after arm's text boxes are 140 frames + at (6, 30) of map 0x33 and smaller counts on seven other tiles, with no Pokemon Center in the + run at all; the nurse's `YES` loop needs its own checkpoint to measure. +- **The road north is still reported rather than asserted.** The ROM-gated run leaves the forest + and reaches maps 50, 13, 12, 1, 41, 44, 42 and 0 with `GO OBJECTIVE` on the overworld pad of all + nine, but it does not reach map 47 -- the forest's north gate -- in sixty-seven brain minutes of + the game-blind rotation. Row 40's note stands. + +### Gates + +- `cargo test --workspace` with `FLY_ROM` set: green except + `flysim::integration::the_service_streams_takes_sugar_checkpoints_and_resumes_after_being_killed`, + which fails identically on v0.4.2 on this box with the same assertion (a debug build of the + service does not finish booting inside the test's window here). Pre-existing and unrelated to + the macro layer. +- `cargo clippy --all-targets`: clean. +- `infra/tests/lint.sh`: all checks passed, de-PII guard included. +- `--print-compatibility`: **648 bytes, sha256 `0d9bfde7...707fa`** -- byte-identical to v0.4.1 and + v0.4.2. Decoder, reward catalog, adapter version and roles untouched.