Byte-exact, all four functions, whole object. Source was a pure skeleton with no code; status MISSING work now NonMatching -> Matching. An iterative quicksort for 2-byte and 4-byte elements with a 30-deep bounds stack and a selection-sort cutoff at 8. The two code_* symbols are the selection sorts and take hi in EAX under VC7's custom convention for statics. The comparator takes values, not pointers, and the zero-extension in the target pins the 2-byte element type to word rather than short. Five source-shape findings, each read off the disassembly: - the swap loads both elements then stores to *best first, which is temp = *best; *best = *hi; *hi = temp - the mirror of the natural form. That alone made both selection sorts exact. - element_count and size are unsigned: the target uses jb, ja and shr where signed types give jl, jg and sar. - the source writes lo+1 < j, not the algebraically identical lo < j-1, which reverses the lea and cmp operands. - the larger-partition test compares raw byte differences. On word pointers every element-count form makes MSVC emit two sars or cancel them and mask, costing 16 bytes; only an explicit byte-basis comparison gives the plain dec. j-1-lo >= hi-i, j-lo > hi-i and the byte form are integer-identical and only the last matches. - depth = 0 is assigned before hi; the reverse order swaps the two stores. Both qsort bodies went exact from the same five fixes, which is good evidence the shapes are right rather than coincidental. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
6.6 KiB
source/cseries/sort.obj — MATCHING
Status: byte-exact. All four functions, whole object.
Lane: claude/libcmt-stream · Object status in config/config.json: Matching
Toolchain: XDK 3911 CL.Exe 13.00.9254.1 · Flags: the game set, /O2 /Oy- /DDEBUG /Dxbox
Start state: the source was a pure skeleton — symbol-map header and empty
section comments, no code.
| symbol | target | ours | exact |
|---|---|---|---|
_code_00080360 (2-byte selection sort) |
96 B / 0 rel | 96 B / 0 rel | yes |
_code_000803c0 (4-byte selection sort) |
80 B / 0 rel | 80 B / 0 rel | yes |
_qsort_2byte |
336 B / 1 rel | 336 B / 1 rel | yes |
_qsort_4byte |
320 B / 1 rel | 320 B / 1 rel | yes |
section_infos_equal passes for all four; .text sections are identical with
relocation fields masked. Normalized SHA-256: 21df9a493f4dd024,
c98ad64dbdb87dbc, c09f5aead7542561, 1cedc780c4faa025.
1. What the object is
An iterative quicksort specialised for 2-byte and 4-byte elements, with an
explicit 30-deep bounds stack and a selection-sort cutoff at 8 elements. The two
_code_* symbols are the selection sorts; they take hi in EAX under VC7's
custom calling convention for statics with fully-known call sites.
The comparator takes values, not pointers: the selection sort does
xor eax,eax; mov ax,[ebx] before pushing, i.e. zero-extension — so the 2-byte
element type is word (unsigned short), not short. texture_page.c, itself an
exact object, declares the callee as boolean (*)(short, short); that prototype
disagrees with this definition but is ABI-compatible (both push 4 bytes), and the
zero-extension in the target is decisive for what sort.c itself uses.
compare(a, b) returns true when a sorts after b — proven by the
selection sort using it to find the maximum and swap it to the high end.
2. Source-shape findings
Five corrections were needed; each was read directly off the disassembly.
-
Swap order. The target loads both elements, then stores to
*bestfirst:dx=*hi; cx=*best; *best=dx; *hi=cx. That istemp = *best; *best = *hi; *hi = temp;— the mirror of the naturaltemp = *hi; *hi = *best; *best = temp;. This alone made both selection sorts exact. -
Unsigned comparisons throughout. The target uses
jbfor theelement_count < 2guard,jafor the cutoff test, andshr(notsar) for the midpoint. Soelement_countand the runningsizeare unsigned long. -
lo+1 < j, notlo < j-1. The target computeslea eax,[esi+2]and compares againstj; writing the algebraically identicallo < j-1produceslea eax,[edi-2]with the operands reversed. -
The larger-partition test is a byte-basis comparison. The target computes raw byte differences with no shift and no mask:
e0 mov eax, edi ; j e2 sub eax, esi ; j - lo, in BYTES e4 mov edx, ecx ; hi e6 dec eax e7 sub edx, ebx ; hi - i, in BYTES e9 cmp eax, edx eb jl <right-larger>Written on
word *pointers, any element-count form makes MSVC either emit twosars or cancel them and mask (and eax,0xfffffffe) — 16 bytes too many. Only an explicit byte-basis comparison reproduces the plaindec:if ((char *)j-(char *)lo-1>=(char *)hi-(char *)i)Note
j-1-lo >= hi-i,j-lo > hi-iand the above are all integer-identical; only the third matches the emitted code. -
depth = 0is assigned beforehi. The target stores[ebp+0xc](which the optimiser reuses fordepthonce theelement_countparameter is dead) at 0x1f andhiat 0x26. Ordering the source the other way emits the two stores swapped. This was the last 3 instructions.
3. Notes
dwordis not acseries.htype; the 4-byte variant useslong.- Both
qsortbodies are identical modulo element type, and both went exact from the same five fixes — a useful confirmation that the shapes are right rather than coincidental. - The bounds stacks are
lo_stack[30]/hi_stack[30], fixed by the frame size (sub esp,0xf4= 4 + 120 + 120) and the[ebp+eax*4-0x7c]/[ebp+eax*4-0xf4]addressing.
4. Current-integration revalidation (2026-08-13)
The completed source was recovered from Claude's local claude/libcmt-stream
campaign commit bf44b00d3e97f5781068b85d35d86f6d8751226e, then cherry-picked onto the
current authoritative integration base 66af3489. This was an audit and
admission lane, not a fresh reconstruction: reusing the already-proven work
avoided repeating the source-shape search above.
This checkout successfully rebuilt the object through Ninja with XDK 3911
CL.Exe 13.00.9254.1 and the configured /O2 /Oy- /DDEBUG /Dxbox flags. The
hardened comparator then reproduced the complete hashes and relocation evidence:
| symbol | padded bytes | relocations | normalized SHA-256 | strict result |
|---|---|---|---|---|
_code_00080360 |
96 / 96 | 0 / 0 | 21df9a493f4dd0244dfd9e3bdf4c1cfc68b33b8fa11f2e6bc8e30a76102b9205 |
exact |
_code_000803c0 |
80 / 80 | 0 / 0 | c98ad64dbdb87dbc89d00c9ff63b6ae4b1ec6685291add335aac30724f7032d4 |
exact |
_qsort_2byte |
336 / 336 | 1 / 1 | c09f5aead7542561d64a5e7b41b4ba79c23bfbdf1cd45ed8c713fafa99dab200 |
exact |
_qsort_4byte |
320 / 320 | 1 / 1 | 1cedc780c4faa02599391876115607221a735e6d1cd51f7bcecb98eeca37dd3c |
exact |
Both quicksort relocations match in address, type, destination symbol, and
addend. The January object owns only these four .text COMDAT sections; it has
no .data, .bss, or .rdata to reconstruct. The candidate's .drectve and
.debug$S sections are the ordinary configured compiler artifacts and contain
no game-owned data.
The fail-closed TU regression gate reported all four symbols as still_exact,
with no changed non-exact functions, warnings, or failures. The full 466-object
halobetacache_build succeeded, ordinary and semantic progress generation
completed with zero unit errors, and all 173 tooling unit tests passed.
House-rule audit
The implementation is readable C: no assembly, volatile, __forceinline,
undefined behavior, byte patching, special flags, or comparator exceptions are
used. Every parameter is on its own line, every function has an explicit final
return;, and the private helpers are correctly file-local. The two public
quicksort functions also retain their direct element_count < 2 early return.
This is the narrow exception to the preferred single-return style: it is the
January-proven control-flow shape, and replacing it with an artificial shared
exit would change otherwise exact code. No tag/object access macros apply to
this generic cseries utility.