Skip to content

Decompile 42 functions in world.c and type the chunk/triangle records - #134

Merged
Xeeynamo merged 14 commits into
Xeeynamo:mainfrom
devcheckra1n:worldfunc3
Sep 8, 2026
Merged

Decompile 42 functions in world.c and type the chunk/triangle records#134
Xeeynamo merged 14 commits into
Xeeynamo:mainfrom
devcheckra1n:worldfunc3

Conversation

@devcheckra1n

Copy link
Copy Markdown
Contributor

42 functions in world.c, 144 down to 102.

two types came out of it. WorldTriangleRef {WorldChunkHeader* chunk;
WorldTriangle* tri; s8 unk8} lives at D_8010C83C and is what D_8010CA1C points
at, and WorldChunkNode {next; WorldChunkHeader* chunk} is the D_800E582C pool.
D_8010CA24 is a WorldChunkHeader*[20] with D_8010CA74 as the bump pointer over
it, so the bound that shows up as &D_8010CA74 in the asm is just
&D_8010CA24[20].

some existing declarations were wrong rather than missing:

  • WorldZolomSegment.x and .z are u16. retail reads them with lhu and never
    sign-extends. they had only ever been written before, so nothing caught it.
  • func_800A19FC returns s32, not void. its only caller ignored the result.
  • D_80109D3C is a WorldChunkHeader*; D_800E5810 and D_800E580C are
    WorldListNode*.

three things decided most of these, in case it saves anyone time:

  • an outer if plus an inner while on the same condition beats a do/while. gcc
    drops the redundant guard, where an explicit do/while gets rotated and costs
    a j and a duplicated load.
  • assigning a local inside the if rather than at its declaration keeps the
    global's address inside the branch, which is where retail puts it.
  • p->r0 = p->r1 = c->r loads once and stores twice. as two statements it
    reloads, since the first store may alias.

what didn't come out, left as INCLUDE_ASM:

  • func_800A5AD8 is one word short on a load-delay nop, and it compares
    chunk->x and chunk->z as one 32-bit word. that needs a union on
    WorldChunkHeader or it's a fake match, so it's out either way.
  • func_800A4138 and func_800B58F8 both have gcc hoisting the 2 of
    GetGraphType() == 2 into an extra callee-saved register where retail
    materialises it inline.
  • func_800A12AC and func_800AE4B8 are the right length with the right
    instructions and differ only in prologue order.
  • func_800ADB30 wants D_8010AE24..30 as four independent symbols, which the
    array typing can't produce.

func_800B6348, func_800B5DD8 and func_800B85D4 need structs that their
still-asm callers build, so they are left alone rather than guessed at.

@Xeeynamo Xeeynamo left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most changes are required based on the new style guide:
https://github.com/Xeeynamo/ff7-decomp/blob/main/STYLE.md

Comment thread src/world/world.c Outdated
s32 col;
s16 idx;

if (D_800E5814 == 0 && D_800E5810 != NULL) {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (D_800E5814 == 0 && D_800E5810 != NULL) {
if (D_800E5814 == 0 && D_800E5810 != NULL) {
return;
}

early return?

Comment thread src/world/world.c Outdated
s16 idx;

if (D_800E5814 == 0 && D_800E5810 != NULL) {
if (TryAcquireLoadSlot(0) != 0) {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

early return here too?

Comment thread src/world/world.c Outdated
Comment on lines +689 to +696
if (c != NULL) {
do {
if ((((c->z >> 2) * 9) + (c->x >> 2)) == arg0->unk4) {
func_800A5AD8(c);
}
c = c->next;
} while (c != NULL);
}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (c != NULL) {
do {
if ((((c->z >> 2) * 9) + (c->x >> 2)) == arg0->unk4) {
func_800A5AD8(c);
}
c = c->next;
} while (c != NULL);
}
while (c != NULL) {
if ((((c->z >> 2) * 9) + (c->x >> 2)) == arg0->unk4) {
func_800A5AD8(c);
}
c = c->next;
}

Comment thread src/world/world.c Outdated
Comment on lines +718 to +719
if (chunk != NULL) {
do {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (chunk != NULL) {
do {
while (chunk != NULL) {

this is also a while loop

Comment thread src/world/world.c Outdated
WorldChunkHeader* chunk;

chunk = D_80109D3C;
if (chunk != NULL) {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (chunk != NULL) {

isn't this if unnecessary for the match?

Comment thread src/world/world.c Outdated
WorldChunkHeader* chunk;
SVECTOR* verts;

if (arg1 != NULL) {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

early return

Comment thread src/world/world.c Outdated
Comment on lines +3188 to +3189
i++;
} while (i < 0x2B);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for loop

Comment thread src/world/world.c Outdated

INCLUDE_ASM("asm/us/world/nonmatchings/world", func_800B6C84);
void StartStreamRead(void) {
if (D_80115A60 == 0) {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

early return

Comment thread src/world/world.h Outdated
Comment on lines +414 to +441
extern u8 D_8010CB24[];
extern s32 D_8010D930;
extern u8* D_8010D9A4;
extern s32 D_8010D9A8;
extern s32 D_8010D9AC;
extern s32 D_8010D9B0;
extern s32 D_8010D9B4;
extern s32 D_80115A44;
extern s32 D_80115A4C;
extern s32 D_80115A54;
extern u8 D_80115A6C[];
extern u8* D_800C80BC;
extern u32 D_8014A608;
extern s32 D_8014A610;
extern s32 D_800C74C4[][2];
extern s32 D_80115A48;
extern s32 D_80115A5C;
void func_800B6AEC(void);
extern u8 D_800C6940;
extern u8 D_800C6A10[];
extern MATRIX D_800C6808;
extern MATRIX D_800C6828;
extern MATRIX D_800C6848;
extern s32 D_8010CB20;
extern u8 D_800C6748[];
extern s32 D_8010AD50;
extern u8 D_8010D9BA[];
extern u16 D_8009D2A6;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are not sorted correctly

Comment thread src/world/world.h Outdated
extern s32 D_800C74C4[][2];
extern s32 D_80115A48;
extern s32 D_80115A5C;
void func_800B6AEC(void);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

intruder

@devcheckra1n

Copy link
Copy Markdown
Contributor Author

all applied, pushed as 52473e2, 15 overlays match.

the D_8010B434 one was a good catch. 0x8010B47C - 0x8010B434 = 0x48, so exactly two 0x24 entries, DR_MODE at 0x0 and 0x18 unidentified after it. matched first try with &D_8010B434[i].mode at both call sites.

two things that would not take the rewrite as-is:

the triangle flag loop at 2848 is a plain for over WorldTriangle* now, so the walking pointer is gone. the flag itself has to stay a byte write though. retail does lbu / andi 0xbf / sb at +11, and textureAndLocationAndFlags being a u16 gives lhu / andi 0xbfff / sh instead. splitting 0xA into two bytes would fix it but it cuts through the 9 bit texture id, so i left the field alone and wrote the flag as a byte into it. happy to change the struct instead if you would rather have that.

the range condition at 2881 is half done. the outer test reads pos.vx >= 0x36000 && pos.vx <= 0x3FFFF and matches fine. the inner one on pos.vz costs a register in every readable form i tried; the subtraction lands in a0 instead of v0 and the following sltu follows it. so that one still has the unsigned subtract.

Three list initialisers: func_800A59A0 and func_800A60D8 thread the 0x40
WorldListNode entries at D_800E582C and the 0x20 WorldChunkHeader entries at
D_80109A38, and func_800B392C threads the 32 0x74-byte records at D_8010CB24
backwards from its last element. func_800A0BE4 is the bump allocator over the
buffer D_800D05DC points at, taking the panic path when the 0x20800-byte arena
would overflow. func_800A7EA4, func_800B5274 and func_800B667C are subsystem
resets.

The rest: func_800A5B88 walks the loaded chunks for the one whose 9-column grid
index matches, func_800AD928 reports whether any actor still has a script
running, func_800AD970 packs an actor's position, type and direction into a
two-word display record, func_800AF110 picks the light and colour matrices for
the current mode, func_800B65E0 drives the AKAO fade commands, func_800B6C84
and func_800B7620 handle the streamed read for slot 2 and report its timing,
and func_800B7AC0 copies a name out of the table func_800A40F0 indexes.

D_8009A000, D_800E582C and D_80109A38 gain real types; the two ascending
free-list loops derive their &array[1] base from the array itself, so the
symbols splat named there need no declaration. func_800B6AEC, func_800B6DCC and
func_800BBA34 gain prototypes because their addresses are taken.
No func_ADDR left in the file's decompiled half. Most are clear from the
code -- the fog ranges, the two bounded layer scrolls, the AKAO track and
ambient calls, the stream state and its read. UpdateWorldMode,
ResetEffectState and GetSavedParams describe what the code does rather
than claim a purpose I could not pin down.
…order

Rewrite the hand-expanded do/while loops as plain for and while, invert
the guard conditions into early returns, drop the redundant chunk-search
guard, give D_8010B434 a real struct so the draw-mode setup indexes it,
and put the added externs in address order with the stray prototypes
back in the prototype block.
Xeeynamo#142 named 13 of the same functions this branch did, and named 27 more
that it calls; take upstream's names for all of them rather than renaming
merged work. Xeeynamo#141 renamed DS_read to SysCdromStartLoadLzs.
@devcheckra1n

devcheckra1n commented Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

@Xeeynamo ready to merge

… from parkedfuncs

Three of the four functions parkedfuncs had beyond what PR Xeeynamo#134 shipped:
InitSpritePrims needed plain array indexing over a hand-rolled byte
offset, the skybox quad setup needed explicit address bindings for two
new CVECTOR globals nothing had referenced before, and the shadow quad
setup matched as-is. The fourth, WmLoadTxzFile (parkedfuncs called it
ReadFileRetry), was a bad parked claim -- retail branches on useDsRead
before calling a specifically-named SysCdromStartLoadLzs, not a
function-pointer dispatch between two generic readers -- so it stays
INCLUDE_ASM.
Comment thread src/world/world.c Outdated
Comment thread src/world/world.c Outdated
Comment thread src/world/world.c Outdated
Comment thread src/world/world.c Outdated
Comment thread src/world/world.c Outdated
Comment thread src/world/world.c Outdated
a = &D_800C6768;
b = &D_800C676C;
do {
p = (POLY_G4*)((i * 0x24) + (s32)D_800C6770);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
p = (POLY_G4*)((i * 0x24) + (s32)D_800C6770);
p = &D_800C6770[i];

with the real D_800C6770 type, the code can be simplified

Comment thread src/world/world.c Outdated
Comment on lines +2525 to +2534
if (WmGetWmId() == mode) {
r = 0x20;
g = 0x20;
b = 0x30;
} else {
r = 0x40;
g = 0x40;
b = 0x40;
}
SetBackColor(r, g, b);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (WmGetWmId() == mode) {
r = 0x20;
g = 0x20;
b = 0x30;
} else {
r = 0x40;
g = 0x40;
b = 0x40;
}
SetBackColor(r, g, b);
if (WmGetWmId() == mode) {
SetBackColor(0x30, 0x30, 0x30);
} else {
SetBackColor(0x40, 0x40, 0x40);
}

can be simplified, which also allows the removal of s32 r, g, b

Comment thread src/world/world.c Outdated
s32 inside;
s32 zoff;

if (D_8010CA8C == 0 || D_8010CA8C == 2 || D_8010CA8C == 3) {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread src/world/world.c Outdated
Comment on lines +3027 to +3028
void WmCreateShadowPacket(u8* prims, RECT* rect) {
POLY_FT4* p;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
void WmCreateShadowPacket(u8* prims, RECT* rect) {
POLY_FT4* p;
void WmCreateShadowPacket(POLY_FT4* prims, RECT* rect) {

can you change the type and get rid of POLY_FT4* p;?

Comment thread src/world/world.c Outdated
Comment on lines +3120 to +3134
if (D_8010CB20 < arg0) {
D_8009A000[0] = 0x20;
D_8010CB20 = arg0;
D_8009A004[0] = 0x40;
D_8009A008[0] = arg0;
} else if (arg0 == -D_8010CB20) {
D_8010CB20 = 0;
D_8009A000[0] = 0xF1;
SystemAkaoExecute();
D_8009A000[0] = 0xBC;
D_8009A004[0] = 0;
} else {
return;
}
SystemAkaoExecute();

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (D_8010CB20 < arg0) {
D_8009A000[0] = 0x20;
D_8010CB20 = arg0;
D_8009A004[0] = 0x40;
D_8009A008[0] = arg0;
} else if (arg0 == -D_8010CB20) {
D_8010CB20 = 0;
D_8009A000[0] = 0xF1;
SystemAkaoExecute();
D_8009A000[0] = 0xBC;
D_8009A004[0] = 0;
} else {
return;
}
SystemAkaoExecute();
if (D_8010CB20 < arg0) {
D_8009A000[0] = 0x20;
D_8010CB20 = arg0;
D_8009A004[0] = 0x40;
D_8009A008[0] = arg0;
SystemAkaoExecute();
} else if (arg0 == -D_8010CB20) {
D_8010CB20 = 0;
D_8009A000[0] = 0xF1;
SystemAkaoExecute();
D_8009A000[0] = 0xBC;
D_8009A004[0] = 0;
SystemAkaoExecute();
}

did you try this?

InitSpritePrims and the walk-to-tail loops become plain for/while, the
tpage vsync window check drops the unsigned-subtract trick for a
readable range, and func_800AA640 drops a redundant cast. Kept the
(s16) cast on ExpireChunks' age compare; removing it (as asked, with a
question mark) breaks the match, verified by rebuild.
@devcheckra1n

Copy link
Copy Markdown
Contributor Author

applied the rest, pushed as a248107.

InitSpritePrims and the walk to tail loops (AllocRegionNode, UpdateRegionLoad, WmGetElementWithBlockIdAndSetItFirst, IsRegionLoading, ExpireChunks, AllocChunk, WmAbortMapLoading) are plain for/while now, dropped the redundant if (x != NULL) { while (x != NULL) guards. the fade overlay vsync check lost the unsigned subtract trick for a readable range test. func_800AA640 dropped a redundant cast.

kept the (s16) cast on ExpireChunks' age compare though, tried removing it and it breaks the match on rebuild. figured you meant it as a question rather than a firm ask given the wording.

@Xeeynamo Xeeynamo left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comments regarding D_800C6770 have not been addressed yet.

@devcheckra1n

Copy link
Copy Markdown
Contributor Author

D_800C6770 typed as POLY_G4, both call sites simplified. also went back through the rest of the review and found four more that hadn't actually landed: WmRestoreEntityPosAndDirFromSavemap and SetCurrentTriangle are early returns now, WmSetGteColourSettings drops r/g/b for direct SetBackColor calls, WmCreateShadowPacket takes POLY_FT4* directly, ToggleAmbientSound duplicates the SystemAkaoExecute call into each branch instead of a shared tail call. pushed as c1ffd83 and 2ebfc7d, 17 overlays match.

@Xeeynamo
Xeeynamo merged commit cc83524 into Xeeynamo:main Sep 8, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants