acdream/tests/AcDream.Core.Tests/Physics/SupportProbeClassifierTests.cs
Erik 49a7e90652 probe(physics): ACDREAM_PROBE_SUPPORT + ACDREAM_WIRE_MESH — separate #337's three candidates
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>
2026-08-06 19:49:59 +02:00

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 _));
}
}