Skip to content

feat(collision): shape-precise shielded-prism collision at the visible shell - #618

Open
gradies wants to merge 9 commits into
bleeding-edgefrom
claude/skimmers-shielded-prisms-hek21c
Open

feat(collision): shape-precise shielded-prism collision at the visible shell#618
gradies wants to merge 9 commits into
bleeding-edgefrom
claude/skimmers-shielded-prisms-hek21c

Conversation

@gradies

@gradies gradies commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

What & why

#617 kept shielded prisms skimmable by keeping the primitive box trigger, but interaction happened at the bare prism size — the 3× visible shell was cosmetic. This delivers shape-precise interaction at the visible shell, without a mesh collider: the box trigger resizes to the shell AABB (no swap), and impact dispatch refines it to the true octahedron/stella via an analytic signed-margin gate. Reuses the on-branch ContainsPointLocal math, and fixes the two failure modes that sank the parallel tetrahedral-collider-cost branch — grazing skims rejected (a containment-only gate), and pop-then-destroy (a collider swap).

Per-system summary

  • Shells (PrismOctahedronShield, PrismStellatedOctahedronShield) — implement the new IShieldContainmentGate: SignedMargin (octahedron L1 / stella 4-form, positive-inside) + SignedMarginSphere(centre, R) = SignedMargin(centre) + R·√((invA/sx)² + (invB/sy)² + (invC/sz)²) (sphere-vs-shell, conservative — never a dead zone). The shielded pose resizes the box to authoredSize × interactionShellScale (config knob, default = the 3× visual), publishes Prism.SetActiveShieldGate on settle, restores + clears on withdraw. Box is resize-only, never swapped/disabled (enabled stays owned by the spawn window / collider-LOD / destruction).
  • IShieldContainmentGate (new) — SignedMargin / SignedMarginSphere / ContainsWorldPoint.
  • PrismActiveShieldGate (server-private) + internal setter, cleared on reset / OnDisable.
  • ImpactorBase — narrowphase at the OnTriggerEnter chokepoint: sphere touchers gate on SignedMarginSphere (world centre + world radius), others on a ClosestPoint point margin, both vs ShieldMarginThreshold (default 0 = "reaches the shell"). Contacts that enter the enlarged box outside the shell park and re-test on OnTriggerStay, and drop if their shell went null since parking — impactee-side (ActiveShieldGate == null) and self-side (HasOwnShieldGate) — closing the same-tick pop-then-destroy.
  • SkimmerImpactor — dropped the _skimBand + threshold override; skim and pop unify at threshold 0 (the sphere radius supplies the grazing reach, so "pop in thin air" is gone). Stay/Exit call base first.
  • GeneratorsSignedMarginLocal siblings to ContainsPointLocal.
  • DocsDocs/CollisionLOD/DESIGN.md (plan, exact contracts, the round-by-round findings, and the §6 editor checklist); CLAUDE.md shield row updated to the shipped mechanism.

Verification status

  • Reviewed, not run. Three fable-5 adversarial rounds drove this — they caught and fixed: the impactee-side no-op probe, the pose force-enable fighting LOD, the pop-threshold conflation, the point-probe skim dead-zones, and the impactee-side same-tick race. The final round's verifiers hit the fable-5 usage limit, so the closing adversarial pass was manual (Opus) and caught one more — the self-side parked-drop. Compile verified by inspection.
  • Not compiled in Unity and not play-tested here — collision feel/timing is not headless-verifiable. The editor pass is the gate (DESIGN.md §6):
    1. Rhino pops a shielded prism at the shell distance (~3×), not point-blank; a graze does not destroy the inner prism.
    2. Squirrel skims register while grazing the shell (not only center-punch); danger-skim energy still applies.
    3. Super-shielded (stella) — same two checks; watch the non-convex inter-spike gaps for skim dead-zones.
    4. LOD — fly away → shielded collider culls; fly back → restores.
    5. ProfilerPhysics.SendEvents / *.AcceptImpactee at dense shielded mass.

Follow-ups

  • One-swing pop+destroy left intentionally emergent (no bespoke post-pop grace): a swipe that reaches through a popped shell hits the prism; a graze pops only. If it should always take a second swing, add a small post-pop grace (noted in DESIGN.md §7.1).
  • Base OnTriggerStay now delivers Stay callbacks to every impactor (previously only the skimmer). Cheap early-out when nothing is parked, but new per-frame callback volume on the prism hot path — profile Physics.SendEvents; if material, restrict Stay to impactors that actually park.
  • Sphere margin is conservative across L1 octant / stella facet seams (fires slightly early — the safe direction); tune interactionShellScale per shell if reach feels off.
  • The point / sphere LOD tiers are already realized (collider-LOD + PrismSpatialIndex for AOE); Phase 2 formalization is measure-first (DESIGN.md §4).

Collider / perf impact (ecology gate)

No new physics queries, no mesh colliders, one trigger per prism (unchanged), still collider-LOD-cullable. The shielded box grows to 3× (more broadphase over-cover → more candidate OnTrigger callbacks near a shielded prism), each resolved by a ~box-AABB analytic margin test. New per-frame cost: the base OnTriggerStay dispatch to all impactors (see Follow-ups).

🤖 Generated with Claude Code


Generated by Claude Code

claude and others added 9 commits July 22, 2026 07:21
…spec

Robust build spec for shape-precise shielded-prism collision: keep the primitive
box trigger but resize it to the shell AABB (no swap), add a signed-margin
narrowphase (octahedron L1 / stella 4-form, already on-branch as ContainsPointLocal)
at the ImpactorBase dispatch chokepoint, with a per-interaction threshold (skim =
proximity band, pop = containment) and pending re-test. Fixes the two failure modes
of the parallel tetrahedral branch (grazing skims rejected; pop-then-destroy from a
collider swap) by construction. Point/sphere tiers are already realized (LOD cull +
PrismSpatialIndex); this front-loads the bespoke-bounds tier. Includes exact
contracts and the in-editor verification steps.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R78zS1KKCafHhXuebwgWmv
…nds narrowphase)

Phase 0-1 of Docs/CollisionLOD/DESIGN.md. Shielded/super-shielded prisms keep the
one primitive box trigger (never a mesh, never swapped) but resize it to the shell
AABB (interactionShellScale, default = the 3x visual); a signed-margin analytic
narrowphase then refines contacts down to the true octahedron / stella surface at
the ImpactorBase dispatch chokepoint, with a per-interaction threshold (skim = a
grazing proximity band, pop/damage = containment) and a pending re-test on
OnTriggerStay for contacts that enter an AABB corner before reaching the shell.

- SignedMarginLocal on both mesh generators (positive-inside; ContainsPointLocal
  redefined through it, one source of truth); IShieldContainmentGate; both shields
  implement it and publish Prism.ActiveShieldGate on settle, clear it on withdraw
  (ReferenceEquals-guarded for the shield<->super handoff) and on pool return.
- Both tetrahedral-branch failure modes are avoided by construction: grazing skims
  pass (band, not containment), and the collider is resize-only (no enable/disable
  cycle) so a single continuous overlap cannot double-dispatch (no pop-then-destroy).

Post-implementation adversarial review (fable-5) caught and this commit fixes:
the impactee-side gate probed the prism's OWN box (returning the centre, margin
always ~+1, gate never bit) -> now probes THIS impactor's own collider; and the
pose methods force-enabled the collider (fighting the spawn window / collider-LOD)
-> removed, poses are resize-only. Stella union math and compile verified clean.

In-editor verification (DESIGN.md section 6) is the human gate and still pending.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R78zS1KKCafHhXuebwgWmv
Adversarial re-verify confirmed the lifecycle/compile fixes and stella math, but
found shell-precision needs a real sphere-vs-shell margin (point sample toward the
prism centre under-measures grazes -> dead zones, worst on the non-convex stella),
a unified threshold (pop dispatches via the skimmer damage effect, so skim+pop share
one threshold), and PhysX-timing handling for two pop-then-destroy paths (same-tick
parked dispatch after gate clear; one-swing re-enter after the 3x->1x shrink). The
last is a gameplay-feel fork. Shell-precision is not headless-verifiable; #617
(authored-size shielded skimming) remains the working baseline.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R78zS1KKCafHhXuebwgWmv
Shell-precise narrowphase for shielded/super-shielded prisms, on top of the
#617 box-trigger baseline. The primitive box trigger resizes to the shell AABB
(no swap), and impact dispatch refines it to the true octahedron/stella via an
analytic signed-margin gate.

- SignedMargin/SignedMarginSphere on both shells (octahedron L1, stella 4-form),
  reusing the on-branch ContainsPointLocal math. Sphere touchers gate on the
  sphere-vs-shell margin (centre + radius x world gradient) so grazes register
  without dead zones; point touchers use ClosestPoint.
- Unified threshold 0 ("sphere reaches the shell") for skim and pop; removed the
  skim band that popped shields in thin air.
- Parked-contact re-test drops on a null shell gate (impactee-side and self-side)
  so a pop cannot destroy the freshly-unshielded prism in its own tick.
- Collider stays resize-only; enabled owned by spawn window / collider-LOD /
  destruction. One-swing pop+destroy left emergent (no bespoke grace).

Reach is a per-shell config knob (interactionShellScale, default = the 3x
visual). Not yet play-tested: the in-editor checklist in Docs/CollisionLOD/DESIGN.md
sec 6 is the gate for feel/timing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R78zS1KKCafHhXuebwgWmv
…collision

The shield's box trigger resizes to the shell AABB and dispatch refines to the
true octahedron/stella via the analytic signed-margin narrowphase (no mesh
collider); update the "planned follow-up" note to the shipped mechanism.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R78zS1KKCafHhXuebwgWmv
… tips

First playtest: the 3x box reach is live but a vessel hull clipped through the
pointy octahedron/stella tips because the non-sphere gate tested a SINGLE point
(nearest approach to the prism centre), blind to the thin tips and the hull body.
Only the sphere skimmer path was shape-aware.

Make every collider shape-aware via ImpactorBase.ColliderReachesShell — the
analytic equivalent of a convex octahedron/stella mesh collider:
- Sphere: exact sphere-vs-shell margin (unchanged).
- Any other convex/primitive collider: MAX shell margin over two support points -
  the collider's farthest point toward the shell interior
  (ClosestPoint(centre + ShellInwardNormal*big), catches the tips) and its nearest
  point to the prism centre (deep body).
- New IShieldContainmentGate.ShellInwardNormal returns the nearest-facet inward
  normal (octahedron: octant L1 coefficients; stella: active linear form).
- Impactee-side and self-side both route through the one helper.

Needs the in-editor pass: hull should catch at the tips without over-reaching in
open space.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R78zS1KKCafHhXuebwgWmv
…-vs-shell SAT

Two confirmed root causes (5-agent diagnosis) of the Squirrel hull passing
through shielded octahedron/stella tips:

1) The vessel's hull BoxColliders live on CHILD GameObjects (VesselImpactor sits
   on the root with the kinematic Rigidbody). ImpactorBase resolved the toucher
   collider via TryGetComponent on its OWN GameObject -> null -> the narrowphase
   fell back to a single point at the ship pivot, so the shape-aware test never
   ran on the vessel side (only the pivot-deep region interacted). Fix: cache the
   impactor's collider SET (own GO, else GetComponentsInChildren) and dispatch if
   ANY reaches the shell; pivot fallback only when no collider exists at all.

2) The two-point sample false-rejected ~18% of rotated-OBB tip overlaps. Fix:
   exact analytic OBB-vs-shell overlap via a Separating-Axis Test
   (IShieldContainmentGate.OverlapsWorldBox -> OverlapsBoxNormalized): octahedron
   as the L1 unit ball (25 axes); stella as the union of two tetrahedra, SAT per
   tet OR'd. BoxCollider touchers use it exactly; other colliders use a
   conservative world-AABB OBB; the sphere skimmer path is unchanged.

The SAT is numerically proven against exact LP + dense-grid ground truth:
0/72751 false-reject on the octahedron, 0/94373 on the stella, 0% false-accept
both, across 200k cases biased toward the tips/spikes. Still gated on the
in-editor pass (cannot run Unity headless). Docs/CollisionLOD/DESIGN.md sec 7.3.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R78zS1KKCafHhXuebwgWmv
remove collider
…hase

The exact OBB-vs-shell SAT was correct, but the authored hull box is a loose
bounding box larger than the visible mesh, so it popped a shielded prism the
instant the box EDGE reached the small octahedron shell — "the box outline
pops it, not the octahedron."

Add HullNarrowphaseScale (ImpactorBase, default 1) that uniformly shrinks the
toucher's box half-edges / sphere radius about its own centre in the analytic
shell test ONLY — never the physics broadphase trigger, so the parked-contact
re-test window (and tunneling safety) is unchanged; the shrink only decides
dispatch within an existing overlap. VesselImpactor exposes it as a live
Range(0.2,1) inspector knob (hullNarrowphaseScale, default 0.7) so each
vessel's effective hull hugs its visible silhouette; the skimmer sphere stays
scale-1/exact to avoid a tangential-skim dead zone.

Also remove the now-dead ShellInwardNormal (+ stella CoeffOf helper): the
two-point support sample it fed was replaced by the SAT in a prior commit,
leaving zero callers. Interface, both shields, and DESIGN.md §7.2 updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R78zS1KKCafHhXuebwgWmv
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants