Prune queued-card rows whose pooled item paints another entry

A pooled paint item reused by another card keeps a fresh frame stamp, so
rows for cards that stopped painting survived every prune and the index
grew for the whole session. The prune now also drops a row whose item
paints a different entry id or source, and a test pins the bound.
This commit is contained in:
German Khokhlov committed 2026-11-06 14:45:37 +04:00
1 parent da00b096d2
commit 1015b16664
2 files changed
+56 -1

No files matched your search

+9 -1
View File
@@ -2298,7 +2298,15 @@ function addPaintItem(record, placement, temporalAlpha, selected) {
function pruneQueuedItemIndex() {
for (const [sourceId, bySource] of _queuedItemBySource) {
for (const [entryId, item] of bySource) {
if (item.stamp !== _frameStamp) bySource.delete(entryId);
// A pooled item that now paints another entry keeps a fresh stamp.
const entry = item.record.entry;
if (
item.stamp !== _frameStamp ||
entry.id !== entryId ||
entry.source !== sourceId
) {
bySource.delete(entryId);
}
}
if (bySource.size === 0) _queuedItemBySource.delete(sourceId);
}
+47
View File
@@ -2353,3 +2353,50 @@ test('promoting an ambient entry to selected paints it once with no fading dupli
assert.equal(paint(), 1, 'deselect restores the ambient card without re-entry cooldown');
} finally { env.cleanup(); }
});
test('the queued-card index stays bounded while card identities churn', () => {
// The index is internal: capture the per-source map the same way the
// projection-record test captures its map, through Map.prototype.set.
const originalSet = Map.prototype.set;
let queuedIndex = null;
Map.prototype.set = function captureQueuedIndex(key, value) {
if (typeof key === 'string' && key.startsWith('churn-card-')
&& value && typeof value === 'object' && 'temporalAlpha' in value
&& 'placement' in value && 'stamp' in value) {
queuedIndex = this;
}
return originalSet.call(this, key, value);
};
const env = installMockEnvironment({ width: 400, height: 300, dpr: 1 });
try {
initWorldOverlay(env.viewer);
const FRAMES = 1300;
for (let frame = 0; frame < FRAMES; frame++) {
// One painted card whose identity changes every frame: the same pooled
// paint item serves every id (a vessel cohort republished as the camera
// moves, a CCTV or radio card swapping).
setOverlayEntries('cards', [selectedEntry(`churn-card-${frame}`, {
position: positionAtScreen(200, 150),
})]);
env.advanceTime(16);
env.postRender.raise();
assert.equal(getQueuedOverlayEntryAlpha('cards', `churn-card-${frame}`), 1,
'the live card is queued in the frame just painted');
if (frame > 0) {
assert.equal(getQueuedOverlayEntryAlpha('cards', `churn-card-${frame - 1}`), 0,
'a stale row never reports the previous card as queued');
}
}
assert.ok(queuedIndex, 'the queued-card index was never populated');
// Rows are pruned every 600 painted frames, so with one new id per frame
// the index holds at most one prune window of ids plus the live card.
const bound = 600 + 1;
assert.ok(
queuedIndex.size <= bound,
`queued-card index holds ${queuedIndex.size} rows after ${FRAMES} identities (bound ${bound})`,
);
} finally {
Map.prototype.set = originalSet;
env.cleanup();
}
});