diff --git a/docs/re/BACKLOG.md b/docs/re/BACKLOG.md index fea8d8c0..1a0f21dc 100644 --- a/docs/re/BACKLOG.md +++ b/docs/re/BACKLOG.md @@ -389,6 +389,35 @@ unknown, what evidence exists, and what the first step would be. Move an item in the ring buffer at `bus+4` (capacity `+8`, head `+12`, count `+16`), not what reads `bus+8216`. Whether running out of time ends the mission is unanswered. +* ✅ **(2026-08-27) BUILT-IN 104 IS NAMED — `request_next`, from the game's own + log string. [structures/isl-condition-builtins](structures/isl-condition-builtins.md).** + Built-in 104 returns `[phase+10160]`, whose **only writer in the image** is one + site in the mission frame loop `sub_821AA1B0`. Following that writer instead of + the reader: `r29` is `obj->slot2()` on an object fetched from the registry at + `[r30+2424]` by id **`0x20FFFF02`**, published to the phase, and then the same + object is `slot1(0)` — **read, publish, clear, every frame**. + 🔑 **The id namespace has exactly three members** (`0x20FFFF00/01/02`), each + built at exactly 4 sites image-wide — a closed family, so this is not the + coincidence the last two iterations kept tripping on. Two of those sites are in + `sub_821D5178`, which gets **both** `0x20FFFF01` and `0x20FFFF02` and passes + their values to a log call whose format string (`0x820A4968`) is: + *"silph::GamePart_ReadyRoom::Impl::OnCommand - Wait() command is requested. + Check flow control valiables. **WAIT_MODE : %d, REQUEST_NEXT : %d**"*. + Argument order settles it — `r4` (first `%d`) is `0x20FFFF01` = **WAIT_MODE**, + `r5` is `0x20FFFF02` = **REQUEST_NEXT**. ⇒ **`[phase+10160]` is REQUEST_NEXT** + and built-in 104 is **`request_next`**. Corroborated by + `PrepareScript`, which sets `WAIT_MODE = 1` and `REQUEST_NEXT = 0` before an ISL + script runs. So the six tutorial stages' single dominating condition + `request_next() != 1` is the script waiting on the surrounding game part's + *proceed* flag. ⚠️ Three other hits on offset 10160 are `lfs` on unrelated + objects — the recurring-offset trap, excluded. + ✅ Renamed in `isl.py`; **6 artefact lines changed and all 6 pair exactly.** + 🟡 Not settled: what the `(16, 32]` gate on `[r30+104]`'s `+12` selects; the + third id `0x20FFFF00` (used by the `GRAPH_PATH`/`EX_FONT`/`SYSTEM` code in + `sub_821D6350`/`sub_821D6A40`); and `builtin103`'s `[phase+10152]` / + `[phase+10156]`, which are neighbours of REQUEST_NEXT but have 14 and 11 + touching sites rather than one — a different, busier mechanism. + ## ✅✅ SOLVED — the mission freeze was a modal sign-in dialog (2026-08-26) `XamShowSigninUI` opens a modal dialog and `xeXamDispatchDialog` blocks the diff --git a/docs/re/data/isl-phase-guards-all.txt b/docs/re/data/isl-phase-guards-all.txt index 0a538b7e..bb708586 100644 --- a/docs/re/data/isl-phase-guards-all.txt +++ b/docs/re/data/isl-phase-guards-all.txt @@ -1131,7 +1131,7 @@ An exit with no TRIGGER is not fired by a branch — a timeout exit is fired by time passing, which is not a property of the graph. ## phase 1 — END_PHASE at 0x002958: 1 necessary condition(s) - precond 0x002898 builtin104() != 1 + precond 0x002898 request_next() != 1 ------------------------------------------------------------------------ @@ -1152,7 +1152,7 @@ An exit with no TRIGGER is not fired by a branch — a timeout exit is fired by time passing, which is not a property of the graph. ## phase 1 — END_PHASE at 0x001950: 1 necessary condition(s) - precond 0x001890 builtin104() != 1 + precond 0x001890 request_next() != 1 ------------------------------------------------------------------------ @@ -1173,7 +1173,7 @@ An exit with no TRIGGER is not fired by a branch — a timeout exit is fired by time passing, which is not a property of the graph. ## phase 1 — END_PHASE at 0x00261C: 1 necessary condition(s) - precond 0x00255C builtin104() != 1 + precond 0x00255C request_next() != 1 ------------------------------------------------------------------------ @@ -1194,7 +1194,7 @@ An exit with no TRIGGER is not fired by a branch — a timeout exit is fired by time passing, which is not a property of the graph. ## phase 1 — END_PHASE at 0x001250: 1 necessary condition(s) - precond 0x001190 builtin104() != 1 + precond 0x001190 request_next() != 1 ------------------------------------------------------------------------ @@ -1215,7 +1215,7 @@ An exit with no TRIGGER is not fired by a branch — a timeout exit is fired by time passing, which is not a property of the graph. ## phase 1 — END_PHASE at 0x00208C: 1 necessary condition(s) - precond 0x001FCC builtin104() != 1 + precond 0x001FCC request_next() != 1 ------------------------------------------------------------------------ @@ -1236,7 +1236,7 @@ An exit with no TRIGGER is not fired by a branch — a timeout exit is fired by time passing, which is not a property of the graph. ## phase 1 — END_PHASE at 0x001208: 1 necessary condition(s) - precond 0x001148 builtin104() != 1 + precond 0x001148 request_next() != 1 ------------------------------------------------------------------------ diff --git a/docs/re/structures/isl-condition-builtins.md b/docs/re/structures/isl-condition-builtins.md index c299449a..2cf6a546 100644 --- a/docs/re/structures/isl-condition-builtins.md +++ b/docs/re/structures/isl-condition-builtins.md @@ -40,8 +40,53 @@ which is exactly why their exits have one dominating condition each and why ``` inside `sub_821AA1B0`, gated on a type/kind field being in `(16, 32]`. `r29` -there comes from `or r29, r3, r3` — the return of a preceding call — so **what -the value means is not established**, and neither is what the gate selects. +there comes from `or r29, r3, r3` — the return of a preceding call. + +## ✅ (2026-08-27) NAMED — built-in 104 is `request_next`, and the game says so itself + +`r29` is the return of a **getter on an object fetched from a registry by id**: + +``` +821AAD1C lwz r4, 2424(r30) ; the registry +821AAD20 addis r5, r0, 0x20FF +821AAD28 ori r5, r5, 0xFF02 ; <- the id +821AAD38 bcctrl ; registry->slot1(&out, 0x20FFFF02) +821AAD40 lwz r11, 0(r3) ; lwz r11, 8(r11) +821AAD4C bcctrl ; r29 = obj->slot2() -- GET +821AAD9C stw r29, 10160(r11) ; -> [phase+10160] +821AADA0..DD4 ; then the SAME id again, obj->slot1(0) -- CLEAR +``` + +So the frame loop **reads the value, publishes it to the script, and clears it**. + +**The id namespace has exactly three members** — `0x20FFFF00`, `0x20FFFF01`, +`0x20FFFF02` — each built at exactly 4 sites image-wide. Two of those sites are +in `sub_821D5178`, which fetches `0x20FFFF01` and `0x20FFFF02`, calls the same +`slot2()` getter on each, and passes both to a log call whose format string is +at `0x820A4968`: + +> `silph::GamePart_ReadyRoom::Impl::OnCommand - Wait() command is requested.` +> `Check flow control valiables. WAIT_MODE : %d, REQUEST_NEXT : %d` + +Argument order settles which is which: `r4` (first `%d`, **WAIT_MODE**) is +`0x20FFFF01`'s value, `r5` (second, **REQUEST_NEXT**) is `0x20FFFF02`'s. + +> ⇒ **`[phase+10160]` is `REQUEST_NEXT`, and built-in 104 returns it.** + +That is the game's own name for the variable, not a shape-based guess. +Corroboration: `GamePart_ReadyRoom::Impl::PrepareScript` sets `WAIT_MODE = 1` +and `REQUEST_NEXT = 0` before an ISL script runs, and `sub_821AA1B0` clears both +each frame after reading. `[phase+10160]` has **exactly one writer and one +reader** in the whole image (the two above); the three other hits on offset +10160 are `lfs` on unrelated objects. + +So the six tutorial stages' exit condition `request_next() != 1` is the script +waiting on the surrounding game part's *proceed* flag — which is why S18–S23 +each have exactly one dominating condition. + +🟡 Still unread: what the `(16, 32]` gate on `[r30+104]`'s `+12` selects, and +`0x20FFFF00`, the third id (used by the `GRAPH_PATH` / `EX_FONT` / `SYSTEM` +code in `sub_821D6350` and `sub_821D6A40`). 🟡 Its neighbours belong to the same cluster: `builtin103` reads `[phase+10156]` and `[phase+10152]` (9 and 7 writers), and a sibling vtable stub diff --git a/tools/re-capture/isl.py b/tools/re-capture/isl.py index 0921f84c..448ed94a 100755 --- a/tools/re-capture/isl.py +++ b/tools/re-capture/isl.py @@ -110,7 +110,7 @@ BUILTIN = { 81: 'banner_objective_update', 82: 'banner_mission_failed', 135: 'banner_mission_restart', 93: 'stopwatch_stop', 94: 'is_engaged', 95: 'unit_hp_pct', 100: 'reset_phase_threads', - 102: 'prompt_yes_no', 108: 'deploy_squadron_ex', + 102: 'prompt_yes_no', 104: 'request_next', 108: 'deploy_squadron_ex', 109: 'set_unit_flags', 115: 'named_event', 120: 'wait_cmds_drained', 123: 'timer_resume', 124: 'timer_stop', 125: 'timer_reset', 126: 'timer_elapsed', 127: 'timer_set',