From b7a069069db18c937348935db6bc5f871d36e45c Mon Sep 17 00:00:00 2001 From: m3ultra Date: Sat, 18 Jul 2026 19:03:36 +1000 Subject: [PATCH] =?UTF-8?q?debris:=20delete=20the=20dead=20sail.nodes=20se?= =?UTF-8?q?am=20=E2=80=94=20sail.=5FapplyDebris=20is=20the=20one=20contact?= =?UTF-8?q?=20model?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- web/world/js/debris.js | 41 +++++++---------------------------------- 1 file changed, 7 insertions(+), 34 deletions(-) diff --git a/web/world/js/debris.js b/web/world/js/debris.js index 7fccd11..3c24fbd 100644 --- a/web/world/js/debris.js +++ b/web/world/js/debris.js @@ -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; }