docs: section 12.12, the nurse's box

What was live, what the survey read, and what the three candidate
readings in the brief turned out to be: not the prompt, not HEAL, but the
dialog pad dealt unconditionally with TALK to get back into it. The
scene-by-scene table splits the dialog row in two and the pad-empty sweep
gains the reopen ledger's own residual. macros-wram.md narrows its "no
observable for a choice" claim rather than withdrawing it.
This commit is contained in:
acamilo 2026-09-22 10:39:15 +00:00
parent 0bf4b965fb
commit 5b9f5a54ca
2 changed files with 92 additions and 3 deletions

View file

@ -145,6 +145,7 @@ forces. A battle that is neither — text, an animation, the turn resolving —
| a submenu | the weakest rule here, and the reason `Unknown` exists: `wListMenuID` is the bag or an elevator list, or the party list geometry outside a battle. A submenu none of those catch reads as `Unknown`, never as `Overworld`. | trace |
| mart | `wTextBoxID` = `BUY_SELL_QUIT_MENU` (`$15`) for the BUY / SELL / QUIT choice, and `engine/events/pokemart.asm:17` is its only user in the game; the buy list is `wListMenuID` = `$02` and the sell list is the bag's own `$03`, recognised only while the mart's template is still the last one drawn. | trace |
| PC | `wMiscFlags` bit 3, above. | trace |
| a two-option YES/NO box | **new 2026-09-22** (`docs/design/macros.md` section 12.12). `wFontLoaded` bit 0, plus the border `DisplayTwoOptionMenu` draws at (11, 6)-(19, 11), plus the shared cursor parked at `wTopMenuItemY` 8, `wTopMenuItemX` 12 with `wMaxMenuItem` 1 and `wMenuWatchedKeys` = A\|B. **Both halves are load-bearing**: the cursor bytes survive the box closing, so all forty-six frames of a Pokémon Center nurse's conversation carry that geometry while the box is drawn on exactly one of them. It does **not** answer "is a choice open" in general — Red places a two-option menu where the script asking for it says, and a prompt drawn elsewhere reads `false`. | ROM (the rung-10 Pokémon Center checkpoint, surveyed one raw A pulse at a time: `examples/scene_probe.rs`, `FLY_PROBE_CATCH=nurse`) |
### Money and bag
@ -474,6 +475,13 @@ Two facts about the emulator came out of building these and are worth keeping:
which is two battles away from anything a fixed-seed walk reaches quickly. The trace is built
from `ChooseNextMon` and `PartyMenuInit`, and the distinguishing byte
(`wPartyMenuTypeOrMessageID` = `BATTLE_PARTY_MENU`) is asserted both ways.
- **`yes_no_prompt` is "*this* two-option box is drawn", not "a choice is open".** The claim in
earlier revisions of this file — that pokered has no observable for a choice — stands for the
general question and is now narrowed rather than withdrawn: the box the Pokémon Center nurse's
offer is drawn in has been surveyed on the cartridge and is readable, and every other
two-option menu in the game is not, because `DisplayTwoOptionMenu` takes its coordinates from
the script that calls it. A prompt this reading misses is a plain dialog, which is the pad it
had before the reading existed.
- **`text_box().waiting` is "the dialogue box is drawn", not "the game wants a button".** Pokered
has no flag for the second thing; A is the right press either way, so the distinction has no
consequence for the palette, but it is not what the field's name might suggest.

View file

@ -1003,6 +1003,83 @@ cursor inverted and it needs a WRAM reading rather than a pad change.
The decoder, the reward catalog, the adapter version, the roles and the compatibility string are
untouched.
### 12.12 The nurse's box is a ring, and `YES` and `NEXT` are one press (2026-09-22, rung 10, row 41)
Rank 10 (PEWTER CITY), the fly in the Pewter Pokémon Center, and since the v0.4.4 restart the
macro starts were `YES` **2,142**, `TALK` 107, `GO FRONTIER` 26, `BACK` 24, with the event log's
tail `YES start/done` for ever. This is **row 41**, first measured in the rung-9 trap hunt (`YES`
1,278 of 1,295 starts on one tile of map `0x3a`) and named by 12.11 as the next trap. Reproduced
from the live checkpoint with the real cartridge and surveyed press by press
(`examples/scene_probe.rs`, `FLY_PROBE_CATCH=nurse`); `infra/docs/macros-traps.md` has the survey
whole.
- **The conversation is a ring of forty-six A presses, and the box is a *choice* on one of them.**
Welcome, "We heal your POKéMON back to perfect health!", the **YES/NO box**, "OK. We'll need your
POKéMON.", the machine, "Your POKéMON are fighting fit!", "We hope to see you again!", the box
closes for a single frame, and the next A press at a nurse two tiles away over her counter opens
the whole thing again. The party read **70/70 and healthy** on every frame of it, so not one of
those presses changed anything. That is section 12.2's rule at conversation scale: a macro whose
precondition is already satisfied where the fly stands is a trap.
- **The brief allowed three readings and the survey settles it as none of them.** The box open at
the checkpoint is not the prompt, it is the closing line, and `YES` there is an A press on plain
text -- forty-five of the forty-six frames are like that. `HEAL` is not in the loop at all: its
precondition already reads the live party, `party_needs_rest` answers `false`, and the button was
off the pad the whole time. And `HEAL`'s own wait is a *read* of the party, not a loop waiting
for a timer, so it cannot spin on a party that is already full. What was in the loop was the
**dialog pad**, dealt unconditionally, and `TALK` to get back into it.
- **On a box that is a choice, `NEXT` is `YES` under another name.** An A press at a two-option menu
confirms the option the cursor is on, and the cursor opens on YES, so `NEXT` and `YES` are one
press with two channels -- 12.10's rule about a pair of buttons, in a dialog rather than a
battle. `NEXT` is off any pad dealt for a readable YES/NO prompt; the pad is the box's own two
answers.
- **At the nurse's prompt the answer that changes something is the only one bound.** Hurt or
statused, `YES`; full and healthy, `NO`. Section 13 has read that byte for `HEAL` since the
errands existed; this is the same byte read for the box `HEAL` opens. Knowledge inside the macro
as a precondition, and nothing ranks the two: one of them simply is not there.
- **`TALK` is not offered at a nurse the party has no use for.** The nurse is an object with a
purpose rather than a person to chat with, and she is the one person in Red whose conversation
has a precondition the cartridge publishes. This is the door into the ring, and closing it is
what makes the rest of the section a backstop rather than the fix. Nobody else is narrowed: an
ordinary villager is `TALK`'s whatever the party reads.
- **The nurse enters the *talked* ledger on a completed heal or a declined prompt.** A completed
`HEAL` has had the conversation with its own presses, so `TALK` has nothing left to open, and the
reached window would otherwise expire in ten brain minutes and offer the ring again. A declined
prompt is 12.4's rule deliberately inverted, for one person: "the thing it said no to is still on
offer" is true of a villager with something to say and false of a service the party does not
need -- and the pad only ever offers `NO` at her prompt when the party is already full.
- **`TALK`'s precondition and `TALK`'s ledger entry were two different questions, which is why she
was never retired.** The precondition reaches over a counter, because
`IsSpriteOrSignInFrontOfPlayer` does; the entry was read one tile ahead. So `TALK` was *bound* at
the counter and *recorded* nothing at all, 107 times. A precondition and the ledger that answers
it have to be the same reading, and they are now.
- **The general rule: an answer that brings the same prompt straight back is excluded for the
blocked window.** Same map, same tile, same readable prompt, within one hold of the answer
finishing -- nothing moved and nothing was settled, so the press did nothing the next press will
not undo. It is 12.1's ledger doing for an answer what it does for a walk, with the same ten
brain minutes, keyed `TargetKey::Answer { at, yes }`. The exclusion *narrows* a pad and never
empties one: with both answers excluded both come back, because a box nothing can answer is a
screen nothing can leave.
- **What the reading rests on, and what it does not claim.** `docs/design/macros-wram.md` said
outright that there is no "a choice is open" flag, and there is not -- so `yes_no_prompt` is the
construction `text_box`'s `waiting` already makes: `wFontLoaded` plus the figure the game draws,
a border at (11, 6)-(19, 11) with the shared cursor parked at row 8, column 12, one item below
the first, watching A and B. **Both halves are needed**: the cursor bytes are not cleared when
the box closes, so all forty-six of the nurse's frames carry that geometry while the box itself
is drawn on exactly one. Red places a two-option menu where the script asking for it says, so a
prompt drawn somewhere else reads `false` and its dialog keeps the pad it has always had. That is
a named limit, not a gap being papered over.
**What the harness holds.** Unit: `HEAL` is off a full party's pad, including the rung-10 party's
own numbers; `TALK` is off a rested nurse's pad and on a hurt one's; the nurse's 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 an answer that settled the box is not; a
completed heal and a declined prompt both write the nurse into the talked ledger. ROM-gated from
the live checkpoint: the fly leaves map `0x3a`, `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 decoder, the reward catalog, the adapter version, the roles 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.")
@ -1025,7 +1102,9 @@ Knowledge inside macros, never in the choice; the pad still lists buttons and th
marks nothing visited; the errand was paid on entering.
- **Center scene.** `HEAL` is a macro: walk to the counter, face the nurse, talk, answer YES,
wait for the heal animation to end (read the party HP back to full), close the box. On the
pad only when at least one party member is not at full HP or has a status. `LEAVE` walks out.
pad only when at least one party member is not at full HP or has a status — verified against the
live party on the cartridge (**12.12**), which is also what takes `TALK` at the nurse off the pad
and what picks the one answer her YES/NO box is dealt. `LEAVE` walks out.
- **Ladder.** Unchanged; no reward for shopping or healing (rewards are the adapter's,
untouched).
- **Screen.** Two new channel tags; the cells and the MACROS rate row take them as they come.
@ -1075,8 +1154,9 @@ observe is not a precondition, it is a guess.
| Overworld, inside a Pokémon Center | the indoor pad plus HEAL | new. A centre is a sub-state of the overworld, not a `Scene`: pokered has no "a Pokémon Center is open" byte, so the only honest observable is the map id, and a new `Scene` would be a new `game.scene` on the wire |
| Overworld, inside a mart | the indoor pad | the counter is a `Shop`; the mart's *floor* is an ordinary interior, with the one exception below |
| Overworld, inside a mart or a centre, counter unfaced | GO SHOP or GO HEAL, TALK when facing the counter | new, and it is the one place a pad is deliberately *narrow*. The errand is paid on entering and never offered again, so a walk that leaves the building spends the one visit the area gets — measured: the fly reached the mart in 1.7 brain minutes and `GO OBJECTIVE` walked it straight back out over the doormat. While the counter is unfaced nothing on the pad leaves (row 34b) |
| Overworld, inside a mart or a centre, counter faced | the indoor pad, plus HEAL in a centre | the suppression is released by facing the counter, by talking to it, or by a walk to it failing |
| Dialog | NEXT, YES, NO | unchanged. There is no "a choice is open" flag (`macros-wram.md`), and A and B both advance a plain box, so all three are dealt for every box — what it buys is the fly being able to answer *no* |
| Overworld, inside a mart or a centre, counter faced | the indoor pad, plus HEAL in a centre | the suppression is released by facing the counter, by talking to it, or by a walk to it failing. Since **12.12** `TALK` is not on it 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 |
| Dialog, a plain text box | NEXT, YES, NO | A and B both advance a plain box, so all three are dealt for one — what it buys is the fly being able to answer *no*. Forty-five of the nurse's forty-six frames are this row (**12.12**) |
| Dialog, a readable YES/NO box | YES, NO — or **one of them** at a Pokémon Center's nurse | **new, 12.12.** `NEXT` is off it: an A press at a two-option menu confirms the option the cursor is on, which is what `YES` is, so the two are one press under two names (12.10). At the nurse's own prompt the bound answer is the one that changes something — `YES` with a hurt or statused party, `NO` with a full one. An answer whose prompt comes straight back is excluded for the blocked window, and the exclusion never empties the pad |
| Menu (the start menu) | CLOSE, CONFIRM, BACK | unchanged as a *scene*, and since **12.11** nothing on any other pad opens it: the fly reaches it with the **raw** START button, which still reaches the cartridge in macros mode, and moves its cursor with the raw D-pad. A SAVE or a POKéDEX button would be a macro per start-menu entry and is not asked for -- which is precisely why `MENU` had nothing behind it |
| 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 |
@ -1120,6 +1200,7 @@ measured where it cannot.
| cause | closed by |
| --- | --- |
| a dialog whose one answer the reopen ledger excludes | **not a cause** (12.12): the exclusion narrows a pad and never empties one, so a box with both answers excluded is dealt both again. A box nothing can answer is a screen nothing can leave |
| a scene that binds nothing at all | the table above: every playable scene but the overworld has at least one unconditional button (`NEXT` in a dialog and in a battle, `CLOSE` in a menu, `LEAVE` in a shop and a PC). The overworld's was `MENU`, and **12.11** took it off rather than keep a button whose only effect is a screen its own scene closes again; what stands in its place is the row below |
| an overworld map with no way out at all | **not closed, and named**: with `MENU` gone this is a genuinely empty pad. No map in Red is that -- an interior has its front door or its staircase, an outdoor map has its connections -- so it is asserted as a residual in the pad-empty sweep rather than covered, and `game.padEmptyMs` reports it |
| an *indoors* overworld where every ledger excludes everything and the blocked window is resting the one door | **fixed** (12.11): `ways`' last resort is a room's too, not only `GO ROUTE`'s. With nothing else on this map worth walking to, the exits of that kind come back ignoring the blocked window, the one toward the objective preferred. This is the rung-10 museum: its only way out is a *passage*, so tier 3 was built out of the excluded list and emptied with it |