Polish movement hall interiors: clear doors and bay columns, tone floors and windows.
Document hang keep-outs and period-module fixes; leave columns fixed while packing frames into gaps. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
1b760faf0d
commit
13f6f088b6
25 files changed
+1147
-318
No files matched your search
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* 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`);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user