Files
galaxy-game/ui/docs/calc-bridge.md
T
Ilia Denisov 7c8b5aeb23 ui/phase-16: cargo routes inspector + map pick foundation
Add per-planet cargo routes (COL/CAP/MAT/EMP) to the inspector with
a renderer-driven destination picker (faded out-of-reach planets,
cursor-line anchor, hover-highlight) and per-route arrows on the
map. The pick-mode primitives are exposed via `MapPickService` so
ship-group dispatch in Phase 19/20 can reuse the same surface.

Pass A — generic map foundation:
- hit-test now sizes the click zone to `pointRadiusPx + slopPx` so
  the visible disc is always part of the target.
- `RendererHandle` gains `onPointerMove`, `onHoverChange`,
  `setPickMode`, `getPickState`, `getPrimitiveAlpha`,
  `setExtraPrimitives`, `getPrimitives`. The click dispatcher is
  centralised: pick-mode swallows clicks atomically so the standard
  selection consumers do not race against teardown.
- `MapPickService` (`lib/map-pick.svelte.ts`) wraps the renderer
  contract in a promise-shaped `pick(...)`. The in-game shell
  layout owns the service so sidebar and bottom-sheet inspectors
  see the same instance.
- Debug-surface registry exposes `getMapPrimitives`,
  `getMapPickState`, `getMapCamera` to e2e specs without spawning a
  separate debug page after navigation.

Pass B — cargo-route feature:
- `CargoLoadType`, `setCargoRoute`, `removeCargoRoute` typed
  variants with `(source, loadType)` collapse rule on the order
  draft; round-trip through the FBS encoder/decoder.
- `GameReport` decodes `routes` and the local player's drive tech
  for the inline reach formula (40 × drive). `applyOrderOverlay`
  upserts/drops route entries for valid/submitting/applied
  commands.
- `lib/inspectors/planet/cargo-routes.svelte` renders the
  four-slot section. `Add` / `Edit` call `MapPickService.pick`,
  `Remove` emits `removeCargoRoute`.
- `map/cargo-routes.ts` builds shaft + arrowhead primitives per
  cargo type; the map view pushes them through
  `setExtraPrimitives` so the renderer never re-inits Pixi on
  route mutations (Pixi 8 doesn't support that on a reused
  canvas).

Docs:
- `docs/cargo-routes-ux.md` covers engine semantics + UI map.
- `docs/renderer.md` documents pick mode and the debug surface.
- `docs/calc-bridge.md` records the Phase 16 reach waiver.
- `PLAN.md` rewrites Phase 16 to reflect the foundation + feature
  split and the decisions baked in (map-driven picker, inline
  reach, optimistic overlay via `setExtraPrimitives`).

Tests:
- `tests/map-pick-mode.test.ts` — pure overlay-spec helper.
- `tests/map-cargo-routes.test.ts` — `buildCargoRouteLines`.
- `tests/inspector-planet-cargo-routes.test.ts` — slot rendering,
  picker invocation, collapse, cancel, remove.
- Extensions to `order-draft`, `submit`, `order-load`,
  `order-overlay`, `state-binding`, `inspector-planet`,
  `inspector-overlay`, `game-shell-sidebar`, `game-shell-header`.
- `tests/e2e/cargo-routes.spec.ts` — Playwright happy path: add
  COL, add CAP, remove COL, asserting both the inspector and the
  arrow count via `__galaxyDebug.getMapPrimitives()`.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 20:01:34 +02:00

6.6 KiB
Raw Blame History

Calc bridge

The Galaxy frontend renders predictive numbers (free production potential, forecast output for a chosen production type, ship build progress, tech progress) that depend on the same formulas the engine uses at turn cutoff. To keep one source of truth, those formulas live in Go under pkg/calc/ and are surfaced to the UI through a planned Go → WASM → TypeScript bridge mounted under ui/core/calc/ and a matching TS adapter in ui/frontend/src/.

The bridge does not exist yet. This document is the audit trail for what it must expose, what is already in place, and what is missing.

Current pkg/calc/ exports

Function Purpose
ShipProductionCost(shipEmptyMass float64) float64 Production units required per unit of ship empty mass (×10).
PlanetProduceShipMass(L, Mat, Res float64) float64 Ship mass produced per turn given free production L, material stockpile Mat, resources Res.
DriveEffective, Speed, EmptyMass, FullMass, … Ship-level derivations (pkg/calc/ship.go).
ValidateShipTypeValues, CheckShipTypeValueDWSC Ship-design validators (pkg/calc/validator.go).

Nothing else lives in pkg/calc/ today. Production-side formulas (industry / materials / per-tech research / production capacity) sit in game/internal/model/game/planet.go and …/science.go and have never been exported.

Required calc functions per UI feature

The table below tracks what UI features need from the bridge and whether the underlying Go function exists.

UI feature Go formula In pkg/calc/? Surfaced to TS?
Free production potential (freeIndustry) Planet.ProductionCapacityindustry*0.75 + population*0.25 (game/internal/model/game/planet.go) no no
Industry production output per turn Planet.ProduceIndustry(freeProduction) (planet.go); freeProduction/5 modulo material constraint no no
Materials production output per turn Planet.ProduceMaterial(freeProduction) (planet.go); freeProduction * resources no no
Per-tech research progress (DRIVE/WEAPONS/…) ResearchTech (game/internal/model/game/science.go); freeProduction / 5000 per tech level no no
Custom-science progress weighted form of ResearchTech driven by Race.Sciences[].(Drive|Weapons|Shields|Cargo) (science.go) no no
Ship build progress PlanetProduceShipMass(L, Mat, Res) / ShipProductionCost(class.EmptyMass) (combination of two existing exports) partial no

partial means the Go primitives exist in pkg/calc/ but the composition (and the conversion of TS-side ReportPlanet/ ShipClass to the formula inputs) is not implemented anywhere.

Phase 15 waiver

Phase 15 ships the inspector's planet production controls (segmented control + sub-pickers + collapse-by-planetNumber order command) but deliberately does not surface the per-type forecast number. The planning gate explicitly raised the gap as a blocker per the plan's audit clause ("if any are missing in pkg/calc/, raise as blocker") and the project owner approved deferring the forecast to a dedicated future bridge phase. The inspector still renders the existing freeIndustry row (free production potential) — that number is computed engine-side and ships in the report payload, so no calc-bridge access is required for it today.

Acceptance criterion 3 of Phase 15 ("forecast output number reflects the chosen production type and matches pkg/calc/ outputs") is therefore intentionally not satisfied; the rewritten Phase 15 stage text records this decision and points back at this document.

Phase 16 waiver

Phase 16 introduces ship-reach filtering for the cargo-route destination picker. The engine formula is trivial:

flightDistance = driveTech * 40

(game/internal/model/game/race.go.FlightDistance). The original Phase 16 stage text described surfacing this through pkg/calc/ and ui/core/calc/; with the calc-bridge phase still deferred, implementing the bridge for one constant-time multiplication would be premature scaffolding. The picker therefore computes reach inline in TypeScript using torusShortestDelta(planet.x, candidate.x, mapWidth) and Math.hypot against 40 * report.localPlayerDrive, where localPlayerDrive is decoded from the report's Player block by matching Player.name to report.race (api/game-state.ts.findLocalPlayerDrive).

When the calc-bridge phase ships, the inline formula is replaced with a single call into the bridge: calc.Reach(driveTech) becomes the source of truth for both the picker and the cargo-route arrow auto-removal at turn cutoff. Until then, the UI duplicates flightDistance knowingly — same precedent as the production forecast deferral above.

Planned bridge shape (follow-up phase)

When the bridge phase lands, the contract should be:

  1. Promote every formula in the table above into pkg/calc/ so the engine and the UI share one Go-side implementation. The engine continues to call them through game/internal/... wrappers.
  2. Mount a ui/core/calc/ Go module that re-exports the subset the UI needs. Keep it WASM-friendly (no unsafe, no goroutines, simple in/out values).
  3. Wire the WASM glue in ui/wasm/main.go so each calc function is reachable from globalThis.galaxyCore.
  4. Add a TypeScript adapter under ui/frontend/src/platform/core/ that wraps the WASM calls in typed helpers (forecastIndustry(freeProduction, …) etc.).
  5. Update this document with the live function inventory and delete the "missing" rows above.