The user is wedged at the top of Neftet rock plateaus, jumps sink into the mesh, and a corpse falls straight through. ACDREAM_PROBE_REACH already ruled out its own domain: blocked=0, every candidate tested-ok. Three candidates remain — terrain support, a collision mesh not where its visual is, or the transition wedging on an unobstructed path. ACDREAM_PROBE_RESOLVE alone cannot separate them. It prints a three-value contact-plane token, no plane normal, no plane height, no terrain sample and no plane provenance, so all three produce the same line. Two additions: [support] — one line per resolve for EVERY body, not just the player. A corpse is a plain physics body with no player-specific logic, so its fall-through is the cheapest available control on "movement code vs geometry data", and it is invisible to any player-filtered probe. The line samples the outdoor terrain INDEPENDENTLY at the body's own out-XY and prints the contact plane's own height at that same XY. Two heights at one point make support=terrain / object / none a measurement rather than an inference, and cpSrc= names the site that asserted the plane so provenance and classification cross-check. [geom] — once per GfxObj that comes near a mover: the object's physics-BSP vertex cloud against its visual mesh AABB in the same local frame, through the same prepared accessors the resolver queries. verdict=coincident REFUTES the working hypothesis for that object outright; no-physics-bsp / empty-physics-bsp / displaced / extent-mismatch each name a specific data defect. Built to refute, not to confirm — two diagnoses on this defect's lineage have already been refuted by measurement. ACDREAM_WIRE_MESH upgrades the existing F2 overlay, which drew a broadphase proxy cylinder for BSP objects and so could not answer the question at all, to the real physics-BSP polygon edges (cyan) beside the visual mesh box (magenta) and the terrain surface (yellow). Own class per code-structure rule 1. The provenance latch lives on PhysicsDiagnostics, not on CollisionInfo. Two fields there first — the obvious home — broke the flat/graph differential referee and the scratch-reset poison test, both of which compare CollisionInfo member-for-member. Teaching either to skip a member is a one-line green fix that puts a permanent hole in a referee whose whole job is comparing everything. Captured as feedback_probe_state_off_compared_types. Seven tests cover the support classifier's boundaries: a wrong classifier does not fail to answer, it answers confidently wrong. Gates: Release build 0 errors; complete suite 11,225 passed / 4 skipped / 0 failed from a cleaned tree — baseline 11,218/4/0 plus exactly the seven new tests, skips unchanged. Issue #337 filed with the symptom set, what is ruled out, and a table of what each possible output means. All of this is TEMPORARY and recorded for stripping with the physics-probe family. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
120 lines
4 KiB
C#
120 lines
4 KiB
C#
using System.Numerics;
|
|
using AcDream.Core.Physics;
|
|
using Xunit;
|
|
|
|
namespace AcDream.Core.Tests.Physics;
|
|
|
|
/// <summary>
|
|
/// #337 (2026-08-06 — TEMPORARY, delete with the <c>[support]</c> probe).
|
|
///
|
|
/// <para>
|
|
/// The <c>[support]</c> line's whole value is its <c>support=</c> verdict:
|
|
/// terrain, an object surface, or nothing. If that classifier is wrong, a
|
|
/// capture does not merely fail to answer — it answers CONFIDENTLY WRONG, and
|
|
/// this campaign has already spent two diagnoses on confident wrong answers.
|
|
/// These cover the decision boundaries directly, so the live capture can be
|
|
/// read at face value.
|
|
/// </para>
|
|
/// </summary>
|
|
public sealed class SupportProbeClassifierTests
|
|
{
|
|
private const float FlatNormalZ = 1f;
|
|
|
|
[Fact]
|
|
public void NoContactPlane_IsUnsupported()
|
|
{
|
|
Assert.Equal(
|
|
"none",
|
|
PhysicsDiagnostics.ClassifySupport(
|
|
contactPlaneValid: false,
|
|
terrainSampled: true,
|
|
contactPlaneZAtXY: 100f,
|
|
contactPlaneNormalZ: FlatNormalZ,
|
|
terrainZ: 100f,
|
|
terrainNormalZ: FlatNormalZ));
|
|
}
|
|
|
|
[Fact]
|
|
public void PlaneAtTerrainHeightAndTilt_IsTerrain()
|
|
{
|
|
Assert.Equal(
|
|
"terrain",
|
|
PhysicsDiagnostics.ClassifySupport(
|
|
contactPlaneValid: true,
|
|
terrainSampled: true,
|
|
contactPlaneZAtXY: 41.25f,
|
|
contactPlaneNormalZ: 0.94f,
|
|
terrainZ: 41.26f,
|
|
terrainNormalZ: 0.94f));
|
|
}
|
|
|
|
[Fact]
|
|
public void PlaneWellAboveTerrain_IsObject()
|
|
{
|
|
// The rock-plateau shape: the body rests six metres above the ground.
|
|
Assert.Equal(
|
|
"object",
|
|
PhysicsDiagnostics.ClassifySupport(
|
|
contactPlaneValid: true,
|
|
terrainSampled: true,
|
|
contactPlaneZAtXY: 47.5f,
|
|
contactPlaneNormalZ: FlatNormalZ,
|
|
terrainZ: 41.5f,
|
|
terrainNormalZ: 0.9f));
|
|
}
|
|
|
|
[Fact]
|
|
public void SameHeightDifferentTilt_IsReportedSeparately()
|
|
{
|
|
// A collision surface lying flat against sloped ground. This must NOT
|
|
// collapse into either answer: it is precisely the ambiguous case, and
|
|
// guessing between them is what the probe exists to avoid.
|
|
Assert.Equal(
|
|
"coplanar-tilt-mismatch",
|
|
PhysicsDiagnostics.ClassifySupport(
|
|
contactPlaneValid: true,
|
|
terrainSampled: true,
|
|
contactPlaneZAtXY: 41.5f,
|
|
contactPlaneNormalZ: 1.0f,
|
|
terrainZ: 41.5f,
|
|
terrainNormalZ: 0.72f));
|
|
}
|
|
|
|
[Fact]
|
|
public void NoTerrainUnderTheBody_SaysSoRatherThanGuessing()
|
|
{
|
|
Assert.Equal(
|
|
"no-terrain",
|
|
PhysicsDiagnostics.ClassifySupport(
|
|
contactPlaneValid: true,
|
|
terrainSampled: false,
|
|
contactPlaneZAtXY: 12f,
|
|
contactPlaneNormalZ: FlatNormalZ,
|
|
terrainZ: float.NaN,
|
|
terrainNormalZ: float.NaN));
|
|
}
|
|
|
|
[Fact]
|
|
public void PlaneHeightIsEvaluatedAtTheBodysOwnXy()
|
|
{
|
|
// A 45-degree ramp through the origin: height must track X, or a body
|
|
// standing on a slope would read as displaced from its own support.
|
|
var slope = new Plane(Vector3.Normalize(new Vector3(-1f, 0f, 1f)), 0f);
|
|
|
|
Assert.True(PhysicsDiagnostics.TryPlaneZAt(slope, 0f, 0f, out float atOrigin));
|
|
Assert.Equal(0f, atOrigin, 3);
|
|
|
|
Assert.True(PhysicsDiagnostics.TryPlaneZAt(slope, 10f, 0f, out float atTen));
|
|
Assert.Equal(10f, atTen, 3);
|
|
}
|
|
|
|
[Fact]
|
|
public void VerticalPlaneHasNoHeight()
|
|
{
|
|
// A wall is never a floor. Reporting a height for one would read as a
|
|
// wildly displaced surface and manufacture a false positive.
|
|
var wall = new Plane(new Vector3(1f, 0f, 0f), -5f);
|
|
|
|
Assert.False(PhysicsDiagnostics.TryPlaneZAt(wall, 0f, 0f, out _));
|
|
}
|
|
}
|