feat(render) Campaign FW1: NINE of ten fixtures conformant - the degrade arm
The moving-fixture divergence was the building degrade ladder: GfxObjDegradeInfo::get_degrade @0x0051e4b0 (Ghidra-verified - BN's FPU-flag pseudo-C misread BOTH arm selection and one formula) slides each level's threshold from ideal toward MAX as the multiplier approaches 1, and the live client runs the positive arm at ~0.99, so level 0's portal-bearing BSP survives to ~max_dist (48 for the Holtburg cottages), not ideal (24). The recon note's "deg_mul = -0.99" was a sign misread; the negative arm's threshold slides toward MIN and contradicts the fixtures from both directions. With the two-arm port, holtburg-walkout, holtburg-transitions, and holtburg-walkabout pass every pairable frame - the walkout-F2 microscope's prediction (103,100 | 100x3 | 124 through the cottage exit views) landed exactly. Also this round, falsified and reverted: a BN-driven swap of the portal walker's negative/in-plane arms (Ghidra shows side 1 = negative EMITS, side 2 = in-plane does not - the original port was correct; the swap broke four fixtures). The walker docs and unit tests now pin the Ghidra-verified truth table. Parked with findings: foundry-entry F67 (right flood set, one plane-side classification at the +/-eps boundary orders 11d before 116/118) and doorway-still (retail shows zero floods at a pose one meter from walkout-F2's flooding pose; multi-portal clip boundary). Suites: Walk 124/3 skips; hermetic 6,687/0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
17ee543cb1
commit
c1a029edf9
4 changed files with 201 additions and 22 deletions
|
|
@ -77,23 +77,38 @@ public sealed class WalkBuilding
|
|||
/// client; registry-configurable).</summary>
|
||||
public const float DefaultDegradeDistance = 100f;
|
||||
|
||||
/// <summary>The live capture client's <c>Render::deg_mul</c> arm:
|
||||
/// magnitude 0.99 pinned by the walkout-F2 fixture (building a9b4001e,
|
||||
/// effective 30.3, level 0 ideal/max 24/48 — retail floods, so its
|
||||
/// threshold ≈ max ⇒ the POSITIVE arm; the recon note's "−0.99" sign was
|
||||
/// a misread — the negative arm's threshold ≈ min contradicts the
|
||||
/// fixture from both directions). Re-dump at the next retail session.</summary>
|
||||
public const float DefaultDegradeMultiplier = 0.99f;
|
||||
|
||||
/// <summary>
|
||||
/// <c>GfxObjDegradeInfo::get_degrade</c> @0x0051e4b0 (the live client's
|
||||
/// arm: <c>auto_update_deg_mul</c> with a non-positive multiplier →
|
||||
/// thresholds are the levels' IDEAL distances): effective =
|
||||
/// max(0, distance − degradeDistance); the FIRST level with
|
||||
/// effective < IdealDist wins; no match → the LAST level. The
|
||||
/// positive-bias arm (threshold = ideal − (ideal−max)·mul) is a port
|
||||
/// follow-up if a fixture ever pins a positive bias.
|
||||
/// <c>GfxObjDegradeInfo::get_degrade</c> @0x0051e4b0 (BN flag-mush on
|
||||
/// the FPU compares — cross-checked against the Ghidra decomp, which is
|
||||
/// clean): effective = max(0, |distance| − degradeDistance), then the
|
||||
/// FIRST level whose threshold exceeds effective wins; no match → the
|
||||
/// LAST level. The threshold depends on the multiplier's sign:
|
||||
/// mul ≥ 0 → <c>ideal − (ideal − max)·mul</c> (ideal→max as mul→1);
|
||||
/// mul < 0 → <c>ideal + (ideal − min)·mul</c> (ideal→min as mul→−1).
|
||||
/// mul is <c>Render::deg_mul</c> when <c>auto_update_deg_mul</c> is on,
|
||||
/// else <c>s_rUserSuppliedDegradeBias</c>.
|
||||
/// </summary>
|
||||
public WalkBspNode? SelectDrawingBsp(
|
||||
float viewerDistance, float degradeDistance = DefaultDegradeDistance)
|
||||
float viewerDistance,
|
||||
float degradeDistance = DefaultDegradeDistance,
|
||||
float degradeMultiplier = DefaultDegradeMultiplier)
|
||||
{
|
||||
if (DegradeLevels.Length == 0) return DrawingBsp;
|
||||
float effective = MathF.Max(0f, MathF.Abs(viewerDistance) - degradeDistance);
|
||||
foreach (WalkBuildingDegradeLevel level in DegradeLevels)
|
||||
{
|
||||
if (effective < level.IdealDist)
|
||||
float threshold = degradeMultiplier >= 0f
|
||||
? level.IdealDist - (level.IdealDist - level.MaxDist) * degradeMultiplier
|
||||
: level.IdealDist + (level.IdealDist - level.MinDist) * degradeMultiplier;
|
||||
if (effective < threshold)
|
||||
return level.DrawingBsp;
|
||||
}
|
||||
return DegradeLevels[^1].DrawingBsp;
|
||||
|
|
@ -130,10 +145,16 @@ public static class WalkBuildingPortals
|
|||
|
||||
/// <summary>
|
||||
/// <c>BSPTREE::build_draw_portals_only</c> @0x00539860 + the node/portal
|
||||
/// walkers: dispatch the root, then walk plane-side ordered — the child
|
||||
/// OPPOSITE the viewer first, so portals emit far-to-near. PORT nodes
|
||||
/// emit every in_portal on the POSITIVE and NEGATIVE arms; the IN_PLANE
|
||||
/// arm (|d| ≤ ε) visits the positive child and emits NOTHING.
|
||||
/// walkers (<c>BSPNODE</c> @0x0053c100, <c>BSPPORTAL</c> @0x0053d870;
|
||||
/// side arms verified against the GHIDRA decomp 2026-08-30 — BN's
|
||||
/// FPU-flag pseudo-C reads the negative/in-plane split ambiguously, and
|
||||
/// a "corrected" swap of these arms broke four fixtures before being
|
||||
/// falsified; Ghidra: <c>d ≤ ε → side=1 unless −ε ≤ d → side=2</c>):
|
||||
/// dispatch the root, then walk plane-side ordered — the child OPPOSITE
|
||||
/// the viewer first, so portals emit far-to-near. PORT nodes emit every
|
||||
/// in_portal on the POSITIVE (d > ε) and NEGATIVE (d < −ε) arms;
|
||||
/// the IN_PLANE arm (|d| ≤ ε) visits the positive child and emits
|
||||
/// NOTHING. Plain nodes group IN_PLANE with NEGATIVE.
|
||||
/// </summary>
|
||||
public static void BuildDrawPortalsOnly(
|
||||
WalkBspNode? root, int pass, Vector3 viewpointInBuilding,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue