T3.c2: includers follow -Iinclude; Unkstruct_80078E78; fold Blk98_80029274; decisions text

This commit is contained in:
Drew T
2026-09-29 21:10:25 -06:00
parent 5aaa251545
commit e4341ee118
5 changed files with 110 additions and 9 deletions
+12 -3
View File
@@ -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<u<p). Every overlap resolves as SPLIT: loser sites keep their casts and the byte oracle judges them later. A union is recorded as a candidate (loser >= 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
+64
View File
@@ -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 */
+25
View File
@@ -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/<name>`; 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.
+1 -3
View File
@@ -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;
+8 -3
View File
@@ -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/<name>
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)