mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
merge main f13c60c into feat/voice-outlines
reconcile voice outlines with main f13c60c, retaining German/Japanese,
Operator HUD retirement and Context/Radio cleanup, named-building
containment, courtyard-hole, non-Latin identity, attribution, cancellation
and antimeridian repairs
keep outline callouts above the open voice preview when an above-panel
slot fits without colliding with another callout,
and clip the compact dock location label on narrow screens
This commit is contained in:
commit
578ebdc030
449 files changed
+30400
-3429
No files matched your search
@@ -295,21 +295,8 @@ function decodeSharedOpenFreeMapTile(bytes, z, x, y) {
|
||||
let baseBytes = 0;
|
||||
let projectionBytes = 0;
|
||||
let resizeCacheEntry = null;
|
||||
let cacheBound = false;
|
||||
const idleCallbacks = new Map();
|
||||
|
||||
const cancelIdleProjection = (name) => {
|
||||
if (!idleCallbacks.has(name)) return;
|
||||
const handle = idleCallbacks.get(name);
|
||||
idleCallbacks.delete(name);
|
||||
globalThis.cancelIdleCallback?.(handle);
|
||||
};
|
||||
const cancelIdleProjections = () => {
|
||||
for (const name of [...idleCallbacks.keys()]) cancelIdleProjection(name);
|
||||
};
|
||||
const project = (name) => {
|
||||
if (projections.has(name)) return projections.get(name);
|
||||
cancelIdleProjection(name);
|
||||
const projector = TILE_PROJECTIONS.get(name);
|
||||
if (!projector)
|
||||
throw new Error(`Unknown OpenFreeMap tile projection: ${name}`);
|
||||
@@ -320,45 +307,16 @@ function decodeSharedOpenFreeMapTile(bytes, z, x, y) {
|
||||
resizeCacheEntry?.(baseBytes + projectionBytes);
|
||||
return value;
|
||||
};
|
||||
const scheduleIdleProjections = () => {
|
||||
if (
|
||||
!cacheBound ||
|
||||
typeof globalThis.requestIdleCallback !== 'function' ||
|
||||
typeof globalThis.cancelIdleCallback !== 'function'
|
||||
)
|
||||
return;
|
||||
for (const name of TILE_PROJECTIONS.keys())
|
||||
if (!projections.has(name) && !idleCallbacks.has(name)) {
|
||||
const handle = globalThis.requestIdleCallback(
|
||||
() => {
|
||||
if (!cacheBound || idleCallbacks.get(name) !== handle) return;
|
||||
idleCallbacks.delete(name);
|
||||
try {
|
||||
project(name);
|
||||
} catch {
|
||||
// The explicit consumer path retries and reports projection errors.
|
||||
}
|
||||
},
|
||||
{ timeout: 1000 },
|
||||
);
|
||||
idleCallbacks.set(name, handle);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
...base,
|
||||
project,
|
||||
bindSharedCacheEntry(resize, initialBytes) {
|
||||
cancelIdleProjections();
|
||||
resizeCacheEntry = resize;
|
||||
baseBytes = initialBytes;
|
||||
cacheBound = true;
|
||||
resizeCacheEntry(baseBytes + projectionBytes);
|
||||
scheduleIdleProjections();
|
||||
},
|
||||
releaseSharedCacheEntry() {
|
||||
cacheBound = false;
|
||||
cancelIdleProjections();
|
||||
projections.clear();
|
||||
baseBytes = 0;
|
||||
projectionBytes = 0;
|
||||
|
||||
@@ -17,9 +17,8 @@ import { normalizePlaceName } from '../data/placeBoundaries.js';
|
||||
/**
|
||||
* Street and building outlines from the same OpenFreeMap z14 vector tiles the
|
||||
* traffic layer reads. No new tile provider: the source below points at the
|
||||
* same immutable tile URLs and bounded decoded owner. Traffic prepares the
|
||||
* outline projection in idle time; nothing here starts provider I/O without
|
||||
* an explicit traffic or outline ask.
|
||||
* same immutable tile URLs and bounded decoded owner. Outline extraction runs
|
||||
* only when an outline consumer asks for it; traffic loading never warms it.
|
||||
*/
|
||||
|
||||
/** Tile zoom used for outlines (the highest OpenMapTiles zoom with buildings). */
|
||||
@@ -120,6 +119,7 @@ export function decodeOpenFreeMapOutlineTile(
|
||||
const height = Number(props.render_height);
|
||||
return { heightM: Number.isFinite(height) && height > 0 ? height : null };
|
||||
},
|
||||
true,
|
||||
);
|
||||
// Open areas that can enclose grounds: parks, landuse and park-like cover.
|
||||
const areas = [];
|
||||
@@ -172,7 +172,15 @@ function pixelToLonLat(px, py, extent, z, x, y) {
|
||||
* and tile, and is marked `clipped` so it is never used as a whole outline. A
|
||||
* neighbouring tile may carry the same feature complete within its buffer.
|
||||
*/
|
||||
function decodePolygons(layer, z, x, y, keep, describe) {
|
||||
function decodePolygons(
|
||||
layer,
|
||||
z,
|
||||
x,
|
||||
y,
|
||||
keep,
|
||||
describe,
|
||||
componentLocal = false,
|
||||
) {
|
||||
if (!layer) return [];
|
||||
if (layer.length > LAYER_FEATURE_LIMIT)
|
||||
throw new Error('Vector tile feature limit exceeded');
|
||||
@@ -210,41 +218,43 @@ function decodePolygons(layer, z, x, y, keep, describe) {
|
||||
: geometry.type === 'MultiPolygon'
|
||||
? geometry.coordinates
|
||||
: [];
|
||||
// Preserve one feature's complete topology, including features without an
|
||||
// id. A cut or malformed part makes every part incomplete, not a smaller
|
||||
// exact building that happens to contain the pointer.
|
||||
const incomplete =
|
||||
!polygons.length ||
|
||||
polygons.some(
|
||||
(rings) =>
|
||||
!Array.isArray(rings) ||
|
||||
!rings.length ||
|
||||
rings.some(
|
||||
(ring) =>
|
||||
!Array.isArray(ring) ||
|
||||
ring.length < 4 ||
|
||||
ring.some(
|
||||
(point) =>
|
||||
!Array.isArray(point) ||
|
||||
point.length < 2 ||
|
||||
!Number.isFinite(point[0]) ||
|
||||
!Number.isFinite(point[1]),
|
||||
),
|
||||
),
|
||||
);
|
||||
const clipped =
|
||||
incomplete || polygons.some((rings) => rings[0].some(onCut));
|
||||
// A tile building feature may aggregate unrelated structures. Validate its
|
||||
// selected component independently; named grounds retain whole topology.
|
||||
const incompleteParts = polygons.map(
|
||||
(rings) =>
|
||||
!Array.isArray(rings) ||
|
||||
!rings.length ||
|
||||
rings.some(
|
||||
(ring) =>
|
||||
!Array.isArray(ring) ||
|
||||
ring.length < 4 ||
|
||||
ring.some(
|
||||
(point) =>
|
||||
!Array.isArray(point) ||
|
||||
point.length < 2 ||
|
||||
!Number.isFinite(point[0]) ||
|
||||
!Number.isFinite(point[1]),
|
||||
),
|
||||
),
|
||||
);
|
||||
const clippedParts = polygons.map(
|
||||
(rings, part) => incompleteParts[part] || rings[0].some(onCut),
|
||||
);
|
||||
for (let part = 0; part < polygons.length; part++) {
|
||||
const rings = polygons[part];
|
||||
if (!Array.isArray(rings?.[0]) || rings[0].length < 4) continue;
|
||||
out.push({
|
||||
rings,
|
||||
featureParts: polygons,
|
||||
featureParts: componentLocal ? null : polygons,
|
||||
id: feature.id ?? null,
|
||||
part,
|
||||
tile: `${z}/${x}/${y}`,
|
||||
clipped,
|
||||
incomplete,
|
||||
clipped: componentLocal
|
||||
? clippedParts[part]
|
||||
: clippedParts.some(Boolean),
|
||||
incomplete: componentLocal
|
||||
? incompleteParts[part]
|
||||
: incompleteParts.some(Boolean),
|
||||
...describe(props),
|
||||
});
|
||||
}
|
||||
@@ -297,12 +307,13 @@ const STREET_WORDS = Object.freeze({
|
||||
|
||||
const DIRECTIONS = new Set(['north', 'south', 'east', 'west']);
|
||||
|
||||
/** Comparable street name: accents off, abbreviations expanded. */
|
||||
/** Comparable street name: Latin accents off, abbreviations expanded. */
|
||||
export function normalizeStreetName(value) {
|
||||
return String(value ?? '')
|
||||
.split(',')[0]
|
||||
.normalize('NFKD')
|
||||
.replace(/\p{M}+/gu, '')
|
||||
.replace(/(\p{Script=Latin})\p{M}+/gu, '$1')
|
||||
.normalize('NFKC')
|
||||
.toLocaleLowerCase('en-US')
|
||||
.replace(/[\p{P}\p{S}]+/gu, ' ')
|
||||
.split(/\s+/)
|
||||
@@ -470,8 +481,8 @@ function distanceToLineM(lon, lat, ring) {
|
||||
* The building at a point: the smallest footprint containing it, else the
|
||||
* nearest within `maxDistanceM`, else null.
|
||||
*
|
||||
* The selected part remains primary; `polygons` carries every part and hole
|
||||
* of the same complete feature, regardless of whether it has an id.
|
||||
* Returns only the selected footprint component and its holes. A vector-tile
|
||||
* feature id does not certify that disjoint siblings are one physical building.
|
||||
*/
|
||||
export function buildingFromTiles(
|
||||
tiles,
|
||||
@@ -521,11 +532,9 @@ export function buildingFromTiles(
|
||||
const pick =
|
||||
containing?.building || (fragmentContains ? null : nearest?.building);
|
||||
if (!pick) return null;
|
||||
const parts = pick.featureParts || [pick.rings];
|
||||
const polygons = [
|
||||
pick.rings,
|
||||
...parts.filter((part) => part !== pick.rings),
|
||||
].map((part) => part.map((ring) => closeRing(ring.map((p) => [p[0], p[1]]))));
|
||||
const polygons = [pick.rings].map((part) =>
|
||||
part.map((ring) => closeRing(ring.map((p) => [p[0], p[1]]))),
|
||||
);
|
||||
const rings = polygons[0];
|
||||
return {
|
||||
ring: rings[0],
|
||||
|
||||
@@ -59,6 +59,9 @@ test('street names compare with abbreviations and a leading direction only', ()
|
||||
assert.ok(!streetNameMatches('Congress Avenue', 'South Congress Avenue'));
|
||||
assert.ok(!streetNameMatches('Congress Street', 'Congress Avenue'));
|
||||
assert.ok(!streetNameMatches('East 11th Street', '11th'));
|
||||
assert.ok(!streetNameMatches('कुल मार्ग', 'काली मार्ग'));
|
||||
assert.ok(streetNameMatches('काली मार्ग', 'काली मार्ग'));
|
||||
assert.ok(streetNameMatches('Champs-Élysées', 'Champs Elysees'));
|
||||
});
|
||||
|
||||
test('same-name segments merge into polylines around the point (Congress Avenue)', () => {
|
||||
@@ -153,7 +156,7 @@ test('tile enclosure alone does not identify the named Capitol grounds', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('traffic idle preparation gives outlines the same decoded tile without another request', async () => {
|
||||
test('traffic projects outlines only on demand and shares the decoded tile', async () => {
|
||||
clearSharedOpenFreeMapTiles();
|
||||
const idle = [];
|
||||
const previousIdle = globalThis.requestIdleCallback;
|
||||
@@ -163,6 +166,11 @@ test('traffic idle preparation gives outlines the same decoded tile without anot
|
||||
return idle.length;
|
||||
};
|
||||
globalThis.cancelIdleCallback = () => {};
|
||||
let projectionRuns = 0;
|
||||
registerOpenFreeMapProjection('requested-outlines-test', (...args) => {
|
||||
projectionRuns++;
|
||||
return decodeOpenFreeMapOutlineTile(...args);
|
||||
});
|
||||
let requests = 0;
|
||||
const bytes = readFileSync(
|
||||
new URL(
|
||||
@@ -183,16 +191,26 @@ test('traffic idle preparation gives outlines the same decoded tile without anot
|
||||
fetchImpl,
|
||||
});
|
||||
const outlines = createOpenFreeMapOutlineSource({
|
||||
projection: 'requested-outlines-test',
|
||||
template:
|
||||
'https://tiles.openfreemap.org/planet/v/20250910_001001_pt/{z}/{x}/{y}.pbf',
|
||||
fetchImpl,
|
||||
});
|
||||
await traffic.fetchBounds(box, { zoom: 14, tiles });
|
||||
assert.ok(idle.length > 0, 'traffic schedules the registered projection');
|
||||
for (const callback of idle)
|
||||
callback({ didTimeout: false, timeRemaining: () => 50 });
|
||||
const trafficResult = await traffic.fetchBounds(box, { zoom: 14, tiles });
|
||||
assert.equal(idle.length, 0, 'traffic schedules no outline extraction');
|
||||
assert.equal(projectionRuns, 0);
|
||||
const result = await outlines.fetchBounds(box, { zoom: 14, tiles });
|
||||
assert.ok(result.tiles[0].buildings.length > 0);
|
||||
await outlines.fetchBounds(box, { zoom: 14, tiles });
|
||||
assert.equal(
|
||||
projectionRuns,
|
||||
1,
|
||||
'the first explicit request owns the cached projection',
|
||||
);
|
||||
assert.deepEqual(
|
||||
(await traffic.fetchBounds(box, { zoom: 14, tiles })).tiles,
|
||||
trafficResult.tiles,
|
||||
);
|
||||
assert.equal(requests, 1);
|
||||
} finally {
|
||||
globalThis.requestIdleCallback = previousIdle;
|
||||
@@ -201,20 +219,9 @@ test('traffic idle preparation gives outlines the same decoded tile without anot
|
||||
}
|
||||
});
|
||||
|
||||
test('idle projections are retired on shared eviction, clear, and self-eviction', async () => {
|
||||
const previousIdle = globalThis.requestIdleCallback;
|
||||
const previousCancelIdle = globalThis.cancelIdleCallback;
|
||||
const scheduled = new Map();
|
||||
const cancelled = new Set();
|
||||
let nextHandle = 1;
|
||||
globalThis.requestIdleCallback = (callback) => {
|
||||
const handle = nextHandle++;
|
||||
scheduled.set(handle, callback);
|
||||
return handle;
|
||||
};
|
||||
globalThis.cancelIdleCallback = (handle) => cancelled.add(handle);
|
||||
test('an explicitly requested over-budget projection releases its shared entry', async () => {
|
||||
let projectionRuns = 0;
|
||||
registerOpenFreeMapProjection('idle-lifecycle-test', () => {
|
||||
registerOpenFreeMapProjection('projection-lifecycle-test', () => {
|
||||
projectionRuns++;
|
||||
return { coordinates: Array.from({ length: 20_000 }, (_, i) => [i, i]) };
|
||||
});
|
||||
@@ -224,23 +231,15 @@ test('idle projections are retired on shared eviction, clear, and self-eviction'
|
||||
import.meta.url,
|
||||
),
|
||||
);
|
||||
const secondBytes = readFileSync(
|
||||
new URL(
|
||||
'../data/fixtures/ofm-outlines-austin-14-3743-6746.pbf',
|
||||
import.meta.url,
|
||||
),
|
||||
);
|
||||
const sharedDecodedCache = createSharedDecodedTileCache({
|
||||
maxEntries: 1,
|
||||
maxCacheBytes: 100_000,
|
||||
});
|
||||
const source = createOpenFreeMapSource({
|
||||
template: 'https://tiles.openfreemap.org/planet/v/test/{z}/{x}/{y}.pbf',
|
||||
projection: 'projection-lifecycle-test',
|
||||
sharedDecodedCache,
|
||||
fetchImpl: async (url) =>
|
||||
new Response(
|
||||
String(url).includes('/6746.pbf') ? secondBytes : firstBytes,
|
||||
),
|
||||
fetchImpl: async () => new Response(firstBytes),
|
||||
});
|
||||
const box = { south: 30.24, north: 30.29, west: -97.75, east: -97.73 };
|
||||
try {
|
||||
@@ -248,55 +247,10 @@ test('idle projections are retired on shared eviction, clear, and self-eviction'
|
||||
zoom: 14,
|
||||
tiles: [{ z: 14, x: 3743, y: 6745 }],
|
||||
});
|
||||
const firstHandles = [...scheduled.keys()];
|
||||
assert.ok(firstHandles.length >= 2);
|
||||
|
||||
await source.fetchBounds(box, {
|
||||
zoom: 14,
|
||||
tiles: [{ z: 14, x: 3743, y: 6746 }],
|
||||
});
|
||||
assert.ok(firstHandles.every((handle) => cancelled.has(handle)));
|
||||
for (const handle of firstHandles) scheduled.get(handle)();
|
||||
assert.equal(projectionRuns, 0, 'late callbacks after eviction are inert');
|
||||
|
||||
const secondHandles = [...scheduled.keys()].filter(
|
||||
(handle) => !firstHandles.includes(handle),
|
||||
);
|
||||
sharedDecodedCache.clear();
|
||||
assert.ok(secondHandles.every((handle) => cancelled.has(handle)));
|
||||
for (const handle of secondHandles) scheduled.get(handle)();
|
||||
assert.equal(projectionRuns, 0, 'late callbacks after clear are inert');
|
||||
|
||||
const selfEvictingCache = createSharedDecodedTileCache({
|
||||
maxEntries: 1,
|
||||
maxCacheBytes: 100_000,
|
||||
});
|
||||
const selfEvictingSource = createOpenFreeMapSource({
|
||||
template: 'https://tiles.openfreemap.org/planet/v/self/{z}/{x}/{y}.pbf',
|
||||
sharedDecodedCache: selfEvictingCache,
|
||||
fetchImpl: async () => new Response(firstBytes),
|
||||
});
|
||||
const beforeSelfEviction = nextHandle;
|
||||
await selfEvictingSource.fetchBounds(box, {
|
||||
zoom: 14,
|
||||
tiles: [{ z: 14, x: 3743, y: 6745 }],
|
||||
});
|
||||
const selfEvictingHandles = [...scheduled.keys()].filter(
|
||||
(handle) => handle >= beforeSelfEviction,
|
||||
);
|
||||
scheduled.get(selfEvictingHandles.at(-1))();
|
||||
assert.equal(projectionRuns, 1);
|
||||
assert.deepEqual(selfEvictingCache.stats(), { entries: 0, bytes: 0 });
|
||||
assert.ok(
|
||||
selfEvictingHandles.slice(0, -1).every((handle) => cancelled.has(handle)),
|
||||
);
|
||||
for (const handle of selfEvictingHandles)
|
||||
if (!cancelled.has(handle)) scheduled.get(handle)();
|
||||
assert.equal(projectionRuns, 1, 'self-eviction retires sibling idle work');
|
||||
assert.deepEqual(sharedDecodedCache.stats(), { entries: 0, bytes: 0 });
|
||||
} finally {
|
||||
source.clear();
|
||||
sharedDecodedCache.clear();
|
||||
globalThis.requestIdleCallback = previousIdle;
|
||||
globalThis.cancelIdleCallback = previousCancelIdle;
|
||||
}
|
||||
});
|
||||
@@ -64,43 +64,53 @@ test('decoded polygons keep identity and mark tile-cut fragments', () => {
|
||||
});
|
||||
const multipart = decode(parts);
|
||||
assert.equal(multipart.buildings[0].id, null);
|
||||
assert.equal(
|
||||
multipart.buildings[0].featureParts,
|
||||
multipart.buildings[1].featureParts,
|
||||
'null ids do not split one feature into independent buildings',
|
||||
);
|
||||
const picked = buildingFromTiles([multipart], { lon: 0.0035, lat: 0.0005 });
|
||||
assert.deepEqual(
|
||||
picked.polygons,
|
||||
[parts[1], parts[0]],
|
||||
'selected part first; all parts and holes retained',
|
||||
[parts[1]],
|
||||
'an anonymous tile feature does not certify sibling building identity',
|
||||
);
|
||||
assert.deepEqual(
|
||||
buildingFromTiles([multipart], { lon: 0.0008, lat: 0.0008 }).polygons,
|
||||
[parts[0]],
|
||||
'the selected component retains its courtyard hole',
|
||||
);
|
||||
const clipped = decode(
|
||||
[parts[0], [square(-180 - 360 / 4096, 0, 0.001)]],
|
||||
true,
|
||||
);
|
||||
assert.ok(clipped.buildings.every((part) => part.clipped));
|
||||
assert.equal(clipped.buildings[0].clipped, false);
|
||||
assert.equal(clipped.buildings[1].clipped, true);
|
||||
assert.deepEqual(
|
||||
buildingFromTiles([clipped], { lon: 0.0008, lat: 0.0008 }).polygons,
|
||||
[parts[0]],
|
||||
'a remote cut sibling cannot disqualify an intact component',
|
||||
);
|
||||
assert.equal(
|
||||
buildingFromTiles([clipped], { lon: 0.0008, lat: 0.0008 }),
|
||||
buildingFromTiles([clipped], {
|
||||
lon: -180 - 360 / 4096 + 0.0005,
|
||||
lat: 0.0005,
|
||||
}),
|
||||
null,
|
||||
'a cut sibling prevents a partial exact footprint',
|
||||
);
|
||||
const malformed = decode([parts[0], [parts[1][0], null]]);
|
||||
const neighbor = {
|
||||
rings: [square(0.0011, 0, 0.001)],
|
||||
rings: [square(0.0041, 0, 0.001)],
|
||||
heightM: 20,
|
||||
clipped: false,
|
||||
};
|
||||
assert.ok(
|
||||
malformed.buildings.every((part) => part.clipped && part.incomplete),
|
||||
assert.deepEqual(
|
||||
buildingFromTiles([malformed], { lon: 0.0008, lat: 0.0008 }).polygons,
|
||||
[parts[0]],
|
||||
'a remote malformed sibling cannot disqualify an intact component',
|
||||
);
|
||||
assert.equal(
|
||||
buildingFromTiles(
|
||||
[{ ...malformed, buildings: [...malformed.buildings, neighbor] }],
|
||||
{ lon: 0.0008, lat: 0.0008 },
|
||||
{ lon: 0.0038, lat: 0.0008 },
|
||||
),
|
||||
null,
|
||||
'a malformed sibling cannot become a partial exact building',
|
||||
'a selected malformed component never substitutes its neighbour',
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -89,6 +89,9 @@ export function createOverpassFeatureSource({
|
||||
}
|
||||
}
|
||||
return {
|
||||
// Only this concrete OSM adapter declares outline origin; generic injected
|
||||
// feature sources do not acquire attribution from their feature IDs.
|
||||
outlineCredit: 'osm',
|
||||
getFocusFootprints({ lat, lon }, options = {}) {
|
||||
validPoint(lat, lon);
|
||||
return query(
|
||||
|
||||
@@ -7,3 +7,46 @@ export function phaseTiming(phase, start, detail = {}, scope = 'roads') {
|
||||
detail,
|
||||
});
|
||||
}
|
||||
|
||||
/** Bounded development evidence for one physical outline installation. */
|
||||
export function createWorkTrace() {
|
||||
if (!import.meta.env?.DEV) return null;
|
||||
try {
|
||||
return {
|
||||
timeOrigin: performance.timeOrigin,
|
||||
slices: [],
|
||||
dropped: 0,
|
||||
probes: 0,
|
||||
samplerStart: null,
|
||||
samplerStatus: null,
|
||||
rendererStatus: null,
|
||||
status: 'pending',
|
||||
completedAt: null,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Never let optional diagnostic recording change an installation outcome. */
|
||||
export function recordWorkSlice(trace, phase, start, end, detail) {
|
||||
if (!trace) return;
|
||||
try {
|
||||
if (trace.slices.length >= 16) {
|
||||
trace.dropped += 1;
|
||||
return;
|
||||
}
|
||||
trace.slices.push({
|
||||
phase,
|
||||
start,
|
||||
end: end ?? performance.now(),
|
||||
...(detail === undefined ? {} : { detail }),
|
||||
});
|
||||
} catch {
|
||||
try {
|
||||
trace.dropped += 1;
|
||||
} catch {
|
||||
// Missing or incomplete evidence fails the measurement, not the product.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { createWorkTrace, recordWorkSlice } from './phaseTiming.js';
|
||||
|
||||
test('unsupported DEV does not create a recorder and absent recording is inert', () => {
|
||||
assert.equal(createWorkTrace(), null);
|
||||
assert.doesNotThrow(() => recordWorkSlice(null, 'unused', 0));
|
||||
});
|
||||
|
||||
test('work intervals preserve raw endpoints and fail closed on bounded-record loss', () => {
|
||||
const trace = { slices: [], dropped: 0 };
|
||||
recordWorkSlice(trace, 'ground-step', 100, 123);
|
||||
assert.deepEqual(trace.slices[0], {
|
||||
phase: 'ground-step',
|
||||
start: 100,
|
||||
end: 123,
|
||||
});
|
||||
for (let i = 1; i <= 16; i += 1)
|
||||
recordWorkSlice(trace, 'ground-step', 200 + i, 201 + i);
|
||||
assert.equal(trace.slices.length, 16);
|
||||
assert.equal(trace.dropped, 1);
|
||||
assert.deepEqual(trace.slices[0], {
|
||||
phase: 'ground-step',
|
||||
start: 100,
|
||||
end: 123,
|
||||
});
|
||||
|
||||
const detail = { index: 0, clampStart: 2, clampEnd: 3 };
|
||||
const nested = { slices: [], dropped: 0 };
|
||||
recordWorkSlice(nested, 'ground-step', 1, 4, detail);
|
||||
assert.deepEqual(nested.slices[0], {
|
||||
phase: 'ground-step',
|
||||
start: 1,
|
||||
end: 4,
|
||||
detail,
|
||||
});
|
||||
|
||||
const unavailable = { slices: Object.freeze([]), dropped: 0 };
|
||||
assert.doesNotThrow(() =>
|
||||
recordWorkSlice(unavailable, 'ground-step', 1, 2, detail),
|
||||
);
|
||||
assert.equal(unavailable.dropped, 1);
|
||||
});
|
||||
Reference in new issue
Block a user