Files
houseplan-card/test/clean-floor.test.mjs
T
Claude f5b20e07f6 perf(floor): key the floor-geometry caches by the floor's content (#744)
The physical bodies, the wall union pool, the inner room contours and the
clean floor carried the global config epoch in their keys. Every edit of
any floor bumps it, so after one edit every other floor was cold again:
in large-house the first visit to an untouched floor rebuilt its wall
union and paid ~0.7 s flat / ~0.65 s 2.5D instead of ~40-55 ms.

A floor's geometry reads only its own config record (spaceModels) and
constants, so the key is now a content fingerprint of that record
(src/floor-geometry-key.ts), remembered per epoch and per record object.
The geometry also reads the current floor's config next to the model it
is given; when those records differ the key covers both. The live resize
preview is its own record, so preview frames get their own key; the
editor runtime seeds the pool and re-keys the bodies through the same
reader. The stairs editor no longer clears the clean floors of every
floor: the stairs are part of the floor's record.

The #735 switch-cycle guard now also sees the union pool and the inner
contours (optional members of the large-house card contract, so an older
comparison bundle reads 0). smoke_floor_geometry_cache proves the warm other floor and the
invalidation against an independent card (multi-floor push with shared
walls, a stair, a resize preview and its cancel); two mutants guard it.

Issue: #744
User-Visible: yes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-10-02 00:56:04 +03:00

77 lines
3.1 KiB
JavaScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { cleanFloorForRoom } from '../test-build/clean-floor.js';
import { geometryAreaMinusStairs } from '../test-build/stairs.js';
// #669: the four render paths read only `path`; the room tooltip and the PDF
// read `area`. The stair subtraction behind `area` must wait for that read.
const floor = [[100, 100], [500, 100], [500, 500], [100, 500]];
const room = { id: 'room-a' };
const stair = {
id: 'stair-a', kind: 'straight', x: 0.3, y: 0.3, angle: 0,
direction: 'forward', length: 0.1, width: 0.05, target_space_id: 'upper',
};
const column = [[400, 400], [440, 400], [440, 440], [400, 440]];
const space = { id: 'space-a', rooms: [room], stairs: [stair] };
const counting = () => {
const calls = { count: 0 };
const areaMinusStairs = (source, stairs) => {
calls.count += 1;
return geometryAreaMinusStairs(source, stairs);
};
return { calls, areaMinusStairs };
};
const build = (overrides = {}) => cleanFloorForRoom({
room, floor, space, floorKey: (spaceId) => `${spaceId}|3`, resizePreview: false, cache: new Map(),
physicalBodies: () => [column], ...overrides,
});
test('#669 AC1 a floor path does not subtract stairs until the area is read', () => {
const { calls, areaMinusStairs } = counting();
const cache = new Map();
const result = build({ cache, areaMinusStairs });
assert.ok(result.path.length > 0, 'the path is built from the floor minus the column');
assert.equal(calls.count, 0, 'building the path must not run the stair subtraction');
const area = result.area;
assert.equal(calls.count, 1, 'the first read subtracts once');
assert.equal(result.area, area);
assert.equal(calls.count, 1, 'a second read reuses the value');
const again = build({ cache, areaMinusStairs });
assert.equal(again, result, 'the cached result is returned');
assert.equal(again.area, area);
assert.equal(calls.count, 1, 'a cache hit does not subtract again');
});
test('#669 AC1 the lazy area equals the eager subtraction and still removes stairs', () => {
const result = build();
const expected = geometryAreaMinusStairs(result.geom, space.stairs);
assert.ok(Math.abs(result.area - expected) <= expected * 1e-9);
const withoutStairs = build({ space: { ...space, stairs: [] } }).area;
assert.ok(result.area < withoutStairs - 1, 'the stair footprint is subtracted');
});
test('#669 AC1 the resize preview is not cached and stays lazy', () => {
const { calls, areaMinusStairs } = counting();
const cache = new Map();
const preview = build({ cache, resizePreview: true, areaMinusStairs });
assert.equal(cache.size, 0);
assert.equal(calls.count, 0);
assert.ok(preview.area > 0);
assert.equal(calls.count, 1);
});
test('#669 AC1 a room without independent bodies subtracts stairs from the plain floor', () => {
const { calls, areaMinusStairs } = counting();
const result = build({ physicalBodies: () => [], areaMinusStairs });
assert.equal(result.geom, null);
assert.equal(calls.count, 0);
const plain = geometryAreaMinusStairs([[[...floor, floor[0]]]], space.stairs);
assert.ok(Math.abs(result.area - plain) <= plain * 1e-9);
assert.equal(calls.count, 1);
});