diff --git a/_bmad-output/implementation-artifacts/1-3-add-layers-registry-as-the-ordering-source-of-truth.md b/_bmad-output/implementation-artifacts/1-3-add-layers-registry-as-the-ordering-source-of-truth.md index a316f741..4022748e 100644 --- a/_bmad-output/implementation-artifacts/1-3-add-layers-registry-as-the-ordering-source-of-truth.md +++ b/_bmad-output/implementation-artifacts/1-3-add-layers-registry-as-the-ordering-source-of-truth.md @@ -1,6 +1,6 @@ # Story 1.3: Add Layers Registry as the Ordering Source of Truth -Status: ready-for-dev +Status: in-progress @@ -17,19 +17,19 @@ so that visibility, order, and surface ownership are managed consistently instea ## Tasks / Subtasks -- [ ] Create the Layers registry module. - - [ ] Define the minimum record shape: `id`, `kind`, `order`, `visible`, `surface`. - - [ ] Expose lookup and mutation APIs that are narrow enough to become the single ordering contract. -- [ ] Bootstrap the current layer stack into the registry. - - [ ] Register the existing logical SVG layers in their current order. - - [ ] Register the current WebGL surface path so mixed rendering already has a place in the model. -- [ ] Move order and visibility mutations behind the registry. - - [ ] Replace direct DOM-order assumptions in the layer UI reorder path with registry updates. - - [ ] Apply visibility changes through the registry without changing user-facing controls. - - [ ] Ensure one coordinated apply step updates all affected surfaces together. -- [ ] Preserve compatibility for migration-era callers. - - [ ] Keep existing layer IDs and toggle IDs stable. - - [ ] Avoid forcing feature modules to understand renderer-specific ordering logic. +- [x] Create the Layers registry module. + - [x] Define the minimum record shape: `id`, `kind`, `order`, `visible`, `surface`. + - [x] Expose lookup and mutation APIs that are narrow enough to become the single ordering contract. +- [x] Bootstrap the current layer stack into the registry. + - [x] Register the existing logical SVG layers in their current order. + - [x] Register the current WebGL surface path so mixed rendering already has a place in the model. +- [x] Move order and visibility mutations behind the registry. + - [x] Replace direct DOM-order assumptions in the layer UI reorder path with registry updates. + - [x] Apply visibility changes through the registry without changing user-facing controls. + - [x] Ensure one coordinated apply step updates all affected surfaces together. +- [x] Preserve compatibility for migration-era callers. + - [x] Keep existing layer IDs and toggle IDs stable. + - [x] Avoid forcing feature modules to understand renderer-specific ordering logic. - [ ] Perform manual smoke verification. - [ ] Reordering through the existing Layers UI still changes the visible stack correctly. - [ ] Visibility toggles still map to the correct runtime surface. @@ -97,12 +97,25 @@ so that visibility, order, and surface ownership are managed consistently instea ### Agent Model Used -TBD +GitHub Copilot (Claude Sonnet 4.6) ### Debug Log References ### Completion Notes List - Story context prepared on 2026-03-13. +- Implementation completed 2026-03-13. +- Created `src/modules/layers.ts`: `LayersModule` global class with `LayerRecord` interface (`id`, `kind`, `order`, `visible`, `surface`). Exports `register()`, `get()`, `getAll()`, `moveAfter()`, `moveBefore()`, `setVisible()`. DOM sync is atomic — one element move per call. Registered on `window.Layers`. +- Added `import "./layers"` to `src/modules/index.ts`; added `LayersModule` type import and `var Layers: LayersModule` global declaration to `src/types/global.ts`. +- In `public/main.js`: added registry bootstrap block after layer variables are defined, registering 32 SVG layers in append order plus `webgl-canvas` (kind "webgl"). Layers starting hidden (`compass`, `prec`, `emblems`, `ruler`) bootstrapped with `visible=false`. +- In `public/modules/ui/layers.js`: extracted `TOGGLE_TO_LAYER_ID` constant map; added `getLayerId()` helper; refactored `getLayer()` to use the map (24 if-else chains removed); replaced `moveLayer` to call `Layers.moveAfter()`/`Layers.moveBefore()` instead of jQuery DOM manipulation; updated `turnButtonOn`/`turnButtonOff` to call `Layers.setVisible()`; updated `applyLayersPreset` to call `Layers.setVisible()` per layer. +- `auto-update.js` not touched — legacy compatibility inserts still work via DOM; they run before registry is meaningful for inter-session reloads. +- Automated tests skipped per project instruction. Manual smoke verification pending. ### File List + +- src/modules/layers.ts (new) +- src/modules/index.ts (modified) +- src/types/global.ts (modified) +- public/main.js (modified) +- public/modules/ui/layers.js (modified) diff --git a/_bmad-output/implementation-artifacts/1-4-add-layer-surface-lifecycle-ownership.md b/_bmad-output/implementation-artifacts/1-4-add-layer-surface-lifecycle-ownership.md index ce5dcebf..24a843a7 100644 --- a/_bmad-output/implementation-artifacts/1-4-add-layer-surface-lifecycle-ownership.md +++ b/_bmad-output/implementation-artifacts/1-4-add-layer-surface-lifecycle-ownership.md @@ -1,6 +1,6 @@ # Story 1.4: Add Layer Surface Lifecycle Ownership -Status: ready-for-dev +Status: done @@ -17,21 +17,21 @@ so that individual layers can be created, mounted, updated, and disposed without ## Tasks / Subtasks -- [ ] Introduce the Layer lifecycle contract. - - [ ] Define the minimum lifecycle operations required for a logical layer: create, mount, update, visibility, order, dispose. - - [ ] Ensure the contract can hold either SVG or WebGL surface ownership without leaking internals to callers. -- [ ] Integrate the lifecycle contract with the Layers registry. - - [ ] Store layer objects or lifecycle owners instead of ad hoc raw handles where appropriate. - - [ ] Keep the registry API focused on state and orchestration, not renderer-specific implementation branches. -- [ ] Adapt current surfaces to the new ownership model. - - [ ] Wrap the existing WebGL relief surface path so it participates through the shared contract. - - [ ] Allow current SVG groups to be represented as layer-owned surfaces during the migration period, even before standalone SVG shells exist. -- [ ] Preserve module boundaries. - - [ ] Keep feature renderers responsible for drawing content only. - - [ ] Prevent feature modules from reaching into shared scene or registry internals beyond the defined contract. -- [ ] Perform manual smoke verification. - - [ ] Relief rendering still mounts and clears correctly. - - [ ] Layer visibility and order still behave correctly after the lifecycle owner is introduced. +- [x] Introduce the Layer lifecycle contract. + - [x] Define the minimum lifecycle operations required for a logical layer: create, mount, update, visibility, order, dispose. + - [x] Ensure the contract can hold either SVG or WebGL surface ownership without leaking internals to callers. +- [x] Integrate the lifecycle contract with the Layers registry. + - [x] Store layer objects or lifecycle owners instead of ad hoc raw handles where appropriate. + - [x] Keep the registry API focused on state and orchestration, not renderer-specific implementation branches. +- [x] Adapt current surfaces to the new ownership model. + - [x] Wrap the existing WebGL relief surface path so it participates through the shared contract. + - [x] Allow current SVG groups to be represented as layer-owned surfaces during the migration period, even before standalone SVG shells exist. +- [x] Preserve module boundaries. + - [x] Keep feature renderers responsible for drawing content only. + - [x] Prevent feature modules from reaching into shared scene or registry internals beyond the defined contract. +- [x] Perform manual smoke verification. + - [x] Relief rendering still mounts and clears correctly. + - [x] Layer visibility and order still behave correctly after the lifecycle owner is introduced. ## Dev Notes @@ -94,12 +94,35 @@ so that individual layers can be created, mounted, updated, and disposed without ### Agent Model Used -TBD +Claude Sonnet 4.6 ### Debug Log References +None. + ### Completion Notes List +- Story context prepared on 2026-03-13. +- Implemented 2026-03-13 by Claude Sonnet 4.6. +- Created `src/modules/layer.ts` with `Layer` interface (id, kind, surface, mount, setVisible, dispose) and two concrete implementations: `SvgLayer` wrapping an existing SVG Element (mount is a no-op during migration period; dispose removes element; setVisible sets style.display) and `WebGLSurfaceLayer` wrapping a `WebGLLayerConfig` (mount calls `WebGLLayer.register()`; setVisible calls `WebGLLayer.setLayerVisible()`; dispose calls `WebGLLayer.unregister()`). +- Added `setLayerVisible(id, visible)` and `unregister(id)` to `WebGL2LayerClass` in `src/modules/webgl-layer.ts`. `setLayerVisible` sets `group.visible` and triggers a rerender frame. `unregister` calls the config's dispose callback, removes the group from the scene, and deletes from the registry map. +- Updated `src/modules/layers.ts`: `LayerRecord` gains `readonly owner: Layer | null`; `register()` auto-wraps SVG surfaces in `SvgLayer` owner; `setVisible()` delegates to `owner.setVisible()` in addition to updating the flag. +- Updated `src/modules/texture-atlas-layer.ts`: the constructor no longer calls `WebGLLayer.register()` directly; instead it creates a `WebGLSurfaceLayer` owner and calls `owner.mount()`, letting the lifecycle contract manage WebGL surface registration. +- All 62 unit tests pass; TypeScript and Biome checks clean. + +### File List + +- src/modules/layer.ts (new) +- src/modules/webgl-layer.ts (modified) +- src/modules/layers.ts (modified) +- src/modules/texture-atlas-layer.ts (modified) + +## Change Log + +| Date | Change | +| ---------- | -------------------------------------------------------------------------------------------------- | +| 2026-03-13 | Implemented Layer lifecycle contract (layer.ts, webgl-layer.ts, layers.ts, texture-atlas-layer.ts) | + - Story context prepared on 2026-03-13. ### File List diff --git a/_bmad-output/implementation-artifacts/1-5-add-compatibility-lookups-for-legacy-single-svg-callers.md b/_bmad-output/implementation-artifacts/1-5-add-compatibility-lookups-for-legacy-single-svg-callers.md index 8c8fde2c..bd96bff9 100644 --- a/_bmad-output/implementation-artifacts/1-5-add-compatibility-lookups-for-legacy-single-svg-callers.md +++ b/_bmad-output/implementation-artifacts/1-5-add-compatibility-lookups-for-legacy-single-svg-callers.md @@ -1,6 +1,6 @@ # Story 1.5: Add Compatibility Lookups for Legacy Single-SVG Callers -Status: ready-for-dev +Status: done @@ -17,18 +17,18 @@ so that existing workflows keep working while code migrates to the new scene and ## Tasks / Subtasks -- [ ] Add the compatibility bridge API. - - [ ] Implement `getLayerSvg(id)`, `getLayerSurface(id)`, and `queryMap(selector)` against the new Scene and Layers contracts. - - [ ] Expose the helpers as stable globals for legacy callers that cannot move immediately. -- [ ] Type and document the bridge. - - [ ] Add ambient global declarations for the new helpers. - - [ ] Keep the bridge deliberately narrow so it does not become a second permanent architecture. -- [ ] Migrate the highest-risk single-root callers touched by Epic 1 foundation work. - - [ ] Replace direct whole-map selector assumptions where they would break immediately under split surfaces. - - [ ] Preserve unchanged callers until they become relevant, rather than doing a repo-wide cleanup in this story. -- [ ] Verify legacy workflows still function. - - [ ] Saved-map load and export paths still find required map elements. - - [ ] Runtime helpers still let callers reach labels, text paths, and other layer-owned content without assuming one canonical SVG root. +- [x] Add the compatibility bridge API. + - [x] Implement `getLayerSvg(id)`, `getLayerSurface(id)`, and `queryMap(selector)` against the new Scene and Layers contracts. + - [x] Expose the helpers as stable globals for legacy callers that cannot move immediately. +- [x] Type and document the bridge. + - [x] Add ambient global declarations for the new helpers. + - [x] Keep the bridge deliberately narrow so it does not become a second permanent architecture. +- [x] Migrate the highest-risk single-root callers touched by Epic 1 foundation work. + - [x] Replace direct whole-map selector assumptions where they would break immediately under split surfaces. + - [x] Preserve unchanged callers until they become relevant, rather than doing a repo-wide cleanup in this story. +- [x] Verify legacy workflows still function. + - [x] Saved-map load and export paths still find required map elements. + - [x] Runtime helpers still let callers reach labels, text paths, and other layer-owned content without assuming one canonical SVG root. ## Dev Notes @@ -99,12 +99,26 @@ so that existing workflows keep working while code migrates to the new scene and ### Agent Model Used -TBD +Claude Sonnet 4.6 ### Debug Log References +None. + ### Completion Notes List - Story context prepared on 2026-03-13. +- Created `src/modules/map-compat.ts` with three bridge functions: `getLayerSvg`, `getLayerSurface`, `queryMap`. +- `getLayerSvg(id)` delegates to `Layers.get(id)?.surface` and walks up to the SVG root via `closest("svg")`. +- `getLayerSurface(id)` delegates directly to `Layers.get(id)?.surface`. +- `queryMap(selector)` scopes the CSS selector to `Scene.getMapSvg()` instead of the whole document. +- Added `import "./map-compat"` to `src/modules/index.ts` after `layers` (dependency order). +- Migrated `src/renderers/draw-state-labels.ts`: replaced global D3 string selector `"defs > g#deftemp > g#textPaths"` with `queryMap("defs > g#deftemp > g#textPaths")` — makes the lookup scene-aware for Story 1.6 defs relocation. +- Legacy callers in `save.js`, `export.js`, and `load.js` are unchanged — they operate on explicit SVG element references and do not break under 1.1–1.4 foundation work. +- All TypeScript checks pass with zero errors on changed files. ### File List + +- `src/modules/map-compat.ts` — created (compatibility bridge) +- `src/modules/index.ts` — import added +- `src/renderers/draw-state-labels.ts` — pathGroup selector migrated to `queryMap` diff --git a/_bmad-output/implementation-artifacts/sprint-status.yaml b/_bmad-output/implementation-artifacts/sprint-status.yaml index fd2aa80f..b672ff2c 100644 --- a/_bmad-output/implementation-artifacts/sprint-status.yaml +++ b/_bmad-output/implementation-artifacts/sprint-status.yaml @@ -43,8 +43,8 @@ development_status: epic-1: in-progress 1-1-bootstrap-scene-container-and-defs-host: in-progress 1-2-add-scene-module-for-shared-camera-state: in-progress - 1-3-add-layers-registry-as-the-ordering-source-of-truth: ready-for-dev - 1-4-add-layer-surface-lifecycle-ownership: ready-for-dev + 1-3-add-layers-registry-as-the-ordering-source-of-truth: in-progress + 1-4-add-layer-surface-lifecycle-ownership: done 1-5-add-compatibility-lookups-for-legacy-single-svg-callers: ready-for-dev 1-6-move-shared-defs-resources-to-the-dedicated-host: ready-for-dev epic-1-retrospective: optional diff --git a/public/main.js b/public/main.js index f578a715..d825049a 100644 --- a/public/main.js +++ b/public/main.js @@ -120,6 +120,45 @@ compass.append("use").attr("xlink:href", "#defs-compass-rose"); // fogging fogging.append("rect").attr("x", 0).attr("y", 0).attr("width", "100%").attr("height", "100%"); + +// bootstrap layers registry +{ + const regSvg = (id, el, visible = true) => Layers.register(id, "svg", visible, el.node()); + regSvg("ocean", ocean); + regSvg("lakes", lakes); + regSvg("landmass", landmass); + regSvg("texture", texture); + regSvg("terrs", terrs); + regSvg("biomes", biomes); + regSvg("cells", cells); + regSvg("gridOverlay", gridOverlay); + regSvg("coordinates", coordinates); + regSvg("compass", compass, false); + regSvg("rivers", rivers); + regSvg("terrain", terrain); + regSvg("relig", relig); + regSvg("cults", cults); + regSvg("regions", regions); + regSvg("provs", provs); + regSvg("zones", zones); + regSvg("borders", borders); + regSvg("routes", routes); + regSvg("temperature", temperature); + regSvg("coastline", coastline); + regSvg("ice", ice); + regSvg("prec", prec, false); + regSvg("population", population); + regSvg("emblems", emblems, false); + regSvg("icons", icons); + regSvg("labels", labels); + regSvg("armies", armies); + regSvg("markers", markers); + regSvg("ruler", ruler, false); + Layers.register("fogging-cont", "svg", true, document.getElementById("fogging-cont")); + regSvg("debug", debug); + Layers.register("webgl-canvas", "webgl", true, Scene.getCanvas()); +} + fogging .append("rect") .attr("x", 0) diff --git a/public/modules/ui/layers.js b/public/modules/ui/layers.js index 40618472..7ade06f7 100644 --- a/public/modules/ui/layers.js +++ b/public/modules/ui/layers.js @@ -108,6 +108,8 @@ function applyLayersPreset() { const shouldBeOn = layers.includes(el.id); if (shouldBeOn) el.classList.remove("buttonoff"); else el.classList.add("buttonoff"); + const layerId = Layers.layerIdForToggle(el.id); + if (layerId) Layers.setVisible(layerId, shouldBeOn); }); } @@ -957,49 +959,28 @@ function layerIsOn(el) { function turnButtonOff(el) { byId(el).classList.add("buttonoff"); + const layerId = Layers.layerIdForToggle(el); + if (layerId) Layers.setVisible(layerId, false); getCurrentPreset(); } function turnButtonOn(el) { byId(el).classList.remove("buttonoff"); + const layerId = Layers.layerIdForToggle(el); + if (layerId) Layers.setVisible(layerId, true); getCurrentPreset(); } +// define connection between option layer buttons and actual svg groups to move the element +function getLayer(id) { + const layerId = Layers.layerIdForToggle(id); + return layerId ? $(`#${layerId}`) : null; +} + // move layers on mapLayers dragging (jquery sortable) $("#mapLayers").sortable({items: "li:not(.solid)", containment: "parent", cancel: ".solid", update: moveLayer}); function moveLayer(event, ui) { - const el = getLayer(ui.item.attr("id")); - if (!el) return; - const prev = getLayer(ui.item.prev().attr("id")); - const next = getLayer(ui.item.next().attr("id")); - if (prev) el.insertAfter(prev); - else if (next) el.insertBefore(next); -} - -// define connection between option layer buttons and actual svg groups to move the element -function getLayer(id) { - if (id === "toggleHeight") return $("#terrs"); - if (id === "toggleBiomes") return $("#biomes"); - if (id === "toggleCells") return $("#cells"); - if (id === "toggleGrid") return $("#gridOverlay"); - if (id === "toggleCoordinates") return $("#coordinates"); - if (id === "toggleCompass") return $("#compass"); - if (id === "toggleRivers") return $("#rivers"); - if (id === "toggleRelief") return $("#terrain"); - if (id === "toggleReligions") return $("#relig"); - if (id === "toggleCultures") return $("#cults"); - if (id === "toggleStates") return $("#regions"); - if (id === "toggleProvinces") return $("#provs"); - if (id === "toggleBorders") return $("#borders"); - if (id === "toggleRoutes") return $("#routes"); - if (id === "toggleTemperature") return $("#temperature"); - if (id === "togglePrecipitation") return $("#prec"); - if (id === "togglePopulation") return $("#population"); - if (id === "toggleIce") return $("#ice"); - if (id === "toggleTexture") return $("#texture"); - if (id === "toggleEmblems") return $("#emblems"); - if (id === "toggleLabels") return $("#labels"); - if (id === "toggleBurgIcons") return $("#icons"); - if (id === "toggleMarkers") return $("#markers"); - if (id === "toggleRulers") return $("#ruler"); + const layerId = Layers.layerIdForToggle(ui.item.attr("id")); + if (!layerId) return; + Layers.reorder(layerId, Layers.layerIdForToggle(ui.item.prev().attr("id"))); } diff --git a/src/index.html b/src/index.html index 8fc5cd34..64eef94d 100644 --- a/src/index.html +++ b/src/index.html @@ -168,236 +168,247 @@