Files
galaxy-game/ui/core/README.md
T
Ilia Denisov e4dc0ce029 ui/phase-18: ship-class calc bridge with live designer preview
Wires pkg/calc/ship.go into the WASM Core boundary as seven thin
wrappers (DriveEffective, EmptyMass, WeaponsBlockMass, FullMass,
Speed, CargoCapacity, CarryingMass). The ship-class designer reads
Core through a new CORE_CONTEXT_KEY populated by the in-game layout
and renders a five-row preview pane (mass, full-load mass, max
speed, range at full load, cargo capacity) that updates reactively
on every form edit and on the player's localPlayer{Drive,Weapons,
Shields,Cargo} tech levels — three of which are now decoded from
the report's Player block alongside the existing localPlayerDrive.

CarryingMass is the seventh wrapper added to the original six-function
list so that "full-load mass" composes through pkg/calc/ functions
without putting math in TypeScript.
2026-05-09 23:14:40 +02:00

166 lines
7.1 KiB
Markdown

# ui/core — Galaxy Client Compute Module
`ui/core` (Go module `galaxy/core`) is the compute boundary of the
Galaxy cross-platform UI client. It carries v1 transport-envelope
canonical bytes, signature verification, and Ed25519 keypair
helpers. Network I/O and persistent storage live elsewhere on
purpose: this module compiles unchanged to WASM (Phase 5),
gomobile (Phase 32), and Wails-embedded native (Phase 31).
The authoritative byte contract is defined in
[`docs/ARCHITECTURE.md` §15](../../docs/ARCHITECTURE.md). The gateway
mirrors this exact wire format in its own
[`gateway/authn`](../../gateway/authn) package; cross-module byte
parity and round-trip sign/verify are exercised by
[`gateway/authn/parity_with_ui_core_test.go`](../../gateway/authn/parity_with_ui_core_test.go).
## Invariants
- **No network.** No `net/http`, no `net/url`, no gRPC client.
- **No storage.** No `os` (outside `_test.go` fixtures), no SQL, no
filesystem, no keychain.
- **TinyGo-friendly.** Production files do not import
`crypto/x509`, `encoding/pem`, or any package not supported by
the WASM target. PKCS#8 PEM is server-only and stays in
`gateway/authn`.
- **No goroutines, no channels, no `sync` primitives** in
production files. Pure functions, deterministic outputs.
- **No re-export of `crypto/ed25519` types** in the public API.
Callers see opaque `[]byte` blobs and `string` representations
so the WASM bridge can hand them across the JS boundary as
`Uint8Array` and string primitives.
- **Randomness is injected.** `keypair.Generate(reader io.Reader)`
takes a caller-supplied reader. Production code passes
`crypto/rand.Reader`; tests use deterministic `bytes.NewReader`;
WASM later passes a `crypto.getRandomValues` adapter.
## Layout
```text
ui/core/
├── go.mod module galaxy/core (Go 1.26.0)
├── calc/ ship-math wrappers over `pkg/calc/ship.go`
│ └── ship.go Phase 18 designer preview bridge
├── canon/ canonical-bytes builders and verifiers
│ ├── canon.go length-prefix helpers
│ ├── request.go galaxy-request-v1 fields and signing input
│ ├── response.go galaxy-response-v1 fields and verifier
│ ├── event.go galaxy-event-v1 fields and verifier
│ ├── signature.go base64 client-key request verification
│ └── testdata/ committed JSON golden vectors
├── keypair/ Ed25519 generate / sign / verify / marshal
└── types/ full transport envelopes + result codes
```
## Public API
### `galaxy/core/canon`
- `RequestDomainMarkerV1`, `ResponseDomainMarkerV1`, `EventDomainMarkerV1`
— UTF-8 domain prefixes that bind a signature to a specific
envelope kind.
- `RequestSigningFields`, `ResponseSigningFields`, `EventSigningFields`
— exact subsets of envelope fields covered by the v1 signature.
- `BuildRequestSigningInput`, `BuildResponseSigningInput`,
`BuildEventSigningInput` — produce canonical bytes ready for
`ed25519.Sign` / `ed25519.Verify`.
- `VerifyRequestSignature(clientPublicKey string, signature []byte,
fields RequestSigningFields) error` — accepts the base64
string form the backend stores in the device session.
- `VerifyResponseSignature(publicKey ed25519.PublicKey, signature []byte,
fields ResponseSigningFields) error`,
`VerifyEventSignature(publicKey ed25519.PublicKey, signature []byte,
fields EventSigningFields) error` — used by the client to
validate server output.
- `VerifyPayloadHash(payloadBytes, payloadHash []byte) error`.
- Sentinel errors: `ErrInvalidPayloadHash`, `ErrPayloadHashMismatch`,
`ErrInvalidClientPublicKey`, `ErrInvalidRequestSignature`,
`ErrInvalidResponseSignature`, `ErrInvalidEventSignature`.
### `galaxy/core/keypair`
- `Generate(reader io.Reader) (privateKey, publicKey []byte, err error)`.
- `Sign(privateKey, message []byte) ([]byte, error)` — returns a 64-byte
raw Ed25519 signature.
- `Verify(publicKey, message, signature []byte) bool`.
- `MarshalPublicKey(publicKey []byte) (string, error)` — base64
StdEncoding, the wire format documented in §15.
- `UnmarshalPublicKey(value string) ([]byte, error)`.
- `PublicKeyFromPrivate(privateKey []byte) ([]byte, error)`.
- Sentinel errors: `ErrInvalidPrivateKey`, `ErrInvalidPublicKey`,
`ErrInvalidPublicKeyEncoding`.
### `galaxy/core/calc`
Thin Go bridge over `pkg/calc/ship.go`, surfaced via WASM to the
Phase 18 ship-class designer preview. Each function is a one-line
passthrough — no math lives here.
- `DriveEffective(drive, driveTech float64) float64`
- `EmptyMass(drive, weapons float64, armament uint, shields, cargo float64) (float64, bool)`
- `WeaponsBlockMass(weapons float64, armament uint) (float64, bool)`
- `FullMass(emptyMass, carryingMass float64) float64`
- `Speed(driveEffective, fullMass float64) float64`
- `CargoCapacity(cargo, cargoTech float64) float64`
- `CarryingMass(load, cargoTech float64) float64`
The full audit trail (which UI feature uses what, what is still
deferred) lives in [`ui/docs/calc-bridge.md`](../docs/calc-bridge.md).
### `galaxy/core/types`
- `RequestEnvelope`, `ResponseEnvelope`, `EventEnvelope` — full Go
envelope structs mirroring the protobuf messages in
`gateway/proto/galaxy/gateway/v1/`. Each exposes a
`SigningFields()` method to project onto the corresponding
`canon.*SigningFields`.
- `ProtocolVersionV1 = "v1"`, `ResultCodeOK = "ok"` — the only
result string that is part of the stable client contract; any
other `result_code` is downstream-opaque and must not be
hard-coded by clients.
## Testing
```sh
go test -count=1 ./ui/core/...
```
The `canon` test suite combines:
- byte-equality on golden JSON fixtures under
`canon/testdata/` for three request types
(`user.account.get`, `lobby.my.games.list`,
`user.games.command`), one response (`ok`), and one event
(`gateway.server_time`);
- mutation tests proving every signed field is bound into the
signature;
- round-trip sign-then-verify across all three envelope kinds;
- negative tests for tampered hashes, mismatched timestamps and
request IDs, invalid signature lengths, and bad public-key
encodings.
Cross-module parity (gateway accepts ui/core signatures and vice
versa) is enforced from
`gateway/authn/parity_with_ui_core_test.go`.
## What this module is **not**
- Not a network client. ConnectRPC over `@connectrpc/connect-web`
on the TypeScript side is the only network surface (Phase 5+).
- Not a key store. Per-platform secure storage lives in Phase 6.
- Not a freshness gate. Server-side `±5 min` freshness checks
remain in `gateway/internal/grpcapi/freshness_replay.go`. The
client is expected to stamp its own `timestamp_ms` accurately
via `time.Now`, but does not enforce a window.
- Not a FlatBuffers codec — that lands in a later phase, so the
module today is small on purpose.
## Cross-references
- [`../../docs/ARCHITECTURE.md` §15](../../docs/ARCHITECTURE.md) —
authoritative byte contract.
- [`../../gateway/authn`](../../gateway/authn) — server mirror of
the same canonical bytes.
- [`../PLAN.md`](../PLAN.md) Phase 3 — the staged plan that
describes how this module fits into the wider client.