diff --git a/docs/struct-map-decisions.md b/docs/struct-map-decisions.md index 3348fbb0e2..4be9cbade3 100644 --- a/docs/struct-map-decisions.md +++ b/docs/struct-map-decisions.md @@ -2,15 +2,24 @@ ## Canonical file set -TODO(T3 expert) +- One file, `include/struct_types.h`, included from `include/common.h` (the header splat gives every TU; precedent: `gte_inline.h`, P36 T5). `CANON_HEADERS = (src/shared/engine_types.h, include/struct_types.h)`; engine_types.h stays the ov-only canonical file, struct_types.h holds layouts whose cluster spans spaces. +- Evidence (body_base_type.json, census at 42a9cd7f5): `Unkstruct_80126B58` bodies ov 16,773 · md 311 · main 0; `Unkstruct_800B5CB8` bodies ov 15,245 · md 502 · main 22 (src/800.c) · boot.c 1. Each is ONE union-find cluster (one id, one layout) across spaces, so a per-space file would duplicate the definition: identity by cluster, never by name. +- No per-space files (`src/shared/main/types.h`, the slots'): main's shared types stay in `src/800_shared.h` (not canonical yet, T5); slot headers are reached through the prelude already. +- Tool fix that this needed: the restruct/delever include graph (`tools/delever.py includers`) now follows the build's `-Iinclude`; before, `type_visible` was false for every header under include/. ## Resolution rule -TODO(T3 expert) +- Per head: candidates = the map's layout entries plus every conflict alternative; misaligned candidates dropped; the non-overlapping set with the most sites wins (weighted-interval DP; ties: lower offset, wider, sign s= 50 sites and >= 20 % of the winner) but never emitted: the rewriter spells flat members only (`restruct.py member_spelling`). +- binding: head-type overlaps resolve by max-site split; unions wait until the rewriter spells union members. +- Field names: unkHEX throughout; `docs/actor-struct.md` does not describe these layouts (next section), so no field has cited evidence. +- `Unkstruct_800B5CB8` is `suspect` in the map (20+ AT globals, 4,724 params, 50 conflict offsets, 25,393 of 101,110 candidate sites dropped): a merge of several real types through argument conduits; its split is provisional until the cluster is separated (T5 candidate). ## Unkstruct_80078E00 (the player block) -TODO(T3 expert) +- Not defined: no such layout exists. `D_80078E00` has 0 occurrences in src/; the struct map has no cluster anchored at 0x80078E00 (only scalar clusters: `AT:D_80078EB4` u16 x270, `AT:D_80078EB0` s16 x152, ...); both heads have s32 at +0xB4 (1,744 / 404 sites) where the player's hp_current is a u16 at 0x80078EB4, so the player block is an instance of neither (9 scalar globals compared: 1 agrees with 80126B58, 0 with 800B5CB8; `.run/P38/T3/player_vs_heads.py`). `docs/actor-struct.md`'s "0x24C at 0x80078E00" was a Phase-17 m2c inference matched by the coincidence of +0xB4. +- What the code has: a 0x98-byte record at 0x80078E78: `Blk98_80029274` (src/800.c, `s32 words[38]`, struct-copied into the save slot in func_80029774), the block-scope `Blk80078E78` (src/resident/resident_jr_800D00E4.c:546), and `u8 D_80078E78[]` indexed [0]/[0x1A]/[0x37]/[0x49] in 880 TUs. +- Defined instead: `Unkstruct_80078E78` (0x98) in include/struct_types.h; widths = the type each USING TU declares for the scalar global (`.run/P38/T3/player_use_widths.py`, boilerplate externs excluded); names only for rows memory-map 3.4 / actor-struct.md mark verified (gold, tiredness_raw, dayCounter, hour, hp_max, hp_current, bp_max, bp_current, dayOfWeek, posX, posZ). Code-width overlaps recorded in the field comments (+0x38 s16 over hour: the verified name wins; +0x1C/+0x20/+0x24/+0x2C wide over narrow: the wide majority wins). Reported-only names stay in comments. +- `Blk98_80029274` folded onto it (`typedef Unkstruct_80078E78 Blk98_80029274;`, src/800.c), R22-judged. No typed body exists for this record in the map, so the `--try-file` proof does not apply; R22 is its byte proof. `Blk80078E78` (block scope) is rung-L work. ## Per-offset records diff --git a/include/struct_types.h b/include/struct_types.h index 0845e04ed3..b027d8feb4 100644 --- a/include/struct_types.h +++ b/include/struct_types.h @@ -302,4 +302,68 @@ typedef struct { } Unkstruct_80126B58; /* size = 0x24c */ /* head_types:end */ +/* Unkstruct_80078E78 — the player record at 0x80078E78 (0x98 bytes; was Blk98_80029274 in src/800.c, Blk80078E78 in + src/resident/resident_jr_800D00E4.c, u8 D_80078E78[] in 880 TUs). Hand-written (P38 T3): field widths are the type the + USING TUs declare for each scalar global (.run/P38/T3/player_use_widths.py); names only where docs/memory-map.md 3.4 + or docs/actor-struct.md mark the row verified. Decisions: docs/struct-map-decisions.md. */ +typedef struct { + /* 0x0000 */ s32 unk0; /* D_80078E78 word 0 */ + /* 0x0004 */ u8 unk4; /* D_80078E7C; reported: gameClock */ + /* 0x0005 */ u8 unk5; /* D_80078E7D */ + /* 0x0006 */ u8 unk6; /* D_80078E7E */ + /* 0x0007 */ u8 unk7; /* D_80078E7F */ + /* 0x0008 */ s32 unk8; /* D_80078E80; reported: storyProgress; map: pointer into Unkstruct_800B5CB8 */ + /* 0x000c */ s32 unkC; /* D_80078E84; map: pointer into Unkstruct_800B5CB8 */ + /* 0x0010 */ s32 unk10; /* D_80078E88; map: pointer into Unkstruct_800B5CB8 */ + /* 0x0014 */ s32 gold; /* D_80078E8C; verified live (stored as gold/10), memory-map 3.4; code width s32 */ + /* 0x0018 */ s16 unk18; /* D_80078E90 */ + /* 0x001a */ s16 unk1A; /* D_80078E92 */ + /* 0x001c */ s32 unk1C; /* D_80078E94; s16 D_80078E96 at +0x1E loses (5 uses) */ + /* 0x0020 */ s32 unk20; /* D_80078E98; s16 D_80078E9A at +0x22 loses (1 use) */ + /* 0x0024 */ s32 unk24; /* D_80078E9C; s16 D_80078E9E at +0x26 loses (2 uses) */ + /* 0x0028 */ s32 unk28; /* D_80078EA0 */ + /* 0x002c */ s32 tiredness_raw; /* D_80078EA4; verified live, memory-map 3.4; u16 D_80078EA6 at +0x2E loses (8 uses) */ + /* 0x0030 */ s32 unk30; /* D_80078EA8 */ + /* 0x0034 */ u16 dayCounter; /* D_80078EAC; verified live + code (func_8014B084), memory-map 3.4 */ + /* 0x0036 */ u8 unk36; /* D_80078EAE */ + /* 0x0037 */ u8 unk37; /* D_80078EAF; not dayOfWeek (refuted, memory-map 3.4) */ + /* 0x0038 */ u8 unk38; /* D_80078EB0 low byte; s16 D_80078EB0 (524 uses) spans +0x38..+0x39: union candidate */ + /* 0x0039 */ u8 hour; /* D_80078EB1; verified live, memory-map 3.4 */ + /* 0x003a */ u16 hp_max; /* D_80078EB2; verified live */ + /* 0x003c */ s16 hp_current; /* D_80078EB4; verified live; s16 by 689 vs 135 uses */ + /* 0x003e */ u16 bp_max; /* D_80078EB6; verified live */ + /* 0x0040 */ s16 bp_current; /* D_80078EB8; verified live; s16 by 12 vs 3 uses */ + /* 0x0042 */ u8 dayOfWeek; /* D_80078EBA; verified (code, func_8014B084) */ + /* 0x0043 */ u8 unk43; /* D_80078EBB */ + /* 0x0044 */ u8 pad44[0x3]; + /* 0x0047 */ u8 unk47; /* D_80078EBF */ + /* 0x0048 */ u8 unk48; /* D_80078EC0 */ + /* 0x0049 */ u8 unk49; /* D_80078EC1 */ + /* 0x004a */ u8 unk4A; /* D_80078EC2 */ + /* 0x004b */ u8 unk4B; /* D_80078EC3 */ + /* 0x004c */ s8 unk4C; /* D_80078EC4 */ + /* 0x004d */ u8 unk4D; /* D_80078EC5 */ + /* 0x004e */ s8 unk4E; /* D_80078EC6 */ + /* 0x004f */ u8 pad4F[0x1]; + /* 0x0050 */ s32 unk50; /* D_80078EC8 */ + /* 0x0054 */ s32 unk54; /* D_80078ECC */ + /* 0x0058 */ s32 unk58; /* D_80078ED0 */ + /* 0x005c */ s32 unk5C; /* D_80078ED4 */ + /* 0x0060 */ s32 unk60; /* D_80078ED8 */ + /* 0x0064 */ s32 unk64; /* D_80078EDC */ + /* 0x0068 */ u8 unk68; /* D_80078EE0 */ + /* 0x0069 */ u8 pad69[0x3]; + /* 0x006c */ s32 unk6C; /* D_80078EE4; reported: bodyLevel */ + /* 0x0070 */ s32 unk70; /* D_80078EE8; reported: bodyExp */ + /* 0x0074 */ s32 unk74; /* D_80078EEC; reported: mindLevel */ + /* 0x0078 */ s32 unk78; /* D_80078EF0; reported: mindExp; actor-struct.md's s16 step counter conflicts with code width s32 */ + /* 0x007c */ s32 unk7C; /* D_80078EF4; reported: fusionLevel */ + /* 0x0080 */ s32 unk80; /* D_80078EF8; reported: fusionExp */ + /* 0x0084 */ s32 unk84; /* D_80078EFC; reported: luminaLevel */ + /* 0x0088 */ s32 unk88; /* D_80078F00; reported: luminaExp */ + /* 0x008c */ s32 unk8C; /* D_80078F04 */ + /* 0x0090 */ s32 posX; /* D_80078F08; verified live + code width 4 (actor-struct.md, census control) */ + /* 0x0094 */ s32 posZ; /* D_80078F0C; verified live + code width 4 */ +} Unkstruct_80078E78; /* size = 0x98 */ + #endif /* STRUCT_TYPES_H */ diff --git a/phase-ends/current/logs/T3.c2.md b/phase-ends/current/logs/T3.c2.md new file mode 100644 index 0000000000..ec3be89e37 --- /dev/null +++ b/phase-ends/current/logs/T3.c2.md @@ -0,0 +1,25 @@ +# T3.c2 — include graph follows -Iinclude; Unkstruct_80078E78; Blk98 fold; decisions text + +STATUS: blocked (try-file proofs crash in restruct.py; real selftest calibration stale). Edits committed (green on selftests, audit, R22). + +## Changes +- tools/delever.py includers(): `resolve(rel, name)` = includer-relative path when it exists, else `include/`; scan list adds `include/*.h` (non-recursive). Cache key prefixed `I|` so pre-change cache entries (includer-relative only) rebuild instead of being reused. +- include/struct_types.h: hand-written `Unkstruct_80078E78` (brief text verbatim) after `/* head_types:end */`, inside the guard. +- src/800.c:8: `typedef Unkstruct_80078E78 Blk98_80029274;` replaces `s32 words[38]` struct. (`p->words` at 800.c:7484+ is a different struct.) +- docs/struct-map-decisions.md: three TODO(T3 expert) bodies replaced verbatim. + +## Commands +- `restruct.py --selftest` → 54/54 OK; `type_census.py --selftest` → 24/24 OK. +- scratch `.run/P38/T3/hdrs_check.py`: headers_of(md_SC07_004.c) contains include/common.h True, include/struct_types.h True. +- `--audit-types` (t3c2_audit) → 1182 audited; 26 violations; 0 lines mention struct_types. +- R22 (t3c2_r22) → `extract-all: 217 extracted, 0 failed of 217 (+ main, serial)`, `check-all: 218 passed, 0 failed of 218`, exit 0. Blk98 fold byte-identical, no revert. +- try-file, all 9 TUs (t3c2_try_1..10): no per-file final line; every run raises before printing any body verdict: + - `AttributeError: 'int' object has no attribute 'split'` at restruct.py:526 pass_hint (from work_file_S:1027, DIFFERS path): ov_SC06_032_jr_80182890, ov_MAIN_012_jr_8015A3C8, ov_SC03_107_jr_8015A3C8, md_MAIN_036. + - `Refuse: overlapping edits at N` at restruct.py:687 compose_text (from cands, work_file_S:1019): ov_SC02_027_jr_8017D898, md_SC07_004 (reproduced serially), md_SC07_003, 800.c, md_MAIN_046. +- Controls with HEAD's delever.py (pre-change include graph): ov_SC06_032 → same pass_hint AttributeError; ov_SC02_027 → same overlapping-edits Refuse. Both bugs predate change 1 (ov TUs already saw engine_types.h); change 1 only lets md/main TUs reach the same paths. +- `--selftest --real` (t3c2_real) → 56/57; FAIL `the oracle's calibration is current: calibrated at a8cdd934b, HEAD is 5aaa25154`; linked-control OK. No func_801814AC line in the log. `--help` names no recalibrate flag (harness: gotcha). + +## For the expert +- pass_hint: `residual["pairs"]` entries carry an int at p[2] (or p[1]); the `.split()` assumes strings. +- compose_text overlap: edits for one body overlap each other (inserts empty in try on these TUs? not checked); likely two bases' edits on one span, or dedupe missing before cands (drop_overlaps at :665-677 exists but cands passes raw `edits`). +- Both are restruct.py, outside this brief's file set: not touched. diff --git a/src/800.c b/src/800.c index d24634496b..0b910a4f84 100644 --- a/src/800.c +++ b/src/800.c @@ -5,9 +5,7 @@ typedef struct { u16 unk00; u8 pad[14]; } T16; /* 0x10 */ -typedef struct { - s32 words[38]; -} Blk98_80029274; +typedef Unkstruct_80078E78 Blk98_80029274; /* P38 T3: folded onto the canonical player record (include/struct_types.h) */ typedef struct { s32 a; s32 b[4]; } OtBlk_80016450; typedef struct { /* 0x00 */ u16 type; diff --git a/tools/delever.py b/tools/delever.py index cd9236a33b..626c5940dd 100644 --- a/tools/delever.py +++ b/tools/delever.py @@ -660,18 +660,23 @@ def includers(use_cache=True): new_cache = {} direct = collections.defaultdict(set) hdr_inc = collections.defaultdict(set) - for p in sorted((REPO / "src").rglob("*")): + def resolve(rel, name): + # as the build does (-Iinclude): relative to the includer when that file exists, else include/ + loc = os.path.normpath(os.path.join(os.path.dirname(rel), name)) + return loc if (REPO / loc).exists() else os.path.normpath(os.path.join("include", name)) + + for p in sorted((REPO / "src").rglob("*")) + sorted((REPO / "include").glob("*.h")): if p.suffix not in (".c", ".h") or p.name.startswith("."): continue rel = p.relative_to(REPO).as_posix() st = p.stat() - key = f"{st.st_mtime_ns}|{st.st_size}" + key = f"I|{st.st_mtime_ns}|{st.st_size}" # I|: entries resolved with -Iinclude (P38 T3); older entries are rebuilt ent = cache.get(rel) if ent and ent[0] == key: incs = ent[1] else: text = p.read_text(errors="surrogateescape") - incs = [os.path.normpath(os.path.join(os.path.dirname(rel), mm.group(1))) for mm in sc.INCLUDE_LINE.finditer(sc.mask_text(text))] + incs = [resolve(rel, mm.group(1)) for mm in sc.INCLUDE_LINE.finditer(sc.mask_text(text))] new_cache[rel] = [key, incs] for k2 in incs: (direct if rel.endswith(".c") else hdr_inc)[k2].add(rel)