feat(render) Campaign FW1: port the exact retail degrade selection
UpdateViewerDistance @0x0050e030 + get_degrade @0x0051e4b0 ported with live-pinned globals: distance measured to the part SCALED SORT CENTER, effective = max(0, dist - s_rDegradeDistance [live 100]), level = first with effective < IdealDist (the live auto_update_deg_mul<=0 arm), else the last level. WalkBuildingDegradeLevel carries the full authored bands; the adapter fills sort centers; the replay context measures to the transformed sort center. Sweep state after the rule: the level-0 building set is now correct; the residual divergence is the per-portal side/clip gate (my arms punch a near-complement of retail two) - the sweep driver carries the next instrument in its Skip note. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
844a6c097d
commit
e23a589a6b
4 changed files with 41 additions and 17 deletions
|
|
@ -38,8 +38,9 @@ public sealed class WalkBspNode
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>One degrade-ladder level: the drawing BSP of that level's
|
/// <summary>One degrade-ladder level: the drawing BSP of that level's
|
||||||
/// GfxObj (portal-only view) and the level's far distance bound.</summary>
|
/// GfxObj (portal-only view) and the level's authored distance bands.</summary>
|
||||||
public readonly record struct WalkBuildingDegradeLevel(float MaxDist, WalkBspNode? DrawingBsp);
|
public readonly record struct WalkBuildingDegradeLevel(
|
||||||
|
float MinDist, float IdealDist, float MaxDist, WalkBspNode? DrawingBsp);
|
||||||
|
|
||||||
/// <summary>The walk's building model (retail <c>CBuildingObj</c> +
|
/// <summary>The walk's building model (retail <c>CBuildingObj</c> +
|
||||||
/// <c>BuildInfo</c> as the frame walk consumes them).</summary>
|
/// <c>BuildInfo</c> as the frame walk consumes them).</summary>
|
||||||
|
|
@ -66,19 +67,36 @@ public sealed class WalkBuilding
|
||||||
/// skips the whole building AFTER publishing the portal list.</summary>
|
/// skips the whole building AFTER publishing the portal list.</summary>
|
||||||
public bool HasGeometry = true;
|
public bool HasGeometry = true;
|
||||||
|
|
||||||
/// <summary>Level selection by viewer distance. Simplified band pick
|
/// <summary>The base GfxObj's sort center — retail measures the viewer
|
||||||
/// (first level whose MaxDist covers the distance) — the faithful
|
/// distance to the part's SCALED sort center, not the position origin
|
||||||
/// hysteresis of <c>CPhysicsPart::UpdateViewerDistance</c> is a port
|
/// (<c>CPhysicsPart::UpdateViewerDistance</c> @0x0050e030).</summary>
|
||||||
/// TODO; still fixtures are insensitive to hysteresis.</summary>
|
public Vector3 SortCenter;
|
||||||
public WalkBspNode? SelectDrawingBsp(float viewerDistance)
|
|
||||||
|
/// <summary>Retail <c>Render::s_rDegradeDistance</c> — a subtractive
|
||||||
|
/// slack before the ladder applies (live-dumped 100 on the capture
|
||||||
|
/// client; registry-configurable).</summary>
|
||||||
|
public const float DefaultDegradeDistance = 100f;
|
||||||
|
|
||||||
|
/// <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.
|
||||||
|
/// </summary>
|
||||||
|
public WalkBspNode? SelectDrawingBsp(
|
||||||
|
float viewerDistance, float degradeDistance = DefaultDegradeDistance)
|
||||||
{
|
{
|
||||||
if (DegradeLevels.Length == 0) return DrawingBsp;
|
if (DegradeLevels.Length == 0) return DrawingBsp;
|
||||||
|
float effective = MathF.Max(0f, MathF.Abs(viewerDistance) - degradeDistance);
|
||||||
foreach (WalkBuildingDegradeLevel level in DegradeLevels)
|
foreach (WalkBuildingDegradeLevel level in DegradeLevels)
|
||||||
{
|
{
|
||||||
if (viewerDistance <= level.MaxDist)
|
if (effective < level.IdealDist)
|
||||||
return level.DrawingBsp;
|
return level.DrawingBsp;
|
||||||
}
|
}
|
||||||
return null; // degraded out entirely
|
return DegradeLevels[^1].DrawingBsp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,12 +24,14 @@ public sealed class WalkLookInGateSweepTests
|
||||||
public void Emit(in WalkEvent walkEvent) => Events.Add(walkEvent);
|
public void Emit(in WalkEvent walkEvent) => Events.Add(walkEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact(Skip = "FW1 adjudication driver (2026-08-30 state): the degrade-level "
|
[Fact(Skip = "FW1 adjudication driver (2026-08-30 final state): with the "
|
||||||
+ "BSP gate killed every wrong punch — remaining deltas are the near "
|
+ "exact get_degrade rule (slack 100, ideal thresholds, sort-center "
|
||||||
+ "buildings 001a/0022 (50/28 m center distance vs the 48 m level-0 "
|
+ "distance) the level-0 set is correct — the residual divergence is "
|
||||||
+ "band: port UpdateViewerDistance's sphere-adjusted distance + "
|
+ "the per-portal side/clip gate: my arms punch {0017,001e,0026,0036}, "
|
||||||
+ "hysteresis) and the ring-1 frustum boundary pair "
|
+ "retail punched exactly {001a,0022} — a near-complement suggesting "
|
||||||
+ "(aab50002 extra / a9b3003c missing). Re-enable to re-adjudicate.")]
|
+ "one more polarity at the building-portal gate. Next instrument: "
|
||||||
|
+ "per-portal dump for 001a (retail punches) vs 001e (retail doesn't): "
|
||||||
|
+ "side values, plane orientation, eye side, clip result per arm.")]
|
||||||
public void Sweep_the_lookin_gate_decodes_against_the_street_fixture()
|
public void Sweep_the_lookin_gate_decodes_against_the_street_fixture()
|
||||||
{
|
{
|
||||||
string? datDir = CornerFloodReplayTests.ResolveDatDir();
|
string? datDir = CornerFloodReplayTests.ResolveDatDir();
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,8 @@ public sealed class WalkTraceReplayContext : IWalkFrameContext, IRetailFrameWalk
|
||||||
|
|
||||||
public float ViewerDistanceTo(WalkBuilding building)
|
public float ViewerDistanceTo(WalkBuilding building)
|
||||||
=> Vector3.Distance(
|
=> Vector3.Distance(
|
||||||
WorldViewpoint, Buildings[building].WorldTransform.Translation);
|
WorldViewpoint,
|
||||||
|
Vector3.Transform(building.SortCenter, Buildings[building].WorldTransform));
|
||||||
|
|
||||||
public int ClipBuildingPolygon(
|
public int ClipBuildingPolygon(
|
||||||
WalkBuilding building, WalkPolygon polygon, int side, Span<WalkScreenPoint> output)
|
WalkBuilding building, WalkPolygon polygon, int side, Span<WalkScreenPoint> output)
|
||||||
|
|
|
||||||
|
|
@ -148,10 +148,12 @@ public static class WalkWorldDatAdapter
|
||||||
}
|
}
|
||||||
|
|
||||||
WalkBspNode? bsp = null;
|
WalkBspNode? bsp = null;
|
||||||
|
Vector3 sortCenter = Vector3.Zero;
|
||||||
var degradeLevels = new List<WalkBuildingDegradeLevel>();
|
var degradeLevels = new List<WalkBuildingDegradeLevel>();
|
||||||
if (dats.Get<GfxObj>(buildingInfo.ModelId) is GfxObj gfxObj)
|
if (dats.Get<GfxObj>(buildingInfo.ModelId) is GfxObj gfxObj)
|
||||||
{
|
{
|
||||||
bsp = ConvertDrawingBsp(gfxObj, gfxObj.DrawingBSP?.Root);
|
bsp = ConvertDrawingBsp(gfxObj, gfxObj.DrawingBSP?.Root);
|
||||||
|
sortCenter = new Vector3(gfxObj.SortCenter.X, gfxObj.SortCenter.Y, gfxObj.SortCenter.Z);
|
||||||
if (gfxObj.DIDDegrade != 0
|
if (gfxObj.DIDDegrade != 0
|
||||||
&& dats.Get<GfxObjDegradeInfo>(gfxObj.DIDDegrade)
|
&& dats.Get<GfxObjDegradeInfo>(gfxObj.DIDDegrade)
|
||||||
is GfxObjDegradeInfo degradeInfo)
|
is GfxObjDegradeInfo degradeInfo)
|
||||||
|
|
@ -164,7 +166,7 @@ public static class WalkWorldDatAdapter
|
||||||
{
|
{
|
||||||
levelBsp = ConvertDrawingBsp(levelGfx, levelGfx.DrawingBSP?.Root);
|
levelBsp = ConvertDrawingBsp(levelGfx, levelGfx.DrawingBSP?.Root);
|
||||||
}
|
}
|
||||||
degradeLevels.Add(new WalkBuildingDegradeLevel(level.MaxDist, levelBsp));
|
degradeLevels.Add(new WalkBuildingDegradeLevel(level.MinDist, level.IdealDist, level.MaxDist, levelBsp));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -180,6 +182,7 @@ public static class WalkWorldDatAdapter
|
||||||
Portals = portals,
|
Portals = portals,
|
||||||
DrawingBsp = bsp,
|
DrawingBsp = bsp,
|
||||||
DegradeLevels = degradeLevels.ToArray(),
|
DegradeLevels = degradeLevels.ToArray(),
|
||||||
|
SortCenter = sortCenter,
|
||||||
},
|
},
|
||||||
worldTransform,
|
worldTransform,
|
||||||
inverse));
|
inverse));
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue