diff --git a/DECISIONS.md b/DECISIONS.md index 45dc514..923857a 100644 --- a/DECISIONS.md +++ b/DECISIONS.md @@ -453,3 +453,13 @@ Implementation decisions not spelled out in `memory-bank/implementation-plan.md` - **Two levels of network error:** if the first player-state load fails there is nothing to show, so the app shows an error with Retry. If a later refresh fails, the last state stays on screen under an "Offline — showing last update" banner with Retry, which serves every screen (Phase 11 builds on this for offline mode). Contract boards have their own error and Retry. - **Market and Locations have no empty state:** they list static content, which always exists. Their error handling is the app-level one. - **The placeholder screen is gone;** every route has a real screen. + +## Step 10.1 + +- **PixiJS v8 used directly** (an `Application` created in an effect), not `@pixi/react`: the map is one imperative canvas redrawn each frame from player state and the server clock, which needs no React reconciliation. The effect copes with StrictMode double-mounting. +- **Radial scale is a square root of the distance from the Sun,** normalised so 1,000M km is the map edge. Order is preserved, and Jupiter (~780M km) sits about 2.3× Earth's map radius instead of 5×. Directions from the Sun are unchanged. +- **The Moon is drawn a fixed small offset from Earth** in its true direction; at map scale it would sit on top of Earth. +- **All map logic is plain, testable functions** (projection, camera, scene; hit-testing in Step 10.3). jsdom has no WebGL, so component tests replace `MapCanvas` with a test double (`test/setup.ts`). +- **Camera state (zoom, pan) is a Zustand store**, per the tech stack; zoom is clamped to 0.5–40×, and "Reset view" restores it. +- **The Map screen is lazy-loaded,** so PixiJS (~285 KB) downloads only when the map is opened. +- **Seven tabs:** a Map tab is added after HQ. To fit 375 px, the Contracts tab is labelled "Jobs" and Market "Shop"; screen titles are unchanged. diff --git a/apps/web/package.json b/apps/web/package.json index 83f6451..642e29b 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -14,6 +14,7 @@ "@deadhead/schema": "workspace:*", "@deadhead/sim": "workspace:*", "@tanstack/react-query": "^5.104.1", + "pixi.js": "^8.22.0", "react": "^19.3.0", "react-dom": "^19.3.0", "react-router": "^8.4.0", diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index b82dc9e..89dc96d 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -1,4 +1,4 @@ -import { useEffect, type ReactNode } from "react"; +import { lazy, Suspense, useEffect, type ReactNode } from "react"; import { Route, Routes } from "react-router"; import { useApi } from "./api/context"; import { startClockTicker, syncClock } from "./clock/clock"; @@ -15,8 +15,16 @@ import { Pilots } from "./screens/Pilots"; import { StartScreen } from "./screens/StartScreen"; import { usePlayerState, useRefetchOnArrival } from "./state/player"; +// The map pulls in PixiJS; load it only when the Map tab is opened. +const MapScreen = lazy(() => import("./screens/MapScreen").then((m) => ({ default: m.MapScreen }))); + const SCREEN_COMPONENTS: Record<(typeof SCREENS)[number]["path"], ReactNode> = { "/": , + "/map": ( + Loading map…

}> + +
+ ), "/contracts": , "/fleet": , "/pilots": , diff --git a/apps/web/src/layout/Layout.tsx b/apps/web/src/layout/Layout.tsx index a21e1cc..1b3acc4 100644 --- a/apps/web/src/layout/Layout.tsx +++ b/apps/web/src/layout/Layout.tsx @@ -32,7 +32,7 @@ export function Layout({ credits = "—", clock = , banner }: Layou