Files
Claudeandclaude[bot] b998b0b34a feat(moon): the moon with any background, and its status in General settings (#718)
The owner decided on 30.09 that the moon is not part of the "Follow the Sun"
environment but a switch of its own: with a static background (global or a
space's own) the card showed no moon even with the switch on, and the switch
said nothing about why the moon was missing right now.

With a static background there is no environment, so the moon stands in its
own layer, `.hp-moon-sky`: the first child of `.stage` / `.hp-static-stage`,
the whole scene, no z-index, filter or will-change, under the plan by DOM
order, fading with the #101 View weight. Inside is the very #661 element, so
place, size, art and fades are unchanged, and a background switch moves it to
its new parent in the same render without a flicker. The phase comes from the
same `resolveDayCycle`, computed only while the moon is on and on View; without
`sun.sun` both cards keep their 30 s clock ticker and re-render only when the
phase changes (the environment is still compared by its whole fingerprint).

General settings get a second caption line under the moon switch
(`data-moon-status`): one snapshot per opening, judged by the lazy chunk as if
the switch were on, first reason wins (no home, day, below 3°, under 3 %),
numbers rounded and clamped below the threshold they missed. `moonStatus`
decides "shown" with the same `moonShownAt` as the element. It lives in a
WeakMap beside the draft, so it never makes the dialog dirty; a closed
opening's result is dropped. The dialog loads the chunk through the gate's
loader (`withMoon`), now shared by every caller while a load is in flight, so
there is still one fingerprint check and one retry token.

Bundle (same build, against origin/dev): initial View 300 072 -> 300 248 B gzip
(+176 B, under the 500 B of the spec; budget and ceiling not raised); lazy
editor 238 558 -> 238 991 B (+433 B, the line and English strings); lazy moon
11 385 -> 11 712 B (+327 B, layer CSS and status). `src/moon.ts` stays out of
the initial and the editor graph; bundle-budget now refuses an editor/moon
overlap. Monolith metrics: hostRefs 4 885 -> 4 888 — the three `host.` reads of
`src/editors/moon-status.ts` (hass, `_settingsDialog`, requestUpdate) through
its own three-member interface, not the editor port; the other five metrics
are unchanged. houseplan-editor-runtime.ts grows by two lines (import, call).

Tests: AC9/AC10/AC15 and the sky layer in test/moon.test.mjs (the #661
"static -> nothing" check inverted), AC14 and the opening lifecycle in
test/moon-settings.test.mjs, smokes demo/smoke_moon_static.mjs (AC1-AC6; AC1
and AC3 were red on dev) and demo/smoke_moon_status.mjs (AC11/AC12), AC7 in
smoke_daycycle_layer_budget. Golden: two new scenes
(static-bg-moon-gibbous-white-light, static-bg-moon-crescent-south-dark,
matrix v70), the harness checks the moon's parent by background and waits for
the status line in the General settings frames. Four new mutants; the clock
ticker one is a browser guard (201 at the guideline of 200).

Issue: #718
User-Visible: yes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-10-01 05:25:21 +00:00

1318 lines
77 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { fixtureWallKey } from '../fixtures/visual-matrix.mjs';
/** Data-only HP-QA-01 capture matrix. Bump when framing or scenarios change. */
export const GOLDEN_MATRIX_VERSION = 70;
const stage = { capture: 'stage', threshold: { maxChannelDelta: 10, maxDiffRatio: 0.0005 } };
const page = { capture: 'page', threshold: { maxChannelDelta: 10, maxDiffRatio: 0.0008 } };
const sunWindow = { capture: 'sun-window', threshold: { maxChannelDelta: 10, maxDiffRatio: 0.001 } };
const openingRoomContract = {
kind: 'room', wallCm: 30, surface: 'flat',
openings: [
{ id: 'golden-centred-door', type: 'door', at: 0.25, length: 0.10,
flipV: false, offset: 'center' },
{ id: 'golden-centred-window', type: 'window', at: 0.50, length: 0.10,
flipV: false, offset: 'center' },
{ id: 'golden-centred-gate', type: 'gate', at: 0.75, length: 0.10,
flipV: false, offset: 'center' },
],
};
const openingPartitionContract = {
kind: 'partition', wallCm: 30, surface: 'flat',
a: [0.14, 0.22], b: [0.86, 0.78],
openings: [
{ id: 'golden-diagonal-door', type: 'door', at: 0.22, length: 0.09,
flipV: false, offset: 'center' },
{ id: 'golden-diagonal-window', type: 'window', at: 0.50, length: 0.09,
flipV: false, offset: 'center' },
{ id: 'golden-diagonal-gate', type: 'gate', at: 0.78, length: 0.09,
flipV: false, offset: 'center' },
],
};
const openingFlipContract = {
kind: 'room', wallCm: 30, surface: 'flat',
openings: [
{ id: 'golden-door-default', type: 'door', at: 0.16, length: 0.08,
flipV: false, offset: 'center' },
{ id: 'golden-door-flipped', type: 'door', at: 0.30, length: 0.08,
flipV: true, offset: 'center' },
{ id: 'golden-window-default', type: 'window', at: 0.44, length: 0.08,
flipV: false, offset: 'center' },
{ id: 'golden-window-flipped', type: 'window', at: 0.58, length: 0.08,
flipV: true, offset: 'center' },
{ id: 'golden-gate-default', type: 'gate', at: 0.72, length: 0.08,
flipV: false, offset: 'center', turnPair: 'golden-gate-turn' },
{ id: 'golden-gate-flipped', type: 'gate', at: 0.86, length: 0.08,
flipV: true, offset: 'center', turnPair: 'golden-gate-turn' },
],
};
const openingIsoContract = {
kind: 'room', wallCm: 30, surface: 'iso',
openings: [
{ id: 'golden-iso-door', type: 'door', at: 0.20, length: 0.09,
flipV: false, offset: 'selected-face' },
{ id: 'golden-iso-window', type: 'window', at: 0.40, length: 0.09,
flipV: false, offset: 'center' },
{ id: 'golden-iso-gate-default', type: 'gate', at: 0.62, length: 0.09,
flipV: false, offset: 'selected-face', turnPair: 'golden-iso-gate-turn' },
{ id: 'golden-iso-gate-flipped', type: 'gate', at: 0.82, length: 0.09,
flipV: true, offset: 'selected-face', turnPair: 'golden-iso-gate-turn' },
],
};
const stage3DenseMarkers = [
{ id: 'golden-light-one', binding: 'device:golden-light-one', value_badge: {
enabled: true,
source: { kind: 'entity_state', entity_id: 'light.golden_light_one' },
position: 'right',
} },
{ id: 'golden-light-two', binding: 'device:golden-light-two' },
{ id: 'golden-presence', binding: 'device:golden-presence', display: 'icon_ripple' },
{ id: 'golden-climate', binding: 'device:golden-climate', value_badge: {
enabled: true,
source: {
kind: 'entity_attribute', entity_id: 'climate.golden_climate',
attribute: 'current_temperature',
},
position: 'top',
} },
{ id: 'golden-left-linkquality', binding: 'device:golden-left-linkquality', value_badge: {
enabled: true,
source: { kind: 'entity_state', entity_id: 'sensor.golden_left_linkquality' },
position: 'bottom',
} },
{ id: 'golden-right-linkquality', binding: 'device:golden-right-linkquality' },
];
const stage3DenseLayout = {
'golden-light-one': { s: 'golden-lighting', x: 0.487, y: 0.22 },
'golden-light-two': { s: 'golden-lighting', x: 0.43, y: 0.76 },
'golden-presence': { s: 'golden-lighting', x: 0.513, y: 0.22 },
'golden-climate': { s: 'golden-lighting', x: 0.56, y: 0.64 },
'golden-left-linkquality': { s: 'golden-lighting', x: 0.487, y: 0.43 },
'golden-right-linkquality': { s: 'golden-lighting', x: 0.513, y: 0.76 },
};
const stage3VacuumTrail = {
maxDeviationCm: 17.5,
current: [
[[180, 220], [360, 220], [360, 420], [520, 420]],
[[650, 240], [780, 240], [780, 500], [650, 620], [850, 760]],
],
previous: [[160, 760], [300, 650], [420, 740], [560, 600]],
};
const stage3RequiredOverlays = ['device', 'room-label', 'opening-lock'];
/** Exact existing baseline impact of the opening-symbol change (#242/#250, matrix
* v37): measured by comparing `actualSha256` for HEAD and origin/dev under the
* same Chromium build (baseline status alone was not used — dev already had
* unrelated pending candidates). Every listed frame uses a shared fixture with
* an affected opening or keeps that plan behind an editor/dialog; no other
* existing frame changed. Keep this list reviewable: this is its only copy
* since the testing-notes checklists were removed (#681). */
export const OPENING_SYMBOL_EXISTING_GOLDEN_IMPACT = Object.freeze([
'isometric-geometry-view-dark',
'isometric-geometry-view-light',
'isometric-live-layers-dark',
'isometric-no-borders-dark',
'isometric-touch-kiosk-dark',
'isometric-large-warm-remount-dark',
'geometry-view-dark-fit',
'geometry-view-light-fit',
'room-label-parity-view-dark',
'room-label-parity-plan-dark',
'room-label-parity-view-light',
'room-label-parity-plan-light',
'day-cycle-dawn-dark',
'day-cycle-day-dark',
'day-cycle-dusk-dark',
'day-cycle-night-dark',
'geometry-plan-editor-dark',
'space-tab-drop-before-light',
'space-tab-drop-after-dark',
'plan-snap-endpoint-light',
'plan-snap-line-gaps-dark',
'junction-patch-resilience-plan-dark',
'opening-placement-door-thick-wall-dark',
'opening-placement-passage-thick-wall-dark',
'opening-placement-passage-thick-wall-light',
'geometry-devices-editor-dark',
'geometry-decor-editor-dark',
'tray-wide-selection-en',
'tray-wide-tool-ru',
'tray-medium-group-en',
'tray-medium-selection-ru',
'tray-narrow-palette-en',
'tray-narrow-tool-ru',
'geometry-diagonal-45-opening-dark',
'openings-thick-wall-dark',
'lighting-glow-sun-dark',
'device-value-badge-positions-dark',
'device-icon-state-table-light',
'device-icon-state-table-dark',
'device-text-shell-long-light',
'device-text-shell-long-dark',
'lighting-sun-window-state-only-dark',
'lighting-fill-light-axis-split-dark',
'lighting-fill-temp-axis-split-dark',
'lighting-fill-lqi-axis-split-dark',
'lighting-temp-glow-dark',
'lighting-temp-glow-light',
'lighting-custom-glow-dark',
'lighting-opaque-glow-two-doorways-dark',
'lighting-custom-glow-light',
'lighting-temp-glow-no-sources-dark',
'lighting-temp-glow-room-override-dark',
'lighting-manual-auto-spill-overlap-dark',
'hover-over-glow-dark',
'hover-nested-room-dark',
'large-house-zoom-040-dark',
'large-house-zoom-250-dark',
'large-house-warm-remount-dark',
'device-dialog-desktop-en',
'device-help-popover-light-ru',
'decor-color-popover-desktop-en',
'general-color-popover-desktop-en',
'space-room-color-popover-desktop-ru',
'backup-full-preview-desktop-en',
'backup-plan-only-export-desktop-en',
'optimize-preflight-dialog-dark-en',
'optimize-preflight-dialog-light-ru',
]);
const decorLayerFixture = [
{ id: 'golden-decor-axis', kind: 'line', x1: 0.10, y1: 0.54, x2: 0.90, y2: 0.54,
color: '#ff0033', opacity: 1, width_cm: 8 },
{ id: 'golden-decor-rect', kind: 'rect', x: 0.15, y: 0.28, w: 0.16, h: 0.12,
color: '#ff0033', opacity: 1, width_cm: 2, fill: true,
fill_color: '#ff6680', fill_opacity: 1 },
{ id: 'golden-decor-ellipse', kind: 'ellipse', x: 0.69, y: 0.28, w: 0.16, h: 0.12,
color: '#ff0033', opacity: 1, width_cm: 2, fill: true,
fill_color: '#ff6680', fill_opacity: 1 },
{ id: 'golden-decor-text', kind: 'text', x: 0.24, y: 0.72, text: 'DECOR',
color: '#ff0033', opacity: 1, size_cm: 14 },
{ id: 'golden-decor-sofa', kind: 'furniture', symbol: 'sofa',
x: 0.68, y: 0.66, w: 0.18, h: 0.13, color: '#ff0033', opacity: 1, width_cm: 2 },
];
const decorLayerProbes = {
color: '#ff0033', radius: 2, minMatchingFraction: 0.6,
points: [
{ id: 'left-room', x: 0.25, y: 0.54 },
{ id: 'opening-tunnel', x: 0.50, y: 0.54 },
{ id: 'right-room', x: 0.75, y: 0.54 },
],
};
const stairLayerFixture = [
{ id: 'golden-stair-straight-large', kind: 'straight', x: 0.25, y: 0.27,
length: 0.28, width: 0.10, angle: 0, direction: 'forward',
target_space_id: 'golden-lighting' },
{ id: 'golden-stair-straight-small', kind: 'straight', x: 0.72, y: 0.27,
length: 0.14, width: 0.065, angle: 45, direction: 'backward',
// #683 AC13: explicit user-owned style stays identical in light/dark themes.
color: '#0066cc', opacity: 0.85, fill_color: '#ffd23f', fill_opacity: 0.55,
target_space_id: 'golden-lighting' },
{ id: 'golden-stair-spiral-large', kind: 'spiral', x: 0.25, y: 0.73,
radius: 0.10, angle: 15, direction: 'clockwise',
target_space_id: 'golden-lighting' },
{ id: 'golden-stair-spiral-small', kind: 'spiral', x: 0.72, y: 0.73,
radius: 0.06, angle: 210, direction: 'counterclockwise',
target_space_id: 'golden-lighting' },
];
/**
* #649/#673 Stage 6 acceptance scenes
* (docs/design/649-25d-stage6/ACCEPTANCE.md): tiles, floor shadows, soft sun
* wash and theme-free walls in the four theme × floor combinations of the
* approved sketch 07 (attachments 09, 10, 12, 13) plus the lifted hover frame
* (attachment 11); sun as the lab default 10:00. These exact objects are part
* of GOLDEN_SCENARIOS below; the separate export keeps the focused diagnostic
* side-by-side capture script useful without defining a second matrix.
*/
export const STAGE6_ACCEPTANCE_SCENARIOS = Object.freeze([
...[['light', 'light'], ['light', 'dark'], ['dark', 'dark'], ['dark', 'light']].map(([theme, floor]) => ({
id: `isometric-stage6-${theme}-${floor}floor`, fixture: 'visual',
space: 'golden-lighting', mode: 'view', projection: 'iso',
stage3Fixture: { roomMetrics: true, lockOpening: 'light-door', lockState: 'locked' },
stage3Golden: { requiredKinds: stage3RequiredOverlays },
markerOverrides: stage3DenseMarkers,
layoutOverrides: stage3DenseLayout,
stateOverrides: {
'light.golden_light_one': { attributes: { lqi: 40 } },
'light.golden_light_two': { attributes: { lqi: 120 } },
'sensor.golden_right_linkquality': { state: '210' },
'sun.sun': { attributes: { azimuth: 150, elevation: 52 } },
},
fillMode: 'custom', customFill: { c: floor === 'light' ? '#eee8de' : '#737777', a: 1 },
glowEnabled: false, sunRays: true, northDeg: 180,
// The lab's walls are the default white; the colour is the user's either way.
wallFill: { c: '#ffffff', a: 1 },
theme, viewport: { width: 1000, height: 900 }, ...stage,
})),
{ id: 'isometric-stage6-hover-light', fixture: 'visual',
space: 'golden-lighting', mode: 'view', projection: 'iso',
stage3Fixture: { roomMetrics: true, lockOpening: 'light-door', lockState: 'locked' },
stage3Golden: { requiredKinds: stage3RequiredOverlays },
markerOverrides: stage3DenseMarkers,
layoutOverrides: stage3DenseLayout,
fillMode: 'custom', customFill: { c: '#eee8de', a: 1 },
glowEnabled: false, sunRays: false,
hoverDevice: 'golden-light-two',
theme: 'light', viewport: { width: 1000, height: 900 }, ...stage,
},
]);
export const GOLDEN_SCENARIOS = Object.freeze([
...STAGE6_ACCEPTANCE_SCENARIOS,
// #663 AC3/AC10/AC12: both stair geometries, both rise directions and
// small/large footprints across themes, flat/2.5D and interaction states.
{ id: 'stairs-flat-normal-light', fixture: 'visual', space: 'golden-geometry', mode: 'view',
stairsOverride: stairLayerFixture, language: 'en', theme: 'light',
viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'stairs-flat-hover-dark', fixture: 'visual', space: 'golden-geometry', mode: 'view',
stairsOverride: stairLayerFixture, hoverStair: 'golden-stair-straight-large',
language: 'ru', theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'stairs-flat-selected-light', fixture: 'visual', space: 'golden-geometry', mode: 'plan',
stairsOverride: stairLayerFixture, stairSelection: 'golden-stair-spiral-large',
language: 'en', theme: 'light', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'stairs-isometric-dark', fixture: 'visual', space: 'golden-geometry', mode: 'view',
projection: 'iso', stairsOverride: stairLayerFixture,
language: 'en', theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
// #486 AC7: the custom HA panel owns the full viewport. These three focused
// frames pin its populated View, its Plan-editor chrome and the deliberately
// inert read-only first-run state without duplicating the card matrix below.
{ id: 'panel-wide-view-light-en', fixture: 'visual', space: 'golden-geometry',
panelHost: true, mode: 'view', language: 'en', theme: 'light',
viewport: { width: 1280, height: 800 }, ...page },
{ id: 'panel-wide-plan-editor-dark-en', fixture: 'visual', space: 'golden-geometry',
panelHost: true, mode: 'plan', language: 'en', theme: 'dark',
viewport: { width: 1280, height: 800 }, ...page },
{ id: 'panel-narrow-read-only-empty-light-en', fixture: 'visual',
panelHost: true, narrow: true, emptyPlan: true, canWrite: false, userIsAdmin: false,
preloadEditorRuntime: false, mode: 'view', language: 'en', theme: 'light',
viewport: { width: 320, height: 720 }, ...page },
// #462 AC12: the card-level version recovery notice must stay compact,
// readable and outside layout/fit flow on desktop, narrow touch and the
// manual-only kiosk state after this tab already attempted the target.
{ id: 'version-mismatch-desktop-dark-en', fixture: 'visual', space: 'golden-geometry',
mode: 'view', integrationVersion: '0.0.0-golden-backend',
language: 'en', theme: 'dark', viewport: { width: 1000, height: 900 }, ...page },
{ id: 'version-mismatch-touch-light-ru', fixture: 'visual', space: 'golden-geometry',
mode: 'view', integrationVersion: '0.0.0-golden-backend', touchViewport: true,
language: 'ru', theme: 'light', viewport: { width: 390, height: 760 }, ...page },
{ id: 'version-mismatch-kiosk-attempted-dark-de', fixture: 'visual', space: 'golden-geometry',
mode: 'view', integrationVersion: '0.0.0-golden-backend',
versionRecoveryAttempted: true, kiosk: true,
language: 'de', theme: 'dark', viewport: { width: 820, height: 760 }, ...page },
{ id: 'hidden-wall-diagnostics-plan-light', fixture: 'visual',
space: 'golden-coincident-partition', coincidentPartition: 'before',
hiddenWallDiagnostics: true, mode: 'plan', theme: 'light',
viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'hidden-wall-diagnostics-plan-dark', fixture: 'visual',
space: 'golden-coincident-partition', coincidentPartition: 'before',
hiddenWallDiagnostics: true, mode: 'plan', theme: 'dark',
viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'coincident-partition-before-dark', fixture: 'visual',
space: 'golden-coincident-partition', coincidentPartition: 'before',
mode: 'view', theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'coincident-partition-thin-dark', fixture: 'visual',
space: 'golden-coincident-partition', coincidentPartition: 'thin',
mode: 'view', theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'coincident-partition-thick-dark', fixture: 'visual',
space: 'golden-coincident-partition', coincidentPartition: 'thick',
mode: 'view', theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'coincident-partition-virtual-dark', fixture: 'visual',
space: 'golden-coincident-partition', coincidentPartition: 'virtual',
mode: 'view', theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
// #316 §3.1: a legacy span over a wall with a door — after the migration the
// door keeps its carrying atom while the border is zero on both sides.
{ id: 'span-over-door-migrated-dark', fixture: 'visual',
space: 'golden-span-over-door', spanOverDoor: true,
mode: 'view', theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'split-corner-wall-before-dark', fixture: 'visual', space: 'golden-corner-split',
cornerSplitWall: 'before', mode: 'view', theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'split-corner-wall-thin-dark', fixture: 'visual', space: 'golden-corner-split',
cornerSplitWall: 'thin', mode: 'view', theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'split-corner-wall-thick-dark', fixture: 'visual', space: 'golden-corner-split',
cornerSplitWall: 'thick', mode: 'view', theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'split-zero-divider-taper-dark', fixture: 'visual', space: 'golden-zero-divider',
cornerSplitWall: 'zero-taper', mode: 'view', theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'isometric-geometry-view-dark', fixture: 'visual', space: 'golden-geometry', mode: 'view',
// Stage 2 material/floor-edge plus door, window, gate and nested-room coverage.
projection: 'iso',
theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'isometric-geometry-view-light', fixture: 'visual', space: 'golden-geometry', mode: 'view',
projection: 'iso',
theme: 'light', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'isometric-live-layers-dark', fixture: 'visual', space: 'golden-lighting', mode: 'view',
projection: 'iso',
theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'isometric-no-borders-dark', fixture: 'visual', space: 'golden-lighting', mode: 'view',
projection: 'iso', showBorders: false, fillMode: 'custom',
customFill: { c: '#486a8f', a: 0.42 }, glowEnabled: true,
stage3Golden: { noBorders: true },
theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'isometric-touch-kiosk-dark', fixture: 'visual', space: 'golden-geometry', mode: 'view',
projection: 'iso', kiosk: true,
theme: 'dark', viewport: { width: 390, height: 760 }, ...stage },
{ id: 'isometric-large-warm-remount-dark', fixture: 'large', space: 'perf-floor-2', mode: 'view',
projection: 'iso', warmRemount: true,
theme: 'dark', viewport: { width: 1180, height: 900 }, ...stage },
...['light', 'dark'].map((theme) => ({
id: `isometric-stage3-overlays-${theme}`, fixture: 'visual',
space: 'golden-lighting', mode: 'view', projection: 'iso',
stage3Fixture: {
roomMetrics: true, lockOpening: 'light-door', lockState: 'locked',
newDevice: 'golden-presence',
},
stage3Golden: {
requiredKinds: stage3RequiredOverlays, requireOneRise: true,
requireMaterialDefs: true, maxMaterialDefs: 12,
requireVacuumFloor: theme === 'dark',
requireDenseFacets: true,
requireNoTetherCues: true,
requireNoGrounding: true,
requireLiveLayers: theme === 'dark',
},
markerOverrides: stage3DenseMarkers,
markerAreaSnapshot: {
'golden-light-one': { binding: 'device:golden-light-one', area: 'golden_light_left' },
'golden-light-two': { binding: 'device:golden-light-two', area: 'golden_light_left' },
'golden-presence': { binding: 'device:golden-presence', area: 'golden_light_right' },
'golden-climate': { binding: 'device:golden-climate', area: 'golden_light_right' },
'golden-left-linkquality': {
binding: 'device:golden-left-linkquality', area: 'golden_light_left',
},
'golden-right-linkquality': {
binding: 'device:golden-right-linkquality', area: 'golden_light_right',
},
},
stateOverrides: {
'light.golden_light_one': { attributes: { lqi: 40 } },
'light.golden_light_two': { attributes: { lqi: 120 } },
'sensor.golden_right_linkquality': { state: '210' },
...(theme === 'dark' ? {
'sun.sun': { attributes: { azimuth: 270, elevation: 24 } },
} : {}),
},
layoutOverrides: stage3DenseLayout,
...(theme === 'dark' ? {
vacuumTrail: stage3VacuumTrail,
fillMode: 'custom', customFill: { c: '#486a8f', a: 0.42 },
hoverRoom: 'light-left', decorOverride: decorLayerFixture,
glowEnabled: true, sunRays: true, northDeg: 90,
} : {}),
theme, viewport: { width: 1000, height: 900 }, ...stage,
})),
{ id: 'isometric-stage3-openings-dark', fixture: 'visual',
space: 'golden-lighting', mode: 'view', projection: 'iso',
showNames: false, glowEnabled: false, sunRays: false,
stage3Fixture: { lockOpening: 'light-door', lockState: 'unlocked' },
extraOpenings: [{
id: 'stage3-passage', type: 'passage', x: 0.50, y: 0.30,
angle: 90, length: 0.12,
}],
stage3Golden: {
requiredKinds: ['device', 'opening-lock'],
requireMaterialDefs: true, maxMaterialDefs: 12,
openingKinds: ['door', 'window', 'gate'], requirePassage: true,
},
theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'isometric-stage3-forced-colors-dark', fixture: 'visual',
space: 'golden-lighting', mode: 'view', projection: 'iso',
forcedColors: true,
stage3Fixture: { roomMetrics: true, lockOpening: 'light-door', lockState: 'locked' },
stage3Golden: {
requiredKinds: stage3RequiredOverlays, requireOneRise: true,
requireNoMaterialDefs: true, requireNoTetherCues: true, requireNoGrounding: true,
},
layoutOverrides: stage3DenseLayout,
theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'isometric-stage3-no-filter-dark', fixture: 'visual',
space: 'golden-lighting', mode: 'view', projection: 'iso',
disableIsoFilters: true,
stage3Fixture: { roomMetrics: true, lockOpening: 'light-door', lockState: 'locked' },
stage3Golden: {
requiredKinds: stage3RequiredOverlays, requireOneRise: true,
requireNoMaterialDefs: true, requireNoTetherCues: true, requireNoGrounding: true,
},
layoutOverrides: stage3DenseLayout,
theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'geometry-view-dark-fit', fixture: 'visual', space: 'golden-geometry', mode: 'view',
theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'geometry-view-light-fit', fixture: 'visual', space: 'golden-geometry', mode: 'view',
theme: 'light', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'pdf-export-geometry-light', fixture: 'visual', space: 'golden-geometry', mode: 'view',
pdfExport: true, theme: 'light', viewport: { width: 1000, height: 900 }, ...page },
{ id: 'pdf-export-polish-light', fixture: 'visual', space: 'golden-geometry', mode: 'view',
pdfExport: true, northDeg: 90,
// One almost-zero seam exercises duplicate collapse in the complete PDF
// pipeline; the zero-thickness bottom edge keeps that material contract in
// the reviewed frame without growing a second fixture family.
wallReplacements: [
{
match: { a: [0.06, 0.08], b: [0.48, 0.08] },
segments: [
{ key: fixtureWallKey([0.06, 0.08], [0.479999, 0.08]),
a: [0.06, 0.08], b: [0.479999, 0.08], cm: 10 },
{ key: fixtureWallKey([0.479999, 0.08], [0.48, 0.08]),
a: [0.479999, 0.08], b: [0.48, 0.08], cm: 10 },
],
},
{
match: { a: [0.06, 0.92], b: [0.48, 0.92] },
segments: [{ key: fixtureWallKey([0.06, 0.92], [0.48, 0.92]),
a: [0.06, 0.92], b: [0.48, 0.92], cm: 0 }],
},
],
pdfSemantic: {
minSceneInkPixels: 2_000,
minSceneCoverage: 0.20,
maxCenterOffsetMm: 3,
minSceneClearanceMm: 0.1,
minWallGrayPixels: 600,
maxWallGrayOutsideFieldRatio: 0.02,
minInteriorHatchPixels: 30,
opening: {
xFraction: (0.26 - 0.06) / (0.94 - 0.06),
controlOffsetFraction: 0.16,
halfWidthFraction: 0.025,
maxGrayRatio: 0.65,
minWhiteFraction: 0.40,
},
minCompassInkPixels: 60,
maxDimensionAxisErrorDeg: 0.5,
minRepeatedDimensionLabels: 1,
forbiddenText: ['wall', 'partition', 'door', 'window', 'gate'],
},
theme: 'light', viewport: { width: 1000, height: 900 }, ...page },
{ id: 'pdf-export-stepped-dimensions-light', fixture: 'visual',
space: 'golden-pdf-stepped', mode: 'view', pdfExport: true, pdfSteppedExterior: true,
pdfSemantic: {
minSceneInkPixels: 1_000,
minSceneCoverage: 0.15,
maxCenterOffsetMm: 3,
minSceneClearanceMm: 0.1,
minWallGrayPixels: 400,
maxWallGrayOutsideFieldRatio: 0.02,
minInteriorHatchPixels: 20,
minCompassInkPixels: 60,
maxDimensionAxisErrorDeg: 0.5,
minRepeatedDimensionLabels: 0,
requiredDimensionLabels: ['1.20 m', '2.40 m', '3.60 m', '3.75 m', '7.35 m', '9.75 m'],
forbiddenText: ['wall', 'partition', 'door', 'window', 'gate'],
},
theme: 'light', viewport: { width: 1000, height: 900 }, ...page },
{ id: 'vacuum-trail-smoothing-dark', fixture: 'visual', space: 'golden-geometry', mode: 'view',
vacuumTrail: {
maxDeviationCm: 17.5,
current: [
[[180, 220], [360, 220], [360, 420], [520, 420]],
[[650, 240], [780, 240], [780, 500], [650, 620], [850, 760]],
],
previous: [[160, 760], [300, 650], [420, 740], [560, 600]],
},
theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'room-label-parity-view-dark', fixture: 'visual', space: 'golden-lighting', mode: 'view',
roomLabelParity: true, theme: 'dark', viewport: { width: 1000, height: 900 }, ...page },
{ id: 'room-label-parity-plan-dark', fixture: 'visual', space: 'golden-lighting', mode: 'plan',
roomLabelParity: true, theme: 'dark', viewport: { width: 1000, height: 900 }, ...page },
{ id: 'room-label-parity-view-light', fixture: 'visual', space: 'golden-lighting', mode: 'view',
roomLabelParity: true, theme: 'light', viewport: { width: 1000, height: 900 }, ...page },
{ id: 'room-label-parity-plan-light', fixture: 'visual', space: 'golden-lighting', mode: 'plan',
roomLabelParity: true, theme: 'light', viewport: { width: 1000, height: 900 }, ...page },
{ id: 'washer-active-cycle-dark', fixture: 'visual', applianceLifecycle: true,
space: 'golden-appliance', mode: 'view',
stateOverrides: { 'sensor.golden_washer_status': { state: 'start' } },
theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'washer-idle-cycle-dark', fixture: 'visual', applianceLifecycle: true,
space: 'golden-appliance', mode: 'view',
stateOverrides: { 'sensor.golden_washer_status': { state: 'done' } },
theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'day-cycle-dawn-dark', fixture: 'visual', space: 'golden-geometry', mode: 'view',
bgMode: 'daynight', dayCycle: { phase: 'dawn', top: '#aabdd1' },
stateOverrides: { 'sun.sun': { attributes: { azimuth: 95, elevation: -2, rising: true } } },
theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'day-cycle-day-dark', fixture: 'visual', space: 'golden-geometry', mode: 'view',
bgMode: 'daynight', dayCycle: { phase: 'day', top: '#dce9ef' },
stateOverrides: { 'sun.sun': { attributes: { azimuth: 180, elevation: 42, rising: false } } },
theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'day-cycle-dusk-dark', fixture: 'visual', space: 'golden-geometry', mode: 'view',
bgMode: 'daynight', dayCycle: { phase: 'dusk', top: '#48536c' },
stateOverrides: { 'sun.sun': { attributes: { azimuth: 265, elevation: -2, rising: false } } },
theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'day-cycle-night-dark', fixture: 'visual', space: 'golden-geometry', mode: 'view',
bgMode: 'daynight', dayCycle: { phase: 'night', top: '#111a27' },
stateOverrides: { 'sun.sun': { attributes: { azimuth: 0, elevation: -12, rising: false } } },
theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
// #661 AC7: the moon in its phase at the top-left of the scene, behind the
// plan — the plan deliberately covers part of the disc (owner decision 7).
// The lit side is the left one in both hemispheres (owner 2026-09-29).
{ id: 'day-cycle-night-moon-gibbous-dark', fixture: 'visual', space: 'golden-geometry', mode: 'view',
bgMode: 'daynight', dayCycle: { phase: 'night', top: '#111a27' },
moon: { clock: '2026-10-21T18:00:00Z', latitude: 55.75, longitude: 37.62, k: '0.79' },
stateOverrides: { 'sun.sun': { attributes: { azimuth: 0, elevation: -12, rising: false } } },
theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'day-cycle-dusk-moon-crescent-south-dark', fixture: 'visual', space: 'golden-geometry', mode: 'view',
bgMode: 'daynight', dayCycle: { phase: 'dusk', top: '#48536c' },
moon: { clock: '2026-10-14T09:00:00Z', latitude: -33.87, longitude: 151.21, k: '0.14' },
stateOverrides: { 'sun.sun': { attributes: { azimuth: 265, elevation: -2, rising: false } } },
theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
// #718 AC8: the moon with a static background — its own sky layer at the
// top-left of the scene, the plan over part of the disc, the chosen scene
// colour around the plan unchanged. White in the light theme, dark grey in
// the dark one; the same art and lit side as over "Follow the Sun".
{ id: 'static-bg-moon-gibbous-white-light', fixture: 'visual', space: 'golden-geometry', mode: 'view',
bgMode: 'static', bgColor: '#ffffff',
moon: { clock: '2026-10-21T18:00:00Z', latitude: 55.75, longitude: 37.62, k: '0.79' },
stateOverrides: { 'sun.sun': { attributes: { azimuth: 0, elevation: -12, rising: false } } },
theme: 'light', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'static-bg-moon-crescent-south-dark', fixture: 'visual', space: 'golden-geometry', mode: 'view',
bgMode: 'static', bgColor: '#202126',
moon: { clock: '2026-10-14T09:00:00Z', latitude: -33.87, longitude: 151.21, k: '0.14' },
stateOverrides: { 'sun.sun': { attributes: { azimuth: 265, elevation: -2, rising: false } } },
theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'geometry-plan-editor-dark', fixture: 'visual', space: 'golden-geometry', mode: 'plan',
theme: 'dark', viewport: { width: 1180, height: 900 }, ...page },
{ id: 'safe-resize-handles-clamp-light', fixture: 'visual', space: 'golden-safe-resize',
safeResizeFixture: true, safeResizePreview: true, iconSize: 0.8, mode: 'plan',
theme: 'light', viewport: { width: 1180, height: 900 }, ...page },
{ id: 'safe-resize-handles-clamp-dark', fixture: 'visual', space: 'golden-safe-resize',
safeResizeFixture: true, safeResizePreview: true, iconSize: 0.8, mode: 'plan',
theme: 'dark', viewport: { width: 1180, height: 900 }, ...page },
{ id: 'space-tab-drop-before-light', fixture: 'visual', applianceLifecycle: true,
space: 'golden-geometry', mode: 'plan', tabDrag: 'before',
theme: 'light', viewport: { width: 1180, height: 900 }, ...page },
{ id: 'space-tab-drop-after-dark', fixture: 'visual', applianceLifecycle: true,
space: 'golden-geometry', mode: 'plan', tabDrag: 'after',
theme: 'dark', viewport: { width: 1180, height: 900 }, ...page },
{ id: 'plan-snap-endpoint-light', fixture: 'visual', space: 'golden-geometry', mode: 'plan',
planSnap: { tool: 'draw', pointer: [0.48, 0.48], expectedKind: 'endpoint' },
theme: 'light', viewport: { width: 1180, height: 900 }, ...page },
{ id: 'plan-snap-line-gaps-dark', fixture: 'visual', space: 'golden-geometry', mode: 'plan',
planSnap: {
tool: 'draw', anchor: [0.70, 0.455], pointer: [0.482, 0.455], expectedKind: 'line',
},
theme: 'dark', viewport: { width: 1180, height: 900 }, ...page },
{ id: 'wall-junctions-plan-preview-light', fixture: 'visual',
space: 'golden-wall-junctions', wallJunctions: true, mode: 'plan',
wallJunctionPreview: {
path: [[0.16, 0.15], [0.38, 0.15]], pointer: [0.38, 0.25], cms: [12], cm: 24,
},
theme: 'light', viewport: { width: 1180, height: 900 }, ...page },
{ id: 'wall-junctions-plan-t-dark', fixture: 'visual',
space: 'golden-wall-junctions', wallJunctions: true, mode: 'plan',
wallJunctionPreview: {
path: [[0.50, 0.54]], pointer: [0.50, 0.70], cms: [], cm: 24,
},
theme: 'dark', viewport: { width: 1180, height: 900 }, ...page },
{ id: 'wall-junctions-view-dark', fixture: 'visual',
space: 'golden-wall-junctions', wallJunctions: true, mode: 'view',
theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'junction-patch-resilience-plan-dark', fixture: 'visual',
space: 'golden-junction-patch-resilience', junctionPatchResilience: true, mode: 'plan',
retainedWedgeProbe: [0.8955, 0.556],
absentWallProbes: [[0.420833333, 0.37625], [0.92, 0.348]],
theme: 'dark', viewport: { width: 1180, height: 900 }, ...page },
{ id: 'junction-patch-resilience-view-dark', fixture: 'visual',
space: 'golden-junction-patch-resilience', junctionPatchResilience: true, mode: 'view',
retainedWedgeProbe: [0.8955, 0.556],
absentWallProbes: [[0.420833333, 0.37625], [0.92, 0.348]],
theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'wall-union-isolation-view-light', fixture: 'visual',
space: 'golden-wall-union-isolation', wallUnionIsolation: true, mode: 'view',
theme: 'light', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'wall-union-isolation-view-dark', fixture: 'visual',
space: 'golden-wall-union-isolation', wallUnionIsolation: true, mode: 'view',
theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'multiwall-junction-bevel-view-dark', fixture: 'visual',
space: 'golden-multiwall-junction', mode: 'view',
multiWallJunction: {
node: [0.329166667, 0.141666667], rays: 3,
// #302: the chamfer is strip-safe — this point sits in the overlap of
// two incident strips and must stay FILLED (it used to be discarded by
// the old subtractive bevel, which is exactly the hole class fixed).
retainedOverlapProbe: [0.3303808442725, 0.1488560107825],
// Product contract (#272): a bounded exterior bevel is allowed, but no
// empty component may remain trapped inside the continuous masonry.
enclosedHoles: 0,
},
theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'orthogonal-strip-cell-5-view-dark', fixture: 'visual',
space: 'golden-orthogonal-strip-cell-5', mode: 'view',
orthogonalStripContainment: { caseId: 'cell-5-mixed-depth-t', minSamples: 300 },
theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'orthogonal-strip-cell-1-view-dark', fixture: 'visual',
space: 'golden-orthogonal-strip-cell-1', mode: 'view',
orthogonalStripContainment: { caseId: 'cell-1-thick-crossbar-t', minSamples: 300 },
theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'wall-key-roundtrip-view-dark', fixture: 'visual',
space: 'golden-wall-key-roundtrip', mode: 'view',
wallKeyRoundtrip: {
variant: 'affected',
node: [0.8875, 0.345833333],
incidentArm: [0.8875, 0.333833333],
},
theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'isometric-wall-junctions-dark', fixture: 'visual',
space: 'golden-wall-junctions', wallJunctions: true, mode: 'view',
projection: 'iso',
theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'opening-placement-door-thick-wall-dark', fixture: 'visual', space: 'golden-geometry',
// The shared centre edge is a long 25 cm physical wall. It can contain the
// complete 90 cm door preset while still proving rotation, centreline
// placement and ruler geometry on a thick wall.
mode: 'plan', openingPreview: { type: 'door', pointer: [0.48, 0.65] },
openingPreviewPixels: { minPixels: 150, minInsideWallPixels: 8, minChannelDelta: 4 },
theme: 'dark', viewport: { width: 1180, height: 900 }, ...page },
{ id: 'opening-placement-passage-thick-wall-dark', fixture: 'visual', space: 'golden-geometry',
mode: 'plan', openingPreview: { type: 'passage', pointer: [0.48, 0.65] },
openingPreviewPixels: { minPixels: 150, minInsideWallPixels: 8, minChannelDelta: 4 },
theme: 'dark', viewport: { width: 1180, height: 900 }, ...page },
{ id: 'opening-placement-passage-thick-wall-light', fixture: 'visual', space: 'golden-geometry',
mode: 'plan', openingPreview: { type: 'passage', pointer: [0.48, 0.65] },
openingPreviewPixels: { minPixels: 150, minInsideWallPixels: 8, minChannelDelta: 4 },
theme: 'light', viewport: { width: 1180, height: 900 }, ...page },
{ id: 'opening-symbol-room-wall-light', fixture: 'visual', space: 'golden-opening-symbols',
mode: 'view', openingSymbolContract: openingRoomContract,
theme: 'light', viewport: { width: 1000, height: 900 }, ...stage },
...[
['static-hatch-openings-132-dpr1-light', 1.32, 1, 'light'],
['static-hatch-openings-140-dpr1-dark', 1.40, 1, 'dark'],
['static-hatch-openings-132-dpr2-dark', 1.32, 2, 'dark'],
['static-hatch-openings-140-dpr2-light', 1.40, 2, 'light'],
].map(([id, zoom, deviceScaleFactor, theme]) => ({
id, fixture: 'visual', space: 'golden-opening-symbols', mode: 'view',
openingSymbolContract: openingRoomContract,
extraOpenings: [{
id: 'sharpness-passage', type: 'passage', x: 0.62, y: 0.50,
angle: 0, length: 0.06,
}],
staticHatchSharpness: true, zoom, zoomCenter: [500, 500],
deviceScaleFactor, theme, viewport: { width: 1000, height: 900 }, ...stage,
})),
{ id: 'opening-symbol-diagonal-partition-dark', fixture: 'visual', space: 'golden-opening-symbols',
mode: 'view', openingSymbolContract: openingPartitionContract,
theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'opening-symbol-flip-pairs-light', fixture: 'visual', space: 'golden-opening-symbols',
mode: 'view', openingSymbolContract: openingFlipContract,
theme: 'light', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'isometric-opening-symbol-parity-dark', fixture: 'visual', space: 'golden-opening-symbols',
mode: 'view', projection: 'iso',
openingSymbolContract: openingIsoContract,
theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'geometry-devices-editor-dark', fixture: 'visual', space: 'golden-geometry', mode: 'devices',
theme: 'dark', viewport: { width: 1180, height: 900 }, ...page },
{ id: 'device-inbox-desktop-en-light', fixture: 'visual', space: 'golden-lighting',
dialog: 'device-inbox', language: 'en', theme: 'light',
viewport: { width: 1180, height: 900 }, ...page },
{ id: 'device-inbox-desktop-ru-dark', fixture: 'visual', space: 'golden-lighting',
dialog: 'device-inbox', language: 'ru', theme: 'dark',
viewport: { width: 1180, height: 900 }, ...page },
{ id: 'device-inbox-narrow-ru-dark', fixture: 'visual', space: 'golden-lighting',
dialog: 'device-inbox', deviceInboxTab: 'hidden',
markerOverrides: [{ id: 'golden-light-two', binding: 'device:golden-light-two', hidden: true }],
language: 'ru', theme: 'dark', viewport: { width: 390, height: 900 }, ...page },
{ id: 'geometry-decor-editor-dark', fixture: 'visual', space: 'golden-geometry', mode: 'decor',
theme: 'dark', viewport: { width: 1180, height: 900 }, ...page },
// HP-UX-11 visual contract: every adaptive width is captured in both
// languages while the six materially different tray contents are open.
{ id: 'tray-wide-selection-en', fixture: 'visual', space: 'golden-geometry', mode: 'plan',
editorTray: 'plan-selection', language: 'en', theme: 'dark',
viewport: { width: 1180, height: 900 }, ...page },
{ id: 'tray-wide-tool-ru', fixture: 'visual', space: 'golden-geometry', mode: 'plan',
editorTray: 'plan-tool', language: 'ru', theme: 'dark',
viewport: { width: 1180, height: 900 }, ...page },
{ id: 'tray-medium-group-en', fixture: 'visual', space: 'golden-geometry', mode: 'plan',
editorTray: 'group', language: 'en', theme: 'dark',
viewport: { width: 760, height: 820 }, ...page },
{ id: 'tray-medium-selection-ru', fixture: 'visual', space: 'golden-geometry', mode: 'decor',
editorTray: 'decor-selection', language: 'ru', theme: 'dark',
viewport: { width: 760, height: 820 }, ...page },
{ id: 'tray-narrow-palette-en', fixture: 'visual', space: 'golden-geometry', mode: 'decor',
editorTray: 'furniture-palette', furniturePalette: 'categories', language: 'en', theme: 'dark',
viewport: { width: 390, height: 760 }, ...page },
{ id: 'furniture-categories-light', fixture: 'visual', space: 'golden-geometry', mode: 'decor',
furniturePalette: 'categories', furnitureCategoryArt: 'dishwasher',
language: 'ru', theme: 'light',
// #640: keep the appliance row, including Dishwasher, fully visible so a
// menu-art replacement cannot pass behind the palette footer or CI reuse.
viewport: { width: 900, height: 1050 }, ...page },
{ id: 'furniture-variants-dark', fixture: 'visual', space: 'golden-geometry', mode: 'decor',
furniturePalette: 'variants', furnitureCategory: 'sofa', language: 'en', theme: 'dark',
viewport: { width: 900, height: 820 }, ...page },
{ id: 'furniture-variants-light', fixture: 'visual', space: 'golden-geometry', mode: 'decor',
furniturePalette: 'variants', furnitureCategory: 'coffee_table', language: 'ru', theme: 'light',
viewport: { width: 900, height: 820 }, ...page },
{ id: 'furniture-placement-preview-light', fixture: 'visual', space: 'golden-geometry', mode: 'decor',
furniturePlacementPreview: { symbol: 'sofa', widthCm: 180, depthCm: 90, pointer: [0.35, 0.90] },
language: 'ru', theme: 'light', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'furniture-plan-art-dark', fixture: 'visual', space: 'golden-geometry', mode: 'view',
decorOverride: [
...decorLayerFixture.filter((shape) => shape.kind !== 'furniture'),
{ id: 'furn-replaced', kind: 'furniture', symbol: 'sofa', x: 0.16, y: 0.18,
w: 0.18, h: 0.09, color: '#141832', opacity: 1, width_cm: 1.2 },
{ id: 'furn-retained', kind: 'furniture', symbol: 'fridge', x: 0.43, y: 0.18,
w: 0.06, h: 0.065, color: '#141832', opacity: 1, width_cm: 1.2 },
{ id: 'furn-new', kind: 'furniture', symbol: 'sofa_corner_right', x: 0.58, y: 0.18,
w: 0.26, h: 0.17, color: '#141832', opacity: 1, width_cm: 1.2 },
],
language: 'en', theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
// #593 AC7: четыре ID, которых до пакета 0.4.0 не существовало. Кадр — их
// единственное доказательство «видно на плане»: остальные мебельные сцены
// сняты на символах, которые были и раньше.
{ id: 'furniture-new-symbols-light', fixture: 'visual', space: 'golden-geometry', mode: 'view',
decorOverride: [
...decorLayerFixture.filter((shape) => shape.kind !== 'furniture'),
{ id: 'furn-computer', kind: 'furniture', symbol: 'computer', x: 0.14, y: 0.18,
w: 0.12, h: 0.06, color: '#141832', opacity: 1, width_cm: 1.2 },
{ id: 'furn-hood', kind: 'furniture', symbol: 'hood', x: 0.32, y: 0.18,
w: 0.06, h: 0.05, color: '#141832', opacity: 1, width_cm: 1.2 },
{ id: 'furn-oven', kind: 'furniture', symbol: 'oven', x: 0.44, y: 0.18,
w: 0.06, h: 0.06, color: '#141832', opacity: 1, width_cm: 1.2 },
{ id: 'furn-cactus', kind: 'furniture', symbol: 'cactus', x: 0.58, y: 0.18,
w: 0.07, h: 0.12, color: '#141832', opacity: 1, width_cm: 1.2 },
],
language: 'ru', theme: 'light', viewport: { width: 1000, height: 900 }, ...stage },
// #606: the old saved id and the new public id draw the same machine; the
// bookshelf/shelf_floor pair must be visibly different in their fixed boxes.
{ id: 'furniture-corrected-art-light', fixture: 'visual', space: 'golden-geometry', mode: 'view',
decorOverride: [
{ id: 'furn-legacy-cactus', kind: 'furniture', symbol: 'cactus', x: 0.10, y: 0.18,
w: 0.07, h: 0.12, angle: 25, flip_h: true,
color: '#141832', opacity: 1, width_cm: 1.2 },
{ id: 'furn-exercise', kind: 'furniture', symbol: 'exercise', x: 0.28, y: 0.18,
w: 0.07, h: 0.12, color: '#141832', opacity: 1, width_cm: 1.2 },
{ id: 'furn-bookshelf', kind: 'furniture', symbol: 'bookshelf', x: 0.14, y: 0.54,
w: 0.19, h: 0.07, angle: 20, flip_v: true,
color: '#141832', opacity: 1, width_cm: 1.2 },
{ id: 'furn-shelf-floor', kind: 'furniture', symbol: 'shelf_floor', x: 0.14, y: 0.80,
w: 0.19, h: 0.07, color: '#141832', opacity: 1, width_cm: 1.2 },
],
language: 'ru', theme: 'light', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'furniture-transform-light', fixture: 'visual', space: 'golden-geometry', mode: 'decor',
decorOverride: [
...decorLayerFixture.filter((shape) => shape.kind !== 'furniture'),
{ id: 'furn-normal', kind: 'furniture', symbol: 'sofa_corner_right', x: 0.08, y: 0.16,
w: 0.18, h: 0.12, color: '#141832', opacity: 1, width_cm: 1.2 },
{ id: 'furn-flip-h', kind: 'furniture', symbol: 'sofa_corner_right', x: 0.30, y: 0.16,
w: 0.18, h: 0.12, flip_h: true, color: '#141832', opacity: 1, width_cm: 1.2 },
{ id: 'furn-flip-v', kind: 'furniture', symbol: 'sofa_corner_right', x: 0.52, y: 0.16,
w: 0.18, h: 0.12, flip_v: true, color: '#141832', opacity: 1, width_cm: 1.2 },
{ id: 'furn-flip-hv', kind: 'furniture', symbol: 'sofa_corner_right', x: 0.74, y: 0.16,
w: 0.18, h: 0.12, flip_h: true, flip_v: true,
color: '#141832', opacity: 1, width_cm: 1.2 },
],
decorSelection: 'furn-flip-hv', language: 'ru', theme: 'light',
viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'furniture-transform-dark', fixture: 'visual', space: 'golden-geometry', mode: 'view',
decorOverride: [
...decorLayerFixture.filter((shape) => shape.kind !== 'furniture'),
{ id: 'furn-normal', kind: 'furniture', symbol: 'sofa_corner_right', x: 0.08, y: 0.16,
w: 0.18, h: 0.12, angle: 30, color: '#d8def0', opacity: 1, width_cm: 1.2 },
{ id: 'furn-flip-h', kind: 'furniture', symbol: 'sofa_corner_right', x: 0.30, y: 0.16,
w: 0.18, h: 0.12, angle: 30, flip_h: true,
color: '#d8def0', opacity: 1, width_cm: 1.2 },
{ id: 'furn-flip-v', kind: 'furniture', symbol: 'sofa_corner_right', x: 0.52, y: 0.16,
w: 0.18, h: 0.12, angle: 30, flip_v: true,
color: '#d8def0', opacity: 1, width_cm: 1.2 },
{ id: 'furn-flip-hv', kind: 'furniture', symbol: 'sofa_corner_right', x: 0.74, y: 0.16,
w: 0.18, h: 0.12, angle: 30, flip_h: true, flip_v: true,
color: '#d8def0', opacity: 1, width_cm: 1.2 },
],
language: 'en', theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'tray-narrow-tool-ru', fixture: 'visual', space: 'golden-geometry', mode: 'decor',
editorTray: 'decor-tool', language: 'ru', theme: 'dark',
viewport: { width: 390, height: 760 }, ...page },
{ id: 'geometry-diagonal-45-opening-dark', fixture: 'visual', space: 'golden-geometry', mode: 'view',
openingGeometry: { id: 'geo-diagonal-window', type: 'window', angle: 45 },
theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'openings-thick-wall-dark', fixture: 'visual', space: 'golden-lighting', mode: 'view',
fillMode: 'none', glowEnabled: false, theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'openings-filled-tunnel-dark', fixture: 'visual', space: 'golden-lighting', mode: 'view',
fillMode: 'custom', customFill: { c: '#66717c', a: 0.55 }, glowEnabled: false,
hideOpenings: true,
wallReplacements: [{
match: { a: [0.5, 0.1], b: [0.5, 0.88] },
segments: [
{ key: fixtureWallKey([0.5, 0.1], [0.5, 0.5]), a: [0.5, 0.1], b: [0.5, 0.5], cm: 18 },
{ key: fixtureWallKey([0.5, 0.5], [0.5, 0.88]), a: [0.5, 0.5], b: [0.5, 0.88], cm: 32 },
],
}],
tunnelContinuity: { openingId: 'light-door', insetPx: 2, maxChannelJump: 3, dpr2: true },
theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'decor-over-opaque-hover-light', fixture: 'visual', space: 'golden-lighting', mode: 'view',
fillMode: 'custom', customFill: { c: '#2255cc', a: 1 }, glowEnabled: false,
sunRays: false, showBorders: false, showNames: false, hideOpenings: true,
hoverRoom: 'light-left', decorOverride: decorLayerFixture,
decorPixelProbes: decorLayerProbes,
theme: 'light', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'decor-over-glow-base-dark', fixture: 'visual', space: 'golden-lighting', mode: 'view',
fillMode: 'glow', glowEnabled: true, allLightsOff: true,
sunRays: false, showBorders: false, showNames: false, hideOpenings: true,
decorOverride: decorLayerFixture, decorPixelProbes: decorLayerProbes,
theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'openings-hidden-view-dark', fixture: 'visual', space: 'golden-lighting', mode: 'view',
fillMode: 'none', glowEnabled: false, hideOpenings: true, theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'lighting-glow-sun-dark', fixture: 'visual', space: 'golden-lighting', mode: 'view',
theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'device-value-badge-positions-dark', fixture: 'visual', space: 'golden-lighting', mode: 'view',
glowEnabled: false, sunRays: false,
markerOverrides: [
{ id: 'golden-light-one', binding: 'device:golden-light-one', value_badge: {
enabled: true, source: { kind: 'entity_state', entity_id: 'light.golden_light_one' }, position: 'right',
} },
{ id: 'golden-light-two', binding: 'device:golden-light-two', value_badge: {
enabled: true, source: { kind: 'entity_state', entity_id: 'light.golden_light_two' }, position: 'left',
} },
{ id: 'golden-presence', binding: 'device:golden-presence', value_badge: {
enabled: true, source: { kind: 'entity_state', entity_id: 'binary_sensor.golden_presence' }, position: 'top',
} },
{ id: 'golden-climate', binding: 'device:golden-climate', value_badge: {
enabled: true,
source: { kind: 'entity_attribute', entity_id: 'climate.golden_climate', attribute: 'current_temperature' },
position: 'bottom',
} },
],
stateOverrides: { 'climate.golden_climate': { attributes: { lqi: 190 } } },
layoutOverrides: {
'golden-light-one': { s: 'golden-lighting', x: 0.20, y: 0.32 },
'golden-light-two': { s: 'golden-lighting', x: 0.20, y: 0.72 },
'golden-presence': { s: 'golden-lighting', x: 0.80, y: 0.68 },
'golden-climate': { s: 'golden-lighting', x: 0.80, y: 0.28 },
},
theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'device-value-face-cover-source-dark', fixture: 'visual',
space: 'golden-lighting', mode: 'view', glowEnabled: false, sunRays: false,
showNames: false,
markerOverrides: [{
id: 'golden-left-linkquality', binding: 'entity:cover.golden_door_half',
display: 'value', size: 3,
value_source: {
kind: 'entity_attribute', entity_id: 'cover.golden_door_half',
attribute: 'current_position',
},
}],
stateOverrides: {
'cover.golden_door_half': { attributes: { current_position: 42 } },
},
layoutOverrides: {
'golden-left-linkquality': { s: 'golden-lighting', x: 0.50, y: 0.50 },
},
deviceOnly: 'golden-left-linkquality',
theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
...['light', 'dark'].map((theme) => ({
id: `device-icon-state-table-${theme}`, fixture: 'visual', space: 'golden-lighting', mode: 'view',
glowEnabled: false, sunRays: false,
markerOverrides: [
// #588: пятый режим показан там же, где живут остальные четыре. Источник
// выбран цветной (включённая RGB-лампа) намеренно: на нейтральном кадр
// совпал бы с прежним, и эталон не доказывал бы ничего.
{ id: 'golden-light-one', binding: 'device:golden-light-one',
display: 'value_static_icon' },
{ id: 'golden-light-two', binding: 'device:golden-light-two' },
{ id: 'golden-light-three', binding: 'device:golden-light-three' },
{ id: 'golden-presence', binding: 'device:golden-presence' },
{ id: 'golden-climate', binding: 'device:golden-climate' },
{ id: 'golden-left-temperature', binding: 'device:golden-left-temperature', display: 'value' },
{ id: 'golden-right-temperature', binding: 'device:golden-right-temperature', display: 'static_icon' },
{ id: 'golden-left-linkquality', binding: 'device:golden-left-linkquality',
tap_action: 'toggle', controls: ['light.golden_light_three'], value_badge: {
enabled: true,
source: { kind: 'entity_state', entity_id: 'sensor.golden_left_linkquality' },
position: 'right',
} },
{ id: 'golden-right-linkquality', binding: 'device:golden-right-linkquality', value_badge: {
enabled: true,
source: { kind: 'entity_state', entity_id: 'sensor.golden_right_linkquality' },
position: 'bottom',
} },
],
// This scenario deliberately places two devices outside their HA rooms to
// exercise icon states at fixed coordinates. Seed the Area-following
// provenance so #126 does not reinterpret that test-only layout as stale
// legacy placement and turn an unrelated visual matrix into a relocation
// scenario.
markerAreaSnapshot: {
'golden-light-one': { binding: 'device:golden-light-one', area: 'golden_light_left' },
'golden-light-two': { binding: 'device:golden-light-two', area: 'golden_light_left' },
'golden-light-three': { binding: 'device:golden-light-three', area: 'golden_light_right' },
'golden-presence': { binding: 'device:golden-presence', area: 'golden_light_right' },
'golden-climate': { binding: 'device:golden-climate', area: 'golden_light_right' },
'golden-left-temperature': {
binding: 'device:golden-left-temperature', area: 'golden_light_left',
},
'golden-right-temperature': {
binding: 'device:golden-right-temperature', area: 'golden_light_right',
},
'golden-left-linkquality': {
binding: 'device:golden-left-linkquality', area: 'golden_light_left',
},
'golden-right-linkquality': {
binding: 'device:golden-right-linkquality', area: 'golden_light_right',
},
},
stateOverrides: {
'light.golden_light_one': { attributes: { lqi: 40 } },
'light.golden_light_two': { attributes: { lqi: 41 } },
'light.golden_light_three': { state: 'unavailable', attributes: { lqi: 180 } },
'sensor.golden_left_temperature': { state: 'Complete long localized state' },
'sensor.golden_right_temperature': { state: 'unavailable' },
},
layoutOverrides: {
'golden-light-one': { s: 'golden-lighting', x: 0.16, y: 0.25 },
'golden-light-two': { s: 'golden-lighting', x: 0.34, y: 0.25 },
'golden-light-three': { s: 'golden-lighting', x: 0.66, y: 0.25 },
'golden-presence': { s: 'golden-lighting', x: 0.84, y: 0.25 },
'golden-climate': { s: 'golden-lighting', x: 0.16, y: 0.72 },
'golden-left-temperature': { s: 'golden-lighting', x: 0.34, y: 0.72 },
'golden-right-temperature': { s: 'golden-lighting', x: 0.66, y: 0.72 },
'golden-left-linkquality': { s: 'golden-lighting', x: 0.77, y: 0.55 },
'golden-right-linkquality': { s: 'golden-lighting', x: 0.84, y: 0.72 },
},
deviceClassOverrides: {
'golden-light-two': ['lock-locked'],
'golden-light-three': ['lock-unlocked'],
'golden-presence': ['virtual'],
'golden-climate': ['alarm', 'sel'],
'golden-right-linkquality': ['open'],
},
focusDevice: 'golden-climate',
hoverDevice: 'golden-presence',
hideHoverTooltip: true,
theme, viewport: { width: 1000, height: 900 }, ...stage,
})),
...['light', 'dark'].map((theme) => ({
id: `device-text-shell-long-${theme}`, fixture: 'visual', space: 'golden-lighting', mode: 'view',
glowEnabled: false, sunRays: false, showNames: false,
markerOverrides: [{
id: 'golden-left-linkquality', binding: 'device:golden-left-linkquality',
display: 'value', size: 3,
}],
stateOverrides: {
'sensor.golden_left_linkquality': {
state: '498', attributes: { unit_of_measurement: 'ppm' },
},
},
layoutOverrides: {
'golden-left-linkquality': { s: 'golden-lighting', x: 0.50, y: 0.50 },
},
deviceOnly: 'golden-left-linkquality',
theme, viewport: { width: 1000, height: 900 }, ...stage,
})),
{ id: 'lighting-sun-window-state-only-dark', fixture: 'visual', space: 'golden-lighting', mode: 'view',
// The golden screenshot is backed by a second, sun-layer-hidden capture.
// A real painted ray must account for enough changed pixels; DOM-only
// presence or an accidentally accepted empty baseline is not sufficient.
glowEnabled: false, allLightsOff: true, northDeg: 90,
stateOverrides: { 'sun.sun': { attributes: { azimuth: 270, elevation: 24 } } },
sunRayPixels: { minPixels: 500, minChannelDelta: 4 },
theme: 'dark', viewport: { width: 1000, height: 900 }, ...sunWindow },
{ id: 'lighting-sun-window-outer-thick-dark', fixture: 'visual', space: 'golden-lighting', mode: 'view',
// The matching inner scene above is not enough for #577: this frame pins
// the exterior source edge, the physical 15 cm tunnel and its seam with
// the clean room floor on a real rendered wall.
glowEnabled: false, allLightsOff: true, northDeg: 90, sunRayOrigin: 'outer',
stateOverrides: { 'sun.sun': { attributes: { azimuth: 270, elevation: 24 } } },
sunRayPixels: { minPixels: 500, minChannelDelta: 4 },
theme: 'dark', viewport: { width: 1000, height: 900 }, ...sunWindow },
...[1, 2].map((deviceScaleFactor) => ({
id: `lighting-sun-window-outer-occluded-dpr${deviceScaleFactor}-dark`,
fixture: 'visual', space: 'golden-lighting', mode: 'view',
// #580: diagonal light through the real 15 cm tunnel must narrow at the
// inner jamb instead of continuing through solid wall. Pin both raster
// scales because the tunnel/room seam is exactly where subpixels differ.
glowEnabled: false, allLightsOff: true, northDeg: 90, sunRayOrigin: 'outer',
stateOverrides: { 'sun.sun': { attributes: { azimuth: 240, elevation: 24 } } },
sunRayPixels: { minPixels: 400, minChannelDelta: 4 }, deviceScaleFactor,
theme: 'dark', viewport: { width: 1000, height: 900 }, ...sunWindow,
})),
{ id: 'lighting-fill-light-axis-split-dark', fixture: 'visual', space: 'golden-lighting', mode: 'view',
fillMode: 'light', glowEnabled: false, theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'lighting-fill-temp-axis-split-dark', fixture: 'visual', space: 'golden-lighting', mode: 'view',
fillMode: 'temp', glowEnabled: false, theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'lighting-fill-lqi-axis-split-dark', fixture: 'visual', space: 'golden-lighting', mode: 'view',
fillMode: 'lqi', glowEnabled: false, theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'lighting-temp-glow-dark', fixture: 'visual', space: 'golden-lighting', mode: 'view',
fillMode: 'temp', glowEnabled: true, theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'lighting-temp-glow-light', fixture: 'visual', space: 'golden-lighting', mode: 'view',
fillMode: 'temp', glowEnabled: true, theme: 'light', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'lighting-custom-glow-dark', fixture: 'visual', space: 'golden-lighting', mode: 'view',
fillMode: 'custom', customFill: { c: '#486a8f', a: 0.42 }, glowEnabled: true,
roomCustomFill: { 'light-right': { c: '#8f5f48', a: 0.56 } },
theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'lighting-opaque-glow-two-doorways-dark', fixture: 'visual', space: 'golden-lighting', mode: 'view',
fillMode: 'custom', customFill: { c: '#3f4854', a: 1 }, glowEnabled: true, sunRays: false,
allLightsOff: true,
stateOverrides: {
'light.golden_light_one': { state: 'on', attributes: { rgb_color: [255, 196, 112], brightness: 255 } },
},
extraOpenings: [
{
id: 'light-door-second', type: 'door', x: 0.50, y: 0.30,
angle: 90, length: 0.13, contact: 'cover.golden_door_closed',
},
{
id: 'light-door-third', type: 'door', x: 0.50, y: 0.76,
angle: 90, length: 0.13, contact: 'cover.golden_door_half',
},
],
layoutOverrides: { 'golden-light-one': { s: 'golden-lighting', x: 0.40, y: 0.48 } },
// One frame contains the complete #20 matrix: the original unbound door is
// fully open, the upper bound door is closed and the lower positional door
// exposes 50%. Protection is meaningful only if the receiving half also
// contains rendered light; a data-only check once let an empty baseline pass.
warmPixelRegion: { x: 0.5, y: 0, w: 0.5, h: 1, minPixels: 2500, minRedBlueDelta: 25 },
theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'lighting-custom-glow-light', fixture: 'visual', space: 'golden-lighting', mode: 'view',
fillMode: 'custom', customFill: { c: '#486a8f', a: 0.42 }, glowEnabled: true,
roomCustomFill: { 'light-right': { c: '#8f5f48', a: 0.56 } },
theme: 'light', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'lighting-temp-glow-no-sources-dark', fixture: 'visual', space: 'golden-lighting', mode: 'view',
fillMode: 'temp', glowEnabled: true, allLightsOff: true,
theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'lighting-temp-glow-room-override-dark', fixture: 'visual', space: 'golden-lighting', mode: 'view',
fillMode: 'temp', glowEnabled: true, roomGlow: { 'light-left': true, 'light-right': false },
theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'lighting-manual-auto-spill-overlap-dark', fixture: 'visual', space: 'golden-lighting', mode: 'view',
fillMode: 'temp', glowEnabled: true,
markerOverrides: [{
id: 'golden-light-two', binding: 'device:golden-light-two',
glow_color: { c: '#3a8fff', bri: 0.35 }, glow_radius_cm: 420,
}],
layoutOverrides: { 'golden-light-two': { s: 'golden-lighting', x: 0.39, y: 0.57 } },
theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'hover-over-glow-dark', fixture: 'visual', space: 'golden-lighting', mode: 'view',
hoverRoom: 'light-right', theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
{ id: 'hover-nested-room-dark', fixture: 'visual', space: 'golden-geometry', mode: 'view',
hoverRoom: 'geo-nested', theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
// --- #302: junction close-ups. One node per scene, the node fills the
// frame; the same scenes carry the objective no-holes detector in tests.
...[
['junction-t-90-equal15-dark', { arms: [
{ deg: 0, cm: 15 }, { deg: 90, cm: 15 }, { deg: 180, cm: 15 }] }],
['junction-t-90-bar50-leg15-dark', { arms: [
{ deg: 0, cm: 50 }, { deg: 90, cm: 15 }, { deg: 180, cm: 50 }] }],
['junction-t-90-bar70-leg15-dark', { arms: [
{ deg: 0, cm: 70 }, { deg: 90, cm: 15 }, { deg: 180, cm: 70 }] }],
['junction-x-90-equal15-dark', { arms: [
{ deg: 0, cm: 15 }, { deg: 90, cm: 15 }, { deg: 180, cm: 15 },
{ deg: 270, cm: 15 }] }],
['junction-x-90-mixed-dark', { arms: [
{ deg: 0, cm: 15 }, { deg: 90, cm: 50 }, { deg: 180, cm: 15 },
{ deg: 270, cm: 70 }] }],
['junction-x-45-alternating-dark', { arms: [
{ deg: 45, cm: 15 }, { deg: 135, cm: 50 }, { deg: 225, cm: 15 },
{ deg: 315, cm: 50 }] }],
['junction-star5-equal15-dark', { arms: [
{ deg: 0, cm: 15 }, { deg: 72, cm: 15 }, { deg: 144, cm: 15 },
{ deg: 216, cm: 15 }, { deg: 288, cm: 15 }] }],
['junction-y-60-equal50-dark', { arms: [
{ deg: 0, cm: 50 }, { deg: 60, cm: 50 }, { deg: 120, cm: 50 }] }],
['junction-acute30-mixed-dark', { arms: [
{ deg: 0, cm: 50 }, { deg: 30, cm: 70 }, { deg: 180, cm: 15 }] }],
['junction-acute15-equal50-dark', { arms: [
{ deg: 0, cm: 50 }, { deg: 15, cm: 50 }, { deg: 180, cm: 50 }] }],
['junction-splay10-170-dark', { arms: [
{ deg: 0, cm: 15 }, { deg: 10, cm: 15 }, { deg: 180, cm: 50 }] }],
['junction-t-virtual-arm-dark', { arms: [
{ deg: 0, cm: 15 }, { deg: 90, cm: 15, virtual: true },
{ deg: 180, cm: 15 }] }],
['junction-x-virtual-through-dark', { arms: [
{ deg: 0, cm: 15 }, { deg: 90, cm: 15, virtual: true },
{ deg: 180, cm: 15 }, { deg: 270, cm: 15, virtual: true }] }],
['junction-column-node-dark', { column: true, arms: [
{ deg: 0, cm: 15 }, { deg: 90, cm: 15 }, { deg: 180, cm: 15 }] }],
['junction-draft-end-node-dark', { draft: true, arms: [
{ deg: 0, cm: 15 }, { deg: 90, cm: 15 }, { deg: 180, cm: 15 }] }],
].map(([id, junctionNode]) => ({
id, fixture: 'visual', space: `golden-${id}`, mode: 'view', junctionNode,
zoom: 4, theme: 'dark', viewport: { width: 900, height: 900 }, ...stage,
})),
...[
['junction-309-step-dark', 8, [808.33, 1233.33]],
['junction-309-spike-dark', 8, [2220.83, 1350]],
['junction-309-hump-dark', 6, [1758.33, 1612.5]],
].map(([id, zoom, zoomCenter]) => ({
id, fixture: 'visual', space: `golden-${id}`, mode: 'view', junctionTeeth: true,
zoom, zoomCenter, theme: 'dark', viewport: { width: 900, height: 900 }, ...stage,
})),
{ id: 'sharp-apex-legacy-dark', fixture: 'visual',
space: 'golden-sharp-apex', mode: 'view', sharpApex: true,
theme: 'dark',
viewport: { width: 900, height: 900 }, ...stage },
{ id: 'junction-owner-repro-dark', fixture: 'visual',
space: 'golden-junction-owner-repro', mode: 'view', junctionArtifacts: true,
zoom: 6, zoomCenter: [329.17, 141.67], theme: 'dark',
viewport: { width: 900, height: 900 }, ...stage },
{ id: 'large-house-zoom-040-dark', fixture: 'large', space: 'perf-floor-1', mode: 'view',
zoom: 0.4, theme: 'dark', viewport: { width: 1180, height: 900 }, ...stage },
{ id: 'large-house-zoom-250-dark', fixture: 'large', space: 'perf-floor-1', mode: 'view',
zoom: 2.5, theme: 'dark', viewport: { width: 1180, height: 900 }, ...stage },
{ id: 'large-house-warm-remount-dark', fixture: 'large', space: 'perf-floor-2', mode: 'view',
warmRemount: true, theme: 'dark', viewport: { width: 1180, height: 900 }, ...stage },
{ id: 'device-dialog-desktop-en', fixture: 'visual', space: 'golden-lighting',
dialog: 'device', deviceId: 'golden-light-two', deviceLightControls: true,
deviceName: 'Living room light controller with an intentionally long title',
openHelp: 'marker.glow_mode.help', helpTextRegion: { key: 'marker.glow_mode.help', minPixels: 30 },
focusDialogClose: true, language: 'en', theme: 'dark', viewport: { width: 1180, height: 1200 }, ...page },
{ id: 'device-dialog-mobile-ru', fixture: 'visual', space: 'golden-lighting',
dialog: 'device', deviceId: 'golden-light-two', deviceLightControls: true,
deviceName: 'Контроллер освещения гостиной с намеренно очень длинным названием',
language: 'ru', theme: 'dark', viewport: { width: 390, height: 1000 }, ...page },
{ id: 'device-dialog-desktop-de', fixture: 'visual', space: 'golden-lighting',
dialog: 'device', deviceId: 'golden-light-two', deviceLightControls: true,
deviceName: 'Wohnzimmer-Lichtsteuerung mit absichtlich sehr langem Namen',
language: 'de', theme: 'light', viewport: { width: 1180, height: 1200 }, ...page },
{ id: 'room-temperature-dialog-desktop-en', fixture: 'visual', space: 'golden-lighting',
dialog: 'room-temperature', roomTemperature: { min: 18, max: 22 },
language: 'en', theme: 'light', viewport: { width: 900, height: 900 }, ...page },
{ id: 'room-temperature-dialog-mobile-ru', fixture: 'visual', space: 'golden-lighting',
dialog: 'room-temperature', roomTemperature: { min: 18, max: null },
language: 'ru', theme: 'dark', viewport: { width: 390, height: 820 }, ...page },
{ id: 'room-discard-dialog-mobile-ru', fixture: 'visual', space: 'golden-lighting',
dialog: 'room-discard', language: 'ru', theme: 'light',
viewport: { width: 320, height: 820 }, ...page },
{ id: 'german-view-mobile-light', fixture: 'visual', space: 'golden-geometry',
mode: 'view', language: 'de', theme: 'light',
viewport: { width: 390, height: 900 }, ...page },
{ id: 'toggle-entity-dialog-desktop-en', fixture: 'visual', applianceLifecycle: true,
space: 'golden-appliance', dialog: 'device', deviceId: 'golden-washer',
deviceToggleEntity: 'selected', markerOverrides: [{
id: 'golden-washer', binding: 'device:golden-washer', tap_action: 'toggle',
toggle_entity: 'switch.golden_washer_child_lock',
}],
language: 'en', theme: 'light', viewport: { width: 1180, height: 1100 }, ...page },
{ id: 'toggle-entity-dialog-mobile-ru', fixture: 'visual', applianceLifecycle: true,
space: 'golden-appliance', dialog: 'device', deviceId: 'golden-washer',
deviceToggleEntity: 'stale', markerOverrides: [{
id: 'golden-washer', binding: 'device:golden-washer', tap_action: 'toggle',
toggle_entity: 'switch.golden_washer_removed',
}],
language: 'ru', theme: 'dark', viewport: { width: 390, height: 1000 }, ...page },
{ id: 'device-help-popover-light-ru', fixture: 'visual', space: 'golden-lighting',
dialog: 'device', deviceId: 'golden-light-two', deviceLightControls: true,
openHelp: 'marker.glow_mode.help', helpTextRegion: { key: 'marker.glow_mode.help', minPixels: 30 },
language: 'ru', theme: 'light', viewport: { width: 760, height: 900 }, ...page },
{ id: 'decor-color-popover-mobile-ru', fixture: 'visual', space: 'golden-geometry',
dialog: 'decor-color', language: 'ru', theme: 'dark',
viewport: { width: 390, height: 760 }, ...page },
{ id: 'decor-color-popover-desktop-en', fixture: 'visual', space: 'golden-geometry',
dialog: 'decor-color', language: 'en', theme: 'light',
viewport: { width: 760, height: 820 }, ...page },
{ id: 'general-color-popover-desktop-en', fixture: 'visual', space: 'golden-geometry',
dialog: 'general-color', language: 'en', theme: 'light',
viewport: { width: 900, height: 900 }, ...page },
// #86 AC7: renderer-level 200% browser zoom is 390 CSS px at DPR 2
// (780 physical px). Keep both themes reviewed because help placement and
// contrast are part of the visual contract, not just a DOM assertion.
{ id: 'settings-help-zoom-200-en-light', fixture: 'visual', space: 'golden-geometry',
dialog: 'general-help', openHelp: 'gs.bg_mode.help', deviceScaleFactor: 2,
helpTextRegion: { key: 'gs.bg_mode.help', minPixels: 30 },
language: 'en', theme: 'light', viewport: { width: 390, height: 900 }, ...page },
{ id: 'settings-help-zoom-200-ru-dark', fixture: 'visual', space: 'golden-geometry',
dialog: 'general-help', openHelp: 'gs.bg_mode.help', deviceScaleFactor: 2,
helpTextRegion: { key: 'gs.bg_mode.help', minPixels: 30 },
language: 'ru', theme: 'dark', viewport: { width: 390, height: 900 }, ...page },
// #43 AC14: the support dialog owns reviewed desktop, phone and tablet
// states. The six scenes deliberately cover both themes while keeping the
// individual states named in the specification independently reviewable.
{ id: 'support-desktop-empty-light-en', fixture: 'visual', space: 'golden-geometry',
dialog: 'support', supportState: 'empty', language: 'en', theme: 'light',
viewport: { width: 1000, height: 980 }, ...page },
{ id: 'support-desktop-preview-dark-en', fixture: 'visual', space: 'golden-geometry',
dialog: 'support', supportState: 'preview', language: 'en', theme: 'dark',
viewport: { width: 1000, height: 980 }, ...page },
{ id: 'support-phone-validation-light-ru', fixture: 'visual', space: 'golden-geometry',
dialog: 'support', supportState: 'validation', language: 'ru', theme: 'light',
viewport: { width: 320, height: 760 }, ...page },
{ id: 'support-phone-success-dark-en', fixture: 'visual', space: 'golden-geometry',
dialog: 'support', supportState: 'success', language: 'en', theme: 'dark',
viewport: { width: 390, height: 820 }, ...page },
{ id: 'support-relay-error-light-en', fixture: 'visual', space: 'golden-geometry',
dialog: 'support', supportState: 'relay-error', language: 'en', theme: 'light',
viewport: { width: 900, height: 900 }, ...page },
{ id: 'support-tablet-preview-dark-ru', fixture: 'visual', space: 'golden-geometry',
dialog: 'support', supportState: 'preview', language: 'ru', theme: 'dark',
viewport: { width: 768, height: 900 }, ...page },
{ id: 'device-ripple-color-popover-mobile-ru', fixture: 'visual', space: 'golden-lighting',
dialog: 'device-ripple-color', deviceId: 'golden-light-two',
language: 'ru', theme: 'dark', viewport: { width: 390, height: 1000 }, ...page },
{ id: 'space-room-color-popover-desktop-ru', fixture: 'visual', space: 'golden-geometry',
dialog: 'space-room-color', language: 'ru', theme: 'dark',
viewport: { width: 900, height: 1000 }, ...page },
{ id: 'backup-full-preview-desktop-en', fixture: 'visual', space: 'golden-geometry',
dialog: 'backup-full', language: 'en', theme: 'dark',
viewport: { width: 1000, height: 900 }, ...page },
{ id: 'backup-plan-only-export-desktop-en', fixture: 'visual', space: 'golden-geometry',
dialog: 'backup-export-plan-only', language: 'en', theme: 'dark',
viewport: { width: 1000, height: 900 }, ...page },
{ id: 'backup-space-preview-mobile-ru', fixture: 'visual', space: 'golden-geometry',
dialog: 'backup-space', language: 'ru', theme: 'light',
viewport: { width: 390, height: 820 }, ...page },
{ id: 'optimize-preflight-dialog-dark-en', fixture: 'visual', space: 'golden-geometry',
dialog: 'optimize-preflight', language: 'en', theme: 'dark',
viewport: { width: 900, height: 760 }, ...page },
{ id: 'optimize-preflight-dialog-light-ru', fixture: 'visual', space: 'golden-geometry',
dialog: 'optimize-preflight', language: 'ru', theme: 'light',
viewport: { width: 900, height: 760 }, ...page },
{ id: 'optimize-orphan-references-dark-en', fixture: 'visual', space: 'golden-geometry',
dialog: 'optimize-orphan-references', language: 'en', theme: 'dark',
markerOverrides: [{
id: 'golden-light-two', binding: 'virtual', space: 'removed-floor',
room_id: 'removed-room', icon: 'mdi:lightbulb', name: 'Living-room lamp',
}, {
id: 'golden-light-one', binding: 'virtual', name: 'Hall lamp',
}, {
id: 'golden-presence', binding: 'virtual', removed: true,
space: 'removed-floor', name: 'Retired marker',
}],
// The orphan-reference fixture intentionally reuses ids of live registry
// devices. Pin their pre-#126 provenance so the lifecycle synchronizer
// cannot consume the deliberately orphaned positions before Optimize
// inspects them.
markerAreaSnapshot: {
'golden-light-one': { binding: 'device:golden-light-one', area: 'golden_light_left' },
'golden-light-two': { binding: 'device:golden-light-two', area: 'golden_light_left' },
'golden-presence': { binding: 'device:golden-presence', area: 'golden_light_right' },
},
layoutOverrides: {
'golden-light-two': { s: 'removed-floor', x: 0.35, y: 0.72 },
'golden-light-one': { s: 'unresolved-floor', x: 0.20, y: 0.34 },
'golden-presence': { s: 'removed-floor', x: 0.45, y: 0.64 },
},
viewport: { width: 900, height: 760 }, ...page },
{ id: 'optimize-orphan-references-light-ru', fixture: 'visual', space: 'golden-geometry',
dialog: 'optimize-orphan-references', language: 'ru', theme: 'light',
markerOverrides: [{
id: 'golden-light-two', binding: 'virtual', space: 'removed-floor',
room_id: 'removed-room', icon: 'mdi:lightbulb', name: 'Лампа в гостиной',
}, {
id: 'golden-light-one', binding: 'virtual', name: 'Лампа в холле',
}, {
id: 'golden-presence', binding: 'virtual', removed: true,
space: 'removed-floor', name: 'Старый маркер',
}],
markerAreaSnapshot: {
'golden-light-one': { binding: 'device:golden-light-one', area: 'golden_light_left' },
'golden-light-two': { binding: 'device:golden-light-two', area: 'golden_light_left' },
'golden-presence': { binding: 'device:golden-presence', area: 'golden_light_right' },
},
layoutOverrides: {
'golden-light-two': { s: 'removed-floor', x: 0.35, y: 0.72 },
'golden-light-one': { s: 'unresolved-floor', x: 0.20, y: 0.34 },
'golden-presence': { s: 'removed-floor', x: 0.45, y: 0.64 },
},
viewport: { width: 900, height: 880 }, ...page },
{ id: 'card-editor-invalid-default-floor-light-en', fixture: 'visual',
cardEditorInvalidDefaultFloor: 'removed-floor', language: 'en', theme: 'light',
viewport: { width: 900, height: 760 }, ...page },
{ id: 'card-editor-invalid-default-floor-dark-ru', fixture: 'visual',
cardEditorInvalidDefaultFloor: 'removed-floor', language: 'ru', theme: 'dark',
viewport: { width: 900, height: 760 }, ...page },
]);