5.3 KiB
ADR #89 — Stage 1 volumetric renderer
- Issue: https://github.com/Matysh/houseplan-card/issues/89
- Status: accepted for Stage 1 implementation
- Date: 2026-08-13
- Normative spec:
docs/specs/089-isometric-view-stage1.md, revision 3 - Activation superseded by #448: the renderer remains authoritative, but its
historical per-feature URL/storage lifetime is replaced by
hp_alpha.
Context
House Plan already has one SVG scene with live room fills, Glow/spill, sunlight, backdrop/decor, furniture, opening symbols and vacuum trails, plus screen-facing HTML devices and room cards. Stage 1 must add low wall volume behind a Labs flag without creating a second rendering model or changing HA actions, configuration or service paths.
Two spikes were evaluated against the existing scene composition:
- CSS
perspective/preserve-3daround.zoomwrapwould put the existingbrightness()filter, SVG blur andmix-blend-mode: screeninto a 3D compositing context. It also requires a second approximate formula for HTML overlays. - One affine floor projection plus explicit SVG wall faces keeps the current live nodes and native hit targets, while pure functions can project the HTML anchors with the identical camera.
The second spike wins. It is deterministic, has no new runtime dependency and can fail back to the untouched flat path.
Decision
Camera and coordinate systems
- Projection is orthographic with
rotDeg = 0andtiltDeg = 20. - Pivot is the fixed plan point
[500, 500](NORM_W / 2), never content or viewport centre. xyScale = 1; floor Y is multiplied bycos(20°)around the pivot.- Logical wall height is
64plan units andzScale = 1. Height therefore moves a top point upward by64 * sin(20°) ≈ 21.9scene units: visibly low, inside the 18–22° family approved by the owner. projectPlanPointandunprojectFloorPointare the sole plan/scene conversion. Client coordinates continue through the existing SVG viewBox.projectedFrameincludes the floor frame atz = 0and the same corners at wall height. Pan, fit and zoom use this frame in the volumetric view.
The floor transform is the SVG affine matrix equivalent of
projectPlanPoint(p, 0). Existing SVG floor/live nodes are grouped under this
one matrix. Screen-facing HTML overlays project their logical anchors through
the same pure function before conversion to percentages.
Wall geometry and draw order
- Source geometry is
wallBodiesGeometry(...).geom, including canonical room wall rings, joined junctions, full-height opening cuts, independent partitions and columns. - Rings are normalized by signed area. Every ring edge is visited once, so the side-face count is O(E).
- With the fixed camera, a side is visible when its outward plan normal faces the camera. Hole normals are reversed relative to outer rings. Jamb edges are ordinary ring edges and therefore produce the two expected vertical faces at a full-height opening.
- Visible side quads are ordered by projected floor depth and a stable
polygon/ring/edge index. The top uses the complete projected MultiPolygon
with
fill-rule: evenodd; there is no strip or floor cap through an opening. - Stage 1 order is: transformed floor/live SVG → visible wall sides → wall top → screen-facing HTML overlays. Markers and room cards deliberately remain above walls and receive no geometric occlusion.
Appearance
- Light theme: top
#f3f3f1, side#a8acae, outline#d7d9d8. - Dark theme: top
#596166, side#3f474c, outline#747d82. - The current room-selected wall colour is not reused for volumetric material; it remains part of flat/editor rendering. No shadows, floor edges, vertical doors/windows or new window-light layer are added in Stage 1.
View state, Labs and fallback
houseplan_card_labs_v1owns the reusable flag set;isois valid from1.62.0and expires at numeric core1.65.0.houseplan_card_view_v1stores onlyflat|isoper space. Flat remains the default even while Labs is enabled.- Switching preserves scalar zoom and logical floor centre. Editors always use flat projection; returning to View restores the preferred projection.
- Warm state records projection and logical centre. A raw viewBox is adopted only when projection and active Labs contract still match.
- Geometry/render exceptions latch flat fallback for the current
(space, fingerprint). Preference is retained; explicit retry or a new fingerprint clears the latch.
Browser and composition result
The selected SVG-only composition uses standard affine transforms, paths,
fill-rule: evenodd, existing filters/clips and existing screen blend. Chromium,
Firefox and WebKit all implement these primitives without a CSS 3D flattening
boundary. Browser validation remains mandatory through the Stage 1 smoke and
golden matrix; a browser-specific failure uses the same latched flat fallback.
Consequences
- Flat rendering takes no Labs geometry path and retains its current DOM.
- HA-only updates reuse the content-fingerprinted wall geometry.
houseplan-space-card, schemas, backend, imports/exports and service actions are unchanged.- Stage 2 may replace material tokens or add vertical opening elements, but it must not replace this shared projection contract without a new decision.