Document hang keep-outs and period-module fixes; leave columns fixed while packing frames into gaps. Co-authored-by: Cursor <cursoragent@cursor.com>
99 lines
4.1 KiB
JavaScript
99 lines
4.1 KiB
JavaScript
/**
|
|
* Side-wall paintings must not hang in front of bay columns/pilasters.
|
|
* Columns stay fixed; the hang skips column keep-outs (and windows must too).
|
|
* node --test server/__tests__/painting-column-clearance.test.js
|
|
*/
|
|
const { describe, it } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
|
|
const ROOT = path.join(__dirname, '..', '..');
|
|
const KEEP = fs.readFileSync(path.join(ROOT, 'client/src/utils/sideColumnKeepOuts.ts'), 'utf8');
|
|
const LAYOUT = fs.readFileSync(path.join(ROOT, 'client/src/utils/movementHallLayout.ts'), 'utf8');
|
|
const GEOMETRY = fs.readFileSync(path.join(ROOT, 'client/src/components/hall-details/geometry.ts'), 'utf8');
|
|
const GALLERY = fs.readFileSync(path.join(ROOT, 'client/src/components/VirtualGallery.tsx'), 'utf8');
|
|
|
|
/** Mirror of bayPositions — must stay in lockstep with sideColumnKeepOuts.ts / geometry.ts */
|
|
function bayPositions(depth, halfD, spacing = 4.2, max = 9) {
|
|
const count = Math.max(2, Math.min(max, Math.floor(depth / spacing)));
|
|
const step = depth / (count + 1);
|
|
return Array.from({ length: count }, (_, i) => -halfD + step * (i + 1));
|
|
}
|
|
|
|
function parseRhythmTable(src) {
|
|
const start = src.indexOf('SIDE_WALL_COLUMN_RHYTHMS');
|
|
assert.ok(start >= 0, 'SIDE_WALL_COLUMN_RHYTHMS export missing');
|
|
const bodyStart = src.indexOf('{', start);
|
|
const bodyEnd = src.indexOf('} as const', bodyStart);
|
|
assert.ok(bodyEnd > bodyStart);
|
|
const body = src.slice(bodyStart, bodyEnd + 1);
|
|
const rhythms = {};
|
|
for (const m of body.matchAll(
|
|
/['"]?([a-z0-9-]+)['"]?:\s*\{\s*spacing:\s*([\d.]+),\s*max:\s*(\d+),\s*halfClear:\s*([\d.]+)\s*\}/g
|
|
)) {
|
|
rhythms[m[1]] = { spacing: Number(m[2]), max: Number(m[3]), halfClear: Number(m[4]) };
|
|
}
|
|
return rhythms;
|
|
}
|
|
|
|
describe('Painting ↔ column clearance', () => {
|
|
it('exports bayPositions shared by geometry and keep-outs', () => {
|
|
assert.match(KEEP, /export function bayPositions/);
|
|
assert.match(GEOMETRY, /bayPositions/);
|
|
assert.match(GEOMETRY, /useBayZ[\s\S]*bayPositions/);
|
|
});
|
|
|
|
it('registers keep-outs for every mid-wall bay column style', () => {
|
|
const rhythms = parseRhythmTable(KEEP);
|
|
for (const kind of [
|
|
'gothic',
|
|
'roman',
|
|
'quattrocento',
|
|
'cinquecento',
|
|
'mannerist',
|
|
'neoclassical',
|
|
'gothic-revival',
|
|
'art-nouveau',
|
|
]) {
|
|
assert.ok(rhythms[kind], `missing rhythm for ${kind}`);
|
|
assert.ok(rhythms[kind].halfClear >= 0.18, `${kind} halfClear too small`);
|
|
}
|
|
});
|
|
|
|
it('layout packs side walls around column keep-outs and grows depth if needed', () => {
|
|
assert.match(LAYOUT, /sideColumnKeepOutIntervals/);
|
|
assert.match(LAYOUT, /depthForSideHang|sideWallGaps/);
|
|
assert.match(LAYOUT, /details\?:\s*MovementDetailKind/);
|
|
assert.match(LAYOUT, /buildAllMovementHallLayouts\(\s*paintings:\s*Painting\[\],\s*details\?/);
|
|
});
|
|
|
|
it('windows treat column keep-outs as occupied so they do not land on columns', () => {
|
|
const fn = LAYOUT.slice(LAYOUT.indexOf('export function computeSideWallWindows'));
|
|
assert.match(fn, /sideColumnKeepOutIntervals/);
|
|
assert.match(fn, /columnKeepOuts/);
|
|
});
|
|
|
|
it('VirtualGallery passes the movement detail kind into hall layout', () => {
|
|
assert.match(GALLERY, /buildAllMovementHallLayouts\(\s*paintings\s*,\s*interiorStyle\?\.details\s*\)/);
|
|
});
|
|
|
|
it('bay keep-out intervals cover each column centre for a typical hall depth', () => {
|
|
const rhythms = parseRhythmTable(KEEP);
|
|
const depth = 14;
|
|
const halfD = depth / 2;
|
|
for (const [kind, r] of Object.entries(rhythms)) {
|
|
const zs = bayPositions(depth, halfD, r.spacing, r.max);
|
|
assert.ok(zs.length >= 2, kind);
|
|
for (const z of zs) {
|
|
const lo = z - r.halfClear;
|
|
const hi = z + r.halfClear;
|
|
assert.ok(lo < z && z < hi, `${kind} keep-out must contain bay centre`);
|
|
// A typical frame half-width centred on the column would collide
|
|
const halfOuter = 0.35;
|
|
assert.ok(z - halfOuter < hi && z + halfOuter > lo, `${kind} frame would overlap column`);
|
|
}
|
|
}
|
|
});
|
|
});
|