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.
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, nonet/url, no gRPC client. - No storage. No
os(outside_test.gofixtures), 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 ingateway/authn. - No goroutines, no channels, no
syncprimitives in production files. Pure functions, deterministic outputs. - No re-export of
crypto/ed25519types in the public API. Callers see opaque[]byteblobs andstringrepresentations so the WASM bridge can hand them across the JS boundary asUint8Arrayand string primitives. - Randomness is injected.
keypair.Generate(reader io.Reader)takes a caller-supplied reader. Production code passescrypto/rand.Reader; tests use deterministicbytes.NewReader; WASM later passes acrypto.getRandomValuesadapter.
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 fored25519.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) float64EmptyMass(drive, weapons float64, armament uint, shields, cargo float64) (float64, bool)WeaponsBlockMass(weapons float64, armament uint) (float64, bool)FullMass(emptyMass, carryingMass float64) float64Speed(driveEffective, fullMass float64) float64CargoCapacity(cargo, cargoTech float64) float64CarryingMass(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 ingateway/proto/galaxy/gateway/v1/. Each exposes aSigningFields()method to project onto the correspondingcanon.*SigningFields.ProtocolVersionV1 = "v1",ResultCodeOK = "ok"— the only result string that is part of the stable client contract; any otherresult_codeis 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-webon 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 minfreshness checks remain ingateway/internal/grpcapi/freshness_replay.go. The client is expected to stamp its owntimestamp_msaccurately viatime.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 — authoritative byte contract.../../gateway/authn— server mirror of the same canonical bytes.../PLAN.mdPhase 3 — the staged plan that describes how this module fits into the wider client.