From 5aaa2515459a7ae46cfe9855f44468832cb370d0 Mon Sep 17 00:00:00 2001 From: Drew T <50529377+Druthulu@users.noreply.github.com> Date: Tue, 29 Sep 2026 21:00:34 -0600 Subject: [PATCH] T3.c1: head_types.py max-site split; struct_types.h + decisions record; canon header --- docs/struct-map-decisions.md | 115 ++++++++++++ include/common.h | 1 + include/struct_types.h | 305 +++++++++++++++++++++++++++++++ phase-ends/current/logs/T3.c1.md | 28 +++ tools/head_types.py | 233 +++++++++++++++++++++++ tools/type_census.py | 2 +- 6 files changed, 683 insertions(+), 1 deletion(-) create mode 100644 docs/struct-map-decisions.md create mode 100644 include/struct_types.h create mode 100644 phase-ends/current/logs/T3.c1.md create mode 100644 tools/head_types.py diff --git a/docs/struct-map-decisions.md b/docs/struct-map-decisions.md new file mode 100644 index 0000000000..3348fbb0e2 --- /dev/null +++ b/docs/struct-map-decisions.md @@ -0,0 +1,115 @@ +# Struct-map decisions — the head types (P38 T3) + +## Canonical file set + +TODO(T3 expert) + +## Resolution rule + +TODO(T3 expert) + +## Unkstruct_80078E00 (the player block) + +TODO(T3 expert) + +## Per-offset records + +Written by `tools/head_types.py` (max-site split). Losers' sites keep their casts; the byte oracle judges them later. +Summary fields: `overlaps` = candidates dropped because a heavier non-overlapping set won; `misaligned` = candidates dropped +for `off % min(w,4) != 0` (or an 8-byte field off 8-alignment); `union_candidates` = offsets whose resolution is +`union-candidate (deferred)`. + + +Source: `.run/P37/census/struct_map.json` head `42a9cd7f5`. + +### Unkstruct_800B5CB8 + +`fields=101 kept_sites=75717 dropped_sites=25393 conflict_offsets=50 overlaps=167 misaligned=4 union_candidates=27` + +- `0x000: chosen 2u n=367; losers: 4s n=25@0x000, 4p n=10@0x000, 2s n=38@0x000, 1u n=24@0x000, 1u n=11@0x001; resolution: split` +- `0x001: chosen none (inside 2u@0x000); losers listed there; resolution: split` +- `0x002: chosen 2s n=3260; losers: 2u n=2271@0x002, 1u n=7@0x002, 1u n=9@0x003; resolution: union-candidate (deferred: rewriter spells flat members only, restruct.py member_spelling) -> split` +- `0x004: chosen 2s n=61; losers: 4s n=1376@0x004, 4u n=538@0x004, 2u n=26@0x004, 1s n=2@0x004; resolution: union-candidate (deferred: rewriter spells flat members only, restruct.py member_spelling) -> split` +- `0x006: chosen 2u n=1597; losers: 2s n=696@0x006; resolution: union-candidate (deferred: rewriter spells flat members only, restruct.py member_spelling) -> split` +- `0x008: chosen 2u n=480; losers: 4s n=466@0x008, 4u n=9@0x008, 2s n=36@0x008; resolution: union-candidate (deferred: rewriter spells flat members only, restruct.py member_spelling) -> split` +- `0x00C: chosen 2u n=470; losers: 4s n=399@0x00C, 4u n=6@0x00C, 2s n=22@0x00C, 1u n=16@0x00C, 1u n=12@0x00D; resolution: union-candidate (deferred: rewriter spells flat members only, restruct.py member_spelling) -> split` +- `0x010: chosen 2u n=1925; losers: 4s n=2139@0x010, 4u n=8@0x010, 4p n=3@0x010, 2s n=935@0x010; resolution: union-candidate (deferred: rewriter spells flat members only, restruct.py member_spelling) -> split` +- `0x012: chosen 2u n=4483; losers: 2s n=2789@0x012; resolution: union-candidate (deferred: rewriter spells flat members only, restruct.py member_spelling) -> split` +- `0x014: chosen 4s n=1156; losers: 4u n=13@0x014, 2s n=416@0x014, 2u n=310@0x014, 2s n=280@0x016; resolution: union-candidate (deferred: rewriter spells flat members only, restruct.py member_spelling) -> split` +- `0x018: chosen 2u n=1062; losers: 4s n=2122@0x018, 4u n=13@0x018, 2s n=778@0x018; resolution: union-candidate (deferred: rewriter spells flat members only, restruct.py member_spelling) -> split` +- `0x01C: chosen 4s n=7913; losers: 4u n=59@0x01C, 2s n=306@0x01C, 2u n=160@0x01C, 1u n=5@0x01C, 2s n=133@0x01E, 1s n=1@0x01F; resolution: split` +- `0x020: chosen 4s n=13163; losers: 4u n=73@0x020, 4p n=389@0x020, 1u n=6@0x020, 1u n=8@0x021, 1u n=12@0x022, 1u n=5@0x023; resolution: split` +- `0x024: chosen 1u n=10; losers: 4s n=111@0x024, 4u n=1@0x024, 4p n=7@0x024, 2s n=5@0x024, 2u n=1@0x024; resolution: union-candidate (deferred: rewriter spells flat members only, restruct.py member_spelling) -> split` +- `0x026: chosen 1u n=11; losers: 2s n=5@0x026, 2u n=3@0x026; resolution: split` +- `0x028: chosen 2s n=26; losers: 4s n=31@0x028, 4u n=3@0x028, 2u n=20@0x028, 1u n=1@0x028; resolution: split` +- `0x02C: chosen 2u n=728; losers: 4s n=141@0x02C, 4u n=5@0x02C, 4p n=15@0x02C, 2s n=87@0x02C; resolution: split` +- `0x030: chosen 4s n=31; losers: 2s n=7@0x030, 2u n=18@0x030, 2s n=11@0x032; resolution: split` +- `0x034: chosen 2u n=2952; losers: 4s n=46@0x034, 4u n=8@0x034, 4p n=4@0x034, 2s n=804@0x034; resolution: union-candidate (deferred: rewriter spells flat members only, restruct.py member_spelling) -> split` +- `0x038: chosen 2s n=1; losers: 4s n=175@0x038, 2u n=1@0x038; resolution: union-candidate (deferred: rewriter spells flat members only, restruct.py member_spelling) -> split` +- `0x03E: chosen 2u n=352; losers: 4s n=125@0x03C; resolution: union-candidate (deferred: rewriter spells flat members only, restruct.py member_spelling) -> split` +- `0x042: chosen 2u n=352; losers: 4s n=176@0x040; resolution: union-candidate (deferred: rewriter spells flat members only, restruct.py member_spelling) -> split` +- `0x044: chosen 4s n=46; losers: 2u n=8@0x044; resolution: split` +- `0x048: chosen 4s n=418; losers: 2u n=13@0x04A; resolution: split` +- `0x050: chosen 4s n=126; losers: 2s n=7@0x050, 2u n=13@0x050, 2s n=48@0x052; resolution: split` +- `0x05C: chosen 2u n=947; losers: 4s n=1@0x05C, 2s n=531@0x05C; resolution: union-candidate (deferred: rewriter spells flat members only, restruct.py member_spelling) -> split` +- `0x05E: chosen 1u n=295; losers: 2s n=138@0x05E, 2u n=234@0x05E; resolution: union-candidate (deferred: rewriter spells flat members only, restruct.py member_spelling) -> split` +- `0x070: chosen 2s n=1813; losers: 4s n=1@0x070, 2u n=938@0x070, 1s n=4@0x070, 1u n=12@0x070; resolution: union-candidate (deferred: rewriter spells flat members only, restruct.py member_spelling) -> split` +- `0x080: chosen 2s n=292; losers: 4s n=94@0x080, 4u n=3@0x080, 4p n=22@0x080, 2u n=26@0x080; resolution: union-candidate (deferred: rewriter spells flat members only, restruct.py member_spelling) -> split` +- `0x0CC: chosen 4s n=1172; losers: 4u n=23@0x0CC, 4p n=269@0x0CC, 2s n=2@0x0CC, 2u n=5@0x0CC, 2u n=5@0x0CE; resolution: union-candidate (deferred: rewriter spells flat members only, restruct.py member_spelling) -> split` +- `0x0D0: chosen 4s n=464; losers: 4p n=75@0x0D0, 2s n=2@0x0D0, 2u n=7@0x0D0, 2s n=3@0x0D2; resolution: split` +- `0x0D4: chosen 4s n=184; losers: 4p n=24@0x0D4, 2u n=5@0x0D4, 2u n=5@0x0D6; resolution: split` +- `0x0D8: chosen 4s n=93; losers: 4p n=12@0x0D8, 2s n=2@0x0D8, 2u n=5@0x0D8; resolution: split` +- `0x0DC: chosen 4s n=487; losers: 4u n=7@0x0DC, 4p n=79@0x0DC, 2s n=155@0x0DC, 2u n=76@0x0DC, 2s n=105@0x0DE; resolution: union-candidate (deferred: rewriter spells flat members only, restruct.py member_spelling) -> split` +- `0x0E0: chosen 4s n=511; losers: 4u n=283@0x0E0, 2s n=69@0x0E0, 2u n=52@0x0E0, 2u n=74@0x0E2; resolution: union-candidate (deferred: rewriter spells flat members only, restruct.py member_spelling) -> split` +- `0x0E4: chosen 4s n=282; losers: 4u n=6@0x0E4, 2s n=46@0x0E4, 2u n=55@0x0E4, 2u n=40@0x0E6; resolution: split` +- `0x0E8: chosen 4s n=313; losers: 4u n=58@0x0E8, 2s n=65@0x0E8, 2u n=10@0x0E8, 2s n=58@0x0EA; resolution: union-candidate (deferred: rewriter spells flat members only, restruct.py member_spelling) -> split` +- `0x0EC: chosen 2s n=36; losers: 4s n=43@0x0EC, 4u n=1@0x0EC, 4p n=1@0x0EC, 2u n=6@0x0EC, 1u n=5@0x0EC, 1u n=5@0x0ED; resolution: split` +- `0x0EE: chosen 2s n=17; losers: 2u n=5@0x0EE, 1u n=5@0x0EE; resolution: split` +- `0x0F0: chosen 1u n=5; losers: 4s n=22@0x0F0, 2s n=5@0x0F0; resolution: split` +- `0x0F2: chosen 2u n=16; losers: 2s n=6@0x0F2, 1u n=5@0x0F2; resolution: split` +- `0x0F4: chosen 4s n=35; losers: 4p n=1@0x0F4, 2s n=26@0x0F4, 2u n=17@0x0F4, 1u n=5@0x0F4, 1u n=5@0x0F5, 2s n=8@0x0F6, 2u n=7@0x0F6, 1u n=6@0x0F6; resolution: split` +- `0x0F6: chosen none (inside 4s@0x0F4); losers listed there; resolution: split` +- `0x0F8: chosen 4s n=21; losers: 4p n=5@0x0F8, 2s n=9@0x0F8, 2u n=8@0x0F8, 1u n=5@0x0F8, 1u n=5@0x0F9, 2s n=5@0x0FA, 2u n=3@0x0FA, 1u n=5@0x0FA; resolution: split` +- `0x0FA: chosen none (inside 4s@0x0F8); losers listed there; resolution: split` +- `0x0FC: chosen 2s n=1922; losers: 4s n=5@0x0FC, 2u n=740@0x0FC, 1u n=31@0x0FC, 1u n=4@0x0FD; resolution: union-candidate (deferred: rewriter spells flat members only, restruct.py member_spelling) -> split` +- `0x0FE: chosen 2s n=1470; losers: 2u n=690@0x0FE, 1u n=7@0x0FE, 1u n=4@0x0FF; resolution: union-candidate (deferred: rewriter spells flat members only, restruct.py member_spelling) -> split` +- `0x100: chosen 2s n=691; losers: 4u n=8@0x100, 2u n=588@0x100, 1u n=5@0x100, 1u n=2@0x101; resolution: union-candidate (deferred: rewriter spells flat members only, restruct.py member_spelling) -> split` +- `0x102: chosen 2s n=364; losers: 2u n=280@0x102, 1s n=4@0x102, 1u n=17@0x102, 1u n=2@0x103; resolution: union-candidate (deferred: rewriter spells flat members only, restruct.py member_spelling) -> split` +- `0x104: chosen 2s n=311; losers: 1u n=6@0x105; resolution: split` +- `0x108: chosen 2u n=173; losers: 2s n=166@0x108, 1u n=36@0x108, 1u n=21@0x109; resolution: union-candidate (deferred: rewriter spells flat members only, restruct.py member_spelling) -> split` + +### Unkstruct_80126B58 + +`fields=152 kept_sites=61599 dropped_sites=1713 conflict_offsets=50 overlaps=71 misaligned=5 union_candidates=4` + +- `0x004: chosen 2u n=5; losers: 4s n=12@0x004; resolution: split` +- `0x006: chosen 2s n=237; losers: 2u n=178@0x006; resolution: union-candidate (deferred: rewriter spells flat members only, restruct.py member_spelling) -> split` +- `0x008: chosen 2u n=5; losers: 4s n=15@0x008; resolution: split` +- `0x00C: chosen 2u n=5; losers: 4s n=13@0x00C; resolution: split` +- `0x010: chosen 4s n=464; losers: 4p n=2@0x010, 2s n=10@0x010, 2u n=283@0x010, 2u n=22@0x012; resolution: union-candidate (deferred: rewriter spells flat members only, restruct.py member_spelling) -> split` +- `0x014: chosen 4s n=440; losers: 4u n=2@0x014, 2u n=6@0x014, 2u n=6@0x016; resolution: split` +- `0x018: chosen 4s n=438; losers: 4u n=2@0x018, 2u n=8@0x018, 2u n=8@0x01A; resolution: split` +- `0x024: chosen 2s n=20; losers: 4s n=142@0x024, 2u n=2@0x024; resolution: union-candidate (deferred: rewriter spells flat members only, restruct.py member_spelling) -> split` +- `0x028: chosen 4s n=12; losers: 2s n=11@0x028, 2u n=1@0x028, 1u n=2@0x028; resolution: split` +- `0x02C: chosen 4s n=4448; losers: 4u n=8@0x02C, 2s n=3@0x02C, 2s n=135@0x02E; resolution: split` +- `0x030: chosen 4s n=171; losers: 4u n=4@0x030, 1u n=2@0x030; resolution: split` +- `0x044: chosen 4u n=1744; losers: 4s n=331@0x044, 2s n=1@0x044, 2s n=1@0x046; resolution: split` +- `0x04D: chosen 1u n=13; losers: 4s n=49@0x04C; resolution: split` +- `0x054: chosen 4s n=2151; losers: 1u n=4@0x054; resolution: split` +- `0x058: chosen 4s n=2145; losers: 4u n=1@0x058, 4p n=6@0x058, 2s n=6@0x058, 2u n=4@0x058, 1u n=3@0x058, 1u n=3@0x059, 2s n=7@0x05A, 2u n=5@0x05A, 1u n=3@0x05A, 1u n=1@0x05B; resolution: split` +- `0x05A: chosen none (inside 4s@0x058); losers listed there; resolution: split` +- `0x05C: chosen 4s n=404; losers: 2s n=3@0x05C, 2u n=1@0x05C, 1u n=3@0x05C, 1u n=3@0x05D, 2s n=2@0x05E, 2u n=1@0x05E, 1u n=3@0x05E, 1u n=1@0x05F; resolution: split` +- `0x05E: chosen none (inside 4s@0x05C); losers listed there; resolution: split` +- `0x060: chosen 2u n=72; losers: 4s n=1@0x060, 2s n=38@0x060; resolution: split` +- `0x066: chosen 2u n=10; losers: 1u n=2@0x067; resolution: split` +- `0x0AE: chosen 2u n=14; losers: 2s n=1@0x0AE, 1u n=4@0x0AE; resolution: split` +- `0x0FC: chosen 2s n=3; losers: 4u n=2@0x0FC; resolution: split` +- `0x204: chosen 4s n=26; losers: 2s n=3@0x206; resolution: split` +- `0x208: chosen 4s n=4; losers: 2u n=1@0x20A; resolution: split` +- `0x20C: chosen 2s n=28; losers: 2u n=21@0x20C, 1s n=1@0x20C, 1u n=11@0x20C, 1s n=4@0x20D; resolution: split` +- `0x218: chosen 1u n=3; losers: 4s n=1@0x218; resolution: split` +- `0x234: chosen 4s n=5937; losers: 4u n=1@0x234, 2u n=6@0x234, 2u n=138@0x236; resolution: split` +- `0x238: chosen 4s n=1475; losers: 4u n=2@0x238, 2u n=2@0x238, 2u n=2@0x23A; resolution: split` +- `0x240: chosen 4s n=274; losers: 2u n=4@0x240; resolution: split` +- `0x244: chosen 2u n=409; losers: 2s n=142@0x244, 1s n=1@0x244, 1u n=5@0x244, 1s n=1@0x245; resolution: union-candidate (deferred: rewriter spells flat members only, restruct.py member_spelling) -> split` + diff --git a/include/common.h b/include/common.h index a06580be7e..2be727d609 100644 --- a/include/common.h +++ b/include/common.h @@ -61,4 +61,5 @@ typedef s64 M2C_UNK64; #define LOWU(x) (*(u32 *)&(x)) #include "gte_inline.h" /* Phase 36 T5: the GTE coprocessor macros, one definition each */ +#include "struct_types.h" /* Phase 38 T3: the struct map's canonical layouts, one definition each, visible to every space */ #endif /* COMMON_H */ diff --git a/include/struct_types.h b/include/struct_types.h new file mode 100644 index 0000000000..0845e04ed3 --- /dev/null +++ b/include/struct_types.h @@ -0,0 +1,305 @@ +/* include/struct_types.h — the struct map's canonical layouts (P38 T3). One definition per type, visible to every space through common.h. Generated block below is written by tools/head_types.py from .run/P37/census/struct_map.json; hand-written text outside the markers survives regeneration. Naming law G62: Unkstruct_, unkHEX fields, 0xNN offset comments. */ +#ifndef STRUCT_TYPES_H +#define STRUCT_TYPES_H + +/* head_types:begin */ +typedef struct { + /* P37 struct map: 107580 sites in 11184 bodies; evidence {'PV': 4724, 'L': 9203, 'PTR': 9757, 'AT': 266, 'P': 4724, 'R': 86, 'FLD': 119}; at D_800B5CB8,D_800D3888,D_80181448; resolved by tools/head_types.py (max-site split); decisions: docs/struct-map-decisions.md */ + /* 0x0000 */ u16 unk0; + /* 0x0002 */ s16 unk2; + /* 0x0004 */ s16 unk4; + /* 0x0006 */ u16 unk6; + /* 0x0008 */ u16 unk8; + /* 0x000a */ u16 unkA; + /* 0x000c */ u16 unkC; + /* 0x000e */ u16 unkE; + /* 0x0010 */ u16 unk10; + /* 0x0012 */ u16 unk12; + /* 0x0014 */ s32 unk14; + /* 0x0018 */ u16 unk18; + /* 0x001a */ u16 unk1A; + /* 0x001c */ s32 unk1C; + /* 0x0020 */ s32 unk20; + /* 0x0024 */ u8 unk24; + /* 0x0025 */ u8 unk25; + /* 0x0026 */ u8 unk26; + /* 0x0027 */ u8 unk27; + /* 0x0028 */ s16 unk28; + /* 0x002a */ u16 unk2A; + /* 0x002c */ u16 unk2C; + /* 0x002e */ u16 unk2E; + /* 0x0030 */ s32 unk30; + /* 0x0034 */ u16 unk34; + /* 0x0036 */ u16 unk36; + /* 0x0038 */ s16 unk38; + /* 0x003a */ u16 unk3A; + /* 0x003c */ u8 pad3C[0x2]; + /* 0x003e */ u16 unk3E; + /* 0x0040 */ u8 pad40[0x2]; + /* 0x0042 */ u16 unk42; + /* 0x0044 */ s32 unk44; + /* 0x0048 */ s32 unk48; + /* 0x004c */ s32 unk4C; + /* 0x0050 */ s32 unk50; + /* 0x0054 */ u16 unk54; + /* 0x0056 */ u8 pad56[0x2]; + /* 0x0058 */ s32 unk58; + /* 0x005c */ u16 unk5C; + /* 0x005e */ u8 unk5E; + /* 0x005f */ u8 pad5F[0x1]; + /* 0x0060 */ s16 unk60; + /* 0x0062 */ u16 unk62; + /* 0x0064 */ s32 unk64; + /* 0x0068 */ s32 unk68; + /* 0x006c */ s32 unk6C; + /* 0x0070 */ s16 unk70; + /* 0x0072 */ u16 unk72; + /* 0x0074 */ u8 unk74; + /* 0x0075 */ u8 unk75; + /* 0x0076 */ s16 unk76; + /* 0x0078 */ s32 unk78; + /* 0x007c */ s16 unk7C; + /* 0x007e */ s16 unk7E; + /* 0x0080 */ s16 unk80; + /* 0x0082 */ s16 unk82; + /* 0x0084 */ s16 unk84; + /* 0x0086 */ u16 unk86; + /* 0x0088 */ u16 unk88; + /* 0x008a */ u16 unk8A; + /* 0x008c */ u16 unk8C; + /* 0x008e */ u8 pad8E[0x2]; + /* 0x0090 */ s32 unk90; + /* 0x0094 */ s32 unk94; + /* 0x0098 */ s16 unk98; + /* 0x009a */ s16 unk9A; + /* 0x009c */ s32 unk9C; + /* 0x00a0 */ u8 padA0[0x4]; + /* 0x00a4 */ s32 unkA4; + /* 0x00a8 */ s16 unkA8; + /* 0x00aa */ s16 unkAA; + /* 0x00ac */ s16 unkAC; + /* 0x00ae */ s16 unkAE; + /* 0x00b0 */ s32 unkB0; + /* 0x00b4 */ s32 unkB4; + /* 0x00b8 */ u8 padB8[0x4]; + /* 0x00bc */ s32 unkBC; + /* 0x00c0 */ u8 unkC0; + /* 0x00c1 */ s8 unkC1; + /* 0x00c2 */ u8 unkC2; + /* 0x00c3 */ u8 unkC3; + /* 0x00c4 */ s32 unkC4; + /* 0x00c8 */ u8 unkC8; + /* 0x00c9 */ u8 unkC9; + /* 0x00ca */ u8 padCA[0x2]; + /* 0x00cc */ s32 unkCC; + /* 0x00d0 */ s32 unkD0; + /* 0x00d4 */ s32 unkD4; + /* 0x00d8 */ s32 unkD8; + /* 0x00dc */ s32 unkDC; + /* 0x00e0 */ s32 unkE0; + /* 0x00e4 */ s32 unkE4; + /* 0x00e8 */ s32 unkE8; + /* 0x00ec */ s16 unkEC; + /* 0x00ee */ s16 unkEE; + /* 0x00f0 */ u8 unkF0; + /* 0x00f1 */ u8 unkF1; + /* 0x00f2 */ u16 unkF2; + /* 0x00f4 */ s32 unkF4; + /* 0x00f8 */ s32 unkF8; + /* 0x00fc */ s16 unkFC; + /* 0x00fe */ s16 unkFE; + /* 0x0100 */ s16 unk100; + /* 0x0102 */ s16 unk102; + /* 0x0104 */ s16 unk104; + /* 0x0106 */ s16 unk106; + /* 0x0108 */ u16 unk108; + /* 0x010a */ s16 unk10A; +} Unkstruct_800B5CB8; /* size = 0x10c */ + +typedef struct { + /* P37 struct map: 65397 sites in 17047 bodies; evidence {'PV': 1050, 'L': 1271, 'AT': 1, 'P': 1050, 'PTR': 1, 'FLD': 14}; at D_80126B58; resolved by tools/head_types.py (max-site split); decisions: docs/struct-map-decisions.md */ + /* 0x0000 */ u16 unk0; + /* 0x0002 */ u16 unk2; + /* 0x0004 */ u16 unk4; + /* 0x0006 */ s16 unk6; + /* 0x0008 */ u16 unk8; + /* 0x000a */ s16 unkA; + /* 0x000c */ u16 unkC; + /* 0x000e */ s16 unkE; + /* 0x0010 */ s32 unk10; + /* 0x0014 */ s32 unk14; + /* 0x0018 */ s32 unk18; + /* 0x001c */ s32 unk1C; + /* 0x0020 */ s32 unk20; + /* 0x0024 */ s16 unk24; + /* 0x0026 */ s16 unk26; + /* 0x0028 */ s32 unk28; + /* 0x002c */ s32 unk2C; + /* 0x0030 */ s32 unk30; + /* 0x0034 */ s32 unk34; + /* 0x0038 */ s32 unk38; + /* 0x003c */ u16 unk3C; + /* 0x003e */ s16 unk3E; + /* 0x0040 */ s16 unk40; + /* 0x0042 */ s16 unk42; + /* 0x0044 */ u32 unk44; + /* 0x0048 */ u8 pad48[0x5]; + /* 0x004d */ u8 unk4D; + /* 0x004e */ u8 unk4E; + /* 0x004f */ u8 unk4F; + /* 0x0050 */ s32 unk50; + /* 0x0054 */ s32 unk54; + /* 0x0058 */ s32 unk58; + /* 0x005c */ s32 unk5C; + /* 0x0060 */ u16 unk60; + /* 0x0062 */ u16 unk62; + /* 0x0064 */ u16 unk64; + /* 0x0066 */ u16 unk66; + /* 0x0068 */ s16 unk68; + /* 0x006a */ s16 unk6A; + /* 0x006c */ s16 unk6C; + /* 0x006e */ u8 pad6E[0x2]; + /* 0x0070 */ s16 unk70; + /* 0x0072 */ s16 unk72; + /* 0x0074 */ s16 unk74; + /* 0x0076 */ u8 pad76[0x8]; + /* 0x007e */ u16 unk7E; + /* 0x0080 */ u8 pad80[0x8]; + /* 0x0088 */ u16 unk88; + /* 0x008a */ u16 unk8A; + /* 0x008c */ u16 unk8C; + /* 0x008e */ u8 pad8E[0x1a]; + /* 0x00a8 */ u8 unkA8; + /* 0x00a9 */ u8 unkA9; + /* 0x00aa */ u16 unkAA; + /* 0x00ac */ u16 unkAC; + /* 0x00ae */ u16 unkAE; + /* 0x00b0 */ s32 unkB0; + /* 0x00b4 */ s32 unkB4; + /* 0x00b8 */ u16 unkB8; + /* 0x00ba */ u16 unkBA; + /* 0x00bc */ u8 unkBC; + /* 0x00bd */ s8 unkBD; + /* 0x00be */ u8 unkBE; + /* 0x00bf */ u8 unkBF; + /* 0x00c0 */ s32 unkC0; + /* 0x00c4 */ u8 padC4[0x8]; + /* 0x00cc */ u32 unkCC; + /* 0x00d0 */ s32 unkD0; + /* 0x00d4 */ u16 unkD4; + /* 0x00d6 */ s16 unkD6; + /* 0x00d8 */ u8 padD8[0x4]; + /* 0x00dc */ s8 unkDC; + /* 0x00dd */ u8 unkDD; + /* 0x00de */ s8 unkDE; + /* 0x00df */ u8 unkDF; + /* 0x00e0 */ s16 unkE0; + /* 0x00e2 */ s16 unkE2; + /* 0x00e4 */ s16 unkE4; + /* 0x00e6 */ u8 padE6[0xc]; + /* 0x00f2 */ s16 unkF2; + /* 0x00f4 */ s16 unkF4; + /* 0x00f6 */ s16 unkF6; + /* 0x00f8 */ u8 padF8[0x4]; + /* 0x00fc */ s16 unkFC; + /* 0x00fe */ u8 padFE[0x2]; + /* 0x0100 */ u16 unk100; + /* 0x0102 */ u16 unk102; + /* 0x0104 */ u16 unk104; + /* 0x0106 */ u8 pad106[0x12]; + /* 0x0118 */ s16 unk118; + /* 0x011a */ s16 unk11A; + /* 0x011c */ s16 unk11C; + /* 0x011e */ u8 pad11E[0x2]; + /* 0x0120 */ s16 unk120; + /* 0x0122 */ s16 unk122; + /* 0x0124 */ s16 unk124; + /* 0x0126 */ u8 pad126[0x2]; + /* 0x0128 */ s16 unk128; + /* 0x012a */ s16 unk12A; + /* 0x012c */ s16 unk12C; + /* 0x012e */ s16 unk12E; + /* 0x0130 */ u16 unk130; + /* 0x0132 */ s16 unk132; + /* 0x0134 */ u8 pad134[0x2]; + /* 0x0136 */ u16 unk136; + /* 0x0138 */ s16 unk138; + /* 0x013a */ s16 unk13A; + /* 0x013c */ s16 unk13C; + /* 0x013e */ s16 unk13E; + /* 0x0140 */ u8 pad140[0x1a]; + /* 0x015a */ u16 unk15A; + /* 0x015c */ u8 pad15C[0x10]; + /* 0x016c */ u16 unk16C; + /* 0x016e */ u16 unk16E; + /* 0x0170 */ s32 unk170; + /* 0x0174 */ s32 unk174; + /* 0x0178 */ s32 unk178; + /* 0x017c */ u8 pad17C[0x8]; + /* 0x0184 */ s32 unk184; + /* 0x0188 */ s16 unk188; + /* 0x018a */ s16 unk18A; + /* 0x018c */ s32 unk18C; + /* 0x0190 */ u8 pad190[0x4]; + /* 0x0194 */ u16 unk194; + /* 0x0196 */ u8 unk196; + /* 0x0197 */ u8 unk197; + /* 0x0198 */ s32 unk198; + /* 0x019c */ u8 pad19C[0x4]; + /* 0x01a0 */ u8 unk1A0; + /* 0x01a1 */ u8 pad1A1[0x9]; + /* 0x01aa */ u8 unk1AA; + /* 0x01ab */ u8 unk1AB; + /* 0x01ac */ s32 unk1AC; + /* 0x01b0 */ s32 unk1B0; + /* 0x01b4 */ s32 unk1B4; + /* 0x01b8 */ u16 unk1B8; + /* 0x01ba */ u16 unk1BA; + /* 0x01bc */ u8 pad1BC[0x4]; + /* 0x01c0 */ u8 unk1C0; + /* 0x01c1 */ u8 pad1C1[0x5]; + /* 0x01c6 */ u8 unk1C6; + /* 0x01c7 */ s8 unk1C7; + /* 0x01c8 */ u16 unk1C8; + /* 0x01ca */ s16 unk1CA; + /* 0x01cc */ s16 unk1CC; + /* 0x01ce */ s16 unk1CE; + /* 0x01d0 */ s16 unk1D0; + /* 0x01d2 */ u8 pad1D2[0x2]; + /* 0x01d4 */ s32 unk1D4; + /* 0x01d8 */ s32 unk1D8; + /* 0x01dc */ u8 pad1DC[0x18]; + /* 0x01f4 */ u32 unk1F4; + /* 0x01f8 */ s32 unk1F8; + /* 0x01fc */ s32 unk1FC; + /* 0x0200 */ s32 unk200; + /* 0x0204 */ s32 unk204; + /* 0x0208 */ s32 unk208; + /* 0x020c */ s16 unk20C; + /* 0x020e */ s16 unk20E; + /* 0x0210 */ s16 unk210; + /* 0x0212 */ s16 unk212; + /* 0x0214 */ u8 unk214; + /* 0x0215 */ s8 unk215; + /* 0x0216 */ u8 unk216; + /* 0x0217 */ u8 pad217[0x1]; + /* 0x0218 */ u8 unk218; + /* 0x0219 */ u8 pad219[0x3]; + /* 0x021c */ s32 unk21C; + /* 0x0220 */ u8 pad220[0x1]; + /* 0x0221 */ s8 unk221; + /* 0x0222 */ u8 pad222[0x1]; + /* 0x0223 */ u8 unk223; + /* 0x0224 */ u8 pad224[0x10]; + /* 0x0234 */ s32 unk234; + /* 0x0238 */ s32 unk238; + /* 0x023c */ s32 unk23C; + /* 0x0240 */ s32 unk240; + /* 0x0244 */ u16 unk244; + /* 0x0246 */ u16 unk246; + /* 0x0248 */ u16 unk248; + /* 0x024a */ s16 unk24A; +} Unkstruct_80126B58; /* size = 0x24c */ +/* head_types:end */ + +#endif /* STRUCT_TYPES_H */ diff --git a/phase-ends/current/logs/T3.c1.md b/phase-ends/current/logs/T3.c1.md new file mode 100644 index 0000000000..62043e5127 --- /dev/null +++ b/phase-ends/current/logs/T3.c1.md @@ -0,0 +1,28 @@ +# T3.c1 — head_types.py, include/struct_types.h, decisions record + +## Changed +- `tools/head_types.py` (new): max-site split resolver (candidates = layout ∪ conflict dicts; drop w∉{1,2,4,8}, sign∉{s,u,p}, `off%min(w,4)`, and 8-byte off%8 (render_struct refuses those), `off+w>size`; weighted interval scheduling DP, ties prefer earlier in sort (off, -w, s@0x..)`. +- Extra lines `dropped (width|sign|size): ...` when nonzero. + +## Verified +- `head_types.py --selftest` → OK +- generate twice → second run "unchanged" for both files +- `restruct.py --audit-types` → 1181 audited; 26 violations, 0 in include/struct_types.h +- `type_census.py --selftest` → 24/24 OK; `restruct.py --selftest` → 54/54 OK +- R22 clean extract+check → `extract-all: 217 extracted, 0 failed of 217`, `check-all: 218 passed, 0 failed of 218` + +## For the expert +- First R22 failed 218/218: the brief's header comment contained a nested `/* 0xNN */`, closing the C comment early (cc1 parse error). Reworded to "0xNN offset comments". +- DP is pure max-Σn: e.g. 800B5CB8 @0x004 picks 2s n=61 (+ neighbour at 0x006) over 4s n=1376 — the split beats the word by sites; flagged union-candidate. Expert may want to review such 4-vs-2+2 splits. diff --git a/tools/head_types.py b/tools/head_types.py new file mode 100644 index 0000000000..d859502436 --- /dev/null +++ b/tools/head_types.py @@ -0,0 +1,233 @@ +#!/usr/bin/env python3 +"""head_types.py — the struct map's head types resolved to one canonical flat layout each (P38 T3). + +For each --type, read the record from the struct map (.run/P37/census/struct_map.json), resolve its layout by MAX-SITE SPLIT +(candidates = layout entries ∪ conflict-dict entries; drop bad widths, misaligned, out-of-size; pick the non-overlapping subset +maximising the summed site count by weighted interval scheduling), render the typedef with struct_layout.render_struct, and +write the per-offset decision record. --header / --decisions rewrite only the text between the marker lines; a missing file is +created with its hand-written skeleton. Flat structs only (the rewriter spells flat members; no unions, no nesting). + + .venv/bin/python tools/head_types.py [--type NAME ...] [--map PATH] [--header PATH] [--decisions PATH] [--selftest] +""" +import argparse +import json +import pathlib +import sys + +sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent)) +import struct_layout as sl # noqa: E402 (render_struct, TYPE_OF — the one writer) + +REPO = pathlib.Path(__file__).resolve().parent.parent +DEFAULT_TYPES = ("Unkstruct_800B5CB8", "Unkstruct_80126B58") +SIGN_ORDER = {"s": 0, "u": 1, "p": 2} +C_BEGIN, C_END = "/* head_types:begin */", "/* head_types:end */" +MD_BEGIN, MD_END = "", "" + +C_SKELETON = f"""/* include/struct_types.h — the struct map's canonical layouts (P38 T3). One definition per type, visible to every space through common.h. Generated block below is written by tools/head_types.py from .run/P37/census/struct_map.json; hand-written text outside the markers survives regeneration. Naming law G62: Unkstruct_, unkHEX fields, 0xNN offset comments. */ +#ifndef STRUCT_TYPES_H +#define STRUCT_TYPES_H + +{C_BEGIN} +{C_END} + +#endif /* STRUCT_TYPES_H */ +""" + +MD_SKELETON = f"""# Struct-map decisions — the head types (P38 T3) + +## Canonical file set + +TODO(T3 expert) + +## Resolution rule + +TODO(T3 expert) + +## Unkstruct_80078E00 (the player block) + +TODO(T3 expert) + +## Per-offset records + +Written by `tools/head_types.py` (max-site split). Losers' sites keep their casts; the byte oracle judges them later. +Summary fields: `overlaps` = candidates dropped because a heavier non-overlapping set won; `misaligned` = candidates dropped +for `off % min(w,4) != 0` (or an 8-byte field off 8-alignment); `union_candidates` = offsets whose resolution is +`union-candidate (deferred)`. + +{MD_BEGIN} +{MD_END} +""" + + +def candidates(rec): + """(size, kept [(off, w, sign, n)], dropped {reason: [(off, w, sign, n)]}) — the filtered candidate set C.""" + size = rec.get("size_hint") or 0 + size = (size + 3) // 4 * 4 + raw = {} + for (off, w, sg, n) in rec.get("layout") or []: + raw[(off, w, sg)] = max(raw.get((off, w, sg), 0), n) + for c in rec.get("conflicts") or []: + off, d = c[0], c[1] + if not isinstance(d, dict): + continue + for k, n in d.items(): + w, sg = int(k[:-1]), k[-1] + raw[(off, w, sg)] = max(raw.get((off, w, sg), 0), n) + kept, dropped = [], {"misaligned": [], "width": [], "size": [], "sign": []} + for (off, w, sg), n in raw.items(): + c = (off, w, sg, n) + if w not in (1, 2, 4, 8): + dropped["width"].append(c) + elif sg not in SIGN_ORDER: + dropped["sign"].append(c) + elif off % min(w, 4) or (w == 8 and off % 8): # render_struct needs 8-byte fields 8-aligned + dropped["misaligned"].append(c) + elif size and off + w > size: + dropped["size"].append(c) + elif n > 0: + kept.append(c) + kept.sort(key=lambda c: (c[0], -c[1], SIGN_ORDER[c[2]])) + return size, kept, dropped + + +def max_split(cands): + """Weighted interval scheduling over cands (sorted by off, -w, sign): the non-overlapping subset maximising Σn. + Ties prefer taking the earlier candidate in the sort order (lower off, then wider, then s= best[i + 1]: + best[i], take[i] = t, True + else: + best[i] = best[i + 1] + out, i = [], 0 + while i < m: + if take[i]: + out.append(cands[i]) + i = nxt[i] + else: + i += 1 + return out + + +def fmt(c): + return f"{c[1]}{c[2]}" + + +def resolve(rec): + """-> (typedef text, summary line, [decision lines]).""" + name = rec["name"] + size, cands, dropped = candidates(rec) + win = max_split(cands) + wset = set(win) + losers = [c for c in cands if c not in wset] + by_winner = {w: [] for w in win} + for c in losers: + for w in win: + if c[0] < w[0] + w[1] and w[0] < c[0] + c[1]: + by_winner[w].append(c) + break + conf_offs = {c[0] for c in rec.get("conflicts") or [] if isinstance(c[1], dict)} + win_at = {w[0]: w for w in win} + lines, n_union = [], 0 + for off in sorted(set(conf_offs) | {w[0] for w in win if by_winner[w]}): + w = win_at.get(off) + if w is None: + cov = next((x for x in win if x[0] < off < x[0] + x[1]), None) + where = f"inside {fmt(cov)}@0x{cov[0]:03X}" if cov else "no field" + lines.append(f"0x{off:03X}: chosen none ({where}); losers listed there; resolution: split") + continue + ls = by_winner[w] + union = any(l[3] >= 50 and l[3] >= 0.2 * w[3] for l in ls) + n_union += union + lt = ", ".join(f"{fmt(l)} n={l[3]}@0x{l[0]:03X}" for l in ls) or "none" + res = "union-candidate (deferred: rewriter spells flat members only, restruct.py member_spelling) -> split" if union else "split" + lines.append(f"0x{off:03X}: chosen {fmt(w)} n={w[3]}; losers: {lt}; resolution: {res}") + for why in ("width", "sign", "size"): + if dropped[why]: + lines.append(f"dropped ({why}): " + ", ".join(f"{fmt(c)} n={c[3]}@0x{c[0]:03X}" for c in sorted(dropped[why]))) + entries = [] + for (off, w, sg, n) in win: + t = "void *" if sg == "p" else sl.TYPE_OF[(w, sg)] + entries.append((off, t, f"unk{off:X}", None, None)) + end = max((o + w for (o, w, _, _) in win), default=0) + sz = max(size, end) + al = 8 if any(w == 8 for (_, w, _, _) in win) else 4 + sz = (sz + al - 1) // al * al + comment = (f"P37 struct map: {rec['sites']} sites in {rec['bodies']} bodies; evidence {rec.get('evidence')}" + + (f"; at {','.join(rec.get('globals_at', [])[:3])}" if rec.get("globals_at") else "") + + "; resolved by tools/head_types.py (max-site split); decisions: docs/struct-map-decisions.md") + txt = sl.render_struct(name, entries, size=sz or None, comment=comment) + summary = (f"fields={len(win)} kept_sites={sum(c[3] for c in win)} dropped_sites={sum(c[3] for c in losers)} " + f"conflict_offsets={len(rec.get('conflicts') or [])} overlaps={len(losers)} misaligned={len(dropped['misaligned'])} " + f"union_candidates={n_union}") + return txt, summary, lines + + +def splice(path, skeleton, begin, end, body): + """Replace the text between the marker lines of path (created from skeleton when absent). True when the file changed.""" + p = REPO / path if not pathlib.Path(path).is_absolute() else pathlib.Path(path) + old = p.read_text() if p.exists() else skeleton + i, j = old.find(begin), old.find(end) + if i < 0 or j < i: + sys.exit(f"head_types: {path} lacks the markers {begin} … {end}") + new = old[:i + len(begin)] + "\n" + body + old[j:] + if p.exists() and new == old: + return False + p.parent.mkdir(parents=True, exist_ok=True) + p.write_text(new) + return True + + +def selftest(): + rec = dict(name="Unkstruct_TEST", sites=10, bodies=2, size_hint=10, evidence={}, globals_at=[], + layout=[[0, 4, "s", 5], [2, 2, "u", 4], [4, 2, "s", 3], [6, 2, "u", 3], [7, 1, "u", 9], [8, 4, "s", 2]], + conflicts=[[0, {"4s": 5, "2u": 3, "2s": 1}], [2, {"2u": 4, "1u": 1}], [3, {"2s": 6}], [6, {"4s": 7, "2u": 3}]]) + txt, summary, lines = resolve(rec) + # C after filters: 0:4s5 0:2u3 0:2s1 2:2u4 2:1u1 4:2s3 6:2u3 7:1u9 8:4s2 (3:2s and 6:4s misaligned; size 10 -> 12) + # best: 0:2u3 + 2:2u4 + 4:2s3 + 7:1u9 + 8:4s2 = 21 vs 0:4s5 + 4:2s3 + 7:1u9 + 8:4s2 = 19 + want = "fields=5 kept_sites=21 dropped_sites=10 conflict_offsets=4 overlaps=4 misaligned=2 union_candidates=0" + ok = summary == want and "/* 0x0007 */ u8 unk7;" in txt and "/* 0x0006 */ u8 pad6[0x1];" in txt and "size = 0xc" in txt + print("OK" if ok else f"FAIL\n{summary}\n{txt}\n" + "\n".join(lines)) + return 0 if ok else 1 + + +def main(): + ap = argparse.ArgumentParser(description=__doc__.split("\n\n")[0]) + ap.add_argument("--type", action="append", help="struct-map type name (repeatable; default the two head types)") + ap.add_argument("--map", default=".run/P37/census/struct_map.json") + ap.add_argument("--header", help="C header to rewrite between /* head_types:begin */ … /* head_types:end */") + ap.add_argument("--decisions", help="markdown to rewrite between … ") + ap.add_argument("--selftest", action="store_true") + a = ap.parse_args() + if a.selftest: + return selftest() + smap = json.loads((REPO / a.map).read_text()) + by = {t["name"]: t for t in smap["types"]} + chunks, md = [], [] + for n in a.type or DEFAULT_TYPES: + if n not in by: + sys.exit(f"head_types: {n} not in {a.map}") + txt, summary, lines = resolve(by[n]) + print(f"{n}: {summary}") + chunks.append(txt) + md.append(f"### {n}\n\n`{summary}`\n\n" + "".join(f"- `{l}`\n" for l in lines)) + if a.header: + ch = splice(a.header, C_SKELETON, C_BEGIN, C_END, "\n".join(chunks)) + print(f"head_types: {a.header} {'written' if ch else 'unchanged'}") + if a.decisions: + body = f"Source: `{a.map}` head `{smap.get('head', '?')}`.\n\n" + "\n".join(md) + ch = splice(a.decisions, MD_SKELETON, MD_BEGIN, MD_END, body) + print(f"head_types: {a.decisions} {'written' if ch else 'unchanged'}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/type_census.py b/tools/type_census.py index 62c2ff1f07..c217fd0b5b 100644 --- a/tools/type_census.py +++ b/tools/type_census.py @@ -53,7 +53,7 @@ import lever_census as lc # noqa: E402 (enumerate_files, header_includ REPO = pathlib.Path(__file__).resolve().parent.parent OUT_DIR_DEFAULT = ".run/P37/census" TOOL_STAMP = hashlib.sha1(pathlib.Path(__file__).read_bytes()).hexdigest()[:10] -CANON_HEADERS = ("src/shared/engine_types.h",) # the canonical type files (T3/T5 add src/shared/main/types.h, the slots') +CANON_HEADERS = ("src/shared/engine_types.h", "include/struct_types.h") # the canonical type files (T3 added include/struct_types.h; T5 may add more) # ---------------------------------------------------------------------------------------------------------------------- # types and widths (o32) — the layout engine lives in tools/struct_layout.py since T3 (shared with restruct.py and the writer,