docs: flag N.2 blocker — WB terrain split formula diverges from retail

Audit during N.3 follow-up uncovered that WB's TerrainUtils
CalculateSplitDirection uses a different math expression than
retail's FSplitNESW (the AC2D-cited polynomial 0x0CCAC033 etc that
our visual terrain mesh and physics already share). Substituting
TerrainSurface.SampleZ with WB's GetHeight in isolation would
re-introduce the triangle-Z hover bug from earlier work — physics
and visual mesh would pick different diagonals on disputed cells.

Updates:
- ISSUE #51 documents the divergence with file references and the
  research that's needed when N.5 picks this up.
- Roadmap N.2 entry flags the dependency on N.5 and the reasoning
  ("not low-risk after all").

N.1's conformance proved slope-filtering equivalence (boolean
walkable verdict), not formula equivalence. The lesson is captured
in memory (feedback_wb_migration_formulas.md, not in-repo).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-05-08 12:05:04 +02:00
parent c189ec0c40
commit 1ede87a135
2 changed files with 74 additions and 4 deletions

View file

@ -574,10 +574,23 @@ for our deletions/additions; merge upstream `master` periodically.
formula was ~180° off from retail's actual `Frame::set_heading` atan2
round-trip). One known cosmetic difference filed in ISSUES.md
(road-edge tree at landblock 0xA9B1).
- **N.2 — Terrain math helpers.** Refactor `TerrainSurface.SampleZ` /
`SampleNormal` / `SampleSurface` to call WB's `TerrainUtils.GetHeight`
/ `GetNormal` internally. ~1-2 days. Smallest remaining N phase, low
risk after N.1's conformance proof on GetNormal.
- **N.2 — Terrain math helpers.** ⚠️ **Blocked on N.5 — do not attempt
in isolation.** Originally scoped as a 1-2 day low-risk substitution
of `TerrainSurface.SampleZ` / `SampleSurface` / `SampleSurfacePolygon`
with WB's `TerrainUtils.GetHeight` / `GetNormal`. Audit during N.3
follow-up uncovered that **WB's `CalculateSplitDirection` uses a
different formula than retail's `FSplitNESW`** (the AC2D-cited
polynomial `0x0CCAC033` / `0x421BE3BD` / `0x6C1AC587` / `0x519B8F25`
that our visual terrain mesh and physics already share). The
formulas pick different cell-diagonals on disputed cells, producing
up to ~2m Z divergence at the same world position. Substituting
physics-side alone would un-sync physics from the still-ours visual
mesh — exactly the triangle-Z hover bug class. N.1's conformance
test proved WB's `GetNormal` is good enough for slope-filtering
(boolean walkable check) but NOT that WB's height formula matches
retail. Resolution: fold this work into **N.5** when the visual
mesh switches to WB's renderer in lockstep with physics. Until
then, leave `TerrainSurface` alone. See ISSUE #51.
- **✓ SHIPPED — N.3 — Texture decoding.** Shipped 2026-05-08. `SurfaceDecoder`
now delegates INDEX16 / P8 / A8R8G8B8 / R8G8B8 / A8 to WB's
`TextureHelpers.Fill*`. The A8 divergence (our old code did R=G=B=A=val