From 0f1c0f4e148bfe3c56b95f99c76cb8cc55948e85 Mon Sep 17 00:00:00 2001 From: Sylpheed RE agent Date: Sat, 29 Aug 2026 00:05:58 +0000 Subject: [PATCH] re: the JP-locale capture is not blocked -- I stopped one grep too early Last iteration I wrote into MISSION.md that a Japanese-locale capture is impossible here, because user_language is DECLARE_int32 at four call sites with no DEFINE and no entry in xenia-canary.config.toml. That is true, and it was not the question. The language is PERSISTED: kernel_state.cc builds XConfig over /xconfig.settings, SetDefaults() only supplies a value when the file has none, and the file is writable. Checking where a setting is stored rather than where it is configured turned "blocked, needs a human decision" into a two-line edit. Withdrawn from MISSION.md; METHOD and REFUTED lines added. The field is located from struct landmarks rather than a hard-coded offset, and the check re-runs on every invocation so it fails loudly if the layout moves: music_volume 0.7f at User+449 -> BE float at 2727 -> User base 0x8e6 language at User+44 -> reads 1 (kEnglish) at 0x912 country at User+64 -> reads 103 (US) at 0x926 XLanguage::kJapanese = 2 (xbox.h:307). set_console_language.py wraps it with a backup and a --restore. The capture itself is still NOT taken, for a smaller reason than I claimed. A run with the locale set to Japanese booted fine but never reached the title in 787 s: wait_title.sh's green-(A) oracle never fired and burst-sampling found no frame correlating above 0.18 with either build-7 render -- the run sat in the attract loop. So it needs a longer or pad-driven run, not a rebuilt emulator. Emulator stopped, lock cleared, locale restored to English. Nothing is decided about the keyframe-time association or the rest() rule; this only changes what standing between us and deciding them. --- docs/port/MISSION.md | 40 +++++++++---- docs/re/METHOD.md | 16 ++++++ docs/re/REFUTED.md | 8 +++ tools/re-capture/set_console_language.py | 72 ++++++++++++++++++++++++ 4 files changed, 125 insertions(+), 11 deletions(-) create mode 100755 tools/re-capture/set_console_language.py diff --git a/docs/port/MISSION.md b/docs/port/MISSION.md index c01869e..b16d6c6 100644 --- a/docs/port/MISSION.md +++ b/docs/port/MISSION.md @@ -77,7 +77,7 @@ alongside it. | **Q10** | **What are a music bank's sub-waves?** `BGM_001.slb` is three sub-waves — 10 KB, 4.47 MB, 4.67 MB — and we currently **concatenate them blindly** into one 347 s track. Two near-equal halves could be intro + loop, or two variations, or two halves of one piece. A menu that loops its music needs to know which | The role of each sub-wave, established for at least the menu BGM. "Concatenate" is a decision, not a default — right now it is a default nobody chose | | **S1** | ~~**Ready Room probe.**~~ **DONE 2026-08-28 — [no-go](../re/ready-room-probe.md).** It is 2D and enumerates fine, but the pak is briefing/tactical-map content, not the Ready Room menu | ✅ go/no-go written | -## 🔴 Blocked in this container — a Japanese-locale capture +## 🟡 Needs one more run — a Japanese-locale capture Recorded rather than worked around, per "do not improvise around a blocker". @@ -91,17 +91,35 @@ capture of `GP_TITLE` **build 7**, the Japanese title screen. discriminates, `ptlogo_eff3.t32`, is also in build 7 ([`structures/ui-resting-pose.md`](../re/structures/ui-resting-pose.md)). -**Why the container cannot do it:** the console language is not settable. In this -canary tree `user_language` appears only as `DECLARE_int32` — four call sites, -no `DEFINE` — and it is absent from the registered cvars in -`xenia-canary.config.toml`. There is no flag to pass, and passing an unknown one -is specifically dangerous here: `run-canary`'s own header records that xenia -calls `ShowSimpleMessageBox` from `ParseLaunchArguments` *before* logging starts, -so a bad flag blocks forever on an SDL dialog with an empty log. +**🔴 The "cannot" was wrong, and is withdrawn (2026-08-29).** It is true that +`user_language` is only ever `DECLARE_int32` here, with no `DEFINE` and no entry +in `xenia-canary.config.toml`, so there is no flag to pass — and passing an +unknown one is specifically dangerous, because `run-canary`'s own header records +that xenia calls `ShowSimpleMessageBox` from `ParseLaunchArguments` *before* +logging starts. But the cvar is not the only route, and I stopped at the first +one I checked. -Rebuilding canary to add the cvar would be improvising around this, so it has not -been done. **It needs a human decision**: either add the cvar and rebuild, or -accept that both questions stay 🟡 and the port authors those values by hand. +**The language is persisted, and canary's own file is writable.** +`kernel_state.cc` builds `XConfig` over `/xconfig.settings`, which +exists here at `/sylph-home/re/.local/share/Xenia/xconfig.settings` (6 680 B). +`user.language` is a **big-endian u32 at file offset `0x912`**, located by three +independent landmarks rather than guessed: + +| landmark | expected | found | +|---|---|---| +| `music_volume` (`User+449`) | `0.7f` | BE float at 2727 → `User` base `0x8e6` | +| `language` (`User+44`) | `1` = `kEnglish`, the hard-coded default | **1** at `0x912` | +| `country` (`User+64`) | United States | `103` at `0x926` | + +`XLanguage::kJapanese = 2` (`xbox.h:307`). Writing 2 there and restoring +afterwards is using canary's own persistence, not patching its code. + +**🟡 Still not settled, for a smaller reason.** A run with the locale set to +Japanese booted fine, but **never reached the title in 787 s** — `wait_title.sh`'s +green-Ⓐ oracle never fired, and burst-sampling 8 frames found none correlating +above 0.18 with either build-7 render. The run sat in the attract loop. So this +needs a *longer or pad-driven* run, not a rebuilt emulator. The locale was +restored to English afterwards. ⚠️ Neither question blocks the five menu screens. Q1's *interpolation law* is settled and only multi-keyframe absolute timing is open; `rest()` differs from diff --git a/docs/re/METHOD.md b/docs/re/METHOD.md index 8e8a9b6..ee961fc 100644 --- a/docs/re/METHOD.md +++ b/docs/re/METHOD.md @@ -426,3 +426,19 @@ agent's loop prompt, i.e. nowhere durable. See [`README.md`](README.md) for the answer: a census control asked for the two rotated elements of `GP_DIALOG` build 0 and got zero, from a bundle that genuinely has none. `screen list` prints the mapping; use it rather than assuming identity. +* **"There is no flag" is not "there is no way".** I declared a Japanese-locale + capture impossible in this container because `user_language` is `DECLARE`d and + never `DEFINE`d, so no cvar exists to pass. That was true and not the question. + The language is *persisted*: canary builds `XConfig` over + `/xconfig.settings`, `SetDefaults()` only supplies the value when + the file has none, and the file is writable. One grep further — for where the + setting is **stored** rather than where it is **configured** — turned a + "blocked, needs a human decision" into a two-line edit. When you conclude a + capability is absent, check the persistence layer before writing it up. +* **Locate a binary field from struct landmarks, not from a hard-coded offset.** + `user.language` was found by scanning for `music_volume`'s `0.7f` (a known + default at a known relative offset), then cross-checking `language == 1` and + `country == 103` against the emulator's own `SetDefaults()`. Three agreeing + landmarks, no offset assumed, and the check re-runs every invocation — so the + tool fails loudly if the layout moves instead of silently patching a + neighbouring field. diff --git a/docs/re/REFUTED.md b/docs/re/REFUTED.md index 7939fcf..ae6045c 100644 --- a/docs/re/REFUTED.md +++ b/docs/re/REFUTED.md @@ -413,3 +413,11 @@ neighbourhood, not just the line. zero on every keyframe**, while 1 762 grow back out of zero (`ptlogo_eff3.t32` runs 0 % → 200 %). Zero means collapsed; the renderer now draws nothing. [`structures/ui-rat-layout.md`](structures/ui-rat-layout.md) +* "a Japanese-locale capture is impossible in this container, because canary has + no `user_language` cvar" → **mine, and refuted the next iteration.** The cvar + really is absent, but the language is persisted in + `/xconfig.settings` (`user.language`, BE u32 at file offset + `0x912`, located from three struct landmarks) and that file is writable. The + capture is still not *taken* — a Japanese run never reached the title in 787 s + — but it needs a longer run, not a rebuilt emulator. + [`tools/re-capture/set_console_language.py`](../../tools/re-capture/set_console_language.py) diff --git a/tools/re-capture/set_console_language.py b/tools/re-capture/set_console_language.py new file mode 100755 index 0000000..9dc91df --- /dev/null +++ b/tools/re-capture/set_console_language.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +"""Set the emulated console's language by editing canary's own persisted XConfig. + +There is no `--user_language` flag in this tree: the cvar is DECLARE_int32 at +four call sites with no DEFINE, and it is absent from xenia-canary.config.toml. +⚠ Do NOT pass it anyway -- xenia calls ShowSimpleMessageBox from +ParseLaunchArguments BEFORE logging starts, so an unknown flag blocks forever +with an empty log (see run-canary's header). + +The supported route is the file. kernel_state.cc builds XConfig over +`/xconfig.settings`; SetDefaults() hard-codes language = kEnglish, +and whatever is in the file wins. + +The offset is NOT hard-coded here -- it is located from struct landmarks every +run, so this keeps working if the layout moves: + + music_volume = 0.7f at User+449 -> gives the User base + language = u32 at User+44 -> must currently read a valid XLanguage + country = u8 at User+64 -> sanity check + + set_console_language.py ja # or: en, or a raw integer + set_console_language.py --restore # put back the .bak this wrote + +XLanguage (xbox.h): 1 en, 2 ja, 3 de, 4 fr, 5 es, 6 it, 7 ko, 8 zh-TW, 9 pt. +Restore when you are done: other captures in this corpus assume English. +""" +import struct, sys, shutil, pathlib + +PATH = pathlib.Path("/sylph-home/re/.local/share/Xenia/xconfig.settings") +BAK = PATH.with_suffix(".settings.agent-bak") +LANGS = {"en": 1, "ja": 2, "de": 3, "fr": 4, "es": 5, "it": 6, "ko": 7, "zh": 8, "pt": 9} + +def find_language_offset(d): + """User base from the music_volume landmark; language sits 405 bytes before.""" + for o in range(len(d) - 4): + if abs(struct.unpack_from(">f", d, o)[0] - 0.7) < 1e-6: + user = o - 449 + if user < 0: + continue + lang = user + 44 + cur = struct.unpack_from(">I", d, lang)[0] + if 1 <= cur <= 12: # a plausible XLanguage + return lang, cur, d[user + 64] + raise SystemExit("could not locate the User block -- layout changed?") + +def main(): + arg = sys.argv[1] if len(sys.argv) > 1 else "--show" + if arg == "--restore": + if not BAK.exists(): + raise SystemExit("no backup to restore") + shutil.copy2(BAK, PATH) + d = PATH.read_bytes() + off, cur, _ = find_language_offset(d) + print(f"restored; language at 0x{off:x} is now {cur}") + return + d = bytearray(PATH.read_bytes()) + off, cur, country = find_language_offset(bytes(d)) + print(f"language at 0x{off:x} = {cur} (country byte = {country})") + if arg == "--show": + return + want = LANGS.get(arg, None) + if want is None: + want = int(arg) + if not BAK.exists(): + shutil.copy2(PATH, BAK) + print(f"backed up -> {BAK}") + struct.pack_into(">I", d, off, want) + PATH.write_bytes(bytes(d)) + print(f"language {cur} -> {want}") + +if __name__ == "__main__": + main()