Fix ShieldManager's missing-envelope fallback regression
ShieldManager's fallback (no ShieldEnvelope yet, e.g. every ship prefab before the operator hand-rigs it per Q5) hard-failed to a fixed Vector3.forward direction with a per-hit Debug.LogError. That silently changed behaviour for every ship in the game until the operator edits Longinus.prefab/Djinni.prefab on their own schedule -- worse than either the deleted code or a loud one-time failure. Extract ShieldEnvelope's local-direction primitive into two static, no-component-required entry points -- SurfaceDirection(Transform, Vector3) and LegacyLocalSurfacePoint(Transform, Vector3) -- so both FieldDriver's and ShieldManager's fallbacks go through one owner instead of each re-deriving the pre-Cut-2 formula by hand. ShieldManager's fallback now reproduces the deleted normalize(shield.transform.InverseTransformPoint(point)) formula exactly via the shared static, logging once (a warning, not a per-hit error). FieldDriver's fallback is rewired onto the same static rather than its own inline copy of the maths. Documented, not silently assumed: FieldDriver's "envelope present" branch (envelope.ProjectToSurface, world-space) and its fallback (the legacy local- space formula) are only numerically identical when the carrier's transform has zero position and identity rotation -- true of the one FieldDriver carrier in the tree today, not true in general. This is a characteristic of Cut 2 as scoped by the map (FieldDriver.cs:125 reads envelope.ProjectToSurface directly, per the map's own text), not something this fix introduces or resolves; flagged so nobody assumes the two branches are interchangeable for a future non-origin, non-identity FieldDriver carrier. ShieldPanelCut2Verify.CheckLegacyFallbackFormulas pins both static entry points against an independent re-derivation of the exact pre-Cut-2 formulas, on a carrier with a non-trivial transform (nonzero position, non-identity rotation) so the check isn't vacuous. mutation_tests_shield_panel_cut2.py gained two cases: SurfaceDirection collapsing back to a fixed Vector3.forward (the exact regression fixed here), and LegacyLocalSurfacePoint collapsing to the raw, untransformed world point -- both correctly fail the probe. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
a454a5a77f
commit
7848a8a86e
5 changed files with 201 additions and 18 deletions
|
|
@ -70,6 +70,13 @@ namespace Aetheria.EditorTools
|
|||
// perfect cube of extents -- see IcosphereExtents above), not a guessed flat 0.5.
|
||||
ok &= CheckRadiiUsesMeasuredExtents(longinus, new Vector3(395.3487f, 235.47029f, 990.37805f), IcosphereExtents, "Longinus");
|
||||
ok &= CheckRadiiUsesMeasuredExtents(cubesphere, new Vector3(4f, 3f, 12f), Vector3.one, "FieldShieldTest cubesphere");
|
||||
|
||||
// Pin the fallback: a carrier with NO ShieldEnvelope component must produce exactly
|
||||
// the pre-Cut-2 hit direction/position, for both FieldDriver's and ShieldManager's
|
||||
// formulas. This is what a Longinus/Djinni prefab looks like before the operator
|
||||
// adds the component (Q5) -- the fallback is not a courtesy, it is load-bearing for
|
||||
// every ship in the game until then.
|
||||
ok &= CheckLegacyFallbackFormulas();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
@ -238,6 +245,75 @@ namespace Aetheria.EditorTools
|
|||
return ok;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pins ShieldEnvelope's two static fallback entry points (SurfaceDirection,
|
||||
/// LegacyLocalSurfacePoint) against an INDEPENDENT re-derivation of the exact pre-Cut-2
|
||||
/// formulas -- not a call into the production code under test -- on a carrier with no
|
||||
/// ShieldEnvelope component and a non-trivial transform (nonzero position, non-identity
|
||||
/// rotation), so the check is not vacuous the way it would be at the origin with identity
|
||||
/// rotation. This is the regression the coordinator flagged: ShieldManager's fallback used
|
||||
/// to silently change behaviour (hard-fail to Vector3.forward) instead of reproducing the
|
||||
/// deleted formula, which would have shown every ship's shield hits in the wrong place
|
||||
/// until the operator hand-edits Longinus.prefab/Djinni.prefab.
|
||||
/// </summary>
|
||||
static bool CheckLegacyFallbackFormulas()
|
||||
{
|
||||
var go = new GameObject("LegacyFallbackProbe");
|
||||
bool ok = true;
|
||||
try
|
||||
{
|
||||
go.transform.position = new Vector3(5f, -2f, 13f);
|
||||
go.transform.rotation = Quaternion.Euler(15f, 40f, -8f);
|
||||
go.transform.localScale = new Vector3(4f, 3f, 12f);
|
||||
// Deliberately no MeshCollider/MeshFilter/ShieldEnvelope on this object -- this is
|
||||
// exactly what Longinus.prefab's "Shield" object looks like before the operator
|
||||
// rigs it (Q5), and what FieldShieldTest's cubesphere looked like before Cut 2.
|
||||
|
||||
var probePoints = new[]
|
||||
{
|
||||
go.transform.position + new Vector3(10f, 2f, -6f),
|
||||
go.transform.position + new Vector3(-3f, 8f, 20f),
|
||||
go.transform.position + new Vector3(0.4f, -0.1f, 0.2f),
|
||||
};
|
||||
|
||||
foreach (var worldPoint in probePoints)
|
||||
{
|
||||
// Independent re-derivation of ShieldManager's deleted formula
|
||||
// (normalize(shield.transform.InverseTransformPoint(point))).
|
||||
var expectedDirection = go.transform.InverseTransformPoint(worldPoint).normalized;
|
||||
var actualDirection = ShieldEnvelope.SurfaceDirection(go.transform, worldPoint);
|
||||
if (Vector3.Distance(expectedDirection, actualDirection) > 1e-5f)
|
||||
{
|
||||
Debug.LogError($"{Tag} SurfaceDirection(transform, p) fallback diverged from "
|
||||
+ $"ShieldManager's deleted formula: expected={expectedDirection:F6} actual={actualDirection:F6}");
|
||||
ok = false;
|
||||
}
|
||||
|
||||
// Independent re-derivation of FieldDriver.AddHit's deleted formula
|
||||
// (normalize(InverseTransformPoint(p)) * transform.localScale).
|
||||
var expectedLocalSurface = Vector3.Scale(
|
||||
go.transform.InverseTransformPoint(worldPoint).normalized, go.transform.localScale);
|
||||
var actualLocalSurface = ShieldEnvelope.LegacyLocalSurfacePoint(go.transform, worldPoint);
|
||||
if (Vector3.Distance(expectedLocalSurface, actualLocalSurface) > 1e-3f)
|
||||
{
|
||||
Debug.LogError($"{Tag} LegacyLocalSurfacePoint(transform, p) fallback diverged from "
|
||||
+ $"FieldDriver's deleted formula: expected={expectedLocalSurface:F6} actual={actualLocalSurface:F6}");
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
UnityEngine.Object.DestroyImmediate(go);
|
||||
}
|
||||
|
||||
if (ok)
|
||||
Debug.Log($"{Tag} Legacy fallback statics (SurfaceDirection, LegacyLocalSurfacePoint) match "
|
||||
+ "the pre-Cut-2 formulas exactly, for a carrier with no ShieldEnvelope component and a "
|
||||
+ "non-trivial transform.");
|
||||
return ok;
|
||||
}
|
||||
|
||||
static Vector3 LocalExtentsOf(GameObject envelopeObj)
|
||||
{
|
||||
var meshCollider = envelopeObj.GetComponent<MeshCollider>();
|
||||
|
|
|
|||
Loading…
Reference in a new issue