From d56e6bc970b106b225e56d6cca7c347f25d4e9bb Mon Sep 17 00:00:00 2001 From: nominalsan <135046937+nominalsan@users.noreply.github.com> Date: Tue, 10 Nov 2026 13:38:38 +0100 Subject: [PATCH] feat(pal,us2): port four functions, mark three original assembly, retain one near miss C ports of matched USA v1.01 functions, each its own -g8.c unit with its original's profile: - func_US2_002D3138: func_002D2188 with text id 0x2BE5 for 0x2D4F. - func_US2_00349C00: func_00348EC0 with the menu fields 0x70 further in and 88-byte label slots. - func_US2_L02_00400E04: func_PAL_L02_00403FF0 with v2.00's data and text ids 0x2DDB, 0x2DDC, 0x2DF8, 0x2DF4, 0x2DDD. - func_PAL_L08_003DF82C (levels 8 and 19): func_L08_003DB518 with the fade step scaled by 1.2f. Marked ASM_FUNC under AGENTS.md's second criterion (needs a maintainer's agreement): func_PAL_002A0F68 is func_002A05A8 plus two mul.s by 1.2f, with the same 26 trapping add/addi/sub/dsub, $at as a working register and two cvt.w.s words the compiler never emits; func_PAL_L01_00305C50 and func_US2_L01_00305170 are func_0029D4D0 (qmtc2, 15 trapping ops, three alternate entries) plus an 84-byte tail with a fourth alternate entry, a trapping addi and two teq. Near miss: func_PAL_L09_0040FD68 (984/984 bytes, 0/1) differs only by a $v0/$v1 and $f3/$f4 swap in case 3. Instrumented local-alloc and the sched1 trace pin it to quantity priorities; the notes record the measured model and the next hypothesis, with a matching-blockers.md entry. Checks: - tools/check.py: func_US2_002D3138 28 of 28, func_US2_00349C00 28 of 28, func_US2_L02_00400E04 1 of 1, func_PAL_L08_003DF82C 2 of 2. - tools/build.py all --strict: 28 programs OK in each of USA v1.01, PAL and USA v2.00; each new C function is in every carrying placed.json. - python3 -m unittest discover -s tests: 540 tests OK (1 skipped); tests/check_repository.py: passed; tools/progress.py --check: passed. Report not regenerated. Co-Authored-By: Claude Opus 5.5 --- config/cflags.txt | 3 + docs/contributing/matching-blockers.md | 28 +++ nonmatching/README.md | 1 + nonmatching/levels/func_PAL_L09_0040FD68.c | 182 ++++++++++++++++++ src/pal/game/002A0F68.c | 5 +- src/pal/levels/shared/L01_00305C50.c | 5 +- src/pal/levels/shared/L08_003DF82C-g8.c | 105 ++++++++++ src/pal/levels/shared/L08_003DF82C.c | 2 - src/us2/game/002D3138-g8.c | 60 ++++++ src/us2/game/002D3138.c | 2 - src/us2/game/00349368-g8.c | 118 ++++++++++++ src/us2/game/00349368.c | 4 - .../levels/02_maktar_nebula/L02_00400E04-g8.c | 97 ++++++++++ .../levels/02_maktar_nebula/L02_00400E04.c | 2 - src/us2/levels/shared/L01_00305170.c | 5 +- 15 files changed, 606 insertions(+), 13 deletions(-) create mode 100644 nonmatching/levels/func_PAL_L09_0040FD68.c create mode 100644 src/pal/levels/shared/L08_003DF82C-g8.c create mode 100644 src/us2/game/002D3138-g8.c create mode 100644 src/us2/game/00349368-g8.c create mode 100644 src/us2/levels/02_maktar_nebula/L02_00400E04-g8.c diff --git a/config/cflags.txt b/config/cflags.txt index 4fcdd0baa..be46449ff 100644 --- a/config/cflags.txt +++ b/config/cflags.txt @@ -4282,3 +4282,6 @@ src/levels/02_maktar_nebula/L02_003FE8B8-g8.c -O2 -G8 -ffunction-sections -frac2 src/levels/02_maktar_nebula/L02_003FDDE8-g8.c -O2 -G8 -ffunction-sections -fno-gcse src/levels/11_joba/L11_00443940-g8.c -O2 -G8 -ffunction-sections -fno-gcse src/levels/shared/L13_0041AD00-g8.c -O2 -G8 -ffunction-sections -frac2-qcopy -frac2-qcopy-barrier -fno-sched-spec -Wa,--r5900-la-addend=sn +src/us2/game/002D3138-g8.c -O2 -G8 -fno-expensive-optimizations -ffunction-sections -fno-builtin +src/us2/levels/02_maktar_nebula/L02_00400E04-g8.c -O2 -G8 -ffunction-sections -frac2-gcse-unsplit-edges -frac2-keep-block-order +src/pal/levels/shared/L08_003DF82C-g8.c -O2 -G8 -ffunction-sections -malign-loops=2 diff --git a/docs/contributing/matching-blockers.md b/docs/contributing/matching-blockers.md index a211aeeaa..1d5ad8901 100644 --- a/docs/contributing/matching-blockers.md +++ b/docs/contributing/matching-blockers.md @@ -251,6 +251,34 @@ the 2.2f constant and the mode load, the last two tie, and the load is allocated out of the block gives retail's pair but moves the FPRs. When two integer registers swap in a block with few locals, count its quantities before trying spellings. +### A PAL constant that reorders local allocation + +**10 November 2026, retained near miss.** `func_PAL_L09_0040FD68` (PAL, level 9, default +`-O2 -G8 -ffunction-sections`) is the USA `func_L09_0040AC48` with 50 Hz constants, and is +984/984 bytes with one `$v0`/`$v1` and `$f3`/`$f4` swap in its tick test. The extra `2.0f` of +`(x * 300.0f + 2.0f) * 0.16666667f` is scheduled by sched1 before the counter's store. That pushes +the conversion after it, so `ticks` loses local-alloc priority to `ticks + 1`, and the converted +value ties `2.0f`, which is born first. Enumerating the block's 16,098 legal orders under +`floor_log2(refs)*refs/(death-birth)` leaves 2 that give retail, both with every constant set +before the conversion and the conversion before the store. About 55 forms (operand orders, +struct-member accesses, counter types and casts, post-increments, named constant locals, an +inline timer accessor, `/ 6.0f` under `-ffast-math`) and six profiles (`-fstrict-aliasing`, +`-fno-schedule-insns` among them) were measured; none moves sched1. The matched `func_PAL_L17_003CBDF4` timer test shows sched1 hoisting all three constants +when no store-to-load chain dominates; here both `lwc1` depend on the counter's `sh`. The `-dS` +trace shows the r5900 model issuing constant moves on the memory unit with `lhu`; at t = 1 the +store-to-load chain makes `lhu` outrank `300.0f`, which every retail-compatible order needs first, +and retail keeps that dependence. So retail's RTL must differ before local-alloc (reference +counts or a transient insn), not its schedule. Retained in +`nonmatching/levels/func_PAL_L09_0040FD68.c`. A private cc1 with a quantity dump in `block_alloc` (identical assembly with +the dump off) confirms it: no suggested registers; `ticks` 3000 against `ticks + 1` 5000, and +the conversion ties `2.0f` at 1250 and loses on quantity number. A second pass (round 7, about 80 block compiles +and 34 profiles) measured that one extra insn between the `lhu` and the conversion, tied to +`ticks`, gives retail's whole assignment: the post-increment's `andi` and a `long` round trip's +`dsll`/`dsra` both do, as real code. Retail fits such an insn that emits nothing, such as a move +or `extendsidi2` between equal registers; cse, combine and shared variables remove every copy tried. +**Next:** a matched function whose retail shows the same `lhu`/`addiu`/`sh`/`mtc1` idiom with a +wider or re-typed counter. + ### 16-bit counters read through a wider local **1 November 2026, local match.** `x++ > n` on a `u16` field keeps the old value in a HImode pseudo diff --git a/nonmatching/README.md b/nonmatching/README.md index 017243a99..cb4d8dfcd 100644 --- a/nonmatching/README.md +++ b/nonmatching/README.md @@ -272,3 +272,4 @@ the placement rules are in `tools/fcheck.py`'s docstring; its tests are | [`func_PAL_L04_0041B5C0`](levels/func_PAL_L04_0041B5C0.c) | 652 | 9 November 2026: PAL, now 652/652 bytes, 0/2 places, 29 real rows (lead follow-ups: kx set after the corner coordinates; x, then the first label coordinate, then y gives retail's interleave; left: y's shifts recomputed after the dead test's boundary, so no $v1 copy; entry load pair; closing x1/y1 registers). First pass 648/652. Menu box with two labels, same shape as USA func_L04_00416E38 (menu_coord idiom). Retail loads the 371.0f constant after the width call and schedules the D_001A73C0/gp-offset loads before the slt; mine hoists them differently. Needs -G8 (L04_0041A660-g8.c). | | [`func_PAL_L03_003CB058`](levels/func_PAL_L03_003CB058.c) | 656 | 9 November 2026: PAL port of func_L03_003C7018 (USA best C, own -Wa,--r5900-la-addend=sn unit): 656/656 bytes, PAL 0/1, 13 masked positions. PAL retail equals USA retail apart from addresses; same leftovers (draw 2 ori/f15, buffer addiu after the key copy, first sprintf rematerialises the buffer). Outer-scope buffer gives 11 but swaps the frame. | | [`func_PAL_L03_00426DE8`](levels/func_PAL_L03_00426DE8.c) | 412 | 9 November 2026: PAL port of func_L03_00421930 (USA flat C, own -fno-schedule-insns unit): 412/412 bytes, PAL 0/1, 12 masked positions, all the USA p s1 / base s3 register swap. PAL retail equals USA retail apart from addresses. | +| [`func_PAL_L09_0040FD68`](levels/func_PAL_L09_0040FD68.c) | 984 | 10 November 2026: PAL port of func_L09_0040AC48 (50 Hz constants, timer as (x * 300.0f + 2.0f) * 0.16666667f): 984/984 bytes, 0/1, only case 3's tick test swaps $v0/$v1 and $f3/$f4: local-alloc priority after sched1 (2 of 16,098 legal block orders give retail's assignment, all with the constants set first). About 55 compiles, then about 80 block compiles in round 7: retail's assignment is reproduced by an extra insn between the lhu and the conversion that is tied to ticks (andi or dsll/dsra in the measured forms), so retail fits one that emits no code; no lawful source for it found. Post-increment gives retail's registers plus an andi. Needs -G8. | diff --git a/nonmatching/levels/func_PAL_L09_0040FD68.c b/nonmatching/levels/func_PAL_L09_0040FD68.c new file mode 100644 index 000000000..24ffab52e --- /dev/null +++ b/nonmatching/levels/func_PAL_L09_0040FD68.c @@ -0,0 +1,182 @@ +/* 10 November 2026, round 7 (about 80 scratch compiles of case 3 in batches, 4 check.py runs): no + match; the C below is unchanged and still the best form (984/984 bytes, the same four register + rows). New model, measured: an extra GPR insn X between the lhu and the conversion + gives retail's whole assignment. With X the conversion is ready one cycle later, so sched1 + emits lhu, 300.0f, +1, 2.0f, sh, X, 1/6, cvt; X's result is tied to ticks (ticks dies in X), + so the quantity has 5 refs (7142 against 5000 for ticks + 1) and takes $v0, and the converted + ticks is born after 1/6 (1428 = 1428, 1/6 first) and beats 2.0f (1111): $f2, $f3, $f4, $f5 as + retail. Two forms show it, both with X as real code: the post-increment (X = andi 0xFFFF, also + through a 16-bit bit-field and through an inline accessor) and long wide = ticks; + (float)(int)wide (X = truncdisi2, dsll/dsra; the sign extension before it is folded into a + paradoxical subreg). Retail therefore fits an X that emits nothing: mips_move_1word prints no + code for a move or an extendsidi2 whose two registers are equal. No lawful source found for + one: cse replaces every in-block copy, combine folds a copy with a single use into + floatsisf2, a copy into a variable shared with another case (next, sound, speed, accel) makes + that variable global (no tie; $s2, $a1 or $f20 appear), and floatdisf2 needs double float. + Without effect (block identical to the C below): the limit, sum or converted ticks through + shared or new locals, compare reversed or negated (c.le.s), statement expression, comma form, + += 1, T = T + 1, an unsigned short local, inline helpers for the tick, the conversion, the + compare and the 50 Hz scale, (x * 60.0f) * 5.0f (adds a multiply), and 34 flag profiles + (-fno-gcse, -fno-regmove, -fno-rerun-cse-after-loop, -fno-sched-interblock, -fno-sched-spec, + -fno-schedule-insns2, -fno-cse-follow-jumps, -fno-cse-skip-blocks, -fno-thread-jumps, + -fforce-addr, -fno-force-mem, -ffloat-store, -fcaller-saves, -O3, -O1 with both schedulers, + -frac2-keep-block-order, -mno-split-addresses and others). Worse: the limit or the sum + computed before the store (the two lwc1 move to the top); (float)(unsigned int)ticks (bltz + fix-up path); old = ticks++ (a daddu copy); T++ after the read (loads first). A do {} while (0) + around the if doubles the references inside it (flow weights by loop depth) and gives ticks + $v0 and ticks + 1 $v1, but its loop note is a scheduling barrier (constants after the + conversion, $f5 for it), and around the whole case it changes nothing. Open point: whether + sched2 would put cvt back before 1/6 once X is gone (retail's final order) is not measured. + Next: find what real construct leaves a tied no-op move (or SI-to-DI extension) of the counter + at local-alloc, e.g. in a matched USA or PAL function whose retail code shows the same + lhu/addiu/sh/mtc1 idiom with a wider or re-typed counter; otherwise leave it. */ +/* 10 November 2026, round 6 (instrumented cc1): a private cc1 built from the same sources with a + quantity dump in local-alloc.c's block_alloc (control: identical assembly with and without the + dump) prints case 3's block exactly as modelled. No quantity has a suggested register. ticks + (r89, refs 3, birth 4, death 14) has priority 3000 against ticks + 1 (r159, 5000), so ticks + 1 + takes $v0. The converted ticks (r160, 14..30) ties 2.0f (r166, 10..26) at 1250 and loses on + quantity number, so 2.0f takes $f3. 1/6 (r168, 1666) takes $f2 and 300.0f (r164, 1111) $f5. + Retail needs ticks above ticks + 1 and the conversion above 2.0f. Measured probes, not + proposals (an invented test is not acceptable without retail evidence): a dead test on ticks is + gone before allocation (no change), and a later use of ticks makes it a global pseudo, so + ticks + 1 keeps $v0. Next: a source difference with retail evidence that changes these counts, + e.g. an inline or macro shape for the 50 Hz conversion that the PAL code shares elsewhere. */ +/* 10 November 2026, round 5 (5 compiles): matched PAL code that reads the same u16 frame counter + (src/pal/levels/18_damosel/L18_003FF850.c) uses an inline moby_timer() accessor and writes + the 50 Hz conversion as (x * 5.0f + 2.0f) / 6.0f in a -ffast-math unit. Here: the accessor + (ticks = moby_timer(obj)) is unchanged; moby_timer(obj) - 1 after the store is 992 bytes; + (x * 300.0f + 2.0f) / 6.0f is 980 bytes without -ffast-math and, with it, compiles to the + multiply form, so the swap stays. About 55 compiles in all. Next: an instrumented cc1 printing + the case-3 block's REG_N_REFS and qty priorities, to find what retail's source changes before + local-alloc. */ +/* 10 November 2026, round 4 (5 compiles plus the sched1 trace): casts and widths on the counter + ((unsigned short)(ticks + 1), + 1u, += 1, (float)(unsigned short)ticks, an s16 local) change + nothing (the s16 local adds an andi). The -dS trace shows why the schedule cannot move. The r5900 model + issues the const_double moves on the memory unit with lhu, sh and the loads, and at t = 1 it + picks lhu (300) over 300.0f (316), because only lhu's chain runs through the sh and the two + dependent lwc1. Both retail-compatible orders need 300.0f at t = 1, which rank_for_schedule's + priority rule excludes while that dependence exists, and retail keeps the loads after the + sh, so the dependence is there too. The round-3 "global pseudo" account would need real uses of + those values in other blocks, which retail does not have: treat it as unlikely. Conclusion: + retail's RTL differs before local-alloc (ticks with more references, or an insn that is gone by + the final code), not in its schedule. Next: find what in PAL's source adds a reference to + the counter, e.g. compare other PAL ports whose USA original reads a u16 timer field. */ +/* 10 November 2026, round 3 (2 compiles plus dumps): compared with the matched PAL timer test in + func_PAL_L17_003CBDF4 (src/pal/levels/17_smolg/L17_003CBDF4-tilt-g8.c). There sched1 hoists the + 300.0f, 2.0f and 1/6 constants to the start of their block, as the two retail-compatible orders + here need. Here the counter's sh is the critical path because both lwc1 depend on it, so the + store and its inputs take the early slots. A const-qualified pointer for the two timer fields does + not mark the loads unchanging (no change). A second account fits retail exactly: if ticks + 1, 300.0f and + 2.0f were global pseudos (live in more than this block, as when cse reuses a constant register + across an extended block), local-alloc would give ticks $v0, 1/6 $f2 and the converted ticks + $f3, and global-alloc then $v1, $f4 and $f5, which is retail's assignment. Next: look for the source shape + that makes those three pseudos non-local without adding instructions. */ +/* 10 November 2026, round 2 (about 30 more compiles): the swap is local-alloc priority, not source + order. tools/regalloc.py shows case 3's block after sched1 as lhu, 300.0f, +1, 2.0f, sh, cvt, + 1/6, the two lwc1 (which depend on the sh: obj and data may alias), add, mul, add, mul, c.lt. + With floor_log2(refs)*refs/(death-birth), ticks (3 refs, 5 insns) loses $v0 to ticks+1 (2 refs, + 2 insns), and the converted ticks ties 2.0f, which is born first and takes $f3. The USA original + schedules the conversion before the sh (no 2.0f there), so ticks wins, as in retail. Of the + 16,098 orders the block's dependencies allow, only 2 give retail's assignment under that + priority: 300.0f, lhu, 2.0f, +1, 1/6, cvt, sh, then the loads. Every constant is set before the + conversion, and the conversion comes before the store. Tried without effect: operand orders of the + compare and the 50 Hz expression, the counter and the timer fields as struct members (alias + classes), unsigned short/unsigned int counters, read-back and pre-increment forms, the + constants as named locals (cse folds them back), and the profiles -frac2-gcse-unsplit-edges, + -fno-expensive-optimizations, -fno-schedule-insns (988 bytes), -g, -fstrict-aliasing (case 2 + and the loads move). Post-increments give retail's registers only with an andi; the recorded + wider-local remedy ("16-bit counters read through a wider local") gives a daddu copy instead. + Next: an RTL difference that makes sched1 set the constants first, e.g. what retail's other + PAL timer conversions look like before sched1 in a matched function with the same idiom. */ +/* 10 November 2026: func_PAL_L09_0040FD68 (984 bytes, level 9), PAL. 984/984 bytes, 0 of 1 places: + only case 3's tick test differs, a register swap. Retail keeps the tick count in $v0 (its +1 in + $v1) and in $f3, with 2.0f in $f4 and 300.0f in $f5; this C gives $v1/$v0 and $f4/$f3. Tried + (10 compiles): the comparison reversed; (float)(count)++; the post-increment into ticks (exact + registers, but an extra andi 0xFFFF before the conversion); a pointer to the count; the + assignment inside the store; the limit or the converted ticks in a named local; the 50 Hz + conversion as an inline helper. Next: a form that reads the count once as retail does without + the extra zero-extension of the post-increment. Needs -G8 (its own L09_0040FD68-g8.c unit). */ +#include "common.h" + +/* func_PAL_L09_0040FD68, the PAL copy of func_L09_0040AC48 (src/levels/09_dobbo/L09_0040AC48-g8.c): + the same C at 50 Hz, where 0.000277777785f becomes 0.00040000002f, the 240-frame hold becomes + 200, the timer's 60.0f becomes the scaled (x * 300.0f + 2.0f) * 0.16666667f, and + func_L09_0040B0C8 becomes func_PAL_L09_00410200. */ +extern void func_002A7750(void *, int, int); +extern int func_002B3868(void *); +extern void func_L09_0040B008(char *); +extern void func_PAL_L09_00410200(char *); +extern int func_002E5CF8(int, int, void *); +extern void func_002E59F8(int); +extern void func_002AA548(float *, float *, float, float, float, float); +extern float D_L09_001B15A8 __attribute__((sda)); +extern float D_L09_001B15AC __attribute__((sda)); +extern char D_00188660[]; +typedef struct { float value[2]; float rate[2]; } DobboDoorMotion; +void func_PAL_L09_0040FD68(char *obj) { + char *data = *(char **)(obj+0x68); + int next, next4, sound, ticks; + float speed, accel, speed4, accel4; + char *entry; + switch (*(unsigned char *)(obj+0x20)) { + case 0: + *(int *)(obj+0x98) = 0; + *(unsigned short *)(obj+0x34) = (*(unsigned short *)(obj+0x34) | 0x2141) & 0xEFFF; + func_002A7750(obj,1,-1); + break; + case 1: + if (*(unsigned char *)(obj+0xBC)) func_002A7750(obj,2,-1); + else { + *(int *)(obj+0x98) = 0; + *(unsigned short *)(obj+0x34) = (*(unsigned short *)(obj+0x34) | 0x41) & 0xEFFF; + } + break; + case 2: + if (func_002B3868(obj)) { + *(unsigned short *)(obj+0x34) = (*(unsigned short *)(obj+0x34) & 0xFFBE) | 0x1000; + *(int *)(obj+0x98) = *(int *)(*(char **)(obj+0x24)+0x10); + func_L09_0040B008(obj); + *(int *)(data+0x1C) = func_002E5CF8(0,4,obj); + } + next = (*(int *)(data+0x14)+1)&1; + speed = D_L09_001B15A8 * 0.00040000002f; + accel = D_L09_001B15AC * 0.00040000002f; + if (((DobboDoorMotion *)data)->value[*(int *)(data+0x14)] < 1.0f) + func_002AA548(&((DobboDoorMotion *)data)->value[*(int *)(data+0x14)],(float *)(data+8)+*(int *)(data+0x14),1.0f,speed,accel,1.0f); + if (((DobboDoorMotion *)data)->value[*(int *)(data+0x14)] > 0.6f) + func_002AA548(&((DobboDoorMotion *)data)->value[next],(float *)(data+8)+next,1.0f,speed,accel,1.0f); + func_PAL_L09_00410200(obj); + if (((DobboDoorMotion *)data)->value[next] >= 1.0f) func_002A7750(obj,3,-1); + break; + case 3: + if (func_002B3868(obj)) *(int *)(data+0x18) = 200; + ticks = *(unsigned short *)(obj+0x96); + *(unsigned short *)(obj+0x96) = ticks + 1; + if ((float)ticks > ((*(float *)(data+0x20)+*(float *)(data+0x24))*300.0f + 2.0f) * 0.16666667f) + func_002A7750(obj,4,-1); + func_PAL_L09_00410200(obj); + break; + case 4: + next4 = (*(int *)(data+0x14)+1)&1; + speed4 = D_L09_001B15A8 * 0.00040000002f; + accel4 = D_L09_001B15AC * 0.00040000002f; + if (((DobboDoorMotion *)data)->value[next4] > 0.2f) + func_002AA548(&((DobboDoorMotion *)data)->value[next4],(float *)(data+8)+*(int *)(data+0x14),0.2f,speed4,accel4,1.0f); + if (((DobboDoorMotion *)data)->value[next4] < 0.6f) + func_002AA548(&((DobboDoorMotion *)data)->value[*(int *)(data+0x14)],(float *)(data+8)+next4,0.2f,speed4,accel4,1.0f); + func_PAL_L09_00410200(obj); + if (((DobboDoorMotion *)data)->value[*(int *)(data+0x14)] <= 0.2f) { + obj[0xBC] = 0; + *(int *)(obj+0x98) = 0; + *(unsigned short *)(obj+0x34) = (*(unsigned short *)(obj+0x34) | 0x41) & 0xEFFF; + func_002A7750(obj,1,-1); + sound = *(int *)(data+0x1C); + if (sound != -1) { + entry = D_00188660 + sound*0x70; + if (*(char **)(entry+0x88) == obj && *(unsigned char *)(entry+0x74)) func_002E59F8(sound); + } + *(int *)(data+0x1C) = -1; + } + break; + } +} diff --git a/src/pal/game/002A0F68.c b/src/pal/game/002A0F68.c index 77192cdce..0b5ec7a06 100644 --- a/src/pal/game/002A0F68.c +++ b/src/pal/game/002A0F68.c @@ -1,3 +1,6 @@ #include "include_asm.h" -INCLUDE_ASM("asm/pal/game", func_PAL_002A0F68); +/* Original assembly: the PAL copy of func_002A05A8 (src/game/002A0430.c), the same routine with + two mul.s by 1.2f (50 Hz) added. Like it: 26 trapping add/addi/sub/dsub, $at as a working + register, and two cvt.w.s words in an encoding the compiler never emits. */ +ASM_FUNC("asm/pal/game", func_PAL_002A0F68); diff --git a/src/pal/levels/shared/L01_00305C50.c b/src/pal/levels/shared/L01_00305C50.c index 8c6dbafbd..7de42ea44 100644 --- a/src/pal/levels/shared/L01_00305C50.c +++ b/src/pal/levels/shared/L01_00305C50.c @@ -1,3 +1,6 @@ #include "include_asm.h" -INCLUDE_ASM("asm/pal/levels", func_PAL_L01_00305C50); +/* Original assembly: the first 720 bytes are func_0029D4D0 (src/game/0029BCC8.c, original assembly: + a qmtc2 VU0 op, 15 trapping add/addi and three alternate entries), followed by an 84-byte tail + with a fourth alternate entry, a trapping addi and two teq traps. */ +ASM_FUNC("asm/pal/levels", func_PAL_L01_00305C50); diff --git a/src/pal/levels/shared/L08_003DF82C-g8.c b/src/pal/levels/shared/L08_003DF82C-g8.c new file mode 100644 index 000000000..d8a65b1e1 --- /dev/null +++ b/src/pal/levels/shared/L08_003DF82C-g8.c @@ -0,0 +1,105 @@ +#include "common.h" + +/* func_PAL_L08_003DF82C (levels 8 and 19), the PAL copy of func_L08_003DB518 + (src/levels/shared/L08_003DB518-g8.c): the same C, where the fade step is scaled by 1.2f (50 Hz). + + Fades a meter in, draws the two centred labels when the text table is loaded, then the + meter icon and its counter at the bottom of the screen. Built with -malign-loops=2 + (config/cflags.txt): the one-pass group starts at the join, and retail does not pad the + label after it. */ + +typedef struct __attribute__((packed)) { s64 word[2]; } Style; +typedef struct { s32 count; f32 fade; } Meter; + +extern u8 *D_L08_001B0488 __attribute__((sda)); +extern u8 *D_L08_001A904C MACRO_ADDR; +extern f32 D_L08_001B046C __attribute__((sda)); +extern f32 D_L08_001B0468 __attribute__((sda)); +extern f32 D_L08_001B0460 __attribute__((sda)); +extern s32 D_L08_001B0484 __attribute__((sda)); +extern s32 D_L08_001B047C __attribute__((sda)); +extern s32 D_L08_001B0480 __attribute__((sda)); +extern f32 D_L08_001B3244 MACRO_ADDR; +extern s32 D_L08_001B3240 MACRO_ADDR; +extern s32 D_001A7340 MACRO_ADDR; +extern s32 D_001A7344 MACRO_ADDR; +extern Style D_L08_001B0490; +extern char D_L08_001B04A0[]; + +extern void func_L00_0037B750(void), func_L08_00300090(void), func_L08_00300100(void); +extern void func_L08_003000C0(void); +extern s32 func_00336D50(void *, s32); +extern void func_00300A00(char *, s32, f32, f32, f32, f32, f32); +extern s32 func_L08_003DB4E8(void), func_0028E588(s32, s32), func_00283CF0(f32); +extern int func_0028EA58(s32, s32, s32, s32, s32, void *); +extern s32 func_00115DA8(char *, char *, ...); +extern u32 func_001157AC(char *); +extern s32 func_0027FAA8(s32, s32, u64, void *, s32); + +/* A screen y scaled for the display and rounded, four pixels lower in display mode 1. */ +static __inline__ s32 screen_y(f32 v, f32 scale, f32 half, s32 active) +{ + f32 scaled = v * scale + half; + if (D_L08_001B3240 == active) return (s32)(scaled + 4.0f); + return (s32)scaled; +} + +/* A size scaled for the display and rounded. The display-mode test has no effect on the + result, but it is real: it goes at flow2, after allocation (as func_L11_004456A0). */ +static __inline__ s32 hud_round(f32 value, f32 scale, f32 half, s32 active) +{ + f32 scaled = value * scale + half; + s32 result = (s32)scaled; + if (result != 0 && D_L08_001B3240 == active) scaled = 1.0f; + return result; +} + +s32 func_PAL_L08_003DF82C(void) +{ + Style style; + char text[16]; + Meter *state = *(Meter **)(D_L08_001B0488 + 0x68); + f32 t, fade, bottom, size, z, half, half2; + s32 r, texture, x, y, x2, y2, width, height, one, one2, entry; + char *label; + + func_L00_0037B750(); + fade = 1.0f; + size = 32.0f; + state->fade += D_L08_001B046C * 1.2f; + if (state->fade > fade) state->fade = fade; + if (D_L08_001A904C) { + entry = 0x8710; + func_L08_00300090(); + func_L08_00300100(); + z = 0.0f; + label = (char *)func_00336D50(D_L08_001A904C + entry, 0xAC); + func_00300A00(label, 0x60442D00, (f32)(D_001A7340 / 2), (f32)(D_001A7344 / 2), fade, fade, z); + label = (char *)func_00336D50(D_L08_001A904C + entry, 0xAD); + func_00300A00(label, 0x55F0C070, (f32)(D_001A7340 / 2), (f32)(D_001A7344 / 2), fade, fade, z); + func_L08_003000C0(); + } + do { + half = 0.5f; + t = (fade - state->fade) * size; + fade = t * half; + r = (s32)(D_L08_001B0468 * D_L08_001B3244 + half); + } while (0); + one = 1; + style = D_L08_001B0490; + bottom = (f32)(D_001A7344 - r); + texture = func_0028E588(0xEAA2, func_L08_003DB4E8()); + x = func_00283CF0(D_L08_001B0460 + fade); + t = D_L08_001B3244; + y = func_00283CF0(bottom + (f32)screen_y(fade - (f32)D_L08_001B0484, t, half, one)); + half2 = 0.5f; + one2 = 1; + width = func_00283CF0(size * state->fade); + height = hud_round((f32)func_00283CF0(size * state->fade), D_L08_001B3244, half2, one2); + func_0028EA58(texture, x, y, width, height, &style); + func_00115DA8(text, D_L08_001B04A0, state->count); + x2 = func_00283CF0(D_L08_001B0460 + (f32)D_L08_001B047C); + y2 = func_00283CF0(bottom) + screen_y((f32)D_L08_001B0480, D_L08_001B3244, half2, one2); + func_0027FAA8(x2, y2, 0x80F0F0F0UL, text, func_001157AC(text)); + return 0; +} diff --git a/src/pal/levels/shared/L08_003DF82C.c b/src/pal/levels/shared/L08_003DF82C.c index 056f02d59..e8c87da2a 100644 --- a/src/pal/levels/shared/L08_003DF82C.c +++ b/src/pal/levels/shared/L08_003DF82C.c @@ -1,3 +1 @@ #include "include_asm.h" - -INCLUDE_ASM("asm/pal/levels", func_PAL_L08_003DF82C); diff --git a/src/us2/game/002D3138-g8.c b/src/us2/game/002D3138-g8.c new file mode 100644 index 000000000..2c46250a6 --- /dev/null +++ b/src/us2/game/002D3138-g8.c @@ -0,0 +1,60 @@ +/* func_US2_002D3138, the v2.00 copy of func_002D2188 (src/game/002D2188-g8.c): the same C, + with text id 0x2BE5 for the last label where v1.01 has 0x2D4F. */ +#include "common.h" + +extern void func_0027C540(s32); +extern void func_0027C660(void); +extern s32 func_0027FAA8(s32, s32, u64, s32, s32); +extern s32 func_00288FB8(s32); +extern void func_0028FC48(s32, s32, s32, s32, u64, s32); +extern void func_002CE5D0(s32, s32); +extern void func_00300A00(s32, s32, f32, f32, f32, f32, f32); +extern s32 func_00336D50(s32, s32); +extern s32 D_001A8CFC; +extern s32 D_001A7340; +extern f32 D_001B23A8; +extern f32 D_001B23A4; +extern s32 ref_US2_002D3138_01 __attribute__((sda)); +extern s32 ref_US2_002D3138_02 __attribute__((sda)); +extern struct { s16 text; s16 reserved; } D_001ABD68[] MACRO_ADDR; + +/* The display mode word beside the HUD scale. */ +extern s32 D_001B23A0; + +/* A HUD coordinate scaled for the display and rounded to a whole pixel. The display-mode + test has no effect on the result, but it is real: its load and compare go at flow2, + after allocation, which leaves the saved but unused $s1 (as func_00326A10). */ +static __inline__ s32 hud_coord(f32 value, f32 scale, f32 half, s32 active) +{ + f32 scaled = scale * value + half; + s32 result = (s32)scaled; + + if (result != 0 && D_001B23A0 == active) + scaled = 1.0f; + return result; +} + +s32 func_US2_002D3138(void) { + f32 z, s, half; + s32 r; + s32 center, one; + + func_0027C540(0); + z = 0.0f; + one = 1; + r = func_00336D50(D_001A8CFC + 0x8710, 0xDE); + func_00300A00(r, 0x60442D00, (f32)(D_001A7340 / 2), (f32)ref_US2_002D3138_01, 1.0f, D_001B23A8, z); + r = func_00336D50(D_001A8CFC + 0x8710, 0xDF); + func_00300A00(r, 0x60241700, (f32)(D_001A7340 / 2), (f32)ref_US2_002D3138_01, 1.0f, D_001B23A8, z); + r = func_00336D50(D_001A8CFC + 0x8710, 0xE0); + func_00300A00(r, 0x55F0C070, (f32)(D_001A7340 / 2), (f32)ref_US2_002D3138_01, 1.0f, D_001B23A8, z); + func_002CE5D0(ref_US2_002D3138_02 != 0, ref_US2_002D3138_02 != 5); + s = D_001B23A4; + half = 0.5f; + func_0028FC48(66, hud_coord(330.0f, s, half, one), 463, hud_coord(332.0f, s, half, one), 0x55F0C070, 0); + center = D_001A7340 / 2; + func_0027FAA8(center, 311, (u64)0x80F0F0F0, func_00288FB8(D_001ABD68[ref_US2_002D3138_02].text), -1); + func_0027FAA8(0x1B0, 0x177, (u64)0x80F0F0F0, func_00288FB8(0x2BE5), -1); + func_0027C660(); + return 0; +} diff --git a/src/us2/game/002D3138.c b/src/us2/game/002D3138.c index 1957c650d..e8c87da2a 100644 --- a/src/us2/game/002D3138.c +++ b/src/us2/game/002D3138.c @@ -1,3 +1 @@ #include "include_asm.h" - -INCLUDE_ASM("asm/us2/game", func_US2_002D3138); diff --git a/src/us2/game/00349368-g8.c b/src/us2/game/00349368-g8.c new file mode 100644 index 000000000..c6be6ffd6 --- /dev/null +++ b/src/us2/game/00349368-g8.c @@ -0,0 +1,118 @@ +#include "common.h" + +/* func_US2_00349C00, the v2.00 copy of func_00348EC0 (src/game/00345358-g8.c): the same C, where the + menu's fields lie 0x70 bytes further in and each label slot is 88 bytes, not 72. + func_00348EC0 draws the list menu's rows centred on the screen. The row spacing is rounded with + label_y after the row count is read; that block end is what gives retail's count, spacing and + 0.5 registers. Standalone declarations, the boot's addresses. */ + +extern int func_002A9588(int, int, int, int, int); +extern void func_00336320(char *); +extern int func_00335E10(char *); +extern int func_00335E20(char *); +extern float D_001AE5F0 __attribute__((sda)); +extern float D_001B23A4[] MACRO_ADDR; +extern int D_001A7344[] MACRO_ADDR; +extern float D_001AE5D0 __attribute__((sda)); +extern float D_001AE5D4[] MACRO_ADDR; +extern float D_001AE5D8 __attribute__((sda)); +extern float D_001AE5E0 __attribute__((sda)); +extern float D_001AE5E4[] MACRO_ADDR; +extern float D_001AE5E8 __attribute__((sda)); + +typedef struct { float x, y, w, h; } ListRect; +typedef struct { + char pad[0x4A4]; + ListRect *origin; + int unknown438; + int selected; + int enabled; + int count; + int showSpecial; +} ListMenu; +typedef struct { short pad[4]; short offset; short pad2; void (*call)(char *); } ListVtable; +typedef struct { + ListRect *position; + ListRect *scale; + void *parameter08; + unsigned int *colors; + float *visibility; + char lifetime[0x1C]; + ListVtable *table; + void *metrics; + unsigned long style; + char *text; + int alignment; + char reserved48[0x10]; +} ListSlot; + +extern int D_001B23A0; + +/* label_y, the HUD label idiom (the shared definition in src/levels/15_gorn/L15_00431590-g8.c, its + mode word the boot's D_001B23A0). Its tests do not change the result: flow deletes their stores + and jump2 their jumps, but only after allocation and both scheduling passes, which see the block + they end. Here it rounds the list row spacing. */ +static __inline__ int label_y(void *handle, float value, float scale, float half, int active) +{ + int mode = D_001B23A0; + float scaled = value * scale + half; + int result = (int)scaled; + void *copy; + + if (mode == active) + scaled = scale; + if (handle != 0) + scaled = scale; + copy = handle; + if (copy != 0) + scaled = half; + return result; +} + +void func_US2_00349C00(char *o) +{ + int color = func_002A9588(0x20FFFEED, 0x70FFFEED, 20, 0, 0); + int start, i, sp, h, n; + float half, spf; + ListMenu *menu = (ListMenu *)o; + + if (menu->enabled) { + func_00336320(o); + func_00336320(o + 0x130); + func_00336320(o + 0x4C); + func_00336320(o + 0x98); + func_00336320(o + 0xE4); + n = menu->count; + half = (float)n * 0.5f; + sp = label_y(o, D_001AE5F0, D_001B23A4[0], 0.5f, 1); + spf = (float)sp; + h = D_001A7344[0] >> 1; + start = (int)((float)h - spf * half); + for (i = 0; i < menu->count; i++) { + ListRect *p; + char *slot; + ListVtable *v; + int c; + + if (i == 7 && !menu->showSpecial) + continue; + p = (ListRect *)((float *)func_00335E10(o + 0x17C)); + p->x = D_001AE5D0 + menu->origin->x; + p->y = (D_001AE5D4[0] + (float)i * D_001AE5D8) + (float)start; + c = i == menu->selected ? color : 0x55F0C070; + *((int *)func_00335E20(o + 0x1C8)) = c; + func_00336320(o + 0x17C); + p = (ListRect *)((float *)func_00335E10(o + 0x1C8)); + p->x = D_001AE5D0 + menu->origin->x; + p->y = (D_001AE5D4[0] + (float)i * D_001AE5D8) + (float)start; + func_00336320(o + 0x1C8); + slot = (char *)&((ListSlot *)(o + 0x218))[i]; + p = (ListRect *)((float *)func_00335E10(slot)); + p->x = D_001AE5E0 + menu->origin->x; + p->y = (D_001AE5E4[0] + (float)i * D_001AE5E8) + (float)start; + v = *(ListVtable **)(o + i * 88 + 0x248); + sp = v->offset; + v->call(slot + sp); + } + } +} diff --git a/src/us2/game/00349368.c b/src/us2/game/00349368.c index 657ee4b9a..68ab79905 100644 --- a/src/us2/game/00349368.c +++ b/src/us2/game/00349368.c @@ -19,7 +19,3 @@ void func_US2_00349378(char *a0, int a1, int *a2) for (i = 0; i < a1; i++) func_00336A38(a0 + 536 + i * 88, func_00288FB8(a2[i])); } - - -INCLUDE_ASM("asm/us2/game", func_US2_00349C00); - diff --git a/src/us2/levels/02_maktar_nebula/L02_00400E04-g8.c b/src/us2/levels/02_maktar_nebula/L02_00400E04-g8.c new file mode 100644 index 000000000..6bf6d2705 --- /dev/null +++ b/src/us2/levels/02_maktar_nebula/L02_00400E04-g8.c @@ -0,0 +1,97 @@ +#include "include_asm.h" + +extern void func_L02_003FDDE8(char *, int); +extern int func_00288FB8(int); +extern long long func_0027C878(int); +extern void func_0027F640(float, float, long long, char *, float, int, long long, char *); +extern void func_002802E0(char *, int, int, int, int, int, int, int, int); +extern unsigned int func_001157AC(char *); +extern void func_00280270(char *, long long, char *, int); +extern void func_00280190(char *, long long, char *, int); +extern int func_00115DA8(char *, char *, ...); +extern int func_L02_003F9B70(int); +extern unsigned char D_001395B8[0x100]; +extern unsigned char D_US2_001A7BBC; +extern int D_US2_L02_001B1460[2] __attribute__((sda)); +extern float D_US2_L02_001B1458 __attribute__((sda)); +extern int D_US2_L02_001B145C __attribute__((sda)); +extern int D_US2_L02_001B1478 __attribute__((sda)); +extern int D_US2_L02_001B147C __attribute__((sda)); +extern int D_US2_L02_001B1480 __attribute__((sda)); +extern int D_US2_L02_001B1484 __attribute__((sda)); +extern int D_US2_L02_001B1488 __attribute__((sda)); +extern int D_US2_L02_001B148C __attribute__((sda)); +extern int D_US2_L02_001B1490 __attribute__((sda)); +extern int D_US2_L02_001B1498 __attribute__((sda)); +extern int D_US2_L02_001B14A8 __attribute__((sda)); +extern int D_US2_L02_001B149C; +extern int D_US2_L02_001B14A0; +extern int D_US2_L02_001B14A4; +extern float D_US2_L02_001B3E04; +extern int D_US2_L02_001B3E00; +extern char D_US2_L02_001B11E8[]; +extern char D_US2_L02_0028EA10[]; + +static __inline__ int scaled_coord(int value, float scale, float half, int active) +{ + int mode = D_US2_L02_001B3E00; + float scaled = (float)value * scale + half; + int result = (int)scaled; + + if (result != 0 && mode == active) + scaled = 1.0f; + return result; +} + +/* func_US2_L02_00400E04, the v2.00 copy of func_PAL_L02_00403FF0 (src/pal/levels/02_maktar_nebula/ + L02_00403FF0-g8.c), itself the PAL copy of func_L02_003FF6E8: the same C with this release's data + and text ids 0x2DDB, 0x2DDC, 0x2DF8, 0x2DF4 and 0x2DDD. The first x is taken per language from + D_US2_L02_001B1460, as in PAL. Its data is named D_US2_L02_. */ +void func_US2_L02_00400E04(char *a0) { + char buf1[0x20]; + char buf2[0x70]; + char *state; + char *text; + char *msg; + float x, s, half; + int r0, r1, r2, r3, one; + + func_L02_003FDDE8(a0, 0); + half = 0.5f; + state = *(char **)(a0 + 0x68); + text = (char *)func_00288FB8(0x2DDB); + one = 1; + s = D_US2_L02_001B3E04; + x = (float)D_US2_L02_001B1460[D_US2_001A7BBC]; + r0 = scaled_coord(D_US2_L02_001B1478, s, half, one); + func_0027F640(x, (float)r0, D_US2_L02_001B145C, text, D_US2_L02_001B1458, -1, + func_0027C878(1), D_US2_L02_0028EA10); + s = D_US2_L02_001B3E04; + r1 = scaled_coord(D_US2_L02_001B1498, s, half, one); + r2 = scaled_coord(D_US2_L02_001B149C, s, half, one); + r3 = scaled_coord(D_US2_L02_001B1484, s, half, one); + func_002802E0(buf1, r1, r2, D_US2_L02_001B14A0, D_US2_L02_001B14A4, D_US2_L02_001B1480, r3, + D_US2_L02_001B14A8, 3); + if (*(short *)(state + 0x118) == 0 && D_001395B8[func_L02_003F9B70(0)] < 2) { + text = (char *)func_00288FB8(0x2DDC); + func_00280270(buf1, D_US2_L02_001B147C, text, func_001157AC(text)); + } else { + text = (char *)func_00288FB8(0x2DF8); + msg = (char *)func_00288FB8(0x2DF4); + func_00115DA8(buf2, D_US2_L02_001B11E8, text, *(int *)(state + 0x110), msg); + func_00280270(buf1, D_US2_L02_001B147C, buf2, func_001157AC(buf2)); + } + text = (char *)func_00288FB8(0x2DDD); + { + float half = 0.5f; + int one = 1; + + s = D_US2_L02_001B3E04; + r1 = scaled_coord(D_US2_L02_001B1498, s, half, one); + r2 = scaled_coord(D_US2_L02_001B149C, s, half, one); + r3 = scaled_coord(D_US2_L02_001B1490, s, half, one); + func_002802E0(buf1, r1, r2, D_US2_L02_001B14A0, D_US2_L02_001B14A4, D_US2_L02_001B148C, r3, + D_US2_L02_001B14A8, 3); + } + func_00280190(buf1, D_US2_L02_001B1488, text, func_001157AC(text)); +} diff --git a/src/us2/levels/02_maktar_nebula/L02_00400E04.c b/src/us2/levels/02_maktar_nebula/L02_00400E04.c index 153791126..e8c87da2a 100644 --- a/src/us2/levels/02_maktar_nebula/L02_00400E04.c +++ b/src/us2/levels/02_maktar_nebula/L02_00400E04.c @@ -1,3 +1 @@ #include "include_asm.h" - -INCLUDE_ASM("asm/us2/levels", func_US2_L02_00400E04); diff --git a/src/us2/levels/shared/L01_00305170.c b/src/us2/levels/shared/L01_00305170.c index 1513224fd..898157beb 100644 --- a/src/us2/levels/shared/L01_00305170.c +++ b/src/us2/levels/shared/L01_00305170.c @@ -1,3 +1,6 @@ #include "include_asm.h" -INCLUDE_ASM("asm/us2/levels", func_US2_L01_00305170); +/* Original assembly: the first 720 bytes are func_0029D4D0 (src/game/0029BCC8.c, original assembly: + a qmtc2 VU0 op, 15 trapping add/addi and three alternate entries), followed by an 84-byte tail + with a fourth alternate entry, a trapping addi and two teq traps. */ +ASM_FUNC("asm/us2/levels", func_US2_L01_00305170);