Commit Graph

174 Commits

Author SHA1 Message Date
MarcelineVQ a7e2f978e2 bone_sse: simplify rotateByQuaternion to use matMul4x4 V4+FMA path 2026-03-16 10:56:48 -07:00
MarcelineVQ 5d41cd03a3 bone_sse: V4 FMA matmul, enable SSE4.1+FMA+AVX target 2026-03-16 10:54:25 -07:00
MarcelineVQ 08d1992eaf bone_sse: replace IsParticleBufferEmpty with pure Zig — 1 game call remains (atexit) 2026-03-16 10:45:06 -07:00
MarcelineVQ 1987dc5622 bone_sse: pure Zig — only 2 game calls remain (atexit init + particle buffer check)
Replaced all remaining game function calls:
- findInterpIdx (0x713D50): full temporal-coherence search reimplementation
- interpAnimKF in boneKeyframeLoop: reuses existing pure Zig version
- applyTranslation/rotateByQuaternion/scaleMatrix3x3 in boneKeyframeLoop
- getInterpolatedFloat (0x71AF20): replaced with interpFloatTrack (identical)
- extractByte (0x71AE90): findInterpIdx + direct byte read
- Child recursion: direct call to transformImpl_SSE instead of 0x714260 hook

Only 2 game calls remain (cannot be replaced):
- 0x409AEF: one-time atexit registration in boneKeyframeLoop
- 0x7B5F60: IsParticleBufferEmpty (reads game particle state)
2026-03-16 10:38:34 -07:00
MarcelineVQ 16c913b9b7 bone_sse: pure Zig findInterpIdx — last major game call in interpolation path 2026-03-16 10:33:40 -07:00
MarcelineVQ 800adbe185 bone_sse: pure Zig bone loop — interpAnimKF, buildRot, scaleMat, matMul all replaced 2026-03-16 10:27:33 -07:00
MarcelineVQ 0aab0d3678 bone_sse: replace getIndexOffset/setShortValue with direct ri16 reads 2026-03-16 10:22:06 -07:00
MarcelineVQ 8736e0b3da bone_sse: clean A/B testing, remove diagnostic comparison code
Remove the double-call REF/SSE bone output comparison diagnostic.
Clean detour: REF baseline, SSE custom, simple toggle.
2026-03-16 10:18:35 -07:00
MarcelineVQ 008db88403 bone_sse: replace matMul/ftol/vec3SqMag with pure Zig, fix visual artifacts
Root cause of billboard visual artifacts: callVec3SqMag used inline asm
to call game's x87 vec3SqMag (0x4549F0) with fstps to capture ST0.
With SSE2 codegen, the x87/SSE state interaction caused corrupted float
values in billboard bone matrices (mat[0][2] wildly wrong).

Replaced with pure Zig: x*x + y*y + z*z — no x87, no inline asm.

Also replaced:
- matMul (0x74A7C0): pure Zig f64 scalar matmul, no alignment needs
- callFtol (0x40A2B0): f64 intermediate + @intFromFloat (cvttsd2si)

Architecture: bone_sse.zig is REF code compiled with SSE2, called as
cdecl from thiscall wrapper in transform44.zig (cross-object to prevent
LLVM inlining AND ESP alignment into thiscall frame).

A/B: other hooks gated behind AB_OTHER_HOOKS=false for isolated testing.
2026-03-16 10:10:02 -07:00
MarcelineVQ 252853dd8f bone_sse: fix findInterpIdx from assembly, runtime constants, interpAnimKF stride
Assembly-verified fixes from t44_helpers_asm.txt:

findInterpIdx (0x713D50):
- Range format is [start, last] not [start, count] (DEC EDI pattern)
- Backward scan entry: delta >= 0xFFFFFE0C not > (JC = unsigned below)
- 500-tick threshold (0x1F4) for forward/backward vs binary search
- GS check is CMP AX,0xFFFF (word compare), not >= 0
- t computation: FILD qword (i64 numer) / FIDIV dword (i32 denom)

interpAnimKF (0x713EA0):
- Keyframe stride is 16 bytes (SHL EAX,0x4), NOT 8 (CompQuat)
- Values are raw floats, no short-to-float conversion needed

Runtime constants — all now read from game memory:
- 0x80297C (3.0) and 0x802990 (6.0) for Hermite/Bezier basis
- 0x80C5C8 for billboard squared magnitude threshold
- 0x811610 and 0x8029D4 already read at runtime

Wrapper pattern: thiscall export delegates to normal fn for AVX alignment.
2026-03-15 18:39:27 -07:00
MarcelineVQ 3a031803ef bone_sse: pure Zig SSE/FMA reimplementation, zero game function calls
Replace bone_sse.zig with a complete pure Zig implementation compiled
with SSE4.1 + FMA + AVX. All 18 game function calls replaced:

- findInterpIdx (0x713D50): temporal-coherence keyframe search
- interpAnimKF (0x713EA0): CompQuat lerp for rotation keyframes
- extractByte (0x71AE90): byte keyframe extraction
- getInterpolatedFloat (0x71AF20): float track with direct blend read
- callFtol (0x40A2B0): @intFromFloat replaces x87 __ftol
- callVec3SqMag (0x4549F0): inline FMA dot product
- callGetIndexOffset/callSetShortValue (0x71AFF0/0x71B010): direct ri16
- matMul (0x74A7C0): V4 FMA matmul (broadcast + 3 @mulAdd per row)
- buildRotFn (0x74B6B5): inline quat→matrix
- rotateQuat (0x7BDDB0): quat→matrix then FMA matmul
- scaleMat (0x7BDCA0): inline scale from vec3 ptr
- applyTrans (0x7BDC40): inline FMA dot product translation

Only 2 game calls remain:
- 0x409AEF: one-time atexit init (boneKeyframeLoop)
- 0x7B5F60: IsParticleBufferEmpty (reads game particle state)

Child recursion calls transformMatrix4x4_SSE directly instead of
going through the hook at 0x714260.

Detour cleaned up: REF is baseline, SSE activates via ab_use_custom
toggle. Diagnostic/bisect/FPU-comparison scaffolding removed.
build.zig: bone_sse gets dedicated target with sse4_1+fma+avx features.
2026-03-15 18:16:07 -07:00
MarcelineVQ 8fb1ece2b5 bone_sse_ref: fix world entry crash — 6 bugs found via full asm stepthrough
Full 5317-instruction walkthrough of t44_full_asm.txt vs bone_sse_reference.zig.

Crash fix (Issues 1-2): When anim_start >= anim_end in the looping animation
path, assembly always writes prim_time/sec_time = anim_start as fallback.
REF skipped the write, leaving garbage in bone_rt timing fields. On newly
loaded world SceneObjects this propagated through findInterpIdx → extractByte
→ ACCESS_VIOLATION at 0x71AEBC with ECX=0x7FFFFFFF (self-reinforcing bad
cached index).

Time clamp fix (Issues 3-4): Clamped-not-passed animation path now clamps
cur_time to sec_start when sec_start > cur_time, matching assembly at
0x7145EB/0x71474B.

Crossfade fix (Issues 5-6): interpVec3Track36 and interpFloatTrack12 had
'else return' for unknown interp modes. Assembly's JNZ skips primary interp
but falls through to crossfade check. Changed to 'else {}' fallthrough.

Also includes prior uncommitted fixes: particle crossfade blend_weight
restoration, bw>0→bw!=0, attach_count==0 early-return removal.
2026-03-15 17:38:14 -07:00
MarcelineVQ 6f8b5ea0e7 bone_sse_ref: disable particle crossfade, fix cross product z, guard cleanup
- Particle interpVec3Track/interpFloatTrack: pass 0.0 blend_weight to
  disable crossfade, matching original which has no crossfade in particle
  sections (only bone loop and boneKeyframeLoop have crossfade).
- interpFloatTrack: add explicit blend_weight parameter instead of
  reading from bone_rt internally, allowing callers to control crossfade.
- Revert extractByte guard (was added then removed during investigation).

Known crash: extractByte (0x71AE90) crashes at 0x71AEBC with idx=0x7FFFFFFF
on world entry. findInterpIdx reads output[0] as cached search position;
if hierarchy buffer contains stale 0x7FFFFFFF, search overflows and
self-reinforces. Investigation ongoing — REF's attachment section matches
original assembly instruction-for-instruction.
2026-03-15 16:44:43 -07:00
MarcelineVQ 582b0dc132 bone_sse_ref: add crossfade blending, word animation section, fix cross product z
- texAnimLoop alpha: add crossfade blend for mode != 0 (mode 0 skips
  crossfade per original assembly JMP at 0x715B49). Fix alpha output
  base to output+0x30 matching original ESI.
- colorAnimLoop: add crossfade blend, same mode 0 skip pattern.
  Mode 0 uses direct short->float copy matching original.
- New wordAnimLoop: implements model_hdr+0x6C/0x70 word animation
  section (assembly 0x715E46-0x715F25). Word copy with crossfade,
  no float blending. Data stride 0x1C, output stride 0x20.
- Fix billboard cross product z-component for types 0x10/0x20:
  was +cross.z, should be -cross.z (r0y*r1x - r0x*r1y).
- Extract shortInterpToFloat helper shared by alpha/color crossfade.

Known: particle emitter crash (pre-existing, idx=0x7FFFFFFF in
secondary findInterpIdx) — exposed by corrected colorAnimLoop count.
2026-03-15 16:21:33 -07:00
MarcelineVQ 36ce1a05ce bone_sse_ref: fix M2 black screen — 5 bugs found via asm comparison
Assembly-level comparison of compiled REF against original 0x714260 revealed:

1. Billboard cross product sign error (types 0x10/0x20): computed +cross
   instead of -cross for components 0/1, corrupting billboard bone matrices
2. colorAnimLoop wrong count field: read model_hdr+0x6C instead of +0x64
3. colorAnimLoop wrong gate offset: checked anim_data+0x04 instead of +0x0C
4. Timestamp delta guard inverted: REF guarded on cur_ts!=0 and always
   wrote to this+0x4C; original guards on this+0x4C!=0 first and never
   seeds the field (something else initializes it)
5. Section 5 emitter_ctx cached instead of re-read after matMul call

Also: build REF with x87-only target (subtract SSE/SSE2 features) to
match original's FLD/FMUL/FSTP codegen, and use callVec3SqMag for all
magnitude computations instead of inline SSE math.

Remaining known issues (not yet fixed):
- texAnimLoop alpha track missing crossfade blend
- colorAnimLoop missing crossfade blend
- Missing word animation section (model_hdr+0x6C/0x70)
- Bisect infrastructure and diagnostic code still present (test scaffolding)
2026-03-15 15:04:05 -07:00
MarcelineVQ 89e49cedf2 bone_sse_ref: replace reimplemented game funcs with actual calls, fix runtime constants
- Replace all reimplemented game functions with actual game calls:
  vec3_sqmag (0x4549F0), __ftol (0x40A2B0), getIndexOffset (0x71AFF0),
  setShortValue (0x71B010) — matching assembly exactly
- Fix 3 wrong hardcoded constants that differ at runtime from Ghidra static values:
  SHORT_TO_FLOAT: 0x38000000→0x38000100 (1/32767 not 1/32768)
  BILLBOARD_EPSILON: 0x3727c5ac→0x34800000
  HERMITE_5: 5.0→6.0
  All now read from game memory at runtime
- Fix timestamp delta guard (this+0x4C): was guarding on stored value,
  assembly guards on anim_ctx pointer — prevents first-frame initialization
- Change REF calling convention to thiscall matching original
- Add comprehensive memory comparison diagnostic (original vs REF)
- Disable interpKfDetour hook (was pure passthrough)
2026-03-15 13:20:05 -07:00
MarcelineVQ c9249c6c96 bench: fix 3 correctness bugs found by benchmarker
mulMat3x4 / mulMat3x4InPlace: translation row had A*B operands swapped.
Original computes A_translation * B_rotation + B_translation, but our
code was doing A_rotation * B_translation + A_translation. Verified by
tracing x87 disassembly: first element loads A[9]*B[col] pattern.
Fixed in both silicon_sse.zig and silicon.zig.

packParticleColor: x87 rounds 127.5 to 128 (round-to-nearest), but SSE
@intFromFloat truncates to 127. Added @round() before @intFromFloat.

42/42 benchmarks now pass correctness. 0 MISMATCHes.
2026-03-15 12:27:59 -07:00
MarcelineVQ 7c88423a41 bench: add remaining silicon functions, total 38 benchmarks
Added 8 more silicon SSE functions to benchmark: normalizeVec3,
testOBBFrustum, calculateSinCos, translateBoundingVol, addToColorAccum,
packParticleColor, setParticleAlpha, and addVec3ToAccumulator (export fn).

Notable results from new entries:
  packParticleColor: 6.8x (109->16) MISMATCH -- alpha byte diff
  setParticleAlpha:  3.4x (24->7)
  calculateSinCos:   3.0x (138->45)
  normalizeVec3:     1.8x (18->10)
  testOBBFrustum:    1.3x (107->81)

3 MISMATCHes total (mulMat3x4, mulMat3x4InPlace, packParticleColor)
need correctness investigation -- likely matrix layout and struct
byte-order differences.
2026-03-15 12:08:59 -07:00
MarcelineVQ 55b4931fcb bench: map WoW PE sections for full-fidelity benchmarking, add silicon SSE
Major bench harness upgrade:
- Maps WoW .text (4MB) and .rdata (160KB) at original virtual addresses
  instead of individual function byte arrays. All CALL targets and float
  constants resolve automatically -- no more manual mapGameConstants().
- Bench binary linked at 0x10000000 to avoid address conflict with WoW
  PE sections at 0x400000-0xD00000.

Added silicon_sse.zig: 18 pure math functions extracted from silicon.zig
as export fn (C ABI) for standalone compilation. Covers frustum culling,
bounding volume ops, quaternion slerp, matrix multiplies, trig, etc.

Silicon benchmark results (all 14 new entries pass correctness):
  checkBoxLineIntersect: 3.2x (72->22)  -- slab AABB intersection
  rotateMatByQuat:       3.1x (172->54) -- quat->mat + mat multiply
  quatSlerp:             2.5x (454->178)
  createZRotMat3x3:      2.5x (140->55)
  createRotMat3x4:       2.2x (163->73)
  normalizeVec3InPlace:  1.8x (34->18)
  mulMat3x4InPlace:      1.6x (106->66) -- MISMATCH (layout diff, needs investigation)
  classifyPointFrustum:  1.5x (60->40)
  mulMat3x4:             1.2x (64->52)  -- MISMATCH (same layout issue)

Two MISMATCH entries on mat3x4 multiply -- likely row/column order
difference between original and our implementation. Correctness needs
verification against game behavior.
2026-03-15 12:04:02 -07:00
MarcelineVQ 008d74ddb6 bench: add original x87 bytes for 22 silicon module functions
Extracted via Ghidra from WoW.exe for the 22 silicon SSE functions that
don't overlap with ssemaths addresses. These cover frustum culling,
bounding volume transforms, quaternion slerp, matrix operations, and
various geometry functions. Ready for benchmarking.
2026-03-15 11:26:38 -07:00
MarcelineVQ 28dd1cf6fa silicon: implement 32 SSE math replacements for x87 FPU functions
Replace probe-only detours with direct SSE/scalar math implementations
for the highest-impact WoW.exe functions by call frequency:

Core transforms (10.8M+ calls/7.5s):
- transformVector3ByMatrix4x4: @Vector(4,f32) dot products
- transformVector4ByMatrix4x4: 4-row vector dots
- MultiplyMatrix3x4, multiplyMatrix3x3: affine matrix multiply
- MultiplyMatrix3x4InPlace: aliasing-safe with stack temp

Frustum/collision (8.2M+ combined):
- ClassifyPointAgainstFrustum: 6-plane dot + bitmask
- CheckBoxLineIntersection: slab-method AABB test
- TestSphereAgainstFrustum: 6-plane sphere test
- TestOBBAgainstFrustum: 8-corner OBB vs 6 planes
- IsPointInsideBounds: 3-component compare
- CalculateDistanceToPlane: ray-plane intersection

Matrix builders (300K+):
- createAxisAngleRotationMatrix 4x4/3x3/3x4: Rodrigues formula
- createZRotationMatrix3x3: sin/cos rotation
- rotateMatrixByQuaternion: quat→matrix + 4x4 multiply
- getTransposedMatrix4x4: inline transpose
- scaleMatrix3x3ByVector, ApplyTranslationMatrix

Vector/scalar:
- normalizeVector3, NormalizeVector3_InPlace
- Vector3_DotProduct, quaternion_slerp
- addVector3ToAccumulator, addToColorAccumulator
- calculateSinCos

Particle:
- packParticleColorToBytes, setParticleAlphaFromFloat

Bounding volume:
- TranslateBoundingVolume, TransformBoundingVolume

84 functions remain as probe-only (orchestrators, Lua internals,
complex renderers — optimization is in their callees, now replaced).
2026-03-15 11:18:35 -07:00
MarcelineVQ 05e016da46 silicon: fix 5 param count bugs, add periodic probe reporting
Fix calling convention mismatches found via systematic RET purge audit:
- interpolateKeyframes: FC4d→FC3d (3 params, not 4)
- ConvertPixelsToScreenAlt: TC2r→TC2d (returns x87 float, not u32)
- UpdateObjectTransform: FC2r→FC3r (3 params, not 2)
- packParticleColorToBytes: FC4v→FC5v (5 params, not 4)
- setParticleAlphaFromFloat: FC2v→FC3v (3 params, not 2)

Disable GetFPUControlWord probe (corrupts FLDCW state).
Use naked asm for __ftol probe (preserves implicit x87 ST(0)).
Add OnWorldUpdate hook for periodic hit count dumps every ~7.5s.
All 115 active probes verified with non-zero counts in-game.
2026-03-15 03:41:28 -07:00
MarcelineVQ f97d27f614 bone_sse: add ribbon visibility gate, fix Track A type, add post-processing
Ribbon emitter section (0x7163FC-0x716AA9) — assembly-verified:
- Add visibility byte animation (entry+0xB8 → output+0xE0/0xEC) with crossfade
- Add visibility gate: skip tracks unless (output+0x100 && output+0xEC) or frame 0
- Fix Track A from interpVec3Track to interpFloatTrack (assembly uses *4 not *12)
- Add post-processing 1: output+0x134 = TrackB_Vec3 * (TrackA_float * render_scale_z)
- Add post-processing 2: output+0x140 = TrackD_Vec3 * (TrackC_float * render_scale_z)

Applied to both bone_sse.zig and bone_sse_reference.zig.
2026-03-15 03:01:39 -07:00
MarcelineVQ d6f1904050 bone_sse: sync ribbon emitter fixes (#18-19) to self-contained version 2026-03-15 02:26:23 -07:00
MarcelineVQ da7155a404 bone_sse: fix ribbon emitter offsets (#18-19), teardown guard
Bug #18: Ribbon emitter track offsets completely wrong. Position was
entry+0x24, actual tracks from assembly (0x716402-0x716AA9):
  Track 1 (Vec3): gate=+0x1C, AnimData=+0x10, output=+0x00
  Track 2 (Vec3): gate=+0x38, AnimData=+0x2C, output=+0x30
  Track 3 (float): gate=+0x70, AnimData=+0x64, output=+0x80
  Track 4 (Vec3): gate=+0x54, AnimData=+0x48, output=+0x50

Bug #19: Track 4 output was +0xA0 (should be +0x50), and tracks 2/4
were float (should be Vec3). Wrong output offsets corrupted stack data,
causing bone_rt pointer to contain float bit patterns.

Teardown: hook World_HandleLogoutCleanup (0x491180) instead of
CleanupWorldAndEntities (0x66FC40). Fires at START of logout sequence
before any Lua callbacks trigger model processing on freed data.
Prologue: PUSH ESI/EDI, epilogue: POP EDI/ESI, JMP (tail call).

Also: added bone_sse_reference.zig as separate compilation unit for
A/B testing the proven-working version independently.
2026-03-15 02:22:18 -07:00
MarcelineVQ e70015579f silicon: add 116 probe hooks for call-counting all explored functions
Probe infrastructure using comptime probeDetour() that generates
per-function detours: atomic counter increment + callOriginal passthrough.
Hit counts reported on shutdown. Hooks installed at lateInit (engine init),
not DLL load time.

Key findings during decompilation:
- 0x40CF81 is GetFPUControlWord, not ftol (silicon mislabel)
- Real __ftol at 0x40A2B0 (51+ callers, hot path)
- 0x7B7A80/7B7B10 use normal stack floats, not FPU register params
- 0x686640/686820/6868E0 are bounding volume ops, not vector ops
- 0x7786A0 is a UI model constructor, not SetModelLighting
2026-03-15 01:55:09 -07:00
MarcelineVQ 66696e993a silicon: decompile and annotate all ~100 hooked WoW.exe functions
Complete catalog of libSiliconPatch240.dll hook targets with verified
calling conventions, parameter layouts, RET stack cleanup, and functional
descriptions from Ghidra analysis. Zero unknown stubs remain.
2026-03-15 01:22:43 -07:00
MarcelineVQ cb5fb46012 bone_sse: fix 3 more bugs from assembly verification (#15-17)
Bug #15: Short-value interpolation read AD+0x0C (nTimestamps count) as
pointer to short array. Should be AD+0x18 (ofsValues). Caused segfault
reading from address ~0x134 (a count value treated as pointer).

Bug #16: boneKeyframeLoop AnimData offsets wrong. Rotation was kf_data+0x10,
should be +0x1C. Scale was kf_data+0x28 with garbled gate, should be +0x38
with gate at +0x44. Translation was correct at +0x00. Entry structure is
3x28-byte AnimBlocks: trans(+0x00), rot(+0x1C), scale(+0x38) = stride 0x54.

Bug #17: particleEmitterLoop (model_hdr+0x124) position AnimData was
entry+0x04, should be entry+0x10 with gate at entry+0x1C. Second track
was entry+0x20, should be entry+0x38/gate +0x44. Third track at
entry+0x60/gate +0x6C was missing entirely.

Also fixed IsParticleBufferEmpty calling convention (bug #14):
was __stdcall with stack param, now __fastcall(ECX=ptr) plain RET.

SSE dispatch enabled for A/B testing.
2026-03-14 23:45:20 -07:00
MarcelineVQ c3a2f1b157 minimapicons: remove unused CheckQuestAvailability address 2026-03-14 23:18:58 -07:00
MarcelineVQ 944e398e41 minimapicons: add quest available tracking to minimap
Reads quest giver status from unit+0xCB8 (populated by client's
SMSG_QUESTGIVER_STATUS packet handler) to show yellow ! icons on the
minimap for NPCs with available quests. Uses the vanilla gossip frame
AvailableQuestIcon texture. Enabled by default, togglable from the
tracking dropdown like other NPC categories.
2026-03-14 23:18:17 -07:00
MarcelineVQ 3c9a95d51e ssemaths: extract UnitXP math polyfill hooks into standalone module
Moved math_sse.zig and all 17 hook declarations + CriticalSection spin
count optimization from transform44 into new ssemaths module. Off by
default (-Dssemaths=true to enable).

transform44 retains its profiling hooks and blit_hub optimization.
ssemaths is a clean standalone module with its own mutex, install/remove
lifecycle, and lateInit for post-UnitXP hook clobbering.

Added performance note to math_sse.zig documenting that hook-based
replacement adds ~5-8 cycles overhead that makes small functions slower,
and that in-place patching is the path to realize the full 2-4x gains
shown in inlined benchmarks.
2026-03-14 22:28:31 -07:00
MarcelineVQ b9603c75f9 bench: add inlined x87 vs SSE comparison, update release notes
Inlined benchmarks use x87 inline asm vs direct Zig @Vector code with
no CALL/RET on either side. Shows the true instruction-level comparison
that in-place patching would achieve:

  dotProduct:  x87=3 SSE=1 -> 3.0x (was 0.3x when called)
  evalPoly:    x87=4 SSE=1 -> 4.0x (was 0.7x when called)
  vec3MulScal: x87=3 SSE=2 -> 1.5x (was 0.8x when called)

Every "loser" from the called benchmarks flips to a winner when inlined.
The entire performance gap was function call overhead (~5 cycles), not
instruction quality. Confirms in-place patching as the right strategy.

Also added RADV_TEX_ANISO env var exploration item to release notes and
updated math polyfill section with full benchmark breakdown.
2026-03-14 22:12:04 -07:00
MarcelineVQ 0671ffce02 math_sse: SIMD optimizations for planeNormal, transformAABox, and others
Added V4 vector helpers (loadV3, storeV3, dot3) and rewrote several
functions to use @Vector(4, f32) operations instead of scalar f64:

- planeNormal: 0.7x -> 1.7x (79 -> 33 cyc) -- V4 cross + normalize
- transformAABox: 0.7x -> 1.2x (118 -> 69 cyc) -- f32 + @min/@max
- evaluatePolynomial: 0.4x -> 0.6x (24 -> 16 cyc) -- drop f64 promotion
- vec3MulScalar: 0.7x -> 0.8x -- V4 splat multiply
- dotProduct/squaredMagnitude: rewritten with dot3 helper

crossProduct reverted from shuffle-SIMD back to scalar -- shuffles added
latency that the x87 pipeline doesn't have (16 vs 13 cyc).

Remaining losers (dotProduct 0.4x, evalPoly 0.6x) are at the function
call overhead floor -- the original x87 is 5-11 cycles, which is close
to bare call/ret cost.
2026-03-14 21:57:14 -07:00
MarcelineVQ 99503f883f bench: fresh data each iteration to fix overflow artifacts
In-place functions (vec3MulAssign, scaleByVec, etc.) were showing fake
speedups (121x, 10x) because repeated application on the same data
caused values to overflow to Inf/NaN. x87 FPU traps on denormals while
SSE handles them in hardware, making the comparison meaningless.

Now each iteration resets from a template copy. Real results show these
functions are ~1.0x (neutral), not 100x wins. The actual winners are
rotMat3x3/4x4 at 2.1x, and several functions where our scalar-through-
pointers approach is slower than the original x87 pipeline (dotProduct
0.3x, evaluatePolynomial 0.4x) — candidates for real SIMD optimization.
2026-03-14 21:41:56 -07:00
MarcelineVQ db28746182 bench: add x86 Linux micro-benchmark harness for math_sse functions
Extracts original x87 FPU bytes from WoW.exe via Ghidra, mmaps them
executable, and benchmarks against our SSE replacements. Covers all 17
UnitXP polyfill functions with correctness validation and cycle counts.

Maps a page at 0x7ff000 for the float 1.0 constant referenced by
rotMat3x3/rotMat4x4/planeNormal via absolute address 0x7ff9d8.

Build: zig build bench / zig build run-bench
2026-03-14 19:29:41 -07:00
MarcelineVQ 8307e525d9 math_sse: fix calling conventions from Ghidra disassembly verification
Hooks 1,3 (vecMulMat4, quatMulMat4): thiscall->fastcall. Assembly
confirms EDX is read as a parameter (FMUL [EDX+...]) before any write.
Detour types TC2r->FC3r, AB wrappers abTC2r->abFC3r.

Hook 15 (0x699330): removed entirely. Was misidentified as vectorNormalize
but Ghidra shows it's a vec3 componentwise >= comparison returning u32.
Silicon-only function (not UnitXP), stub already in silicon.zig. Our
normalize implementation would have silently corrupted comparison results.

Also fixed comment accuracy: hook 8 RET 0x4 (not plain RET), hook 14
__thiscall (not __fastcall). Bitmask selection replaces MATH_TEST_HOOK
single-select with proper bit flags. 17 hooks total, mask 0x77FFE.
2026-03-14 18:44:22 -07:00
MarcelineVQ 2018ca45fa bone_sse: fix IsParticleBufferEmpty calling convention
Was __stdcall with stack param — would corrupt stack for every visible
particle emitter. Fixed to __fastcall(ECX=ptr) matching assembly at
0x717EB8: MOV ECX,[EBP-0x10]; CALL 0x7B5F60. Plain RET, no stack cleanup.

Bug #14 found during final verification pass.
2026-03-14 17:45:46 -07:00
MarcelineVQ c060f0d469 bone_sse: verify remaining particle sections from assembly
Section 12c (model_hdr+0x134): visibility byte animation pattern verified
from assembly at 0x7176C2-0x717774. Byte array indexing, crossfade output
at +0xCC (not +0xBC). Position track at entry+0x24 with 12-byte keyframes.

Section 12e (model_hdr+0x13C): all 10 tracks verified. Tracks 1-6 use
scalar float interpolation (findInterpIdx + 4-byte keyframes). Tracks 7-10
use getInterpolatedFloat (0x71AF20). Track offsets, gate checks, and output
positions all confirmed from assembly.

SSE dispatch remains disabled pending final testing.
2026-03-14 17:40:50 -07:00
MarcelineVQ 2b9b5bc043 bone_sse: assembly-verified reimplementation of transformMatrix4x4
13 bugs fixed by comparing against full assembly dump (5317 instructions):
- Emitter check: this+0x188 -> this+0x1D8
- Animation time: added FILD*time_scale pattern for both primary (+0xB0)
  and secondary (+0xDC) slots
- Conditional multiply: bone_local *= *(bone_rt+0xF0) was missing
- Billboard post-processing: 4 switch cases (types 8/16/32/64) implemented
- Color animation loop bound: model_hdr+0x64 -> +0x6C
- Bone keyframe data stride: 0x24 -> 0x54
- Ribbon emitter output stride: 0x15C -> 0x170
- Particle data/output strides: 0x1FC/0x17C -> 0x1F8/0x16C
- Child SceneObject offsets: attach_idx +0x184->+0x1D4, next +0x190->+0x1E4
- Root bone parent: identity -> this+0xFC

New files:
- BONE_SSE_PROGRESS.md: section-by-section verification status
- t44_full_asm.txt: complete function assembly (ground truth)
- t44_helpers_asm.txt: all 12 helper function assemblies
- math_sse.zig: 18 x87->SSE polyfill stubs (VanillaFixes integration)

Also: OnWorldUpdate hook for true per-frame counting, DUMP_FRAMES=450.
SSE dispatch currently disabled while particle sections are being verified.
2026-03-14 17:34:37 -07:00
MarcelineVQ 236cf95bdd Add file cache and timer fix to release notes and DLL_README 2026-03-14 15:03:31 -07:00
MarcelineVQ 692a02ed2c Move file cache to standalone module, add timer fix, fix refcount crashes
File cache (filecache module):
- Moved from transform44 sub-module to standalone src/filecache/
- 2-way set-associative cache (32768 sets x 2 ways) with FNV-1a hash + finalizer
- Fixed negative cache hit crash: zero output params before returning 0
  (FindFileInArchive reuses filename slot for out_outer_archive)
- Fixed path 2 crash: set out_outer_archive on all cache hit paths
- Fixed stale block_entry crash: cache block index instead of raw pointer,
  recompute from archive+0x290 on each hit
- Fixed archive-freed crash: use game's FindAndIncrementResourceReference
  (0x650780) instead of manual +0x38 increment -- validates archive is alive
- Periodic stats dump with projected time savings (hit=~1000cy vs miss=~30000cy)

Timer fix (transform44 sub-module, ported from VanillaFixes):
- TSC calibration via QPC reference over 500ms
- Enables TSC mode if game was using GetTickCount fallback
- NtSetTimerResolution for 0.5ms OS timer granularity
- SetProcessInformation to disable Windows 11 power throttling
- Always-on (no A/B toggle -- no measurable impact on Wine/Linux)
2026-03-14 14:57:58 -07:00
MarcelineVQ 0833d23e28 WIP: bone_sse SSE reimplementation of transformMatrix4x4 + OnWorldUpdate frame counter
bone_sse.zig: Full standalone SSE reimplementation of the 17703-byte bone
transform engine (0x714260). All helper functions reimplemented inline
(findInterpolationIndices, interpolateAnimationKeyframes, scaleMatrix3x3,
ApplyTranslation, rotateByQuaternion). Currently disabled (A/B dispatch
commented out) due to NULL ofsValues crash under investigation.

transform44.zig: Add OnWorldUpdate (0x482EA0) hook for true per-frame
counting. Previous frame counter used executeSceneRenderPass which fires
multiple times per frame (shadows, reflections, spell effects), causing
A/B periods to be as short as 0.5s during combat instead of ~30s.

SCENEOBJECT_OFFSETS.md: Complete assembly-verified field offset map
(51 offsets) extracted from [EBX+N] patterns in transformMatrix4x4.
Corrects bone_runtime_base from +0x80 to +0x090.
2026-03-14 00:22:36 -07:00
MarcelineVQ 55735d7b9e Add MPQ archive file cache: skip redundant chain walks on repeat file opens
Hook File_FindInArchive (0x6549a0) with a direct-mapped filename-verified
cache. First open does the full MPQ chain walk (~60K cycles), subsequent
opens hit the cache (~300 cycles). 80% hit rate in gameplay testing.

Cache design: 16384 entries, FNV-1a hash for slot index, raw filename
comparison (128 bytes) for collision safety. Stores outer_archive,
inner_archive, and block_entry per file. Negative cache for not-found files.
Refcount at +0x38 incremented on all output archives to match original
File_FindInArchive behavior (verified via Ghidra: FindAndIncrementResourceReference
at 0x650780, DecrementResourceReference at 0x6507e0).

Also: default build changed to ReleaseFast (works around Zig fastcall inreg
bug in Debug mode), logging gate changed to != ReleaseSmall, file cache
integrated as transform44 sub-module with A/B comparison timing in readout.
2026-03-13 23:05:38 -07:00
MarcelineVQ bb8a6ef8a6 Update release notes with all unreleased changes since v0.5.0
Covers new modules (world markers, dpslog, interact, crash fix, outlines,
addonperf), enhancements (clickthrough lootable corpses, BG disable),
bug fixes, and internal performance work (transform44, glyph cache,
file cache, logging, addon system refactor).
2026-03-13 22:27:18 -07:00
MarcelineVQ e978710c44 Clickthrough: lootable corpses prioritize over players
Lootable unit corpses (UNIT_DYNFLAG_LOOTABLE) now click-through over
blocking players, matching the existing behavior for interactable NPCs
and game objects. Adds isLootable() check to the interactable NPC test
and UNIT_DYNAMIC_FLAGS descriptor offset to shared offsets.

Also adds RELEASE_NOTES.md for tracking changes between releases, and
adds a checklist item in RELEASING.md to review it before publishing.
2026-03-13 22:22:54 -07:00
MarcelineVQ d9701c528d Add glyph shadow cache: direct-mapped O(1) bypass for game's 4-bucket hash table
GetOrCreateCharacterGlyph (0x5ca2d0) is the #2 CPU hotspot at 3.65%.
The game's glyph cache uses only 4 hash buckets for ~95 ASCII chars,
causing ~24-entry chain walks with pointer chasing on every lookup.
Text measurement (99.8% of calls) re-walks these chains per character,
thousands of times per frame during UI updates.

Shadow cache: 4096-entry direct-mapped array with Murmur2 hash,
keyed on (FontObject*, charCode, param2). Cache hit returns the
cached float width via FPU ST(0) inline asm, skipping the chain
walk entirely. Gated behind ab_use_custom for A/B benchmarking.
2026-03-13 11:55:17 -07:00
MarcelineVQ 79d9521953 Refactor addon system: derive module list from build.zig, prune inactive prefixes at runtime
- Eliminate hardcoded module_names in addons.zig — now derived from
  build.zig via all_module_names build option
- Add module_active.zig runtime registry: main.zig registers isActive
  pointers during install(), addons.zig queries them without importing
  each module directly
- Prune embedded file prefixes at startup: after all modules claim
  mutexes, build a runtime active_prefixes table excluding modules we
  don't own. findEmbeddedFile searches only active entries — no
  per-lookup isActive check on the hot path
- Tag both addon and asset prefixes with module_name so pruning applies
  to all embedded files for a module
- Rename src/markers/ → src/worldmarkers/, src/outline/api.zig →
  src/outline/outline.zig to follow {name}/{name}.zig convention
2026-03-13 11:40:49 -07:00
MarcelineVQ c770e053aa Add addonperf stub module for TBC+ addon profiling API
Registers GetAddOnMemoryUsage, UpdateAddOnMemoryUsage, GetAddOnCPUUsage,
UpdateAddOnCPUUsage, ResetAddOnCPUUsage, and GetScriptCPUUsage as global
Lua functions. All stubs for now — implementation will hook lua_Alloc
and FrameScript dispatch for per-addon tracking.
2026-03-12 23:38:36 -07:00
MarcelineVQ 751063b4b0 Fix addon files not loading: use @hasDecl instead of @hasField
@hasField only works on struct fields, not module declarations.
build_options is a module, so @hasField always returned false,
silently skipping all addon file embedding and registration.
2026-03-12 23:35:18 -07:00
MarcelineVQ 2b22f59824 Add shared D3D9 device vtable helper module
Extracts direct IDirect3DDevice9 COM vtable wrappers for use by
multiple modules (outline, transform44) bypassing GxDevice abstraction.
2026-03-12 19:13:24 -07:00