The world moves; the footprints stay
The game world is bigger than an N64 can address with float precision, so the engine slides the world under the player, keeping the camera near the origin — gWorldX and gWorldZ record the current shift. Anything stamped into world space must be dragged along when the origin jumps. This function drags the footprints: up to 40 of them, each a little quad of four vertices, each remembering (fields unk78/unk7C) the world offset it was last adjusted to.
Everything in it is something you've matched before, at shipped-game density: %hi/%lo address builds hoisted to the top, a beqzl-guarded loop over 144-byte structs, float deltas computed from globals, and — the capstone fingerprint — a fully unrolled inner loop. The original C says for (k = 0; k < 4; k++); the assembly says the same thing four times with no branch, exactly as tier 3's constant-trip unrolling promised.
Each unrolled iteration subtracts a float from an s16 vertex coordinate. That conversion chain is the one genuinely new-feeling read, so here it is alone — sinkOne, which lowers a single vertex coordinate:
0: mtc1 a1, fa0 # the f32 arg (it followed a pointer arg)
4: nop
8: lh t6, 0(a0) # pt[0], an s16
c: mtc1 t6, ft0 # integer bits over to the FPU…
10: nop
14: cvt.s.w ft1, ft0 # …become a float
18: sub.s ft2, ft1, fa0 # pt[0] - d, in float land
1c: trunc.w.s ft3, ft2 # back to int, rounding toward zero
20: mfc1 t8, ft3
24: nop
28: sh t8, 0(a0) # stored as s16 again
2c: jr ra
30: nop
lh → mtc1/cvt.s.w → sub.s → trunc.w.s → mfc1 → sh: once per coordinate, and the target has eight such chains braided together by the scheduler, two coordinates (ob[0] and ob[2]) times four vertices. Don't try to read them in order — pick one sh, trace its value backwards, and trust that the C is just the loop.
Three details that decide the match:
- The homed argument. The
sw a0, 0(sp) on line one is -g3 homing an argument the function never reads. Declare it anyway, or the store vanishes.
- The loop bound. The
addiu building the end address carries the whole array's size in its %lo — the compiler walks a pointer from the array's start to its end. Your C just loops i to 40; IDO does the strength-reduction, as tier 3 showed.
- The two deltas are computed from the globals once per entry, stored back into
unk78/unk7C via add.s, and only then applied to vertices. Declare them as f32 locals set to 0 before the loop — the original did, and the compiler's layout depends on it.
The struct, with its display-list header and vertex array, is in the context. This is the last target of the course. Take it slow, label the listing, and write the function the way its author did — plainly.
Your task
Write func_802a808c to reproduce the target assembly.