8a236bef14
The cargo-route picker filtered out unidentified planets, so an early-game player who had spotted but not surveyed a destination could not configure a route to it — the engine has no such restriction (`game/internal/controller/route.go.PlanetRouteSet` only checks ownership of the origin and `util.ShortDistance(...) <= FligthDistance`). Drop the unidentified guard and document the contract in `cargo-routes-ux.md` plus a comment over `reachableSet()`. Pick-mode dim now drops both alpha and tint on out-of-reach planets so bright shapes (`STYLE_LOCAL` is `0x6dd2ff`) collapse into a single muted gray. The single-channel `dimAlpha=0.3` was too gentle against the dark theme — the user reported the dim wasn't visible. Tighten to `dimAlpha=0.35 + dimTint=0x303841`; restore both on tear-down. Also threads through the user's `pkg/calc/race.go.FligthDistance` addition: `calc-bridge.md` records the new Go-side reference (the engine's `Race.FlightDistance()` already wraps it), and the picker comment points at the canonical formula location. Tests: - `inspector-planet-cargo-routes.test.ts` adds two cases — a reach-spans-every-kind case (own + foreign + uninhabited + unidentified all picked when in range) and a successful pick to an unidentified destination. - All 356 vitest cases + chromium-desktop / webkit-desktop e2e cargo-routes pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
120 lines
7.0 KiB
Markdown
120 lines
7.0 KiB
Markdown
# 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.ProductionCapacity` → `industry*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
|
||
```
|
||
|
||
The Go-side reference now lives in
|
||
[`pkg/calc/race.go`](../../pkg/calc/race.go) as
|
||
`FligthDistance(driveTech) float64` (alongside the matching
|
||
`VisibilityDistance` for in-space group reports — used in later
|
||
phases). The engine call sites
|
||
(`game/internal/model/game/race.go.FlightDistance`,
|
||
`game/internal/controller/route.go.PlanetRouteSet`) still wrap the
|
||
Go formula directly; promoting them to call `pkg/calc/` is a
|
||
follow-up cleanup outside Phase 16's scope.
|
||
|
||
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 WASM glue 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.FligthDistance(driveTech)`
|
||
becomes the source of truth for both the picker and the
|
||
cargo-route 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.
|