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

7.1 KiB

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. The gateway mirrors this exact wire format in its own gateway/authn package; cross-module byte parity and round-trip sign/verify are exercised by 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

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.

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

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