mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 18:34:02 +00:00
165 lines
7.7 KiB
JavaScript
165 lines
7.7 KiB
JavaScript
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import {
|
|
DEFAULT_FOCUS_DEEMPHASIS_PARAMS,
|
|
advanceFocusEvidenceNowMs,
|
|
advanceSpriteFocus,
|
|
focusNowMs,
|
|
focusTargetEmphasis,
|
|
nearFarScalarValueAtDistance,
|
|
setFocusEvidenceNowMs,
|
|
smoothFocusEmphasis,
|
|
} from './focusDeemphasis.js';
|
|
|
|
const params = { ...DEFAULT_FOCUS_DEEMPHASIS_PARAMS, paddingPx: 0 };
|
|
const target = {
|
|
screenRect: { left: 40, top: 40, right: 60, bottom: 60 },
|
|
paddingPx: 0,
|
|
cameraDistance: 1000,
|
|
};
|
|
|
|
test('focus decision dims a farther sprite inside and preserves one outside', () => {
|
|
assert.equal(focusTargetEmphasis({ x: 50, y: 50 }, 1200, target, params), 0.25);
|
|
assert.equal(focusTargetEmphasis({ x: 61, y: 50 }, 1200, target, params), 1);
|
|
});
|
|
|
|
test('focus decision keeps nearer behavior tunable across allow, dim, and partial', () => {
|
|
assert.equal(focusTargetEmphasis({ x: 50, y: 50 }, 800, target, { ...params, nearerBehavior: 'allow' }), 1);
|
|
assert.equal(focusTargetEmphasis({ x: 50, y: 50 }, 800, target, { ...params, nearerBehavior: 'dim' }), 0.25);
|
|
assert.equal(focusTargetEmphasis({ x: 50, y: 50 }, 800, target, { ...params, nearerBehavior: 'partial' }), 0.625);
|
|
});
|
|
|
|
test('focus decision applies padding and hysteresis at the boundary', () => {
|
|
assert.equal(focusTargetEmphasis({ x: 64, y: 50 }, 1200, target, { ...params, paddingPx: 3 }), 1);
|
|
assert.equal(focusTargetEmphasis({ x: 63, y: 50 }, 1200, target, { ...params, paddingPx: 3 }), 0.25);
|
|
assert.equal(focusTargetEmphasis({ x: 65, y: 50 }, 1200, target, { ...params, hysteresisPx: 6 }, false), 1);
|
|
assert.equal(focusTargetEmphasis({ x: 65, y: 50 }, 1200, target, { ...params, hysteresisPx: 6 }, true), 0.25);
|
|
});
|
|
|
|
test('focus overlap includes the ambient sprite own rendered extent', () => {
|
|
assert.equal(focusTargetEmphasis({ x: 68, y: 50 }, 1200, target, params, false, 8, 8), 0.25);
|
|
assert.equal(focusTargetEmphasis({ x: 68, y: 50 }, 1200, target, params, false, 2, 2), 1);
|
|
});
|
|
|
|
test('focus decision never falls below the configured floor', () => {
|
|
assert.equal(focusTargetEmphasis({ x: 50, y: 50 }, 1200, target, { ...params, dimFloor: 0.41 }), 0.41);
|
|
assert.equal(focusTargetEmphasis({ x: 50, y: 50 }, 1200, target, { ...params, dimFloor: 0 }), 0.01);
|
|
});
|
|
|
|
test('smoothing converges over configured attack and release instead of snapping', () => {
|
|
const attackMid = smoothFocusEmphasis(1, 0.25, 150, params);
|
|
assert.ok(attackMid < 1 && attackMid > 0.25, `attack midpoint=${attackMid}`);
|
|
assert.equal(smoothFocusEmphasis(1, 0.25, 300, params), 0.25);
|
|
const releaseMid = smoothFocusEmphasis(0.25, 1, 300, params);
|
|
assert.ok(releaseMid > 0.25 && releaseMid < 1, `release midpoint=${releaseMid}`);
|
|
assert.equal(smoothFocusEmphasis(0.25, 1, 600, params), 1);
|
|
});
|
|
|
|
test('default attack yields at least four distinct 80 ms sampled values', () => {
|
|
const samples = [0, 80, 160, 240, 320]
|
|
.map((elapsedMs) => smoothFocusEmphasis(1, 0.25, elapsedMs, params));
|
|
assert.ok(new Set(samples.slice(1)).size >= 4, `samples=${samples.join(',')}`);
|
|
assert.equal(samples.at(-1), 0.25);
|
|
});
|
|
|
|
test('per-sprite state attacks, restores after focus clears, and settles at one', () => {
|
|
const sprite = {};
|
|
const input = { screenPosition: { x: 50, y: 50 }, cameraDistance: 1200, target, params };
|
|
assert.equal(advanceSpriteFocus(sprite, { ...input, nowMs: 0 }).factor, 1);
|
|
const attack = advanceSpriteFocus(sprite, { ...input, nowMs: 150 });
|
|
assert.ok(attack.factor < 1 && attack.factor > 0.25);
|
|
assert.equal(advanceSpriteFocus(sprite, { ...input, nowMs: 300 }).factor, 0.25);
|
|
assert.equal(advanceSpriteFocus(sprite, { ...input, target: null, nowMs: 300 }).factor, 0.25);
|
|
const release = advanceSpriteFocus(sprite, { ...input, target: null, nowMs: 600 });
|
|
assert.ok(release.factor > 0.25 && release.factor < 1);
|
|
assert.equal(advanceSpriteFocus(sprite, { ...input, target: null, nowMs: 900 }).factor, 1);
|
|
});
|
|
|
|
test('distance chatter reanchors from current progress and converges toward the majority state', () => {
|
|
const sprite = {};
|
|
const chatterParams = { ...params, nearerBehavior: 'allow', distanceHysteresisRatio: 0.08 };
|
|
const distances = [1200, 990, 1010, 995, 1005, 990, 1200, 990, 1010];
|
|
const factors = distances.map((cameraDistance, index) => advanceSpriteFocus(sprite, {
|
|
screenPosition: { x: 50, y: 50 },
|
|
cameraDistance,
|
|
target,
|
|
params: chatterParams,
|
|
nowMs: index * 80,
|
|
}).factor);
|
|
for (let index = 1; index < factors.length; index += 1) {
|
|
assert.ok(factors[index] <= factors[index - 1] + 1e-12, `factors=${factors.join(',')}`);
|
|
}
|
|
assert.ok(factors.at(-1) < 0.5, `factors=${factors.join(',')}`);
|
|
|
|
const dimSprite = {};
|
|
const dimFactors = [0, 80, 160, 240].map((nowMs, index) => advanceSpriteFocus(dimSprite, {
|
|
screenPosition: { x: 50, y: 50 },
|
|
cameraDistance: index % 2 ? 990 : 1010,
|
|
target,
|
|
params: { ...params, nearerBehavior: 'dim' },
|
|
nowMs,
|
|
}).factor);
|
|
assert.ok(dimFactors.at(-1) < 1, `dimFactors=${dimFactors.join(',')}`);
|
|
});
|
|
|
|
test('alternating desired state reanchors from sampled progress and converges to a stable cycle', () => {
|
|
const sprite = {};
|
|
const chatterParams = { ...params, nearerBehavior: 'allow', distanceHysteresisRatio: 0.08 };
|
|
const factors = Array.from({ length: 20 }, (_, index) => advanceSpriteFocus(sprite, {
|
|
screenPosition: { x: 50, y: 50 },
|
|
// Cross both sides of the range hysteresis band every tick so `desired`
|
|
// truly alternates instead of latching in the prior state.
|
|
cameraDistance: index % 2 === 0 ? 1200 : 800,
|
|
target,
|
|
params: chatterParams,
|
|
nowMs: index * 80,
|
|
}).factor);
|
|
|
|
assert.ok(new Set(factors).size >= 16, `factors=${factors.join(',')}`);
|
|
const earlyEvenDelta = Math.abs(factors[4] - factors[2]);
|
|
const lateEvenDelta = Math.abs(factors[18] - factors[16]);
|
|
const earlyOddDelta = Math.abs(factors[5] - factors[3]);
|
|
const lateOddDelta = Math.abs(factors[19] - factors[17]);
|
|
assert.ok(lateEvenDelta < earlyEvenDelta, `even factors=${factors.join(',')}`);
|
|
assert.ok(lateOddDelta < earlyOddDelta, `odd factors=${factors.join(',')}`);
|
|
});
|
|
|
|
test('advanceSpriteFocus returns its documented module singleton', () => {
|
|
const sprite = {};
|
|
const input = { screenPosition: { x: 50, y: 50 }, cameraDistance: 1200, target, params };
|
|
const first = advanceSpriteFocus(sprite, { ...input, nowMs: 0 });
|
|
const second = advanceSpriteFocus(sprite, { ...input, nowMs: params.attackMs });
|
|
assert.strictEqual(first, second);
|
|
assert.equal(first.factor, params.dimFloor, 'the prior reference reflects the next call');
|
|
});
|
|
|
|
test('evidence clock produces identical alpha sequences across repeated captures', () => {
|
|
const sequenceOffsets = [0, 80, 160, 240, 320, 400, 560, 720, 880, 1040];
|
|
const capture = (productionClockBase, productionClockStep) => {
|
|
setFocusEvidenceNowMs(10_000);
|
|
const sprite = {};
|
|
const sequence = sequenceOffsets.map((offset, index) => {
|
|
if (index > 0) advanceFocusEvidenceNowMs(offset - sequenceOffsets[index - 1]);
|
|
const nowMs = focusNowMs(productionClockBase + index * productionClockStep);
|
|
return advanceSpriteFocus(sprite, {
|
|
screenPosition: { x: 50, y: 50 },
|
|
cameraDistance: 1200,
|
|
target: offset <= 320 ? target : null,
|
|
params,
|
|
nowMs,
|
|
}).factor;
|
|
});
|
|
setFocusEvidenceNowMs(null);
|
|
return sequence;
|
|
};
|
|
assert.deepEqual(capture(1_000, 1), capture(1_000_000, 9_999));
|
|
});
|
|
|
|
test('NearFarScalar rendered-size interpolation matches Cesium clamp endpoints', () => {
|
|
const scalar = { near: 1000, nearValue: 3, far: 8_000_000, farValue: 0.5 };
|
|
assert.equal(nearFarScalarValueAtDistance(scalar, 500), 3);
|
|
assert.equal(nearFarScalarValueAtDistance(scalar, 9_000_000), 0.5);
|
|
const middle = nearFarScalarValueAtDistance(scalar, 1_000_000);
|
|
assert.ok(middle > 0.5 && middle < 3);
|
|
});
|