ui/phase-9: PixiJS map renderer with torus and no-wrap modes
Stand up the vector map renderer in ui/frontend/src/map/ on top of PixiJS v8 + pixi-viewport@^6. Torus mode renders nine container copies for seamless wrap; no-wrap mode pins the camera at world bounds and centres on an axis when the viewport exceeds the world along that axis. Hit-test is a brute-force pass with deterministic [-priority, distSq, kindOrder, id] ordering and torus-shortest distance, validated by hand-built unit cases. The development playground at /__debug/map exposes a window debug surface for the Playwright spec, which forces WebGPU on chromium-desktop, WebGL on webkit-desktop, and accepts the auto-picked backend on mobile projects. Algorithm spec lives in ui/docs/renderer.md, which also pins the new deprecation status of galaxy/client (the entire Fyne client module, including client/world). client/world/README.md and the Phase 9 stub in ui/PLAN.md gain matching deprecation banners. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+59
-40
@@ -71,13 +71,16 @@ The intended v1 architecture is:
|
||||
- Pre-production migration rule from the project root applies: schema
|
||||
changes are inlined into the existing init schema rather than
|
||||
producing new migrations; clean rebuilds on every checkout.
|
||||
- The existing `client/` package is deprecated. New code does not import
|
||||
from it. Existing types in `pkg/model/client/` are not migrated; UI
|
||||
types are written from scratch in `ui/core/types/` as needed.
|
||||
- The `client/world/` algorithm is treated as a reference description
|
||||
for the new TypeScript renderer. Tile-based spatial indexing is
|
||||
intentionally omitted in the first iteration; PixiJS native culling
|
||||
and bounds-based hit testing carry the renderer until profiling
|
||||
- The existing `galaxy/client` Go module is deprecated in full. New
|
||||
code does not import from it; this includes `client/world/`, which
|
||||
is no longer the reference algorithm for the TypeScript renderer.
|
||||
Existing types in `pkg/model/client/` are not migrated; UI types
|
||||
are written from scratch in `ui/core/types/` as needed.
|
||||
- The TypeScript map renderer is specified in `ui/docs/renderer.md`,
|
||||
derived from the renderer's own requirements rather than from any
|
||||
earlier Go code. Tile-based spatial indexing is intentionally
|
||||
omitted in the first iteration; PixiJS native culling and
|
||||
bounds-based hit testing carry the renderer until profiling
|
||||
proves otherwise.
|
||||
- Game math that must stay synchronised between server and client lives
|
||||
in `pkg/calc/`. The UI client never duplicates calc functions; instead
|
||||
@@ -949,9 +952,9 @@ Targeted tests (delivered):
|
||||
invitation removes card and adds the game to My Games. Phase 7
|
||||
auth flow now also runs over the FlatBuffers wire.
|
||||
|
||||
## Phase 9. Map Renderer with Fixture Data
|
||||
## ~~Phase 9. Map Renderer with Fixture Data~~
|
||||
|
||||
Status: pending.
|
||||
Status: done.
|
||||
|
||||
Goal: stand up the PixiJS map renderer with pan/zoom, primitive
|
||||
drawing, torus wrap behaviour and bounded-plane (no-wrap) mode against
|
||||
@@ -961,49 +964,65 @@ a deferred nicety.
|
||||
|
||||
Artifacts:
|
||||
|
||||
- `ui/frontend/src/map/world.ts` data model (`Point2D`, `Primitive`,
|
||||
`Style`, theme bindings) with fixed-point coordinate handling
|
||||
- `ui/frontend/src/map/render.ts` PixiJS scene graph: background
|
||||
layer, primitive container, viewport pan/zoom, torus wrap copies,
|
||||
dual WebGPU/WebGL backend selection
|
||||
- `ui/frontend/src/map/hit-test.ts` PixiJS-native hit test wrapping
|
||||
`eventMode` and per-primitive hit slop
|
||||
- `ui/frontend/src/map/world.ts` data model (`Primitive` =
|
||||
`Point | Circle | Line`, `Style`, single-theme bindings) over plain
|
||||
float64 world coordinates; the renderer is a vector renderer and
|
||||
Pixi's transform pipeline owns the world→screen mapping
|
||||
- `ui/frontend/src/map/math.ts` geometry primitives:
|
||||
`torusShortestDelta`, `distSqPointToSegment`, `clamp`, and
|
||||
`screenToWorld`/`worldToScreen` round-trip transforms
|
||||
- `ui/frontend/src/map/render.ts` PixiJS v8 scene graph driven by
|
||||
`pixi-viewport@^6` for pan/zoom/pinch with WebGPU/WebGL backend
|
||||
selection via `Application.init({ preference })`; torus wrap is
|
||||
rendered through nine container copies at `(±W, 0) × (±H, 0)`
|
||||
- `ui/frontend/src/map/hit-test.ts` brute-force hit-test pass over
|
||||
the world primitives with `[-priority, distSq, kindOrder, id]`
|
||||
ordering and torus-shortest distance in `'torus'` mode
|
||||
- `ui/frontend/src/map/no-wrap.ts` camera clamp helpers
|
||||
(`CorrectCameraZoom`, `ClampCameraNoWrapViewport`,
|
||||
`ClampRenderParamsNoWrap`, `PivotZoomCameraNoWrap`) for bounded
|
||||
plane mode
|
||||
- `ui/frontend/src/routes/playground/+page.svelte` development page
|
||||
rendering a fixture world with a mode switch between torus and
|
||||
no-wrap for visual verification
|
||||
- topic doc `ui/docs/renderer.md` describing departures from the
|
||||
Go reference algorithm in `client/world/`, the rationale for
|
||||
skipping tile-based spatial indexing, and the no-wrap semantics
|
||||
(`clampCameraNoWrap`, `minScaleNoWrap`, `pivotZoom`) for bounded
|
||||
plane mode; `pixi-viewport`'s `clamp`/`clampZoom` plugins are
|
||||
used at the renderer level with a centring hook on `'moved'` so
|
||||
the viewport-larger-than-world case stays centred
|
||||
- `ui/frontend/src/map/fixtures.ts` deterministic 1000-primitive
|
||||
sample world used by the playground and by manual perf checks
|
||||
- `ui/frontend/src/routes/__debug/map/+page.svelte` development page
|
||||
rendering the fixture world with a mode switch between torus and
|
||||
no-wrap, plus a `window.__galaxyMap` debug surface for tests
|
||||
- topic doc `ui/docs/renderer.md` specifying the data model,
|
||||
hit-test math, torus copy rule, no-wrap camera semantics, and
|
||||
the deprecation status of `galaxy/client`
|
||||
|
||||
Dependencies: Phase 1.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- a 1000-primitive fixture world pans and zooms at 60 fps on a
|
||||
mid-range laptop with WebGPU and falls back cleanly to WebGL in
|
||||
both torus and no-wrap modes;
|
||||
- hit testing returns the same primitive as the reference Go algorithm
|
||||
on a shared set of fixture cursor positions, in both modes;
|
||||
- torus wrap renders all four corner copies correctly across the
|
||||
viewport edges;
|
||||
- a 1000-primitive fixture world pans and zooms on a mid-range
|
||||
laptop with WebGPU, falling back to WebGL when WebGPU is
|
||||
unavailable, in both torus and no-wrap modes; the 60 fps target
|
||||
is documented in `ui/docs/renderer.md` as a manual gate, not a
|
||||
CI assertion (CI runners vary too much in CPU/GPU);
|
||||
- hit testing returns the expected primitive on a hand-built
|
||||
fixture set covering wrap copies, line slop, ring vs filled
|
||||
circles, ordering, and zoom-dependent slop;
|
||||
- torus wrap renders all relevant corner copies correctly across
|
||||
the viewport edges;
|
||||
- no-wrap mode clamps the camera at world boundaries; pivot zoom
|
||||
keeps the world point under the cursor stable; viewport never
|
||||
becomes larger than the world.
|
||||
|
||||
Targeted tests:
|
||||
|
||||
- Vitest unit tests for fixed-point math, torus-shortest distance,
|
||||
no-wrap clamps, no-wrap pivot zoom invariants;
|
||||
- Vitest hit-test parity tests against fixtures derived from the Go
|
||||
reference, covering both torus and no-wrap fixtures;
|
||||
- Playwright visual smoke test of the playground page in
|
||||
`chromium-desktop` and `webkit-desktop`, exercising mode switch
|
||||
torus → no-wrap and back, and verifying camera clamp behaviour at
|
||||
bounded-plane edges.
|
||||
- Vitest unit tests for geometry primitives, torus-shortest
|
||||
distance, no-wrap clamps, pivot-zoom invariants
|
||||
(`tests/map-math.test.ts`, `tests/map-no-wrap.test.ts`);
|
||||
- Vitest hit-test cases for every rule in the algorithm spec
|
||||
(`tests/map-hit-test.test.ts`, ~22 cases);
|
||||
- Playwright visual smoke test of the playground page across all
|
||||
four configured projects (`chromium-desktop` forces WebGPU,
|
||||
`webkit-desktop` forces WebGL, mobile projects auto-pick),
|
||||
exercising mode switch torus → no-wrap and back, wheel zoom,
|
||||
no-wrap clamp after a drag past the edge, and live hit-test
|
||||
plumbing (`tests/e2e/playground-map.spec.ts`).
|
||||
|
||||
## Phase 10. In-Game Shell with View-Replacement Skeleton
|
||||
|
||||
|
||||
Reference in New Issue
Block a user