window.GDD = window.GDD || {}; function ArchitecturePage() { return (

Architecture Overview

Key principle: SpacetimeDB owns authoritative game state. Everything else derives from it. There is no localStorage — persistence is always through SpacetimeDB, even in the single-player Era 1 where SpacetimeDB runs locally.

{/* Architecture diagram */}
Layer Architecture
{[ { name: 'React UI', color: 'var(--cyan)', desc: 'Inventory, market, chat, station screens, ship status, debug panel. Owns UI layout and user workflow.' }, { name: 'Local Stores', color: 'var(--green)', desc: 'Selected entity, active panels, camera preferences, filters, sorting. Zustand/React state.' }, { name: 'Renderer Adapter', color: 'var(--purple)', desc: 'Receives view models and emits events: select, move, mine, dock. Boundary that keeps R3F replaceable.' }, { name: 'R3F Scene', color: 'var(--accent)', desc: 'Ships, stations, asteroids, anomalies, fauna, camera, targeting lines, world event effects. Visual layer only.' }, { name: 'Ship AI (Zora)', color: 'var(--purple)', desc: 'Companion AI system with soul state, module gates, and autonomous agent behavior. See the Ship AI page for full design.' }, { name: 'SpacetimeDB SDK', color: 'var(--cyan)', desc: 'Reducer calls and subscriptions. Client bridge to backend.' }, { name: 'SpacetimeDB Module', color: 'var(--red)', desc: 'Tables, reducers, validation, persistence, authoritative game state. Source of truth.' }, ].map((layer, i) => (
{layer.name} {layer.desc}
))}
ARCH-1

Client Architecture

React UI Responsibilities

R3F Responsibilities

ARCH-2

State Management Model

State TypeLives WhereExamples
Authoritative SpacetimeDB tables/subscriptions Ship position, inventory, market orders, asteroid resources.
Derived view Client game store/view models Selected ship details, rendered position, distance to target.
Local UI Zustand/React state Open panels, sort order, camera zoom, modal visibility.
Transient visual Renderer internals Hover glow, particle lifetime, temporary path line.
ARCH-3

Renderer Replaceability

Key principle: Use React Three Fiber for speed now, but prevent it from becoming the game architecture. The renderer consumes view models and emits input events.
// Renderer interface — implementation-agnostic
type GameRendererInput = {
  ships: ShipViewModel[];
  stations: StationViewModel[];
  asteroids: AsteroidViewModel[];
  anomalies: AnomalyViewModel[];
  worldEvents: WorldEventViewModel[];
  selectedEntityId: string | null;
};

type GameRendererEvents = {
  onSelectEntity(entityId: string): void;
  onMoveCommand(position: Vec3): void;
  onMineCommand(asteroidId: string): void;
  onDockCommand(stationId: string): void;
};

Keep renderer-specific

  • Meshes, materials, lights, particles
  • Camera controls
  • Raycasting and pointer interactions
  • Visual interpolation implementation

Keep renderer-independent

  • Game types, reducers, subscriptions
  • Domain rules: mining, docking, selling
  • Inventory logic and market validation
  • Shared movement math and view models
{/* ═══ RECONNECTION & ERROR HANDLING ═══ */}
ARCH-4

Error Handling & Reconnection

Players should never lose progress due to a disconnect. SpacetimeDB is the source of truth and persists all authoritative state server-side. A disconnected player's ship continues to exist in the world, subject to the same rules as every other ship. Reconnection restores the player to their last authoritative state — nothing is lost.

Disconnection Scenarios

{[ { scene: 'Idle in space', server: 'Ship remains in world. Continues orbit or holds position. No auto-actions.', reconnect: 'Full state restore. Ship where it was. No losses.', impact: 'None. Seamless.' }, { scene: 'Mid-mining cycle', server: 'Active mining action continues to completion. Ore deposited to cargo.', reconnect: 'Mining cycle may have completed. Ore in cargo as expected. Partial cycles yield partial ore.', impact: 'Minimal. At worst, lost time on partial cycle.' }, { scene: 'Mid-combat (PvE)', server: 'Ship continues with last known power allocation. Auto-defends with passive tank. If destroyed, insurance applies.', reconnect: 'If ship survived: full combat restore with current HP. If destroyed: respawn at home station, insurance payout queued.', impact: 'Ship may be destroyed. Insurance covers hull. Standard death penalty applies — this is the risk of disconnecting in danger.' }, { scene: 'Mid-warp', server: 'Warp completes normally. Ship arrives at destination.', reconnect: 'Ship at destination. No interruption to warp.', impact: 'None.' }, { scene: 'Docked at station', server: 'No risk. Station is safe. Player remains docked.', reconnect: 'Restored to station. All panels and inventory intact.', impact: 'None.' }, { scene: 'Mid-market order', server: 'If reducer was received, order is placed. If not, no order.', reconnect: 'Check market panel for order state. ISK and inventory reflect server truth.', impact: 'At worst, order was not placed. No double-spend possible (atomic reducers).' }, { scene: 'Mid-combat (PvP)', server: 'Ship continues with last power allocation. Enemy continues attacking.', reconnect: 'Same as PvE: ship may be destroyed. No special protection for PvP disconnect.', impact: 'Ship may be destroyed. Disconnecting during PvP carries the same risk as staying.' }, ].map((row, i) => ( ))}
ScenarioServer BehaviorOn ReconnectPlayer Impact
{row.scene} {row.server} {row.reconnect} {row.impact}

Reconnection Flow

1. Detect disconnect — WebSocket close event or heartbeat timeout (10s no response)
2. Show reconnect banner — "Connection lost. Reconnecting..." with spinning indicator. UI freezes input but continues rendering last known state.
3. Auto-retry — Exponential backoff: 1s, 2s, 4s, 8s, max 30s. Up to 10 attempts over ~5 minutes.
4. Re-establish subscription — On reconnect, re-subscribe to all relevant SpacetimeDB tables. Server sends full state diff.
5. State reconciliation — Client merges server state into local state. Visual positions snap to authoritative positions. Inventory, market, chat all refresh.
6. Resume gameplay — Banner disappears. All inputs re-enabled. Player is back in the game.

Failed Reconnection

If all 10 attempts fail (5+ minutes of no connection):

{'Era 1 (local SpacetimeDB):'} {' This should never happen. If it does, it\'s a bug. Show error screen with "restart game" button. Local SpacetimeDB state is intact.'}

{'Era 2 (remote SpacetimeDB):'} {' Show "Connection lost" screen with two options: [Retry Now] (immediate reconnect attempt) and [Return to Login]. Ship persists on server. No data lost.'}

{'The ship never vanishes on disconnect. It stays in the world, obeying server rules. This is intentional \u2014 disconnecting to escape PvP is not allowed.'}

Anti-exploit: combat disconnect. Players who disconnect during PvP combat receive no special protection. Their ship remains in the world, continuing to fight with last-known power allocation. If destroyed, normal death penalties apply (loot drop, insurance). This prevents "combat logging" as an escape mechanism. Zora may send a message on reconnect: "I sustained damage while you were away. Shields at 40%. The enemy disengaged."
{/* ═══ SESSION PERSISTENCE ═══ */}
ARCH-5

Session Persistence & Save/Load

There is no save button and no manual save/load. SpacetimeDB persists all authoritative state continuously — every reducer call, every tick update, every position change is written to the database as it happens. "Saving" is not a player action; it is the natural consequence of playing the game. Closing the browser and returning tomorrow restores the player to exactly where they left off.

What Gets Persisted

{[ { cat: 'Player Identity', tables: 'players, player_skills, player_standing, player_loyalty_points', guarantee: 'Permanent. Never lost. Bound to SpacetimeDB identity.' }, { cat: 'Ship State', tables: 'ships, ship_fittings, ship_ai_soul, ship_ai_modules, ship_ai_memory, ship_ai_directives', guarantee: 'Permanent. Ship position, fitting, AI state all persist. If destroyed, destroyed state persists until replaced.' }, { cat: 'Economy', tables: 'inventory_items, market_orders, manufacturing_jobs, insurance_policies', guarantee: 'Permanent. Items, orders, and jobs survive restart. In-progress jobs continue server-side during offline time.' }, { cat: 'Navigation', tables: 'bookmarks, waypoints', guarantee: 'Permanent. Saved locations and routes survive indefinitely.' }, { cat: 'Social', tables: 'chat_messages (recent), bounties, kill_feed', guarantee: 'Messages expire after 30 days. Bounties persist until collected or decayed. Kill feed is permanent (story log).' }, { cat: 'World State', tables: 'world_events, faction_relations, galaxy_story_log, anomalies, space_fauna', guarantee: 'Server-owned. Continues evolving while player is offline. Player returns to a changed galaxy.' }, ].map((row, i) => ( ))}
CategoryTablesPersistence Guarantee
{row.cat} {row.tables} {row.guarantee}

Offline Progression

The server continues simulating the galaxy while the player is offline. Faction borders shift, world events spawn and resolve, the economy adjusts. Manufacturing jobs placed before logging off complete on schedule. Market orders can be filled while the player is away.

"Come back tomorrow" is always a valid answer — your manufacturing job finishes whether you watch it or not.

What Does NOT Persist

  • UI layout: Panel positions, sorting preferences, open tabs. These reset on reload. (Future: saved layout profiles.)
  • Camera state: Zoom level, orbit angle. Reset to default on reconnection.
  • In-progress inputs: Half-typed chat messages, unfinalized market orders. Lost on disconnect.
  • Transient effects: Active weapon animations, explosion particles, HUD flash effects. Visual only — not gameplay state.
Era 1 vs Era 2 persistence: Both eras use SpacetimeDB exclusively — there is no localStorage. In Era 1, SpacetimeDB runs locally on the player's machine. "Persistence" means the local database file survives browser restart. In Era 2, SpacetimeDB runs on a server. Persistence is permanent and shared. The only difference is where the database process runs; the persistence model is identical.
{/* ═══ SOUND & AUDIO ═══ */}
ARCH-6

Sound & Audio Design

Audio reinforces the spreadsheet. This game is not a flight sim and audio should not pretend it is. Sound design serves three purposes: (1) information delivery — alerts, status changes, notifications; (2) atmosphere — ambient space sounds that make the galaxy feel alive; (3) feedback — clicks, confirmations, and economic sounds that make spreadsheet actions feel satisfying. Audio is always optional — the game is fully playable with sound muted, but richer with it.

Audio Categories

{[ { cat: 'Alerts', purpose: 'Critical state changes that demand attention', examples: 'Red Alert klaxon (shields <25%), target lock acquired, CONCORD warning, incoming damage alarm, disconnect sound', priority: 'Critical — always audible, respects master volume only' }, { cat: 'UI Feedback', purpose: 'Confirm player actions in panels', examples: 'Order placed (cash register ding), module fitted (mechanical click), skill leveled up (ascending tone), ISK received (soft chime), insurance purchased (stamp sound)', priority: 'High — respects UI volume slider' }, { cat: 'Ambient', purpose: 'Atmosphere and spatial awareness', examples: 'Station hum (docked), solar wind (in space), mining laser drone, market chatter (station background), warp tunnel whoosh', priority: 'Medium — respects ambient volume slider' }, { cat: 'Combat', purpose: 'Combat state feedback', examples: 'Weapon firing (per type: beam hum, bolt crack, missile launch), shield hit (energy crackle), armor hit (metallic impact), hull hit (structural groan), capacitor warning (low power hum), weapon offline (power-down whine)', priority: 'High — respects combat volume slider' }, { cat: 'World Events', purpose: 'Environment storytelling', examples: 'Anomaly detection ping, faction broadcast (radio static + voice), fauna migration rumble, explosion (distant), wormhole opening', priority: 'Medium — respects world volume slider' }, { cat: 'Zora Voice', purpose: 'Ship AI spoken responses', examples: 'Status reports, combat warnings, market tips, tutorial hints. Voice synthesis via Voice Synthesizer module. See Ship AI → Modules.', priority: 'High — respects voice volume slider. Can be disabled entirely.' }, ].map((row, i) => ( ))}
CategoryPurposeExamplesPriority
{row.cat} {row.purpose} {row.examples} {row.priority}

Volume Controls

  • Master: 0–100%. Controls all audio. Default 80%.
  • UI: Panel sounds, market dings, fitting clicks. Default 70%.
  • Combat: Weapons, impacts, Red Alert. Default 80%.
  • Ambient: Background atmosphere. Default 50%.
  • World: Event sounds, fauna, anomalies. Default 60%.
  • Voice: Zora and NPC dialogue. Default 90%. Has separate mute toggle.

Spatial Audio Rules

  • Combat sounds are directional — weapon fire comes from the direction of the source
  • Distant events are muffled (low-pass filter scales with distance)
  • Station ambient sounds only play when docked (cross-fade on dock/undock)
  • Warp tunnel audio fades in during acceleration, peaks at cruise, fades out on deceleration
  • Zora's voice always comes from "center" — she's inside your head, not in space
  • Audio never provides exclusive gameplay information — everything audible has a visual equivalent
Implementation note: Audio is Phase 7 scope (Single-Player Polish). Phase 0–6 can ship with placeholder sounds or no sounds at all. The audio system should be built on the Web Audio API with a thin abstraction layer that maps game events to sound triggers. Sound assets can be procedurally generated or placeholder bleeps until a proper sound design pass is done.
{/* ═══ LOCALIZATION ═══ */}
ARCH-7

Localization & Internationalization

MVP is English-only. This section documents the decision and the architecture that makes future localization non-breaking. Adding languages post-launch should require translators and asset work, not code changes. All user-facing strings flow through a lookup layer from day one — even if that layer only returns English.

What Ships in English Only (MVP)

  • All UI labels, button text, and panel headers
  • Tutorial mission dialogue
  • Zora personality templates (Tier 0)
  • NPC agent dialogue
  • Error messages and status notifications
  • Item, module, and ship names

i18n Architecture (Day-One Foundation)

  • String keys: All user-facing text uses lookup keys, not hardcoded strings. t("market.order.placed") not "Order placed"
  • Number formatting: ISK values, quantities, percentages use Intl.NumberFormat from day one
  • Date/time: Timestamps use Intl.DateTimeFormat. Relative time ("5 minutes ago") via Intl.RelativeTimeFormat
  • Pluralization: Use ICU message format for count-aware strings ("1 item" vs "5 items")
  • Layout: UI components use CSS flexbox/grid. No hardcoded pixel widths that assume English string lengths
  • RTL ready: CSS logical properties (start/end, not left/right) for future Arabic/Hebrew support
Not localized (by design): Player names, chat messages, corporation names, and galaxy story log entries are user-generated content that is never translated. Zora Tier 1+ (LLM-assisted) dialogue would need per-language prompting — that's a Tier 2 scope concern, not MVP.
Post-MVP language priority: The first languages after English would be determined by player population. The i18n architecture supports adding a new language by dropping in a translation file — no code changes. Estimated effort per language: 1–2 weeks for translation + 2–3 days for QA with RTL layout testing if applicable.
{/* ═══ ACCESSIBILITY ═══ */}
ARCH-8

Accessibility

A spreadsheet game should be the most accessible genre in the world. The core gameplay involves reading tables, managing numbers, and clicking buttons — activities that web browsers already excel at supporting. The following accessibility targets are baseline requirements, not nice-to-haves. Every feature listed here ships in Phase 7 (Single-Player Polish).

Accessibility Requirements

{[ { area: 'Color Blindness', req: 'All color-coded information must have a secondary indicator (pattern, icon, label, or shape)', impl: 'Shield (cyan) → label "SHD". Armor (yellow) → label "ARM". Hull (red) → label "HUL". Security levels use text labels + icons, not just color. Market price changes use ▲/▼ arrows alongside green/red.', phase: '7' }, { area: 'Keyboard Navigation', req: 'Every action reachable by keyboard. No mouse-only workflows.', impl: 'Tab order follows logical panel flow. Enter activates focused element. Arrow keys navigate table rows. Escape closes panels. Number keys for power allocation (1=weapons, 2=shields, 3=engines, 4=aux). F1-F8 for module activation.', phase: '7' }, { area: 'Screen Reader', req: 'All panels and data tables announce state changes. Live regions for combat updates.', impl: 'ARIA labels on all interactive elements. role="grid" on data tables with aria-rowcount. aria-live="polite" on ISK balance, cargo capacity, skill XP. aria-live="assertive" on combat damage and Red Alert. Screen reader announcements for market order fills.', phase: '7' }, { area: 'Text Scaling', req: 'UI remains usable at 200% browser zoom and with large font settings.', impl: 'All font sizes in rem. All layouts use CSS grid/flexbox with min/max sizing. Tables scroll horizontally rather than overflow. Panel widths are percentage-based, not fixed pixels.', phase: '7' }, { area: 'Reduced Motion', req: 'Respect prefers-reduced-motion. No animations that could cause discomfort.', impl: 'CSS media query: disable particle effects, smooth scrolling, and HUD animations. Red Alert uses static red border instead of pulsing. Power allocation bars snap instead of animating. Mining cycle uses progress bar, not spinning animation.', phase: '7' }, { area: 'Contrast', req: 'All text meets WCAG AA contrast ratio (4.5:1 for normal text, 3:1 for large text).', impl: 'Var(--fg) on var(--bg) already exceeds 7:1. Dim text (--fg-dim) checked to meet 4.5:1. Interactive elements have visible focus indicators with 3:1 contrast against adjacent colors. Red Alert border is high-contrast red (#ff0000) against dark background.', phase: '7' }, { area: 'Cognitive Load', req: 'Information density is manageable. Players can hide complexity.', impl: 'Collapsible panels. Summary view vs. detail view toggle. Zora provides guided assistance. Red Alert collapses non-essential HUD elements. Tutorial hints can be disabled. Settings persist per player.', phase: '7' }, { area: 'Input Timing', req: 'No time-critical inputs required for core gameplay. Combat is manageable at any APM.', impl: "Power allocation has no per-second requirements — the skill is in choosing distribution, not speed. Mining is click-and-wait. Market orders don't expire mid-interaction. Only exception: PvP combat in Era 2, which is inherently competitive.", phase: '7' }, ].map((row, i) => ( ))}
AreaRequirementImplementationPhase
{row.area} {row.req} {row.impl} {row.phase}
PvP accessibility note: PvP combat in Era 2 inherently involves time pressure — rerouting power, selecting targets, and reacting to damage. These cannot be fully de-timed without removing the competitive element. Players with motor impairments can (1) stay in high-sec where PvP is punished, (2) focus on PvE, industry, and market gameplay which are fully accessible, or (3) use fleet roles that require less real-time input (logistics, scouting). The game should never require PvP to progress.
Testing: Accessibility validation is part of Gate 4 (Era 1 Complete). The acceptance test is: (1) navigate all panels via keyboard only, (2) complete a mining-sell cycle with screen reader enabled, (3) verify all color-coded info has secondary indicators in grayscale. Automated: run axe-core or Lighthouse accessibility audit as part of CI.
); } window.GDD.ArchitecturePage = ArchitecturePage;