mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2026-04-02 05:27:24 +02:00
feat: Update SceneModule to manage camera state and viewport, refactor WebGL layer to utilize Scene methods
This commit is contained in:
parent
42557881bb
commit
52708e50c5
10 changed files with 128 additions and 26 deletions
|
|
@ -50,12 +50,15 @@ so that split layer surfaces can share resources without depending on one map SV
|
|||
- Keep the runtime compatible with the current global-variable model. New TypeScript code must use ambient globals directly, not `window` or `globalThis` wrappers.
|
||||
- New TypeScript modules must follow the project Global Module Pattern: declare global, implement a class, then assign `window.ModuleName = new ModuleClass()`.
|
||||
- Avoid grouped SVG buckets or speculative abstractions here. The goal is bootstrap ownership only.
|
||||
- Keep the implementation lean. Do not expand this story into a broad runtime framework, compatibility bridge, or camera API beyond the host ownership needed for Story 1.1.
|
||||
- Prefer direct code over wrappers. Do not add helpers, comments, or indirection unless at least two concrete call sites need them.
|
||||
|
||||
### Architecture Compliance
|
||||
|
||||
- This story is the Phase 1 foundation from the layered-map architecture: create one scene container, one defs host, and stable references before any layer split begins.
|
||||
- `svg` and `viewbox` remain compatibility-era globals after this story. They must stop being the architectural source of truth, but they are not removed yet.
|
||||
- The runtime defs host must live outside individual layer surfaces so later split SVG shells can reference shared resources by stable ID.
|
||||
- Keep ownership narrow: this story covers bootstrap ownership only. Do not fold in layer registry, compatibility lookups, export behavior, or transform semantics.
|
||||
|
||||
### Project Structure Notes
|
||||
|
||||
|
|
@ -73,7 +76,8 @@ so that split layer surfaces can share resources without depending on one map SV
|
|||
|
||||
- The architecture document explicitly defers formal test work for this tranche.
|
||||
- Do manual verification for fresh load, saved-map load, and relief visibility.
|
||||
- If a pure helper is extracted while implementing bootstrap ownership, keep it testable, but do not expand scope into a new test suite in this story.
|
||||
- Do not add Playwright coverage, browser harnesses, or new automated regression suites in this story.
|
||||
- If a pure helper is extracted while implementing bootstrap ownership, keep it simple and locally testable later, but do not expand scope into a new test suite in this story.
|
||||
|
||||
### References
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Story 1.2: Add Scene Module for Shared Camera State
|
||||
|
||||
Status: ready-for-dev
|
||||
Status: in-progress
|
||||
|
||||
<!-- Note: Validation is optional. Run validate-create-story for quality check before dev-story. -->
|
||||
|
||||
|
|
@ -17,18 +17,18 @@ so that all layer surfaces consume one authoritative transform contract.
|
|||
|
||||
## Tasks / Subtasks
|
||||
|
||||
- [ ] Create a narrow Scene runtime module.
|
||||
- [ ] Expose shared camera and viewport getters derived from the authoritative globals.
|
||||
- [ ] Expose scene-host references established in Story 1.1 through the same contract.
|
||||
- [ ] Centralize transform ownership.
|
||||
- [ ] Move reusable camera-bound calculations behind Scene methods instead of ad hoc DOM reads.
|
||||
- [ ] Keep `scale`, `viewX`, `viewY`, `graphWidth`, and `graphHeight` as the underlying truth during migration.
|
||||
- [ ] Update runtime consumers that already operate across surfaces.
|
||||
- [ ] Rewire the current WebGL layer framework to consume the Scene contract instead of reading transform meaning from DOM structure.
|
||||
- [ ] Ensure zoom and pan updates publish one consistent state for all registered surfaces.
|
||||
- [ ] Keep compatibility intact.
|
||||
- [ ] Preserve existing `viewbox` transform application while older code still depends on it.
|
||||
- [ ] Do not break callers that still read the existing globals directly.
|
||||
- [x] Create a narrow Scene runtime module.
|
||||
- [x] Expose shared camera and viewport getters derived from the authoritative globals.
|
||||
- [x] Expose scene-host references established in Story 1.1 through the same contract.
|
||||
- [x] Centralize transform ownership.
|
||||
- [x] Move reusable camera-bound calculations behind Scene methods instead of ad hoc DOM reads.
|
||||
- [x] Keep `scale`, `viewX`, `viewY`, `graphWidth`, and `graphHeight` as the underlying truth during migration.
|
||||
- [x] Update runtime consumers that already operate across surfaces.
|
||||
- [x] Rewire the current WebGL layer framework to consume the Scene contract instead of reading transform meaning from DOM structure.
|
||||
- [x] Ensure zoom and pan updates publish one consistent state for all registered surfaces.
|
||||
- [x] Keep compatibility intact.
|
||||
- [x] Preserve existing `viewbox` transform application while older code still depends on it.
|
||||
- [x] Do not break callers that still read the existing globals directly.
|
||||
- [ ] Perform manual smoke verification.
|
||||
- [ ] Zoom and pan keep SVG and WebGL content aligned.
|
||||
- [ ] Startup and resize continue to use the correct viewport bounds.
|
||||
|
|
@ -47,12 +47,14 @@ so that all layer surfaces consume one authoritative transform contract.
|
|||
- Use bare ambient globals declared in `src/types/global.ts`. Do not introduce `window.scale` or `globalThis.viewX` usage.
|
||||
- Keep the abstraction narrow: Scene owns shared camera and viewport state, not layer ordering, defs ownership, or export assembly.
|
||||
- Prefer pure helper methods for transform math so later stories can reuse them without DOM coupling.
|
||||
- Keep the module terse. Do not let `Scene` accumulate bootstrap, compatibility, registry, or export responsibilities in this story.
|
||||
|
||||
### Architecture Compliance
|
||||
|
||||
- This story implements the architecture decision that transform ownership moves from `#viewbox` to scene state.
|
||||
- `viewbox` remains a compatibility-era render target, not the source of truth.
|
||||
- The Scene API should be sufficient for both SVG and WebGL consumers once split surfaces arrive.
|
||||
- Developer productivity is architecture here: expose only the few scene methods current consumers actually need.
|
||||
|
||||
### Previous Story Intelligence
|
||||
|
||||
|
|
@ -75,6 +77,7 @@ so that all layer surfaces consume one authoritative transform contract.
|
|||
- Formal automated test work is out of scope for this tranche.
|
||||
- Keep transform calculation logic pure enough for later coverage.
|
||||
- Manual verification should cover zoom, pan, resize, and relief alignment.
|
||||
- Do not add Playwright coverage or new browser-driven tests in this story.
|
||||
|
||||
### Dependencies
|
||||
|
||||
|
|
@ -92,12 +95,21 @@ so that all layer surfaces consume one authoritative transform contract.
|
|||
|
||||
### Agent Model Used
|
||||
|
||||
TBD
|
||||
GPT-5.4
|
||||
|
||||
### Debug Log References
|
||||
|
||||
### Completion Notes List
|
||||
|
||||
- Story context prepared on 2026-03-13.
|
||||
- Scene now exposes shared camera, viewport, and compatibility transform access while reusing the stable scene-host references from Story 1.1.
|
||||
- WebGL camera sync now consumes `Scene.getCameraBounds()` and legacy zoom handling routes `viewbox` transform application through `Scene.applyViewboxTransform()`.
|
||||
- Automated tests were removed and no tests or Playwright checks were run per user instruction.
|
||||
- Manual smoke verification remains pending before the story can move to review.
|
||||
|
||||
### File List
|
||||
|
||||
- public/main.js
|
||||
- src/modules/scene.ts
|
||||
- src/modules/webgl-layer.ts
|
||||
- src/modules/scene.test.ts (removed)
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ so that visibility, order, and surface ownership are managed consistently instea
|
|||
- Keep the public contract minimal. Do not add export metadata yet; that belongs to Epic 4.
|
||||
- Store actual surface handles, not just selectors, so later stories can mount independent SVG shells and WebGL surfaces under one contract.
|
||||
- Ensure reorder application is atomic from the user perspective. Avoid partial states where one surface has moved and another has not.
|
||||
- Keep the registry boring. No factory layers, schema systems, or speculative metadata beyond `id`, `kind`, `order`, `visible`, and `surface`.
|
||||
|
||||
### Architecture Compliance
|
||||
|
||||
|
|
@ -77,6 +78,7 @@ so that visibility, order, and surface ownership are managed consistently instea
|
|||
|
||||
- Manual verification is sufficient for this tranche.
|
||||
- Verify the existing sortable Layers UI still works and that no layer disappears from the visible stack after a reorder.
|
||||
- Do not add Playwright coverage or new automated test infrastructure in this story.
|
||||
|
||||
### Dependencies
|
||||
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ so that individual layers can be created, mounted, updated, and disposed without
|
|||
- Avoid generic factories or strategy trees. One lifecycle owner with SVG and WebGL-capable implementations is enough for this phase.
|
||||
- Do not force feature modules to pass renderer flags around. If two surface kinds need separate logic, isolate that inside the lifecycle owner.
|
||||
- Preserve the current `WebGLLayer` canvas and shared context budget.
|
||||
- Keep the API compact. Do not add lifecycle hooks or extension points that this migration does not use yet.
|
||||
|
||||
### Architecture Compliance
|
||||
|
||||
|
|
@ -75,6 +76,7 @@ so that individual layers can be created, mounted, updated, and disposed without
|
|||
|
||||
- Manual validation is sufficient.
|
||||
- Focus on mount, redraw, hide/show, and cleanup behavior for relief because that is the current live mixed-render surface.
|
||||
- Do not add Playwright coverage or new automated test harnesses in this story.
|
||||
|
||||
### Dependencies
|
||||
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ so that existing workflows keep working while code migrates to the new scene and
|
|||
- Return real layer surfaces or layer-local SVG roots where available. Do not fake a new canonical SVG document.
|
||||
- `queryMap(selector)` should be controlled and predictable. It must not silently reintroduce global DOM coupling as a hidden permanent pattern.
|
||||
- Preserve current IDs and selectors where possible so callers can migrate incrementally.
|
||||
- Keep the bridge thin and temporary. Do not add convenience helpers beyond the three architecture-approved lookups.
|
||||
|
||||
### Architecture Compliance
|
||||
|
||||
|
|
@ -78,6 +79,7 @@ so that existing workflows keep working while code migrates to the new scene and
|
|||
|
||||
- Manual verification is sufficient.
|
||||
- Validate save/export, labels/text paths, and at least one legacy selector-heavy workflow after the helpers are introduced.
|
||||
- Do not add Playwright coverage or new automated regression suites in this story.
|
||||
|
||||
### Dependencies
|
||||
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ so that split surfaces can keep using stable IDs for filters, masks, symbols, ma
|
|||
- The dedicated defs host must be reachable by all layer surfaces after the map DOM is split.
|
||||
- Prefer one focused defs owner module over scattered DOM writes.
|
||||
- Avoid changing export behavior in this story beyond what is necessary to keep runtime resources consistent for later work.
|
||||
- Keep defs ownership isolated. Do not use this story to introduce broader compatibility or export abstractions.
|
||||
|
||||
### Architecture Compliance
|
||||
|
||||
|
|
@ -83,6 +84,7 @@ so that split surfaces can keep using stable IDs for filters, masks, symbols, ma
|
|||
|
||||
- Manual validation is sufficient.
|
||||
- Check at least one feature path mask, one text-path label case, one marker or symbol reference, and fogging/filter behavior.
|
||||
- Do not add Playwright coverage or new automated browser tests in this story.
|
||||
|
||||
### Dependencies
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ story_location: /Users/azgaar/Fantasy-Map-Generator/_bmad-output/implementation-
|
|||
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: ready-for-dev
|
||||
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-5-add-compatibility-lookups-for-legacy-single-svg-callers: ready-for-dev
|
||||
|
|
|
|||
|
|
@ -450,7 +450,7 @@ function findBurgForMFCG(params) {
|
|||
}
|
||||
|
||||
function handleZoom(isScaleChanged, isPositionChanged) {
|
||||
viewbox.attr("transform", `translate(${viewX} ${viewY}) scale(${scale})`);
|
||||
Scene.applyViewboxTransform();
|
||||
|
||||
if (isPositionChanged) {
|
||||
if (layerIsOn("toggleCoordinates")) drawCoordinates();
|
||||
|
|
|
|||
|
|
@ -6,6 +6,58 @@ const WEBGL_CANVAS_ID = "webgl-canvas";
|
|||
const RUNTIME_DEFS_HOST_ID = "runtime-defs-host";
|
||||
const RUNTIME_DEFS_ID = "runtime-defs";
|
||||
|
||||
export interface SceneCameraState {
|
||||
scale: number;
|
||||
viewX: number;
|
||||
viewY: number;
|
||||
}
|
||||
|
||||
export interface SceneViewportState {
|
||||
graphWidth: number;
|
||||
graphHeight: number;
|
||||
svgWidth: number;
|
||||
svgHeight: number;
|
||||
}
|
||||
|
||||
export interface SceneCameraBounds {
|
||||
left: number;
|
||||
right: number;
|
||||
top: number;
|
||||
bottom: number;
|
||||
}
|
||||
|
||||
export function buildCameraBounds(
|
||||
viewX: number,
|
||||
viewY: number,
|
||||
scale: number,
|
||||
graphWidth: number,
|
||||
graphHeight: number,
|
||||
): SceneCameraBounds {
|
||||
const left = normalizeSceneValue(-viewX / scale);
|
||||
const top = normalizeSceneValue(-viewY / scale);
|
||||
const width = graphWidth / scale;
|
||||
const height = graphHeight / scale;
|
||||
|
||||
return {
|
||||
left,
|
||||
right: left + width,
|
||||
top,
|
||||
bottom: top + height,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildViewboxTransform({
|
||||
scale,
|
||||
viewX,
|
||||
viewY,
|
||||
}: SceneCameraState) {
|
||||
return `translate(${viewX} ${viewY}) scale(${scale})`;
|
||||
}
|
||||
|
||||
function normalizeSceneValue(value: number) {
|
||||
return Object.is(value, -0) ? 0 : value;
|
||||
}
|
||||
|
||||
export class SceneModule {
|
||||
private mapContainer: HTMLElement | null = null;
|
||||
private sceneContainer: HTMLDivElement | null = null;
|
||||
|
|
@ -98,6 +150,32 @@ export class SceneModule {
|
|||
return this.runtimeDefs!;
|
||||
}
|
||||
|
||||
getCamera() {
|
||||
return { scale, viewX, viewY };
|
||||
}
|
||||
|
||||
getViewport() {
|
||||
return { graphWidth, graphHeight, svgWidth, svgHeight };
|
||||
}
|
||||
|
||||
getCameraBounds() {
|
||||
const { scale, viewX, viewY } = this.getCamera();
|
||||
const { graphWidth, graphHeight } = this.getViewport();
|
||||
return buildCameraBounds(viewX, viewY, scale, graphWidth, graphHeight);
|
||||
}
|
||||
|
||||
getViewboxTransform() {
|
||||
return buildViewboxTransform(this.getCamera());
|
||||
}
|
||||
|
||||
applyViewboxTransform() {
|
||||
if (typeof viewbox === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
viewbox.attr("transform", this.getViewboxTransform());
|
||||
}
|
||||
|
||||
private requireMapContainer() {
|
||||
const mapContainer = document.getElementById(MAP_CONTAINER_ID);
|
||||
if (!(mapContainer instanceof HTMLElement)) {
|
||||
|
|
@ -173,4 +251,6 @@ declare global {
|
|||
var Scene: SceneModule;
|
||||
}
|
||||
|
||||
window.Scene = new SceneModule();
|
||||
if (typeof window !== "undefined") {
|
||||
window.Scene = new SceneModule();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ export class WebGL2LayerClass {
|
|||
|
||||
init(): boolean {
|
||||
const canvas = Scene.getCanvas();
|
||||
const { graphWidth, graphHeight } = Scene.getViewport();
|
||||
|
||||
this.renderer = new WebGLRenderer({
|
||||
canvas,
|
||||
|
|
@ -53,15 +54,12 @@ export class WebGL2LayerClass {
|
|||
|
||||
private syncTransform() {
|
||||
if (!this.camera) return;
|
||||
const x = -viewX / scale;
|
||||
const y = -viewY / scale;
|
||||
const w = graphWidth / scale;
|
||||
const h = graphHeight / scale;
|
||||
const { bottom, left, right, top } = Scene.getCameraBounds();
|
||||
|
||||
this.camera.left = x;
|
||||
this.camera.right = x + w;
|
||||
this.camera.top = y;
|
||||
this.camera.bottom = y + h;
|
||||
this.camera.left = left;
|
||||
this.camera.right = right;
|
||||
this.camera.top = top;
|
||||
this.camera.bottom = bottom;
|
||||
this.camera.updateProjectionMatrix();
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue