Skip to content

Work on grzebes - #3305

Draft
MarkMcCaskey wants to merge 18 commits into
doldecomp:masterfrom
MarkMcCaskey:match-grzebes
Draft

Work on grzebes#3305
MarkMcCaskey wants to merge 18 commits into
doldecomp:masterfrom
MarkMcCaskey:match-grzebes

Conversation

@MarkMcCaskey

Copy link
Copy Markdown
Collaborator

No description provided.

Each declared local that is copy-propagated away still reserves a vestigial
4-byte stack home, so the trivial one-line helpers and the duplicate
distance/zero locals were only widening grZebes_801DB3CC's frame. Inline
them by hand, symmetrise the left/right neighbour blocks, and fold
effective_dist back into dist. Code generation is unchanged.
@MarkMcCaskey MarkMcCaskey added the ai-assisted Utilizes a LLM to do the heavy lifting label Sep 4, 2026
The zero local was only a staging copy; dropping it is code-generation
neutral.
@decomp-dev

decomp-dev Bot commented Sep 4, 2026

Copy link
Copy Markdown

Report for GALE01 (98dcaaa - 115ead2)

📈 Matched code: 95.52% (+0.07%, +2628 bytes)

✅ 2 new matches
Unit Item Bytes Before After
main/melee/gr/grzebes grZebes_801DBB60 +10 99.48% 100.00%
main/melee/gr/grzebes grZebes_801DB088 +5 99.43% 100.00%
📈 1 improvement in an unmatched item
Unit Item Bytes Before After
main/melee/gr/grzebes grZebes_801DB3CC +4 99.62% 99.81%

The b-side computed its scaled velocity through a one-line inline helper
and clamped in place, while the a-side used a plain multiply and a
separate clamp local. Writing both the same way keeps code generation
identical and drops two more vestigial stack homes.
The retail frame is 0x58; ours was 0x78. The gap was eight locals that MWCC
copy-propagates away but still gives a vestigial 4-byte home: the duplicate
current_x/current_y aliases, the old_x/old_y reads the bound checks can take
straight from the entry, and the one-line scale helper whose result the
b-side clamped in place. Removing them lands the frame on 0x58 with the
three sqrt staging slots at their retail offsets, and drops a diff row.
The grZe_BubbleState cast only existed to reach grZe_8049F170 through a
struct view of three separate arrays, and its state local was an unused
object slot. Every other function in the TU indexes the array directly.
Code generation is unchanged.
MWCC canonicalises `x * C` with the constant in frA, but folds `x / C`
into a multiply that keeps it in frC. Retail's grZebes_801DB3CC has the
latter encoding throughout the bubble-collision loop, so the radii and
the repulsion strength were written as divisions.
Retail loads ej->x08_x before ej->x0C_y and subtracts dx before dy. The
inline subtraction helper is the lever that decides which of the pair is
scheduled first, so it belongs on dy; the declarations follow suit. The
emitted instruction stream now matches retail exactly, leaving only a
callee-saved register permutation.
`(void) 0.0;` in grZebes_801DB3CC's update helper emitted an 8-byte double
that retail's .sdata2 does not contain, shifting every constant after it.
Removing it makes .sdata2 byte-identical and leaves all code untouched; the
remaining .data/.sdata deltas are the usual dtk tail-alignment padding.
`last_idx` must outrank the loop locals it shares a live range with, which
means it is declared last among the function's own locals and the two loop
walkers are declared alongside it rather than inside their blocks. That puts
the four callee-saved registers on retail's assignment; only the
floating-point allocation still differs.
Byte-identical; the split initialisation was a decomp artifact.
The inner loop steps its walker before the counter, which is MWCC's
strength-reduction signature: the source indexes grZe_8049F170[j] and the
compiler derives the pointer. The outer loop has the opposite order, so its
`ei` is a real walked pointer and stays one. Byte-identical.
Same strength-reduction signature as grZebes_801DBB60's pair loop: the
walker steps before the counter, so the source indexed the array and MWCC
derived the pointer. Byte-identical.
The candidate bubble's position is read into locals the same way the popped
entry's already is; those two homed floats are exactly the eight bytes the
PAD_STACK was reserving. Byte-identical, and the frame is unchanged at 0x98.
The dx/dy temporaries were decomp scaffolding: writing the squared distance
directly gives retail's instruction stream for the whole computation and
drops the artificial subtraction helper from this loop. Only the five loaded
coordinates remain permuted.
The five loaded coordinates were claiming exactly the reversed set of
registers. Reading the winning pair's coordinates straight out of the
bubble array pools them as expression temps, and the dead reads in the
loop preheader do the same for the current bubble's, which puts all five
webs in the claim order retail has.
The two point-to-endpoint branches square each component into a fresh
variable instead of in place, which is what puts the first square in its
own register.
Testing the ground and joint pointers with an embedded assignment keeps
each in its own local instead of a compiler temp, which is what breaks
the fifteen-way register jam; walking the candidate bubbles through a
pointer restores the stack slot the wrapper inline used to bill.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-assisted Utilizes a LLM to do the heavy lifting

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant