docs: row 55, and the two claims about a mart the survey narrowed
`macros.md` section 12.18 is the review; `macros-wram.md` section 7.1 corrects the address table, where `wListMenuID` was said to be cleared by every text display and an item's position in the stock was said to be its cursor index; `macros-traps.md` carries row 55, its two arms and its ROM run, and two new residuals -- `wListScrollOffset` is not a pinned address, and a mart that has never drawn a buy list reads `Unknown` rather than `Shop`.
This commit is contained in:
parent
01c199fe18
commit
549df6c323
3 changed files with 148 additions and 1 deletions
|
|
@ -528,9 +528,46 @@ change, 648 bytes, checked on the WSL box.
|
|||
|
||||
| state | symbol | address | encoding | verified |
|
||||
| --- | --- | ---: | --- | --- |
|
||||
| the open mart's stock | `wItemList` | `$cf7b` | `ds 16`. `LoadItemList` (`home/text_script.asm:156`) copies the clerk's `script_mart` list out of its text script the moment the counter opens: **a count byte** (the macro's `_NARG`), then the item ids, then `$ff`. `DisplayPokemartDialogue_` points the buy list's `wListPointer` at the same buffer for `PRICEDITEMLISTMENU`, so **an item's position in this list is its cursor index** in the buy menu — which is what lets a purchase be navigated by reading the cursor rather than by counting presses. The terminator wins over the count, as it does for the bag, and the sixteen-byte buffer bounds both. | ROM (Viridian's counter reads POKE BALL, ANTIDOTE, PARLYZ HEAL, BURN HEAL, in that order, and no Potion), trace |
|
||||
| the open mart's stock | `wItemList` | `$cf7b` | `ds 16`. `LoadItemList` (`home/text_script.asm:156`) copies the clerk's `script_mart` list out of its text script the moment the counter opens: **a count byte** (the macro's `_NARG`), then the item ids, then `$ff`. `DisplayPokemartDialogue_` points the buy list's `wListPointer` at the same buffer for `PRICEDITEMLISTMENU`, so an item's position in this list is its cursor index in the buy menu **for the first three entries only** — see the correction below. | ROM (Viridian's counter reads POKE BALL, ANTIDOTE, PARLYZ HEAL, BURN HEAL, in that order, and no Potion), trace |
|
||||
| the tileset's counter tiles | `wTilesetTalkingOverTiles` | `$d532` | three tile ids from the tileset header (`data/tilesets/tileset_headers.asm`), `$ff` for a tileset with fewer. Mart and Pokecenter are both `$18 $19 $1e`; the overworld and an ordinary house have none. `IsSpriteOrSignInFrontOfPlayer`'s `.extendRangeOverCounter` branch (`home/overworld.asm:1115`) walks exactly this list and doubles the talking range from `$10` to `$20` pixels — one tile to two — when the tile in front of the player is one of them. | ROM (the Viridian mart's column 1 and the centre's (3, 2) read as counters and the floor either side does not), trace |
|
||||
|
||||
### 7.1 Correction, 2026-09-22 (row 55): the list byte and the cursor index both say less than this
|
||||
|
||||
Two claims in the table above were surveyed again from the live checkpoint the stream looped in
|
||||
(`examples/scene_probe.rs`, `FLY_PROBE_CATCH=shop`, in the Pewter mart), and both are narrower
|
||||
than they were written.
|
||||
|
||||
**`wListMenuID` says the counter is open, not which of its screens is up.** Section 2's row for it
|
||||
says it is "zeroed by `DisplayTextIDInit` at the start of every text display, so a stale value
|
||||
cannot outlive one". That holds for text the overworld displays and not for the mart's own: the
|
||||
clerk's "Here you are! Thank you!" is printed from inside `DisplayPokemartDialogue_`, which never
|
||||
calls the routine that clears it. Measured: **every frame of a mart visit read `PRICEDITEMLISTMENU`
|
||||
`$02`** — the counter menu and each of the clerk's text boxes included — and on the counter menu
|
||||
`wTextBoxID` reads `MONEY_BOX` `$0d` rather than `BUY_SELL_QUIT_MENU` `$15`, because the money box
|
||||
is the last template drawn. So `ShopScreen::BuySellQuit` and `Selling` were unreachable in a mart
|
||||
that had ever drawn a buy list, and the frame the stream sat on — a dialogue box waiting for a
|
||||
press, with a two-option box's leftover cursor bytes (`wTopMenuItemY` 8, `wTopMenuItemX` 15,
|
||||
`wMaxMenuItem` 1, `wMenuWatchedKeys` `$03`) — read as "the priced buy list is open".
|
||||
|
||||
The screen is now read from the figure the game draws, the construction [`text_box`]'s `waiting`
|
||||
test and [`yes_no_prompt`] already use: the full-width box drawn and waiting is the clerk
|
||||
(`ShopScreen::Talking`, new), the item window drawn is the buy list, and the item window blank is
|
||||
the counter menu. The item window's own figure is its first name cell, screen (6, 4), which held
|
||||
`$7f` on the counter menu and `P` of `POKE BALL` on every frame the list was drawn.
|
||||
|
||||
**The buy list scrolls, so a position in `wItemList` is a cursor index only for the first three
|
||||
entries.** Walked one DOWN pulse at a time on the live list: the cursor went `0, 1, 2` and then
|
||||
**stopped moving while the window scrolled under it** — the fourth drawn row is a look-ahead the
|
||||
cursor never occupies. The absolute position of the item under the cursor is that index plus
|
||||
`wListScrollOffset`, which is **not** in the reviewed address list and cannot be pinned without the
|
||||
disassembly `gen_symbols.py` reads (the same refusal `$cfc5` met in section 9). Viridian's
|
||||
four-item counter hid it: POKE BALL is 0 and ANTIDOTE is 1, both in reach. Pewter's seven-item
|
||||
counter did not: its ANTIDOTE is **3**.
|
||||
|
||||
So the macros carry `MART_CURSOR_ROWS = 3` and a purchase past it is not on the pad
|
||||
(`infra/docs/macros-traps.md` row 55). Pinning `wListScrollOffset` would widen that, and it is a
|
||||
residual rather than a guess.
|
||||
|
||||
**Why the second one is load-bearing.** A mart clerk is `object_event 0, 5, SPRITE_CLERK` behind a
|
||||
counter running down column 1; a Pokémon Center nurse is `object_event 3, 1, SPRITE_NURSE` behind
|
||||
the counter tile at (3, 2). **None of the four tiles around either of them is standable** — they are
|
||||
|
|
|
|||
|
|
@ -1217,6 +1217,48 @@ walk. Both were measured from the same rung-10 checkpoint, with
|
|||
Nothing here changes which button the fly presses. The decoder, the reward catalog, the adapter
|
||||
version and the compatibility string are untouched.
|
||||
|
||||
### 12.18 A mart's counter is four screens, and one of them is the clerk talking (2026-09-22, row 55)
|
||||
|
||||
Minutes after v0.4.7 went live the watchdog flagged the narrowest loop yet: map `0x38`, scene
|
||||
`shop`, `sequence: [BUY ANTIDOTE], period 1, repeats 747, distinctMacros 1` over ten brain
|
||||
minutes, with `BUY ANTIDOTE start` then `BUY ANTIDOTE blocked` every 0.8 s and **nothing else
|
||||
starting at all**. Surveyed from the live checkpoint with a new probe mode
|
||||
(`FLY_PROBE_CATCH=shop` in `examples/scene_probe.rs`); the bytes are in
|
||||
`docs/design/macros-wram.md` section 7.1.
|
||||
|
||||
- **`wListMenuID` says the counter is open, not which of its screens is up.** The mart prints its
|
||||
own text from inside `DisplayPokemartDialogue_`, which never calls the routine that clears that
|
||||
byte, so it holds `PRICEDITEMLISTMENU` for the **whole visit**. The frame the stream sat on was
|
||||
the clerk's "Here you are! Thank you!" box waiting for a press, and the seam called it the buy
|
||||
list; the cursor bytes on it belong to a two-option box (`max` 1). So which screen is up is now
|
||||
read from the figure the game draws -- the construction the dialogue box's `waiting` test and the
|
||||
YES/NO prompt already use. The clerk talking is `ShopScreen::Talking`, it reports **no listing at
|
||||
all**, and no purchase starts on it: a scene whose menu is not open offers what actually opens it,
|
||||
which here is `CONFIRM` and `LEAVE`, and both are already on the shop's pad.
|
||||
- **A mart's buy list scrolls, so an item's position in the stock is its cursor index only for the
|
||||
first three entries.** Walked one pulse at a time on the live list, the cursor went `0, 1, 2` and
|
||||
then stopped moving while the window scrolled under it. The offset that names the scrolled
|
||||
position is not a pinned address, so the fourth item of a counter and after have no index this
|
||||
seam can aim at. Pewter's counter carries seven items and its ANTIDOTE is the **fourth**:
|
||||
`BUY ANTIDOTE` aimed a cursor step at index 3 in a list reporting a max of 1 and returned
|
||||
`Blocked` **on its own first frame, having pressed nothing** -- three of three attempts, zero
|
||||
frames. A macro that cannot run is not on the pad, so the four purchases are bound on the rows
|
||||
the cursor can reach, and Viridian's four-item counter is why this went unseen: its ANTIDOTE is
|
||||
index 1.
|
||||
- **A purchase has no target, so a blocked purchase records nothing.** Section 12.1's ledger is
|
||||
keyed by what a walk set out for and a press sets out for nothing, so the button was dealt again
|
||||
on the very next hold, for ever -- row 6's shape in a scene with no walk in it. The fix is the
|
||||
precondition rather than a new ledger: the question the ledger would have answered is a fact about
|
||||
the counter, and the pad can ask it before the fly presses.
|
||||
|
||||
The loop was also **the fly's own choice landing on the one button it could afford**: the wallet
|
||||
read 104, so of Pewter's stock only the Antidote was under it, and `BUY ANTIDOTE` was the only
|
||||
purchase bound. Nothing about the choice changes here. What changes is that the button is not
|
||||
offered, and the two presses that leave a counter are.
|
||||
|
||||
Nothing here changes which button the fly presses. 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.")
|
||||
|
|
|
|||
|
|
@ -1552,9 +1552,22 @@ arm fought fifty thousand frames of battle *and* walked twenty thousand frames o
|
|||
|
||||
Raw reports: `hunt-before-20260922T0459.md` and `hunt-after-20260922T0459.md` in the coordination
|
||||
state's `runs/` directory.
|
||||
| 55 | `BUY <item>` is dealt on a frame where no list is accepting input, and aimed at a stock index the counter's cursor cannot reach: it gives up on its own first frame, presses nothing, records nothing, and is dealt again next hold | the Pewter mart, ten brain minutes after v0.4.7: map `0x38`, scene `shop`, `loop.json` `sequence: [BUY ANTIDOTE], period 1, repeats 747, distinctMacros 1`, `BUY ANTIDOTE start` / `BUY ANTIDOTE blocked` every 0.8 s with **nothing else starting**; money 104, so the Antidote was the only purchase money allowed | `the_clerks_text_box_is_not_the_marts_buy_list`, `the_clerks_own_text_box_deals_no_purchase_and_reports_no_list`, `a_purchase_past_the_cursors_reach_is_not_on_the_pad`, `a_purchase_out_of_reach_by_the_time_it_starts_is_refused_without_a_press`, and the ROM run below | **fixed**, two facts, both surveyed with `FLY_PROBE_CATCH=shop`. `wListMenuID` says the **counter is open**, not which screen is up -- the mart prints its own text without clearing it, so every frame of a visit read "the priced buy list", including the clerk's "Here you are! Thank you!" box whose leftover cursor bytes are a two-option box's (`max` 1); the screen is now read from the figure the game draws and the clerk is `ShopScreen::Talking`, which reports no listing and deals no purchase. And the buy list **scrolls**: the cursor walks rows 0, 1, 2 and then the window moves under it, so only the first three of a counter's stock have an index this seam can aim at, and Pewter's ANTIDOTE is its fourth. `docs/design/macros.md` section 12.18, `docs/design/macros-wram.md` section 7.1 |
|
||||
|
||||
### Residuals, named rather than worked around
|
||||
|
||||
- **`wListScrollOffset` is not a pinned address** (row 55), so a mart's fourth item and after have
|
||||
no cursor index this seam can name and their `BUY` buttons are off the pad -- Pewter's ANTIDOTE,
|
||||
BURN HEAL, AWAKENING and PARLYZ HEAL among them. Pinning it is the same survey `$cfc5` is waiting
|
||||
on in `docs/design/macros-wram.md` section 9: `gen_symbols.py` refuses a hand-written address and
|
||||
the checkout it reads is not on this box.
|
||||
- **A mart that has never drawn a buy list reads `Unknown`, not `Shop`** (measured beside row 55).
|
||||
On a first visit `wListMenuID` is 0 and `wTextBoxID` on the counter menu is `MONEY_BOX` `$0d`
|
||||
rather than `BUY_SELL_QUIT_MENU` `$15` -- the money box is the last template drawn -- so
|
||||
`state::shop` answers `None` until the fly has opened the list once. Named rather than worked:
|
||||
widening the test to `MONEY_BOX` would let the Game Corner's prize counter read as a mart, and
|
||||
what that costs has not been surveyed.
|
||||
|
||||
- **`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
|
||||
|
|
@ -2156,3 +2169,58 @@ skipped cleanly without `FLY_ROM` and the checkpoint):
|
|||
- `infra/tests/lint.sh`: all checks passed, de-PII guard included.
|
||||
- `--print-compatibility`: **648 bytes, sha256 `0d9bfde7...707fa`** -- byte-identical to v0.4.1
|
||||
through v0.4.5. Decoder, reward catalog, adapter version and roles untouched.
|
||||
|
||||
## Row 55: the two arms, and the ROM run (2026-09-22, v0.4.7)
|
||||
|
||||
One checkpoint, the mart the stream was standing in. Same seed, same ground, `main` at `5512900`
|
||||
against this branch, twenty brain minutes each.
|
||||
|
||||
| measure | before (`main`) | after |
|
||||
| --- | ---: | ---: |
|
||||
| distinct (map, tile) | **1** | **62** |
|
||||
| windows flagged | 73 / 73 | 72 / 73 |
|
||||
| macros started | 1,494 | 1,136 |
|
||||
| macros **blocked** | **1,494 — every one of them** | 82 |
|
||||
| `BUY ANTIDOTE` starts | **1,494, mean 0 frames, mean net 0.0 tiles** | **0** |
|
||||
| frames in scene `shop` | **71,673 of 71,673** | **225** (longest run 171) |
|
||||
| the repeated sequence | `BUY ANTIDOTE` x149 to x150, every window | `NEXT` x14 to x19 |
|
||||
| the fly leaves the mart | **never** | **at 0.19 brain minutes** |
|
||||
| frames in `battle` | 0 | 66,727 (longest run 17,722, from minute 0.58) |
|
||||
| wall clock | 4,210 s | 2,384 s |
|
||||
|
||||
**The before arm is the live loop whole, with the real brain in it**: the fly started 1,494 macros
|
||||
in twenty brain minutes, **every single one of them `BUY ANTIDOTE`, every single one `blocked`,
|
||||
every one at a mean of nought frames** — so it pressed no button at all for the whole run and stood
|
||||
on one tile. That is the sharpest reproduction any row here has had, and it is what a macro that
|
||||
gives up before its first press looks like from outside.
|
||||
|
||||
**The after arm walks out and then fights.** The shop scene is 225 frames of 71,673 instead of all
|
||||
of them, `CONFIRM` and `LEAVE` are the two macros that end it, and the fly is on the overworld at
|
||||
0.19 brain minutes, takes an item six tiles away, walks three routes at a mean net of 9.0 tiles
|
||||
(max 19) and is in a battle from 0.58. Sixty-two times the ground.
|
||||
|
||||
**The flagged-window count barely moves (73 → 72), and this says so rather than smoothing it.**
|
||||
What the windows are flagging after the fix is **row 50**: 66,727 of the 71,673 frames are one long
|
||||
battle, `MOVE 1`–`MOVE 4` are 74 of the 82 remaining `blocked` finishes at a mean of ~230 frames
|
||||
each, and the repeated sequence is `NEXT`/`BACK` in a battle. The hunt's tile rule cannot tell a
|
||||
long fight from a stall — `docs/design/macros.md` section 15's own measurement, and the third branch
|
||||
in a row to run into it. The loop this row is about is gone; the one underneath it is somebody
|
||||
else's brief.
|
||||
|
||||
**ROM-gated, from the same checkpoint**
|
||||
(`services/flysim/crates/flysim/tests/rom_macros_mode.rs`, skipped cleanly without `FLY_ROM` and
|
||||
the checkpoint):
|
||||
|
||||
- `the_fly_leaves_the_pewter_mart_counter_from_the_rung_eleven_checkpoint` — route `[56, 2, 14]`
|
||||
over 20.1 brain minutes: out of the mart, across Pewter City and on to Route 3. `BUY ANTIDOTE`
|
||||
**0** starts, `LEAVE` 1, `CONFIRM` 1, empty shop pads **0**, and the longest chain of one macro
|
||||
reporting `blocked` in the shop with the map, the tile, the wallet and the scene unchanged is
|
||||
**0**, against a bound of three. The same chain in the *battle* scene is **47** (`MOVE 2`),
|
||||
printed beside it and asserted on by nobody here: it is row 50's.
|
||||
|
||||
### Gates
|
||||
|
||||
- `cargo test --workspace` with `FLY_ROM` and `FLY_DATASET` set.
|
||||
- `cargo clippy --all-targets`.
|
||||
- `infra/tests/lint.sh`, de-PII guard included.
|
||||
- `flysim --print-compatibility`, byte-identical to this branch's base.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue