mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-10-03 00:05:11 -04:00
238 lines
23 KiB
Markdown
238 lines
23 KiB
Markdown
# Gen3 handoff — where Gen2 ends, what comes next, and the levers already in hand
|
||
|
||
> **Written at the Gen2 exit (Phase 33, 2026-09-07).** Gen2's contract is met and published: 218 binaries rebuild
|
||
> byte-identical from C ([`docs/verification.md`](verification.md)), every game-code function in every binary is C (*corrected
|
||
> 2026-09-09, Phase 36 T1b: fourteen were whole-body assembly inside C shells, invisible to the file-scope detector — listed
|
||
> DECOMPILE-NOW / UNCERTAIN in the manifest*), the
|
||
> repository is public with its full rewritten history. This page is the seed for Gen3's first plan — it names the
|
||
> owner's stated next intent, gives the starter census derived from the tree (with the commands, so the next session
|
||
> re-derives rather than trusts), states the one invariant that must survive every Gen3 edit, and inventories the
|
||
> levers, documents and parked ideas Gen3 inherits. The constitution's Gen3 line ("shiftable build, asset repack, native
|
||
> recomp / PC port, randomizer-grade tooling") is unchanged; the order below is a recommendation for plan mode.
|
||
|
||
## 1. Where Gen2 ends
|
||
|
||
| | |
|
||
|---|---|
|
||
| Binaries byte-identical | 218 / 218 — the executable, the resident engine, 141 `ov_*` overlays, 75 `md_*` modules |
|
||
| Functions | 363,214 / 363,214; 360,737 in C (255,632 shared bodies via 2,220 dedup groups); 1,256 Sony PsyQ functions linked (or carried as 1,258 `INCLUDE_ASM` tiles without the SDK); 5 hand-written-assembly bodies kept verbatim |
|
||
| What is not C | Sony's objects (by design) and the verbatim bodies of `config/verbatim_manifest.json` (5 PERMANENT file-scope rows at this writing; **Phase 36 T1b, 2026-09-09: +22 PERMANENT in-function rows** — the per-overlay scratchpad stack-switch trampolines the file-scope detector never saw — and one DECOMPILE-NOW row, `func_80184440`, an `-O0` body in an `-O2` unit) |
|
||
| The record | `phase-ends/` (33 PhaseEnds + the digest), `docs/decision-log.md`, `docs/matching-cookbook.md`, `docs/gcc-2.7.2-map/`, `docs/story.md`, `docs/retrospective.md`, the wiki and `docs/how-to-ai-decomp/` |
|
||
|
||
The matching frontier is empty. Gen3's work is therefore of a different kind: **making the C legible and movable
|
||
without changing a byte**, and then the things a legible, movable source enables.
|
||
|
||
## 2. The owner's next intent: readability and shiftability preparation
|
||
|
||
Stated at the Phase-33 plan: *casts → structs, pins off, names.* Concretely:
|
||
|
||
1. **Raw addresses → declared data.** Every `*(type *)0x80xxxxxx` cast becomes a reference to a declared symbol with
|
||
a type; every `D_80xxxxxx` that is a field of a known structure becomes `actor->field`.
|
||
2. **Register pins off.** Every `register … __asm__("$N")` that a banked body still carries is removed and the body
|
||
re-gated; Phase 32's finding is that the pins were symptoms — every one on the last four functions came off
|
||
byte-identical once the source *shape* was right (cookbook §501-E/P, R73). *Done to its measured floor at Phase 36
|
||
(2026-09-11): 53,234 → 4,010 sites, 37 % by mechanical strip, the rest by a guided search and one-agent-per-TU readings;
|
||
the survivors are marked with their pass, attributed to their instrument, and bucketed for the structs phase — 691 sites in
|
||
parked signature/carve changes, ~1,150 in five proven head classes, 450 GTE clobber variants, 94 missing-parameter pins,
|
||
1,548 drawable singletons (`docs/levers.md`, cookbook §457, the Phase-36 PhaseEnd).*
|
||
3. **Names.** `func_80xxxxxx` / `D_80xxxxxx` → meaningful names, curated in the symbol files and mirrored into
|
||
Ghidra (G6, R15) — never edited in generated assembly.
|
||
|
||
### 2.1 External labels versus derived structure (owner Q&A, 2026-09-07, P33 E2)
|
||
|
||
Asked at the E2 outreach: *do the Archipelago world's "structs" let us turn the unknown casts into structs, and do we need
|
||
that repository at all, or can we derive the structures from our own decomp?* Answer, recorded for the Gen3 plan:
|
||
|
||
- **The AP world carries no structs.** `client.py` (v0.8.1) polls ~342 RAM addresses and attaches gameplay meaning to them
|
||
(HP, BP cap, day of week, chest flags, portal entries). That is *labelling for globals* plus the implied layout of a few
|
||
tables. Against the census in §3 it covers ~342 of 61,898 data symbols, none of the 1,232 struct definitions, none of the
|
||
16,335 function names and none of the 43,925 pins. Most of its player-state block is already imported (memory-map §3.4–§3.6).
|
||
- **Structure is derived from our own source, and ours is the better instrument for it.** Shape comes from access patterns
|
||
the compiler locked into the bytes: many functions reading a `u16` at `+0x3C` from one base means a struct with a `u16`
|
||
there; Ghidra's decompiler already propagates these; `docs/actor-struct.md` and the load-slot map are recovered examples.
|
||
Every struct/name edit is byte-neutral, so the 218-binary gate rejects any mistake for free (§4).
|
||
- **The two sources answer different questions.** The decomp gives the shape (`u16` at 0x80078EB6, written by the
|
||
14-instruction `func_8014BCEC` as `+= a1`, clamped at 0x662); an observer gives the meaning (that is the BP cap). Meaning
|
||
comes from the AP world, our live-RAM harness (R10), string references, the debug menu and community cheat tables. Plan:
|
||
**types and structure from the decomp; names from observation wherever someone already made one, cited (G5).**
|
||
- **Ordering caution (census-derived):** most of the 61,898 data symbols are per-overlay script data that may never earn a
|
||
human name; the 1,232 struct definitions include many drafter-invented duplicates of one type — **unify before naming**
|
||
(`tools/lift_types.py` knows the collision classes); pins-off is mechanical and family-batchable; struct unification is
|
||
semi-mechanical (access-pattern clustering, then a person or model names fields); function naming is judgment plus
|
||
evidence (xrefs, strings, the AP names, TCRF/debug-menu strings). Plan Gen3 in that order, and measure each class's
|
||
size before pricing it (R37/R41).
|
||
|
||
### 2.2 The standard to meet, and where the tree stands against it (measured 2026-09-07, P33 S89)
|
||
|
||
The owner's aim for Gen3 is stated plainly: *turn a machine-produced matching decomp into a decomp the human community would
|
||
accept as proper, at the level of sotn-decomp or the Vagrant Story project.* What those projects actually require, read from
|
||
their documents on 2026-09-07 (as data):
|
||
|
||
- **sotn-decomp `docs/STYLE.md`:** a full naming scheme (locals `camelCase`, globals `g_PascalCase`, statics `s_PascalCase`,
|
||
struct members `camelCase`, types/functions `PascalCase`, enum values and macros `SCREAMING_SNAKE_CASE`, files `snake_case`);
|
||
"We always write our enums and structs as typedefs"; the custom `u8…u32` types; clang-format (4 spaces, 80 columns, pointer
|
||
on the type); decimal for counts and timers, hex for angles, addresses and masks; `bool` for 0/1 returns; braces on every
|
||
block; "It's better to not hardcode array sizes (easier to mod)"; comments for anything strange, `//! @bug`, `// !FAKE:` on
|
||
code that exists only to force a match; **"If you are not sure what something does, it is better to leave it unnamed than
|
||
name it wrongly"**; "All functions should go in the main C file in the same order as the assembly".
|
||
- **sotn-decomp `CONTRIBUTING.md`:** `func_`/`D_`/`Unk` placeholders are the to-be-improved state, renaming is a first-class
|
||
contribution; and, verbatim: **"We require commit messages and Pull Requests to be submitted without autonomous tooling such
|
||
as an LLM or coding agent. Assistance from coding agents is allowed, but changes must be justifiable and manually operable."**
|
||
- **decomp.me FAQ:** "Please do not attempt to scrape the site, hook up an LLM, or otherwise make repeated, automated,
|
||
requests to decomp.me"; contributions accepted "from people who use the site or are actively involved in the wider
|
||
decompilation community".
|
||
- **Vagrant Story (`ser-pounce/rood-reverse`, 62.66% on decomp.dev):** readability refactors called out as goals ("Much of the
|
||
menu code has been refactored and is readable"), `make format` before a PR, the decomp.me claim-and-PR workflow.
|
||
|
||
**What the community's gripe with "AI decomp" actually is** (the record: our own `docs/history/` psxrecomp post-mortem; the
|
||
2026 Macabeus 60-function study; today's permuter episode; the policies above): (1) unsound claims — matches asserted that
|
||
are not, the model "wrongly assuming there is a perfect match when there isn't"; (2) matches that are bytes without
|
||
understanding — register pins, raw casts, magic numbers, macro bodies, anything that forces the compiler without saying what
|
||
the code does; (3) maintainers' time spent on machine-written PRs and issues; (4) automated load on shared infrastructure;
|
||
(5) names invented rather than evidenced — the sotn rule "leave it unnamed rather than name it wrongly" is the one Gen3 must
|
||
honour most, because a model will happily guess. Item (1) this project answers with the byte gate over 218 whole binaries;
|
||
items (2) and (5) are exactly the Gen3 work; (3) and (4) are conduct, governed by rule candidate (j) and the decomp.me rule.
|
||
|
||
**Where the tree stands, measured today:**
|
||
|
||
| Bar | This tree (2026-09-07) |
|
||
|---|---|
|
||
| Byte-identical build, splat, permuter/asm-differ, CI, no-ROM policy, progress publishing | met, and stricter than most: whole-binary SHA1 on every build, 218/218 |
|
||
| Named functions | 1,094 named in the symbol files vs **16,335** `func_80xxxxxx` |
|
||
| Named data | **61,898** `D_80xxxxxx`; most are per-overlay script data |
|
||
| Typed structures | **1,232** struct definitions, many drafter-invented variants of one type; 143 raw address casts remain |
|
||
| No match-forcing tricks | **43,925** `register … __asm__("…")` pin declarations (the §3 command, snapshot 2026-09-07; two earlier figures — 43,857 and 44,243 — came from a stricter and a looser grep, the looser one counting 290 comment lines) — sotn would want each either gone or marked `// !FAKE:` |
|
||
| Readable organisation | **5,147** shared engine functions live as `DEFINE_func_…()` macro bodies in one 8.4 MB, 227,730-line header (`src/shared/engine_core.h`), instantiated per overlay; 3,558 of the 4,287 C files are `_jr_` carve splits — the layout follows the carving tool, not the game's systems |
|
||
| *Phase 35 snapshot (2026-09-08)* | **0** macro bodies: every shared body is a plain-C header under `src/shared/` included at its site; the invariant "one source per unique function" is a `tools-health` gate (`share_census --check`: 10,180/10,180, 0 violations; 51 classes ledgered for the types phase, 3,801 cross-address classes deferred to the names phase) | derived by `tools/share_census.py --check`; re-derive |
|
||
| Formatting | no `.clang-format` in the tree and no `make format` target |
|
||
| Documentation | the method, the cookbook, the decision log and the wiki are unusually complete; the CODE carries almost no comments |
|
||
|
||
The gap is therefore not matching and not process; it is the five rows in the middle. In Gen3 planning order they are:
|
||
pins off (mechanical, family-batched, byte-gated), the macro-body architecture → shared `.c` files a human can read (a
|
||
byte-neutral restructuring; measure it on one family first), struct unification then naming (types from access patterns,
|
||
names only with evidence — xrefs, strings, the debug menu, live RAM, the AP world's labels — else stay `func_`/`D_`),
|
||
formatting, then comments. Each step is checked the only way this project checks anything: 218 binaries rebuild identical.
|
||
|
||
**Sequencing against Phase 33 (owner's question, 2026-09-07):** Gen3 execution — any edit to `src/` — starts after the
|
||
v2.0.0 close in a fresh plan-mode session (P8; the Gen3 plan is Tier 1). While the flip waits on GitHub Support, Gen3
|
||
*preparation* that is measurement-only is legitimate Phase-33 work under G1 and lands in this file: the struct-duplicate
|
||
census, a pins-off dry run on ONE family in a scratch worktree (measured yield and cost, nothing committed to `src/`), a
|
||
naming pilot's evidence ladder. If Support stalls beyond about a week, the runbook's delete-and-recreate route flips the
|
||
repository the same day and the close follows.
|
||
|
||
## 3. The starter census (derived 2026-09-07 — re-derive, do not trust)
|
||
|
||
```bash
|
||
grep -rhoE '\*\)\s*0x80[0-9A-Fa-f]{6}' src --include=*.c --include=*.h | wc -l # raw address casts
|
||
grep -rhoE '\bD_80[0-9A-Fa-f]{6}\b' src --include=*.c --include=*.h | sort -u | wc -l # distinct data symbols
|
||
grep -rhoE '\bfunc_80[0-9A-Fa-f]{6}\b' src --include=*.c --include=*.h | sort -u | wc -l # distinct function names
|
||
grep -rhoE 'register [^;/]*__asm__\("\$?[a-z0-9]+"\)' src --include=*.c --include=*.h | wc -l # register pins (declarations only; excludes comment text)
|
||
grep -rhoE '^\s*INCLUDE_ASM\(' src --include=*.c | wc -l # assembly tiles
|
||
cat config/symbols.us*.txt | grep -cE '^[A-Za-z_]' # symbol-file entries
|
||
```
|
||
|
||
| Quantity | Value | Note |
|
||
|---|---|---|
|
||
| Raw address casts `*(T *)0x80…` | **143** | the low-hanging fruit; each becomes a declared symbol |
|
||
| Distinct `D_80xxxxxx` data symbols | **61,898** | most are fields of a handful of structures the engine indexes; `docs/actor-struct.md` recovered one of them (base `0x80078E00`, ~154 fields, live-verified) |
|
||
| Distinct `func_80xxxxxx` names | **16,335** | across 4,287 C files; the overlays' shared engine functions have one name each fleet-wide (position-locked) |
|
||
| Register-pin declarations | **43,925** (43,857 `"$reg"` + 68 bare `"reg"`; 42,985 in `.c`, 940 in `.h`; snapshot 2026-09-07 — the earlier 44,243 counted 290 comment lines, the earlier 43,857 missed the bare form) | a campaign, not a chore — but batched by family, since a shared body's pins come off once for every member |
|
||
| `INCLUDE_ASM` tiles | 1,258 | all inside the executable's linked Sony regions; not Gen3 work |
|
||
| Symbol-file entries | 1,083 (1,081 curated names, 2 address-named) | `config/symbols.us*.txt` — the names the build already knows |
|
||
| Struct definitions in `src/shared/engine_types.h` | 1,232 | many are drafter-invented variants of the same type (`tools/lift_types.py` knows the collision landscape) |
|
||
| Dedup groups | 2,220 (255,708 instances) — *Phase 35 (2026-09-08): 3,173 groups / 262,573 instances, incl. 38 `h_text` groups; 103,015 bodies written once; `tools/progress.py --fleet`* | a rename or a type change inside a shared body reaches every member |
|
||
| Verbatim `__asm__` bodies | 5 | PERMANENT; the manifest audits drift |
|
||
|
||
## 4. The one invariant
|
||
|
||
**Every Gen3 edit is gated exactly like a match was.** `make check BINARY=<alias>` for every binary a change touches,
|
||
and the clean fleet run (`make clean && make extract-all && make check-all` → `218 passed, 0 failed of 218`) after
|
||
anything that touches a shared body, a shared header or the executable — never an incremental check on the executable
|
||
(R22). Read the exit code (R53). Commit the moment a batch is green (R42). A rename is a symbol-file change mirrored
|
||
into Ghidra by the headless script, never a hand edit of assembly (G6, R15). Readability work is *safe* for precisely
|
||
the reason drafting was: a wrong edit cannot land — but it is also *slow* for the same reason, so batch by family and
|
||
gate in parallel worktrees ([the matching workflow](wiki/The-matching-workflow.md)).
|
||
|
||
Two subtleties the census hides:
|
||
|
||
- **A shared body is one source, many binaries.** Removing a pin or renaming a field inside a `DEFINE_func_…()` macro in
|
||
`src/shared/engine_core.h` changes up to 138 binaries at once; the gate must run on all of them, and a member whose
|
||
bytes happen to depend on that pin (they should not, but the gate decides) has to be split out of the group first.
|
||
- **Types are a comprehension lever, not a byte lever.** Phase 17 proved that recovering the actor structure and giving
|
||
it to the decompiler produced identical bytes (`docs/struct-core-pivot.md`): the compiler does not care what you call
|
||
a field. That is good news for Gen3 — struct-ification is byte-neutral by construction *as long as the layout and
|
||
the access widths are right* — and it is why every such edit still goes through the gate.
|
||
*Corrected 2026-09-12 (Phase 37 T2): in gcc 2.7.2 the SPELLING moves bytes — a member/array access carries `MEM_IN_STRUCT_P`, a cast on a
|
||
sum does not, and the scheduler's alias escape and cse read that flag; a 165-body probe was byte-neutral on 90.6 % and a per-site
|
||
minimal kept-cast set closed the rest (cookbook §458, `docs/decision-log.md` P37 S106). The gate is the arbiter, per access.*
|
||
|
||
## 5. Levers Gen3 inherits
|
||
|
||
| Lever | What it does | Where |
|
||
|---|---|---|
|
||
| `tools/lift_types.py` | Lifts a named list of types fleet-wide into the shared header, picking the canonical (majority) definition among the drafter-invented variants and stripping the local copies; reports the variant users so the reconcile is targeted; dry-run by default | the struct-ification workhorse |
|
||
| `tools/cast_call_sites.py` | Per-site function-pointer / callee casts (cookbook §17a-1) so a call keeps its byte-exact argument codegen while the declaration becomes canonical | when a name/type change alters a call's conversions |
|
||
| `tools/canon_sig_reconcile.py` | Reconciles a definition to its canonical signature byte-neutrally (typedef strip, positional param types, casts at the uses — never through a fresh local, which shifts register allocation) | signature clean-ups |
|
||
| `tools/sync_tu_decls.py`, `fix_arity_callers.py`, `cast_self_callers.py`, `restore_dropped_decls.py`, `decl_from_use.py` | the declaration layer of the reconcile ladder ([chapter 10](how-to-ai-decomp/10-integration-and-propagation.md)) | any edit that touches declarations |
|
||
| `tools/alloc_table.py`, `tools/cc1_dumps_tu.sh` | the allocation order and the per-pass RTL dumps from the real translation unit — read before touching a pin (R73) | the pin-removal campaign |
|
||
| `tools/dedup_propagate.py`, `family_sweep.py`, `twin_rescan.py`, `config/dedup.us.yaml` | the propagation and registry machinery; `dedup_integrate --check` refuses a drifted share | keeping 2,220 groups consistent through renames |
|
||
| `tools/ghidra_apply_symbols.sh`, `tools/ghidra_scripts/ApplySymbols.java`, `config/ghidra/` + `tools/ghidra_rebuild.sh --proof` | names reach the Ghidra database only this way; the database is regenerable from text | the naming campaign |
|
||
| `tools/atlas.py` / `atlas_features.py` | per-function feature layer and similarity groups over the whole fleet | grouping candidates for a rename or a struct |
|
||
| `docs/actor-struct.md`, `docs/idxtab-map.md`, `docs/memory-map.md` | the recovered actor structure; the load/index-table map of the 218 payloads; every address with provenance | the ground truth for naming and typing |
|
||
| `docs/struct-core-pivot.md`, `docs/wall-taxonomy.md` | what struct recovery can and cannot do; the residual classes | expectations |
|
||
|
||
## 6. Shiftability, honestly scoped
|
||
|
||
A *shiftable* build is one whose addresses can move. Three facts frame it:
|
||
|
||
1. **Overlays are position-locked** at one slot (`0x80128158`) by the loader, the resident at `0x800CEDF8`, the modules
|
||
at their slots (`docs/memory-map.md` §S44/§S45). Shifting code *within* a slot (a function grows) needs only that
|
||
every cross-reference be symbolic — which is what §2's cast → symbol work delivers — plus the data-side references
|
||
the splitter still emits as literal addresses in rodata/jump tables.
|
||
2. **LZSS recompression is not byte-stable** (`docs/formats.md` §4.6): a rebuilt overlay cannot be re-encoded to the
|
||
original compressed bytes. The verification layer is therefore the *decompressed* payload (as today); a rebuilt disc
|
||
is a Gen3 deliverable that verifies by booting, not by hash (the constitution's stage 5).
|
||
3. **The executable's Sony regions are linked objects**; shifting them means relinking with the real SDK (or the tiles).
|
||
|
||
Recommended order: symbolic references first (byte-neutral, gated), then a `-Ttext`-shifted overlay that boots in
|
||
PCSX-Redux, then the disc rebuild with recompressed payloads.
|
||
|
||
## 7. The parked Gen3 ideas, with their state
|
||
|
||
- **Asset export / native rebuild** (`docs/gen3-parking-lot.md`, read-only survey 2026-07-01): rendering is stock
|
||
libgpu; model data goes through libgs `GsMapModelingData` (a documented TMD path — verified by real call sites);
|
||
animation is custom; textures are a VRAM rip away. Difficulty read and the decisive probes are in that file.
|
||
- **Native recompilation / PC port** — the original brief's ambition, properly sequenced after readability.
|
||
- **Randomizer-grade tooling** — the Archipelago world's RAM map is already cited in `docs/memory-map.md`; E2's
|
||
outreach note is the first contact.
|
||
- **The community matching model** — pipeline published ([`docs/matching-drafter-pipeline.md`](matching-drafter-pipeline.md));
|
||
the dataset and weights wait on a licensing decision.
|
||
- **JP (SLPS-01490) and the prototypes as extra versions** — the two prototype executables are already imported
|
||
programs with tracked annotations (`config/ghidra/ROSTER.md`).
|
||
- **The decomp.me preset** (E1) and **libs-from-source for the Sony regions** — small and large stretches respectively.
|
||
- **xsig v2 — the whole knowledge base behind one search (owner's direction, 2026-09-08, P34 task 6).** Today
|
||
[`tools/xsig/`](../tools/xsig/) answers one question: an exact match on the relocation-masked word stream (the in-tree
|
||
"normalized" tier), plus a first-difference classifier. Drew's intent for its next form: given a function, find its 1-to-1
|
||
matches first, then **widen along the best expansion path** — the opcode-sequence tier (registers and immediates masked; the
|
||
in-tree `h_seq`), the structural family (`family_sweep` / `family_remap`), then cousins by the atlas's similarity scoring —
|
||
and return the best available result WITH its context: which tier matched, what differs (constant flip, register, length),
|
||
which banked sibling's spelling to port (R71), and how far the expansion had to go. The pieces exist in-tree
|
||
(`sig_image.py`'s tiers, the family engine, `atlas.py`); the work is a portable, game-free re-cut of them behind xsig's
|
||
interface, with fixtures built from our own C as the xsig tests are (R74). Sequenced after the readability phases; the
|
||
measured cross-game numbers in xsig's README are the baseline it must beat.
|
||
|
||
## 8. Governance for Gen3
|
||
|
||
The framework carries over unchanged: the constitution, the session protocol, the digest, the two gates, one task at
|
||
a time, the rules R1–R83 (the Phase-33 candidates were ratified as R74–R83 at the Phase-33.5 gate). A new generation starts with a fresh
|
||
plan in plan mode at Max. Three things the record says to do first:
|
||
|
||
1. **Measure the shape before choosing** (the "characterize the corpus SHAPE first" principle — `docs/how-to-ai-decomp/03-bootstrap-order.md`, Phase 1): which structures own most of the 61,898
|
||
data symbols, and which families own most of the 43,925 pins — a census with a self-asserting scanner, checked
|
||
against a case whose answer is known.
|
||
2. **Build the differential harness for the new question** before the campaign: "is this rename/type change
|
||
byte-neutral?" has two paths — the per-binary gate and the fleet run — and a scanner that asserts every reference
|
||
to a renamed symbol was rewritten (R32).
|
||
3. **Batch by leverage**: a pin in a shared body comes off for 138 binaries at once; a struct that explains a thousand
|
||
`D_` symbols is worth more than a hundred one-off names. Rank, then start.
|