T3.c1: head_types.py max-site split; struct_types.h + decisions record; canon header

This commit is contained in:
Drew T
2026-09-29 21:00:34 -06:00
parent 42a9cd7f5a
commit 5aaa251545
6 changed files with 683 additions and 1 deletions
+115
View File
@@ -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)`.
<!-- head_types:begin -->
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`
<!-- head_types:end -->
+1
View File
@@ -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 */
+305
View File
@@ -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_<addr>, 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 */
+28
View File
@@ -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<u<p)); renders via `struct_layout.render_struct`; splices between markers; `--selftest` one synthetic case.
- `include/struct_types.h` (generated): Unkstruct_800B5CB8 (size 0x10c), Unkstruct_80126B58.
- `docs/struct-map-decisions.md` (generated block + skeleton with TODO(T3 expert) sections).
- `include/common.h:64`: `#include "struct_types.h"` after gte_inline.h.
- `tools/type_census.py:56`: CANON_HEADERS += include/struct_types.h.
## Summary lines
- Unkstruct_800B5CB8: fields=101 kept_sites=75717 dropped_sites=25393 conflict_offsets=50 overlaps=167 misaligned=4 union_candidates=27
- Unkstruct_80126B58: fields=152 kept_sites=61599 dropped_sites=1713 conflict_offsets=50 overlaps=71 misaligned=5 union_candidates=4
## Definitions used (brief left open)
- `overlaps` = count of candidates in C\S; `conflict_offsets` = len(record.conflicts) (the map caps it at 50, incl. "overlap" entries).
- Losers are attributed to the first winner they overlap; a conflict offset with no winner gets `chosen none (inside <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.
+233
View File
@@ -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 = "<!-- head_types:begin -->", "<!-- head_types: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_<addr>, 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<u<p)."""
m = len(cands)
nxt = []
for i, (off, w, _, _) in enumerate(cands):
j = i + 1
while j < m and cands[j][0] < off + w:
j += 1
nxt.append(j)
best, take = [0] * (m + 1), [False] * m
for i in range(m - 1, -1, -1):
t = cands[i][3] + best[nxt[i]]
if t >= 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 <!-- head_types:begin --> … <!-- head_types:end -->")
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())
+1 -1
View File
@@ -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,