debris: delete the dead sail.nodes seam — sail._applyDebris is the one contact model

The SPRINT2-era duck-typed path (if sail.nodes → applyToSail) has been
dead since decision 5 chose option b, and was a double-apply waiting for
the day anyone exposed .nodes. The sail key on step()'s world arg is
accepted and ignored, documented.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
m3ultra 2026-07-18 19:03:36 +10:00
parent 9d6db356d7
commit b7a069069d

View File

@ -182,7 +182,8 @@ export function createDebris(o = {}) {
/**
* @param {number} dt fixed step
* @param {number} t storm time
* @param {object} [world] {player, sail} both optional, both duck-typed
* @param {object} [world] {player} optional, duck-typed. (A `sail` key is
* accepted and ignored: debris-vs-sail is sail.js's `_applyDebris`.)
*/
step(dt, t, world = {}) {
// storm JSON drives the spawns; poll the window so nothing is missed
@ -194,7 +195,6 @@ export function createDebris(o = {}) {
}
const player = world.player;
const sail = world.sail;
for (let i = pieces.length - 1; i >= 0; i--) {
const p = pieces[i];
@ -269,10 +269,11 @@ export function createDebris(o = {}) {
}
}
// --- sphere vs sail nodes: impulse ---
// Duck-typed: lights up the moment Lane B exposes nodes, silent until
// then. See THREADS — B owns sail.js, so this is the seam we agreed on.
if (sail && sail.nodes) applyToSail(p, sail);
// debris-vs-sail lives in sail.js (`_applyDebris` reads `debris.pieces`
// each step — SPRINT2 decision 5, option b). The old duck-typed seam
// here (`if (sail.nodes) applyToSail(...)`) is deleted, not dormant:
// had Lane B ever exposed `.nodes`, every hit would have been applied
// TWICE through two different contact models.
if (p.y < groundAt(p.x, p.z) - 5 || Math.abs(p.x) > bounds.x || Math.abs(p.z) > bounds.z) {
despawn(p);
@ -292,33 +293,5 @@ export function createDebris(o = {}) {
},
};
/**
* Shove any cloth node the piece is intersecting, and lose some of the piece's
* own momentum doing it. Expects sail.nodes: [{x,y,z,px,py,pz}] (verlet, so we
* move position and let the integrator turn it into velocity).
*/
function applyToSail(p, sail) {
const nodes = sail.nodes;
const reach = p.r + 0.15;
const reachSq = reach * reach;
let hits = 0;
for (let i = 0; i < nodes.length; i++) {
const n = nodes[i];
const dx = n.x - p.x, dy = n.y - p.y, dz = n.z - p.z;
const dsq = dx * dx + dy * dy + dz * dz;
if (dsq > reachSq || dsq < 1e-9) continue;
const d = Math.sqrt(dsq);
// push the node out to the sphere surface along the contact normal
const push = (reach - d) / d;
n.x += dx * push; n.y += dy * push; n.z += dz * push;
hits++;
}
if (hits) {
const drag = Math.min(0.5, (hits * p.mass) / 400);
p.vx *= 1 - drag; p.vy *= 1 - drag; p.vz *= 1 - drag;
if (sail.onDebrisHit) sail.onDebrisHit(p, hits);
}
}
return debris;
}