Files
houseplan-card/test/furniture-stroke-contract.test.mjs
Claude e5ab4659f6 feat: 2.5D stage 6 — public setting, raised tiles, soft sun, theme-free walls (#649)
- settings.volumetric_view (General settings › Display, third switch) replaces
  the alpha entry, the header projection toggle and the phone-menu item; one
  rule for card, sidebar page and kiosk; editors stay Flat; backend accepts
  only a boolean, support package copies it.
- Raised tiles in 2.5D View: no ring, rounded body min(0.275 D, 0.3 h), edge
  0.1 D with the lab filters, ×1.12 size also in layout/collision, lift
  0.075 D, one floor-shadow layer under all markers (theme × floor table),
  frames hugging tile and edge (Alert > Focus > Selected > Hover), forced
  colours without edge and shadow (src/iso-tiles.ts, styles/iso-tiles).
- Soft sun wash instead of projected Flat wedges (src/iso-sun.ts): same gates
  and windows as sun_rays, length from elevation, parallelogram along the sun,
  tone and streaks by floor lightness, sill line.
- Walls take the user's wall colour (top opaque, sides × .77/.68/.60); theme
  rules for walls/openings/labels removed; furniture uses the Flat stroke rule.
- Smokes smoke_iso_tiles/iso_sun/iso_theme_walls/volumetric_setting, 16 new
  mutants, acceptance scenes STAGE6_ACCEPTANCE_SCENARIOS (golden with their
  Linux CI baselines), ACCEPTANCE.md side by side.

Issue: #649
User-Visible: yes
2026-09-25 18:55:14 +03:00

52 lines
2.3 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
const card = readFileSync(new URL('../src/houseplan-card.ts', import.meta.url), 'utf8');
const editor = readFileSync(new URL('../src/houseplan-editor-runtime.ts', import.meta.url), 'utf8');
test('#361 saved furniture and placement preview share the physical screen resolver', () => {
const preview = editor.slice(
editor.indexOf('public _renderFurniturePlacementPreview('),
editor.indexOf('public _furnLive('),
);
const layer = card.slice(
card.indexOf('private _renderDecorLayer('),
card.indexOf('// ================= shared editor secondary surface'),
);
assert.match(preview, /furnitureStrokePx\([\s\S]*decorCmToUnits\([\s\S]*furnitureScreenScale/);
assert.match(preview, /stroke-width=\$\{strokeWidth\}/);
assert.doesNotMatch(
preview,
/stroke-width=\$\{decorCmToUnits/,
'preview must not bypass the camera-aware furniture resolver',
);
// #649 3a: 2.5D uses the same camera rule as Flat — no projection constant
// (the former iso fallback 1 made furniture several times thicker there).
assert.match(layer, /const furnitureScreenScale = furniturePlanScreenScale\(/);
assert.doesNotMatch(layer, /_renderProjection === 'iso' \? 1 : furniturePlanScreenScale/);
assert.match(layer, /const visibleStrokePx = furnitureStrokePx\(strokeWidth, furnitureScreenScale\)/);
assert.match(layer, /stroke-width="\$\{visibleStrokePx\}"/);
assert.match(layer, /_renderFurniturePlacementPreview\(furnitureScreenScale\)/);
assert.equal(
(layer.match(/furniturePlanScreenScale\(/g) || []).length,
1,
'the viewport scale is resolved once for the whole decor layer',
);
});
test('#361 keeps local anisotropic protection and screen-oriented erase hit separate', () => {
const layer = card.slice(
card.indexOf('private _renderDecorLayer('),
card.indexOf('// ================= shared editor secondary surface'),
);
assert.ok(
(layer.match(/vector-effect="non-scaling-stroke"/g) || []).length >= 2,
'saved and erase paths must keep rejecting the local artwork scale',
);
assert.match(layer, /class="dshape derasehit"[\s\S]*vector-effect="non-scaling-stroke"/);
assert.match(layer, /data-hp="decor"[\s\S]*data-kind="\$\{sh\.kind\}"[\s\S]*data-symbol="\$\{sh\.symbol\}"/);
});