gods-eye-view/scripts/focus-allocation-check.mjs

140 lines
4.2 KiB
JavaScript

#!/usr/bin/env node
import assert from 'node:assert/strict';
import {
advanceSpriteFocus,
} from '../src/data/focusDeemphasis.js';
import {
applyAircraftBillboardTreatment,
} from '../src/data/aircraftRecession.js';
if (typeof global.gc !== 'function') {
throw new Error('focus-allocation-check.mjs requires node --expose-gc');
}
const CALLS = 100_000;
const TRIALS = 9;
const WARMUP_CALLS = 200_000;
const PURE_CALL_BUDGET = 16;
const PRODUCTION_SPRITE_TICK_BUDGET = 212;
function median(values) {
const sorted = [...values].sort((a, b) => a - b);
return sorted[Math.floor(sorted.length / 2)];
}
function bytesPerCall(fn) {
for (let index = 0; index < WARMUP_CALLS; index += 1) fn();
const samples = [];
for (let trial = 0; trial < TRIALS; trial += 1) {
global.gc();
const before = process.memoryUsage().heapUsed;
let checksum = 0;
for (let index = 0; index < CALLS; index += 1) checksum += fn();
const after = process.memoryUsage().heapUsed;
// Keep the returned scalar observable so V8 cannot delete the calls.
assert.ok(Number.isFinite(checksum));
samples.push((after - before) / CALLS);
}
const rawMedian = median(samples);
return { rawMedian, samples };
}
function netOfLoopBaseline(observed, baseline) {
// V8's callback/checksum loop itself retains roughly one boxed-number word
// per call until the next collection. Subtract the identically measured
// no-op loop so this gate reports allocation attributable to the treatment.
const rawMedian = Math.max(0, observed.rawMedian - baseline.rawMedian);
return {
rawMedian,
roundedMedian: Math.round(rawMedian),
observedRawMedian: observed.rawMedian,
baselineRawMedian: baseline.rawMedian,
samples: observed.samples,
};
}
const target = {
screenRect: { left: 0, top: 0, right: 10, bottom: 10 },
paddingPx: 18,
cameraDistance: 1000,
};
const sprite = {};
const focusInput = {
screenPosition: { x: 5, y: 5 },
cameraDistance: 1200,
nowMs: 1000,
target,
};
const baseColor = { withAlpha: (alpha) => ({ alpha }) };
const billboard = { scale: 1, color: { alpha: 1 } };
const aircraftInput = {
billboard,
baseScale: 1,
baseAlpha: 1,
baseColor,
focusFactor: 1,
cameraDistanceM: 100,
cameraHeightM: 1000,
};
const baseline = bytesPerCall(() => focusInput.nowMs);
const focus = netOfLoopBaseline(
bytesPerCall(() => advanceSpriteFocus(sprite, focusInput).factor),
baseline,
);
const aircraft = netOfLoopBaseline(
bytesPerCall(() => applyAircraftBillboardTreatment(aircraftInput).alpha),
baseline,
);
const productionSpriteTick = netOfLoopBaseline(
bytesPerCall(() => {
// Match the hot civilian/military sprite path: the caller constructs fresh
// option literals, consumes the shared focus result immediately, and then
// constructs the composed aircraft-treatment options for the same sprite.
const focusResult = advanceSpriteFocus(sprite, {
screenPosition: focusInput.screenPosition,
cameraDistance: focusInput.cameraDistance,
nowMs: focusInput.nowMs,
target,
});
const treatment = applyAircraftBillboardTreatment({
billboard,
baseScale: aircraftInput.baseScale,
baseAlpha: aircraftInput.baseAlpha,
baseColor,
focusFactor: focusResult.factor,
cameraDistanceM: aircraftInput.cameraDistanceM,
cameraHeightM: aircraftInput.cameraHeightM,
});
return focusResult.factor + treatment.alpha;
}),
baseline,
);
assert.ok(
focus.roundedMedian <= PURE_CALL_BUDGET,
`advanceSpriteFocus allocated ${focus.rawMedian.toFixed(3)} B/call median`,
);
assert.ok(
aircraft.roundedMedian <= PURE_CALL_BUDGET,
`applyAircraftBillboardTreatment allocated ${aircraft.rawMedian.toFixed(3)} B/call median`,
);
assert.ok(
productionSpriteTick.roundedMedian <= PRODUCTION_SPRITE_TICK_BUDGET,
`production sprite tick allocated ${productionSpriteTick.rawMedian.toFixed(3)} B/call median`,
);
console.log(JSON.stringify({
callsPerTrial: CALLS,
trials: TRIALS,
budgets: {
pureCall: PURE_CALL_BUDGET,
productionSpriteTick: PRODUCTION_SPRITE_TICK_BUDGET,
},
loopBaseline: baseline,
advanceSpriteFocus: focus,
applyAircraftBillboardTreatment: aircraft,
productionSpriteTick,
}));