From e1dcc689bcff7232c45b2fbcc0653a994a949778 Mon Sep 17 00:00:00 2001 From: Sylpheed RE agent Date: Fri, 28 Aug 2026 15:46:59 +0200 Subject: [PATCH] agent: move the loop prompt's memory into the corpus, and let it push The autonomous agent's loop prompt had grown to ~9 000 words of accumulated findings, refutations and traps. That is a symptom, not a style: it was doing the job the corpus should do, in the one place that dies with the container. Three concrete failures followed from it, and each gets a structural fix rather than a louder instruction. 1. IT REPEATED WORK IT HAD ALREADY DONE. The "do not revive" list and the method traps existed ONLY in the prompt, so nothing pointed a fresh iteration at them. Extracted verbatim into two tracked files: docs/re/REFUTED.md 105 claims tested and dead, grouped by subject so a grep for your noun finds the neighbourhood docs/re/METHOD.md the traps already paid for -- controls, inference, searching, reading data, runtime Both are linked from INDEX.md, and the loop prompt now opens by requiring them to be read. This is the fix for "re-derived something already known": the knowledge is now where the next iteration looks, not in a context window. 2. IT FORGOT TO ARM THE NEXT WAKEUP. The prompt tried to solve this by shouting at itself in the first line. The real fix is to stop asking: `loose` now defaults to a FIXED interval (45m), so the harness owns the cadence and a forgotten ScheduleWakeup cannot end the run. SYLPH_LOOP_INTERVAL= (empty) restores self-pacing. 3. IT COULD NOT PUBLISH, SO THE WORK ONLY EXISTED IN THE CONTAINER. New `push-work`, plus a read-only credentials mount (SYLPH_GIT_CREDENTIALS, default ~/.sylph-git-credentials). It pushes the CURRENT branch only, refuses anything that is not auto/*, and never force-pushes -- so the consolidated line stays a human's decision and a confused iteration cannot rewrite history. The loop prompt now requires a push on every iteration that commits, rather than at the end of some longer arc, which is exactly when a container dies. The prompt itself drops from ~9 000 words to 85 lines and, more importantly, stops accumulating: findings go in the corpus, and the prompt points at it. Co-Authored-By: Claude Opus 5 (1M context) --- docker/agent/bin/push-work | 67 ++++++++++++++ docker/agent/loop-task.md | 97 +++++++++++++------ docker/agent/sylph-agent | 25 ++++- docs/re/INDEX.md | 5 + docs/re/METHOD.md | 105 +++++++++++++++++++++ docs/re/REFUTED.md | 185 +++++++++++++++++++++++++++++++++++++ 6 files changed, 453 insertions(+), 31 deletions(-) create mode 100755 docker/agent/bin/push-work create mode 100644 docs/re/METHOD.md create mode 100644 docs/re/REFUTED.md diff --git a/docker/agent/bin/push-work b/docker/agent/bin/push-work new file mode 100755 index 00000000..74ac8a28 --- /dev/null +++ b/docker/agent/bin/push-work @@ -0,0 +1,67 @@ +#!/usr/bin/env bash +# Push the current topic branch to origin — the ONLY sanctioned way out of the +# container. +# +# Why a wrapper instead of plain `git push`: +# +# * **`main` and shared branches are refused.** The agent commits to +# `auto/`; a human merges. A token that can push anywhere is one +# confused iteration away from rewriting the consolidated line. +# * **Force-push is refused**, always. Nothing here needs it, and history +# rewriting is the one mistake that cannot be undone by merging. +# * It pushes the CURRENT branch only, by name, so a stray `--all` cannot +# publish another agent's worktree branch mid-experiment. +# +# Credentials come from a file mounted read-only at ~/.git-credentials (see +# `sylph-agent`). They are never printed, never logged, and never passed on a +# command line. +# +# push-work push the current branch +# push-work --dry-run say what it would do +set -euo pipefail + +DRY=0 +[ "${1:-}" = "--dry-run" ] && DRY=1 + +repo_root=$(git rev-parse --show-toplevel 2>/dev/null) || { + echo "push-work: not inside a git repository" >&2; exit 1; } +cd "$repo_root" + +branch=$(git rev-parse --abbrev-ref HEAD) +if [ "$branch" = "HEAD" ]; then + echo "push-work: detached HEAD — check out a branch first" >&2; exit 1 +fi + +case "$branch" in + auto/*) ;; + *) + echo "push-work: refusing to push '$branch'." >&2 + echo " Only auto/* topic branches may leave the container; a human merges" >&2 + echo " them into main. Move your work: git switch -c auto/" >&2 + exit 1 ;; +esac + +if [ ! -s "$HOME/.git-credentials" ]; then + echo "push-work: no credentials mounted at ~/.git-credentials." >&2 + echo " The host must start the container with SYLPH_GIT_CREDENTIALS pointing" >&2 + echo " at a file containing one line:" >&2 + echo " https://:@git.mc02.dev" >&2 + exit 1 +fi + +# `store` reads the file we mounted; nothing is written back (it is read-only). +git config --local credential.helper "store --file=$HOME/.git-credentials" + +ahead=$(git rev-list --count "origin/$branch..$branch" 2>/dev/null || git rev-list --count HEAD) +echo "push-work: $branch — $ahead commit(s) to publish" + +if [ "$DRY" = 1 ]; then + echo "push-work: --dry-run, stopping here" + exit 0 +fi + +# --force-with-lease is deliberately NOT offered. If this is rejected as +# non-fast-forward, someone else moved the branch: fetch and merge, do not +# overwrite. +git push --set-upstream origin "$branch" +echo "push-work: pushed $branch" diff --git a/docker/agent/loop-task.md b/docker/agent/loop-task.md index 3e03533f..ee561269 100644 --- a/docker/agent/loop-task.md +++ b/docker/agent/loop-task.md @@ -1,48 +1,85 @@ Work the Project Sylpheed reverse-engineering backlog, one item at a time. -Read `Syplheed-Reborn/docker/agent/AGENT.md` first — it has the container's -tooling and, more importantly, the method rules this corpus is built on. +## Read these first, every iteration, before proposing anything + +They are short on purpose, and they are the reason this prompt is short: + +1. `Syplheed-Reborn/docs/re/REFUTED.md` — **claims already tested and dead.** + If your idea is on that list, it is finished; pick another. Grep it for your + nouns before you design anything. +2. `Syplheed-Reborn/docs/re/METHOD.md` — the traps this corpus has already paid + for. Most wasted iterations are one of these repeated. +3. `Syplheed-Reborn/docs/re/INDEX.md` — what is already **decoded**. Do not + re-derive a row that is already ✅. Re-deriving a known format is not a + finding; asking whether its values *resolve* is. +4. `Syplheed-Reborn/docs/re/BACKLOG.md` — the open items. It is long; skim the + `##` headings and open only the one you pick. +5. `Syplheed-Reborn/docker/agent/AGENT.md` — the container's tooling. + +**These files are the memory.** If a finding, a dead end or a trap lives only in +your context, it is lost at the end of the run. Write it down where the next +iteration will find it — that is what makes this a corpus rather than a +transcript. ## Each iteration -1. **Pick one item.** Take the next open entry from - `Syplheed-Reborn/docs/re/BACKLOG.md`, preferring the one whose "first step" - is cheapest and most decisive. If you are mid-item from a previous - iteration, continue it rather than starting another. -2. **Do the smallest experiment that could settle it**, and try to *refute* - your hypothesis before you believe it. -3. **Write the result down** in `docs/re/` under the ✅/🟡/❔ convention, with - the evidence. A withdrawn or refuted result is a real result — record it, - with the reasoning, rather than deleting it. -4. **Commit** to a topic branch (below), one logical change per commit. -5. **Say plainly what you did not settle**, and stop the iteration. +1. **Pick one item** from `BACKLOG.md`, preferring the one whose "first step" is + cheapest and most decisive. If you are mid-item, continue it rather than + starting another. +2. **Do the smallest experiment that could settle it**, and try to *refute* your + hypothesis before believing it. Run the known-positive through any new filter + first; a filter that fails its own control is dead, not tuneable. +3. **Write the result down** in `docs/re/` under the ✅/🟡/❔ convention, with the + evidence and the *reach* of any negative. A withdrawn result is a real + result — record it, with the reasoning. + * If you **refuted** something, add a line to `REFUTED.md`. + * If you were bitten by a general trap, add a line to `METHOD.md`. + * If you **closed** a format, update its `INDEX.md` row. +4. **Commit** to `auto/`, one logical change per commit. +5. **Publish**: `push-work`. Your branch must leave the container or the work + dies with it. See "Publishing" below. +6. **Say plainly what you did not settle**, and stop the iteration. + +## Publishing + +`push-work` pushes the current branch to origin. It refuses anything that is not +`auto/*` and never force-pushes, so the consolidated line stays a human's +decision. Run it **every iteration that produced a commit** — not at the end of +some longer arc, which is exactly when a container dies. + +If it reports no credentials, say so in your reply and continue working; do not +improvise another route out (no remote rewrite, no credential helper of your +own, no alternate transport). A push that is blocked is a blocked push. ## Hard rules -* **Never commit to `main`.** Work on `auto/` in whichever repo you are - touching, branched from the current `main`. Create it if it does not exist. -* **Never push.** No push credentials are mounted, and that is deliberate — a - human reviews before anything leaves the box. -* **One emulator at a time.** `run-canary` enforces this with a lockfile; do not - work around it. -* **Do not edit `main`'s history**, do not rebase shared branches, and do not - delete branches. +* **Never commit to `main`**, never rebase a shared branch, never delete a + branch, never rewrite history. +* **Do not touch another agent's worktree.** `git worktree list` first; branches + marked `+` are checked out elsewhere. +* **One emulator at a time** — `run-canary` enforces it with a lockfile. * **Measure the oracle; never infer it.** An iteration that reasons about the game without running it is a red flag unless it is a pure static-format task. * **Verify with an artifact**, not with "it compiles": `build-reborn test` (it wires up `SYLPHEED_DISC` — without it the disc tests silently self-skip and a - green run means almost nothing), `sylpheed-cli mesh render`, `screen render`, - `save info`, a screenshot. + green run means almost nothing), `sylpheed-cli mesh render` / `screen render` / + `save info`, a screenshot. A regenerated artefact that comes out + byte-identical is a strong check that a change is additive; when it does + change, check that every diff line pairs exactly. ## When you are blocked -If an item needs something the container cannot do — hardware Vulkan for a -rendering question, a push, a decision only the user can make — **do not -improvise around it**. Write what you found, note the blocker in `BACKLOG.md`, -and move to the next item. +If an item needs something the container cannot do — hardware Vulkan, a decision +only the user can make — **do not improvise around it**. Write what you found, +note the blocker in `BACKLOG.md`, and move to the next item. ## Pacing -Self-pace. A useful iteration is one experiment plus its write-up, not a -marathon; stopping with a clean commit and an honest "here is what is still -open" is the goal every time. +One experiment plus its write-up is a good iteration; a marathon is not. Stop +with a clean commit, a push, and an honest list of what is still open. + +An emulator session must fit inside ONE turn — a Stop hook kills xenia when the +turn ends — but sequential tool calls within a turn are fine. + +The loop runs on a fixed interval set by the harness, so you do **not** need to +arm the next wakeup yourself. Spend that attention on the write-up instead. diff --git a/docker/agent/sylph-agent b/docker/agent/sylph-agent index d689b47f..93fdeb15 100755 --- a/docker/agent/sylph-agent +++ b/docker/agent/sylph-agent @@ -21,6 +21,9 @@ # SYLPH_VULKAN=sw force software Vulkan (lavapipe) even if /dev/dri exists # SYLPH_REMOTE=0 do NOT enable Remote Control (default: enabled for `loose`) # SYLPH_REMOTE_NAME Remote Control session name (default: sylpheed-agent) +# SYLPH_GIT_CREDENTIALS file with `https://:@host` for push-work +# (default: $HOME/.sylph-git-credentials) +# SYLPH_LOOP_INTERVAL fixed loop cadence, e.g. 30m (default: 45m) # SYLPH_CPUS / SYLPH_MEM_GB override the computed half set -euo pipefail @@ -125,6 +128,21 @@ docker_args() { echo " packaged SPIRV-Tools is too old. Running is unaffected." >&2 fi + # ── git push ── + # Read-only, and only ever used by `push-work`, which refuses anything but an + # auto/* branch and never force-pushes. Without this the agent's work only + # exists inside the container and dies with it. + GITCRED="${SYLPH_GIT_CREDENTIALS:-$HOME/.sylph-git-credentials}" + if [ -f "$GITCRED" ]; then + _out+=(-v "$GITCRED:/sylph-home/re/.git-credentials:ro") + else + echo "==> NOTE: no git credentials at $GITCRED — the agent cannot push," >&2 + echo " so its work will be lost if the container is destroyed. Create it" >&2 + echo " with a single line and chmod 600:" >&2 + echo " https://:@git.mc02.dev" >&2 + echo " or point SYLPH_GIT_CREDENTIALS elsewhere." >&2 + fi + [ -n "${ANTHROPIC_API_KEY:-}" ] && _out+=(-e "ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY") [ -n "${SYLPH_VULKAN:-}" ] && _out+=(-e "SYLPH_VULKAN=$SYLPH_VULKAN") [ -n "${SYLPH_REMOTE:-}" ] && _out+=(-e "SYLPH_REMOTE=$SYLPH_REMOTE") @@ -191,7 +209,12 @@ case "${1:-}" in TASK="Work the RE backlog in Syplheed-Reborn/docs/re/BACKLOG.md." fi fi - INTERVAL="${SYLPH_LOOP_INTERVAL:-}" # empty = let the model self-pace + # A FIXED interval by default, not self-pacing. Self-pacing requires the + # agent to call ScheduleWakeup itself at the end of every turn, and the one + # thing an agent deep in an experiment reliably forgets is the bookkeeping + # after it. With an interval the harness owns the cadence and a forgotten + # wakeup cannot end the run. Set SYLPH_LOOP_INTERVAL= (empty) to self-pace. + INTERVAL="${SYLPH_LOOP_INTERVAL-45m}" declare -a ARGS; docker_args ARGS ARGS+=(-e SYLPH_AUTONOMOUS=1 -w "$PROJECT") echo "==> loose | cpus=$CPUS mem=${MEM_GB}g shm=${SHM_GB}g" diff --git a/docs/re/INDEX.md b/docs/re/INDEX.md index 4d32711b..5d38026c 100644 --- a/docs/re/INDEX.md +++ b/docs/re/INDEX.md @@ -2,6 +2,11 @@ Confidence: ✅ `CONFIRMED` · 🟡 `PROBABLE` · ❔ `HYPOTHESIS`. See [README](README.md). +Also durable, and worth reading before proposing anything: +[`REFUTED.md`](REFUTED.md) — what has already been tested and died · +[`METHOD.md`](METHOD.md) — the traps this corpus has already paid for · +[`BACKLOG.md`](BACKLOG.md) — what is still open. + Formats we've already reversed are, for now, **documented by their parser + disc round-trip tests** (the executable spec) rather than a prose file — the "Spec" column points there. Promote to a prose `structures/…md` file when a format needs behavioural notes beyond layout. diff --git a/docs/re/METHOD.md b/docs/re/METHOD.md new file mode 100644 index 00000000..387e2870 --- /dev/null +++ b/docs/re/METHOD.md @@ -0,0 +1,105 @@ +# Method traps already paid for + +Each line cost an iteration at least once. They are general — they are not about +Sylpheed, they are about how this kind of measurement goes wrong. + +Like [`REFUTED.md`](REFUTED.md), this list had been living in the autonomous +agent's loop prompt, i.e. nowhere durable. See [`README.md`](README.md) for the +✅/🟡/❔ confidence convention itself. + +## Controls + +* **Every result needs a control. A control that fails kills the instrument.** +* **Run the known-positive through a new filter FIRST.** Three filters have been + killed by their own control. When one fails, **read the known-good's + disassembly** before assuming a shape. +* **A measured negative is a result** — but a negative is only as strong as the + route you ran, so **state its reach**. +* **A null result needs its cause shown to have happened.** +* **A result with NO unknowns is suspicious.** +* **Census the whole set; always run the other population as the control.** + **Zero partials is stronger than a majority.** +* **A 2×2 partition is the sharpest general tool** — both off-diagonals empty is + a law. +* **Re-derive a doc's own numbers as the control.** + +## Inference + +* **Never conclude from ONE sample.** +* **A law proved on one population is a hypothesis on the next.** +* **Finding one exception does not imply a family.** +* **Consistency is not proof. A suggestive coincidence is a coincidence until + measured.** **An analogy is not a measurement.** +* **Same layout ≠ same instance.** **Same record-name set ≠ same object.** +* **A marker is only proven by what it leaves out.** +* **A high-confidence SCORE is not a high-confidence MECHANISM.** +* **Knowing HOW MANY is not knowing WHICH.** +* **Round numbers matching is weak evidence — unless you read the constant.** +* **My own last-turn result is a hypothesis too.** +* **A global partition can understate a per-owner one.** +* **A residual is measured against a population — name it.** + +## Searching and tooling + +* **A search that returns thousands has no power; state the reach.** +* **A substring match is not a hit.** **A regex miss looks like a null result — + print one raw sample before believing a zero.** +* **A derived table can be a cross product — measure its shape first.** +* **After refuting an instrument, sweep everything that depended on it.** +* **Before measuring how wrong a tool is, read what the tool actually does.** +* **The instrument must pass its own control.** +* **Classify a bulk before mining it. The residual is the prize.** +* **Rank by similarity — the cliff is the finding.** But **read the values + before trusting the rank.** +* **Grep the nouns before designing the experiment — and believe it.** +* **Grep gives you a file list — read *every* file on it.** +* **The answer is often already in the doc that owns the subject — read it end + to end.** A 🟡 often names its own route. +* **Before re-trying a blocked idea, check whether the blocker's own doc already + tried it.** +* **Ship a regenerator with every artefact.** An artefact that moves by a pure + reorder is a tool bug. +* **Never print per-entry lines from a disc-wide sweep — aggregate.** + +## Reading the data + +* **Read what a loader NAMES, not where it stores.** +* **A field the disc never values still gets named by the loader.** +* **An indexed read beats a deduped-pool adjacency read.** +* **Check the whole string set, not the one matching word.** +* **A dict keyed by record name across a multi-entry pak is a lie.** +* **A set-difference over names hides reuse — join per USER.** +* **A self-index names records, not files.** +* **Case-insensitive hashing means two spellings can be one entry.** +* **An "unresolved" name may be the wrong kind, namespace or prefix — or part of + a cut asset.** +* **A garbled value may be a real string in another encoding.** +* **Two of my own counts disagreeing is a grammar clue.** +* **A game's own typo is a join key.** +* **A bias constant in the code is a join key.** +* **A prefix trap: enumerate maximal `[A-Za-z0-9_]` runs, not `startswith`.** +* **Re-deriving a format is not a finding — asking whether its values *resolve* + is.** + +## Mechanics that have bitten + +* **Never hand-convert a decimal VA — print `hex()`.** +* **`grep -c` counts LINES** — use `grep -o | wc -l`. +* **`Counter.most_common()` tie-breaks by insertion order — use `sorted()`.** +* **Raw grep cannot see inside compressed pak entries.** +* **Commit messages go in a file** (`git commit -F`); a literal `|` in a table + cell needs escaping; `git log --all -- ` can hang. + +## Runtime / emulator + +* **Look at the PNG** — and check its dimensions. +* **"Animating" is not "still in a mission".** +* **Dedup entity enumerations by position value.** +* **Do not diagnose timing or liveness under gdb.** `ps %cpu` is cumulative. +* **Classify screens by whole-image statistics, not named pixels** — a named + pixel is only valid while the image sits at a known place, and nothing errors + when it moves. +* **Do not poll faster than the guest updates** — it manufactures a clean curve + out of noise. +* **A probe that never performs the action will "prove" the action does not + exist.** diff --git a/docs/re/REFUTED.md b/docs/re/REFUTED.md new file mode 100644 index 00000000..943a5174 --- /dev/null +++ b/docs/re/REFUTED.md @@ -0,0 +1,185 @@ +# Refuted — claims that were tested and died + +**Read this before proposing a hypothesis.** Every line below was believed at +some point, measured, and found false. Reviving one costs a whole iteration and +produces nothing. + +This file exists because the list had been living in the autonomous agent's +*loop prompt* — the only copy, lost the moment the container was. A refuted +result is a real result; it belongs in the corpus like any other. + +**Format:** each entry is the claim as it was believed. Where the true answer is +known it follows after `→`. Grouped by subject so a grep for your noun finds the +neighbourhood, not just the line. + +--- + +## Offsets, structs and the progress singleton + +* `position = instance − 0x12c` → refuted. +* `+0x29d0` → refuted. +* "an offset intersection can find a struct's consumer" → **only for LARGE or + unusual offsets.** Small ones have no power (`+184`: 301/351/115 hits). +* "a `+1956` store means a progress write" → writes go through the COPY, not + direct stores. 9 direct stores, none of them a progress write. +* "the singleton-global filter can find progress writers" → it fails its own + control. +* "the progress copy destination is an `r1`-relative stack local" → it is a + **frame register**. The `r1` assumption returned 0 for all 21 candidates + *including the known-good* — the filter was killed by its own control. +* "word B's writer also stores the Time/Points record" → it does not. +* "the debriefing records the metric with the clear bit" → 44 calls, exactly two + strings (`DEBRIEFING`, `BASE_INFO`), no `Time`, no `Points`. +* "`0x820AF030` holds live state" → all 384 words constant; it is a + spawned-entity record, not live state. + +## Screens, classes and RTTI + +* "the RTTI route can name the anonymous classes" → 1 150 vtables: 1 150 + `ANON_`, 0 `rtti_present`, 0 base classes. +* "the sibling vtable methods name the class" → they cannot. +* "`xrefs` can name the callers of a vtable method" → no. +* "the `ind_call` refutation voids existing corpus claims" → damage bounded, + 4/4 caller claims verify. But **`xrefs.ind_call` is a CROSS PRODUCT** — always + filter `kind='call'`. +* "`BASE_INFO` marks the 5-slot screen family" → it discriminates + screen-config from table-read, 9/9 vs 10/10. +* "a high key count means a rich screen" → `sub_82297550` / `sub_822A2F00`'s 27 + "keys" are coordinate pairs, i.e. a layout table. +* "`EX_` = the CHALLENGE-mission debriefing" → `EX_` is **EXTRA**, mission-kind + 3. +* "the `EX_` selection has not been shown" → it has: `[[obj+4]+184] == 3`. + +## Stages, missions and the challenge set + +* "the disc's stages are numbered 1..28" → S01–S16 story, S17 **cut**, S18–S23 + tutorials, S24–S29 challenge. +* "S24–S29 are story missions" → they are the challenge missions. +* "the challenge missions have their own maps" → they reuse + `GP_MAIN_GAME_E.pak`'s stage records. +* "the challenge `REQUIREMENT` values are 16,25,26,27,29" → the chain is + 16→24→25→26→27→28. +* "the `Extra0n` family shares one leaderboard metric" → `RECORD_TYPE` is + per-stage: 3 Time / 3 Points. +* "stage = filled SHAB count + 1" → refuted. +* "the first TRIGGER is always the point of no return" → refuted. +* "`EnumUnit_S14.tbl` might be missing" / "S14's 13 are a manifest omission" / + "asteroids are exempt from the manifest" → S14's 13 are **dangling + deployments**. NEEDS-HUMAN: fly S14. +* "`StageMessageSet_S02.tbl` is not in the pak" → it is. +* "`S28_p1` has an asteroid volume with no definition" → refuted. +* "`test_s8p1_asteroid.tbl` is test-only" → refuted. +* "the settings family has 28 or 29 objects" → 24. + +## ISL / mission scripting + +* "the bytecode is in the `.embsec_` sections" → refuted. +* "only 31 built-ins take a unit" → refuted. +* "`sub_8230C398` is the message pump" → refuted. +* "`bus+8216` is the subscriber registry" → refuted. +* "the ScriptPhase vtable is ≥200 slots" → 113. + +## IDXD, paks and naming + +* "IDXD record keys are `name_hash`" → record keys are **`tag_hash`** + (case-SENSITIVE); `name_hash` is case-INSENSITIVE and used for pak keys. +* "pak TOC order is stage order" / "TOC order is semantic order" → it is not. +* "the executable holds the asset names" → the image names **no data value at + all**; that route is powerless. +* "the image might name a data VALUE" → powerless. +* "the XPR2 manifest names hash to the DefTables tables" → refuted. +* "the `DefTables` model names are unreachable" → reachable via the `Enumerate` + declaration tables (1 413/1 425, 99.2 %). +* "the `GP_MAIN_GAME_*` unnamed block is undiscovered data" → refuted. +* "each `GP_MAIN_GAME_*` `Enumerate` object declares something" → refuted. +* "`GP_HANGAR_ARSENAL` is missing data tables" → refuted. +* "the `Enumeration` self-index can name objects" → a self-index names + **records, not files**. +* "a per-pak prefix might close the 2D blocker" → no. +* "the `+` paths might name the 2D or `GP_READY_ROOM` keys" → the `+`-dictionary + route is exhausted, 0 of 1 817. +* "the `game:\` paths are unresolved" → refuted. +* "a set-difference over file names can see reuse" → it cannot; **join per + USER**. Per-pak copies are ×6. + +## Units, weapons, effects and assets + +* "`Generic` (394) is the unit datasheet" → refuted. +* "a loadout's `Arm1` names an item" → it names a **hardpoint slot** + (`Turret_NNN`), 59/59. +* "`EnumUnit` and the unit datasheet share a vocabulary" → they do not. +* "the roster is the `Generic.Model` set" → roster 40, `Generic.Model` 46, + `GameResourceID` 480 — three vocabularies. +* "every unit ID is `UN_###__`" → the grammar is + `UN_###_[_]_`. +* "`_EXn` is the `Extra0n` index" → three different `EX` vocabularies exist. +* "the only two `_EX5` names on the disc are the AA gun and the DeltaSaber" → + refuted. +* "running the tutorial will instantiate the `_Ttrl` weapons" → refuted. +* "the disc has exactly three `EnumWeapon` tables" → four. +* "the `wep_NN` package gaps are unshipped weapons" / "`wep_85` is the tip of a + family" → `wep_85` is the **only** declared-but-unshipped asset (59/0/1/26). +* "nothing is deployed without being declared" → refuted. +* "effects are one namespace" → refuted. +* "the 58 undeclared effect names are missing assets" → refuted. +* "all five orphan effects are unshipped" → refuted. +* "`eff_f0002` ships in `Base.xpr`" → refuted. +* "`Base.xpr` holds more bound effects than `ptc_pack`" → refuted. +* "the 34 unlocated are a scatter" → refuted. +* "the 9 unlocated might be under another prefix" → refuted. +* "`ptc_pack` has 532 names" → 727. +* "the `_e`/`_f` law is effect-FIELD-specific" → it is the **faction law**, + 564/564. +* "a disc-wide `.xpr` byte search can show an effect is ABSENT" → it cannot. +* "`rot_n001` is on the disc" → refuted. +* "`rou_f004`'s mesh is in `Stage_S28.xpr`" → it is in `DeltaSaber_A.xpr`. +* "`parent` + `_all` + `_child` is the composite-model convention" → refuted. + `_hangar` **is** real (59 of 166, 59/59 with a bare twin); `_all`/`_child` is + not. +* "`Motion_guard_start` has no damage variants" → refuted. +* "`CoverArea` bits 2 and 3 are mutually exclusive" → refuted. +* "the 27 unresolved `NamePlate` values are missing objects" → refuted. + +## LOD, background and misc tables + +* "`EnumLODSet_*` is a per-stage family" → `EnumLODSet_test.tbl` serves 17 + stages; 17+5+1 = 23. +* "there are 8 orphan LOD tables" → 6. +* "the orphans are stale copies of `_test`" → refuted. +* "S25 is absent from the `DefTables` LOD families" → refuted. +* "`BackGroundID` has no referent anywhere" → it is an **identity**. +* "`BackGroundPackage == BG_.xpr`" → refuted. +* "`ID` + `Package` is a convention" → refuted. +* "`Placement_*` / `RouteTest_*` are unattached" → refuted. +* "the `AsteroidDefinition` join does not reproduce by hash" → it does. +* "the 8-value frame is a new finding" → it was already in the corpus. + +## Loaders, config and tuning + +* "the config reader is XML" → INI. +* "`sub_822F9498` is the unit-definition loader" → it is `PlayerParams`'s. +* "`sub_822AE628` reads the main-game `Tweak`" → refuted. +* "`sub_8230D1F8` is a rank table" → it is the stage-settings loader. +* "`sub_82286BC8`'s key list is new" → refuted. +* "`sub_825F2CF0` / `sub_825F2F88` read a post-processing table" → refuted. +* "`Booster` is a new schema" / "`Booster` is the player craft's flight + envelope" → refuted; nothing selects `Booster`. +* "the `AnalogRevice`/`Tweak` block is unreachable" → reachable + (`sub_821A6CF0`, base `0x820A1630`). +* "a 0-xref string block has no reader" → refuted. +* "the AI table was NEEDS-HUMAN" → refuted. +* "the `PG*` HUD names are undocumented" → they are documented. +* "a base-solver row identifies a FUNCTION" → it does not. +* "a 64K-boundary base is low confidence" → **inverted**; it is high + confidence. +* "the 0x820B0000 cluster is a false positive" → refuted. +* "a pointer to a function in the image implies a registry" → refuted. + `.pdata` is not a registry. +* "the 13 player-facing chatter tables are the WINGMAN tables" → refuted. +* "the 8 undeclared chatter tables are tutorial chatter" → refuted. +* "other datasheets ship a schema too" → refuted. + +## Encoding and text + +* "every IDXD string value is ASCII" → 6 non-ASCII values of 99 328. +* "`文字列` is a dev placeholder" → they are Shift-JIS **type words**.