feat: Add architecture decision and product requirements documents for per-layer SVG map architecture

This commit is contained in:
Azgaar 2026-03-12 23:24:02 +01:00
parent 256f36015b
commit df18f69516
3 changed files with 587 additions and 0 deletions

View file

@ -0,0 +1,179 @@
---
status: complete
workflowType: architecture
project_name: Fantasy-Map-Generator
user_name: Azgaar
date: 2026-03-12
inputDocuments:
- _bmad-output/planning-artifacts/prd-layered-map-dom-split.md
- docs/architecture-globals.md
---
# Architecture Decision Document - Per-Layer SVG Map Architecture
**Project:** Fantasy-Map-Generator
**Author:** Azgaar (via Winston/Architect)
**Date:** 2026-03-12
**Status:** Complete
## 1. Architectural Intent
The current map is one SVG with one `#viewbox`, one shared `<defs>` space, and many callers that treat that structure as the application model. The new architecture separates those concerns. The application model becomes a **layered map scene**. Rendering is only one implementation detail of that scene.
The solution is deliberately simple:
- one authoritative layer registry
- one shared scene state for camera and map data
- one surface per logical layer
- one compatibility layer for legacy single-SVG assumptions
- one export assembler that rebuilds a unified SVG when needed
This avoids over-engineering. We do not introduce a generic rendering engine, a plugin DSL, or a second parallel layer system.
## 2. Core Decisions
### Decision 1: Introduce `Layers` as the Single Source of Truth
Every logical layer is registered once with:
- `id`
- `kind`: `svg` or `webgl`
- `order`
- `visible`
- `surface`
All reordering, toggling, and lookup operations go through this registry. No feature code should infer order from DOM position.
### Decision 2: Replace One Map SVG with a Scene Container and Layer Surfaces
`#layers` remains the outer host. Inside it, the runtime scene is a stack of independent surfaces.
- SVG-backed layers render into their own `<svg data-layer="..."></svg>` shell
- WebGL-backed layers render into registered canvas surfaces
- a dedicated defs host is kept separate from individual layer surfaces
This makes SVG and WebGL peers in the same scene instead of special cases.
### Decision 3: Move Shared Transform Ownership from `#viewbox` to Scene State
Zoom, pan, viewport size, graph bounds, and scale remain shared globals because the project already depends on that runtime model. What changes is their meaning.
- `scale`, `viewX`, `viewY`, `graphWidth`, `graphHeight` remain authoritative scene values (all available on `window` as before)
- `svg` and `viewbox` stop being the architectural source of truth
- legacy code that still needs them is routed through a temporary compatibility layer until it can move to scene or layer lookups
The clean-code rule here is narrow ownership: scene state owns transforms, surfaces consume transforms. High-level scene orchestration manages the rest. No direct DOM queries for camera state, no direct `viewbox` manipulation outside of the compatibility layer. Clean non-leaking abstractions that do one thing.
### Decision 4: Add a Thin Compatibility Layer for Existing Single-SVG Callers
The current codebase depends heavily on single-root access patterns. That is a migration problem, not a reason to keep the old architecture.
Add a minimal compatibility layer that exposes:
- `getLayerSvg(id)`
- `getLayerSurface(id)`
- `queryMap(selector)` for controlled cross-layer queries
This layer exists only to support migration. New code should talk to `Layers`, not to old DOM shortcuts.
### Decision 5: Runtime Rendering and Export Rendering Are Different Pipelines
The live DOM no longer needs to be the export document. Moveover, after the change DOM becomes just an implementation detail of the runtime, not the application model. Save files (.map) won't contain svg anymore, just data and registry state.
- runtime uses split surfaces for layering flexibility and simpler ownership
- export assembles one SVG from registry order, layer outputs, and shared defs resources
This is the only clean way to preserve export fidelity once the runtime is no longer a single SVG.
## 3. Runtime Structure
The target runtime shape is:
```html
<div id="map">
<div id="scene">
<svg data-layer="ocean"></svg>
<svg data-layer="lakes"></svg>
<canvas data-layer="terrain"></canvas>
<svg data-layer="routes"></svg>
<svg data-layer="labels"></svg>
<div data-layer="fog"></div>
</div>
<div id="fixed-elements">
<div id="scaleBar"></div>
<div id="legend"></div>
<div id="vignette"></div>
</div>
<svg id="defs" aria-hidden="true"></svg>
</div>
```
Important constraint: no grouped SVG buckets. Each logical layer gets its own surface so user-defined ordering stays arbitrary.
## 4. Shared Resource Strategy
The architecture uses a **dedicated defs host** at runtime.
- filters, patterns, masks, symbols, markers, and text paths are registered there
- layer SVGs reference those resources by stable IDs
- export clones only the resources actually used by exported layers into the final assembled SVG
This keeps runtime simple and export deterministic.
## 5. Clean Code Rules for This Change
This solution follows a simple clean-code approach:
- keep abstractions narrow and named after graphical software concepts: scene, camera, layer, surface, defs, export assembler
- do not let abstractions leak implementation details such as DOM structure, render technology, or shared state management
- do not add generic factories, strategy trees, or metadata schemas beyond what the current migration needs
- do not let feature code query random DOM globally when a layer or defs lookup exists
- do not hide behavior behind flags when two separate concepts deserve two separate functions
- keep layer modules responsible only for drawing their own surface
- use simple JS-style names, prefer 1-2 words
Preferred module ownership:
- `scene`: shared runtime state
- `layers`: layer registry
- `layer`: one layer surface and local lifecycle
- `defs`: shared SVG resources
## 6. Migration Plan
### Phase 1: Foundation
- migrate shared svg element resources to a dedicated defs host
- create `Scene` module for shared runtime state and camera management
- create `Layers` module for layer registry
- create `Layer` module for individual layer state and surface ownership
- add compatibility lookups for current `svg`, `viewbox`, and shared query patterns
### Phase 2: First Split Layers
- move low-risk SVG layers to standalone SVG shells
- keep their visual behavior unchanged
- validate reorder, toggle, and pointer behavior against the registry
### Phase 3: Mixed Rendering
- register WebGL layers through the same ordering model
- keep SVG and WebGL layers reorderable through one control path
### Phase 4: Export [low priority]
- build unified SVG export from registry state
- clone only required defs resources
- validate text paths, masks, and filtered layers
## 7. Testing Clause
Testing is intentionally out of scope for this update.
Reason:
- the change is architectural and cross-cutting
- intermediate states are not reliable test targets
- partial testing during the split would produce high noise and low confidence
Implementation should focus on completing the runtime architecture first. Automated tests, manual verification, and regression coverage will be planned and executed later as a separate activity once the new layer model is stable enough to test meaningfully.

View file

@ -0,0 +1,162 @@
---
status: draft
basedOn:
- "_bmad-output/brainstorming/brainstorming-session-2026-03-12-002.md"
- "_bmad-output/planning-artifacts/prd.md"
- "docs/architecture-globals.md"
---
# Product Requirements Draft - Per-Layer SVG Map Architecture
**Author:** Azgaar
**Date:** 2026-03-12
## Executive Summary
Fantasy-Map-Generator currently treats the interactive map as a single `#map` SVG with a shared `#viewbox`, shared `<defs>`, and a broad set of rendering and utility code that assumes one canonical SVG root. This blocks the next stage of the rendering architecture: arbitrary interleaving of SVG and WebGL layers in user-defined order.
This initiative replaces the single-root map DOM model with a per-layer surface architecture. Each logical map layer will own its own render surface, typically one SVG root per SVG layer and one registered surface per WebGL layer, while shared map state remains centralized through shared globals and scene orchestration. Users must continue to experience the same layer controls, visibility behavior, edit workflows, and output quality without needing to know whether a layer is rendered with SVG or WebGL.
## Problem Statement
The current architecture couples business concepts like “map layer,” “layer order,” and “layer visibility” to one concrete implementation detail: a single SVG document. That coupling appears in multiple forms:
- One canonical `#map` root and `#viewbox` transform owner.
- Shared `<defs>` and resource assumptions.
- Utilities that operate on one `SVGSVGElement`.
- Direct DOM access by layer ID or CSS selector.
- Existing WebGL support that is additive rather than fully peer-based.
As a result, the application cannot reliably place SVG and WebGL layers in any arbitrary order without accumulating special cases. Splitting the single map SVG is therefore a prerequisite for a generalized mixed-render layer stack.
## Product Goal
Enable the map to render as a stack of independent layer surfaces, one surface per logical layer, while preserving current user-visible behavior and allowing SVG and WebGL layers to be interleaved in any order.
## Core Product Principles
1. The user-visible contract is the layer stack, not the rendering technology.
2. Business behavior must remain stable across SVG and WebGL implementations.
3. Layer ordering must be driven by one authoritative source of truth.
4. Shared map state must remain available through stable globals or equivalent stable contracts.
5. Export, styles, filters, masks, and text resources are first-class requirements, not cleanup work.
## In Scope
- Replace the single-root `#map` SVG architecture with per-layer SVG roots for SVG-backed layers.
- Introduce a central layer registry that owns order, visibility, technology, and DOM/render handles.
- Preserve shared map globals for camera and scene state, while narrowing or adapting globals that currently point at one concrete SVG root.
- Support arbitrary SVG and WebGL interleaving without grouped rendering buckets.
- Define runtime resource ownership for shared defs, masks, filters, symbols, and text paths.
- Define an export assembly strategy for recomposing a unified SVG artifact from split surfaces.
- Inventory and adapt code that assumes one map SVG or one shared selector scope.
## Out of Scope
- Changing user-facing layer semantics or introducing new layer controls.
- Reworking the globe renderer.
- Rewriting all layers to WebGL.
- Solving every export enhancement beyond preserving current expected export fidelity.
- Automated and manual testing for this update during the implementation phase. Testing is deferred and will be handled later as a separate activity after the architectural change is in place.
## Current Architecture Hotspots
The PRD should explicitly cover at least these dependency classes:
- The map root in `src/index.html` is one SVG with shared `<defs>` and one `#viewbox` group.
- The runtime globals described in `docs/architecture-globals.md` expose `svg` and `viewbox` as core contracts.
- Utilities such as font collection currently accept a single `SVGSVGElement` and query multiple layer subtrees within it.
- Relief rendering already straddles SVG and WebGL, but still assumes a specific parent element and shared scene context.
## Proposed Target Architecture
### 1. Shared Scene Contract
The map camera, zoom, pan, viewport dimensions, and map data remain shared. These contracts stay globally accessible, but they no longer imply one DOM root. The PRD should define which globals remain stable, which are redefined, and which move behind compatibility adapters.
### 2. Layer Registry
The layer registry becomes the authoritative source for:
- layer ID
- display name
- order
- visibility
- render technology
- surface handle
- export participation
- dependency capabilities such as defs, masks, text paths, or interaction ownership
### 3. Layer Surface Model
Each logical layer owns one render surface. For SVG layers this is one dedicated SVG shell. For WebGL layers this is one registered draw surface within the same scene ordering model. The user does not see a distinction.
### 4. Shared Resource Federation
The PRD must define how shared defs-based resources are created, referenced, and exported. This includes symbols, patterns, filters, masks, clip paths, and text paths.
### 5. Export Assembly
The export artifact is no longer assumed to be identical to the live runtime DOM. The PRD should define an explicit export assembly step that reconstructs a unified SVG from the layer registry and shared resources.
## Functional Requirements
1. Users can reorder layers in any sequence supported today, regardless of whether the layer is implemented in SVG or WebGL.
2. Visibility toggles behave exactly as they do now.
3. Editing and pointer interaction remain correct after layer splitting.
4. Shared styles, filters, masks, and text path resources remain available where needed.
5. Existing layer-based workflows continue to function without exposing rendering implementation details.
6. Export produces equivalent output to the current behavior for supported layers.
7. Legacy code paths that depend on `svg`, `viewbox`, or one selector scope have a compatibility path during migration.
## Non-Functional Requirements
1. The architecture must support incremental migration rather than a flag-day rewrite.
2. Test design and execution are out of scope for this implementation tranche and will be handled in a separate follow-up activity.
3. Layer order changes must update all runtime surfaces atomically.
4. Performance must not regress for existing SVG-only layers.
5. The architecture must remain compatible with the existing global-variable runtime model.
## Phased Delivery Strategy
### Phase 1: Orchestration Foundation
- Create the layer registry.
- Define the shared scene contract.
- Add compatibility adapters for single-SVG assumptions.
- Build dependency census and migration classification.
### Phase 2: Initial Layer Splits
- Split selected low-risk SVG layers into standalone SVG shells.
- Validate mixed ordering with at least one WebGL layer.
- Prove visibility, reorder, and interaction parity.
### Phase 3: Resource and Export Hardening
- Implement defs federation and export assembly.
- Validate filters, masks, clip paths, and text paths.
- Expand migration coverage to more complex layers.
## Acceptance Criteria Themes
- There is no architectural requirement that a single `#map` SVG must exist at runtime.
- Layer order, visibility, and interaction semantics remain unchanged from the user perspective.
- Shared camera state drives every layer surface consistently.
- Shared defs-dependent features continue to work for runtime and export.
- Dependency hotspots are cataloged and each has a migration or compatibility strategy.
## Open Questions for the Full PRD
1. Which layers should remain unsplit initially because of high defs or text-path complexity?
2. Should runtime shared defs live in one dedicated hidden SVG host, in a primary scene SVG, or in mirrored per-layer subsets?
3. Which globals should remain named `svg` and `viewbox`, and which should be replaced with more stable abstractions?
4. Should export assemble from layer metadata or from cloned runtime surfaces?
5. What is the minimum viable compatibility layer for existing utilities and third-party integrations?
## Recommended Next Inputs
1. A codebase inventory of direct `#map`, `svg`, `viewbox`, and defs usage.
2. A capability matrix for every existing layer.
3. A first-pass list of low-risk versus high-risk candidate layers for splitting.
4. A design note for shared resource federation.