Guard pond accessors before attach; caught driving the live game

pondMass/pondCentroid/drainPondAt/dumpPond threw when this.water didn't
exist yet — Lane A's HUD reads pondMass() every frame, including in the
forecast/prep phases before the sail is rigged. Node tests always attach
first so they never hit it; the live game did on the first frame. Guarded
+ a pre-attach assert so it can't regress. Verified in the assembled game:
a flat quad ponds 780 kg (belly sagging below ground), dumps on break,
and the merged browser selftest is 219/0/0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
m3ultra 2026-07-17 03:32:02 +10:00
parent b7f93d6486
commit 670443eea0
2 changed files with 13 additions and 0 deletions

View File

@ -582,6 +582,7 @@ export class SailRig {
/** Total water on the sail, kg. Lane A's "SAIL PONDING — get the broom" number. */
pondMass() {
if (!this.water) return 0; // the HUD may read this before the sail is rigged
let m = 0;
for (let n = 0; n < this.water.length; n++) m += this.water[n];
return m;
@ -593,6 +594,7 @@ export class SailRig {
* @returns {{x:number,y:number,z:number,mass:number,node:number}|null}
*/
pondCentroid() {
if (!this.water) return null;
let m = 0, x = 0, y = 0, z = 0, node = -1, hw = 0;
for (let n = 0; n < this.water.length; n++) {
const q = this.water[n];

View File

@ -879,6 +879,17 @@ test('ponding: the broom SAVES a flat rig that water would have killed', () => {
return `kept 4/4 by sweeping the belly; unswept the same rig loses corners to water`;
});
test('ponding: pond accessors are safe before the sail is rigged', () => {
// Caught live: Lane A's HUD reads pondMass() every frame, including before the
// player has rigged anything — and this.water doesn't exist until attach().
const bare = new SailRig({ anchors: makeAnchors(HEIGHTS_FLAT) });
assert(bare.pondMass() === 0, 'pondMass threw / was non-zero on an unrigged sail');
assert(bare.pondCentroid() === null, 'pondCentroid should be null on an unrigged sail');
assert(bare.drainPondAt(0, SIM_DT) === 0, 'drainPondAt should no-op on an unrigged sail');
assert(bare.dumpPond() === 0, 'dumpPond should no-op on an unrigged sail');
return 'pondMass/centroid/drain/dump all safe pre-attach';
});
test('ponding: rain that the router swallows cannot silently pass', () => {
// The integrator caught the wind router dropping the rain API this sprint,
// which would have made every test above pass while ponding did nothing in the