From 7c93b8a1aecdec09f8aac2b086063a9a49d2163d Mon Sep 17 00:00:00 2001 From: francy51 Date: Tue, 2 Jun 2026 17:32:15 -0400 Subject: [PATCH] Add game UI panels, keyboard shortcuts, docs narrative overhaul, and unified dev script - Add MiniStarMap, NpcMarketPanel, ShipStatusPanel, useKeyboardShortcuts - Add progress bars for approach/mining operations and cargo fill indicator - Rewrite docs from spreadsheet-first to exploration-first open-world RPG - Replace dev:db + dev:standalone with unified dev script (scripts/dev.sh) - Add Vite chunk splitting for three.js and spacetimedb - Fix displayName dependency in useSpacetimeConnection - Remove stale usePlayerSession.ts - Add AGENTS.md files across all packages --- .gitignore | 1 + AGENTS.md | 42 + README.md | 84 +- apps/AGENTS.md | 16 + apps/docs/AGENTS.md | 56 ++ apps/docs/src/App.tsx | 2 + apps/docs/src/data/nav.ts | 1 + apps/docs/src/pages/docs/EconomyPage.tsx | 34 +- apps/docs/src/pages/docs/GameplayPage.tsx | 32 +- apps/docs/src/pages/docs/OverviewPage.tsx | 151 +-- apps/docs/src/pages/docs/RoadmapPage.tsx | 93 +- apps/docs/src/pages/docs/ShipsPage.tsx | 8 +- apps/docs/src/pages/docs/TodoPage.tsx | 922 ++++++++++++++++++ apps/docs/src/prototypes/AGENTS.md | 21 + .../src/prototypes/existing-demos/AGENTS.md | 22 + apps/docs/src/prototypes/game-slice/AGENTS.md | 42 + apps/docs/src/prototypes/r3f/AGENTS.md | 14 + apps/game/AGENTS.md | 58 ++ apps/game/src/GameShell.tsx | 28 + apps/game/src/scene/AGENTS.md | 24 + apps/game/src/spacetime/AGENTS.md | 32 + apps/game/src/spacetime/usePlayerSession.ts | 58 -- .../src/spacetime/useSpacetimeConnection.ts | 9 +- apps/game/src/ui/AGENTS.md | 18 + apps/game/src/ui/CargoPanel.tsx | 12 +- apps/game/src/ui/CommandRail.tsx | 48 +- apps/game/src/ui/MiniStarMap.tsx | 149 +++ apps/game/src/ui/NpcMarketPanel.tsx | 110 +++ apps/game/src/ui/ShipStatusPanel.tsx | 130 +++ apps/game/src/ui/useKeyboardShortcuts.ts | 68 ++ apps/game/vite.config.ts | 10 + apps/site/AGENTS.md | 20 + archive/AGENTS.md | 18 + package.json | 5 +- packages/AGENTS.md | 9 + packages/ui/AGENTS.md | 31 + scripts/AGENTS.md | 11 + scripts/dev.sh | 61 ++ services/AGENTS.md | 12 + services/spacetimedb/AGENTS.md | 67 ++ 40 files changed, 2263 insertions(+), 266 deletions(-) create mode 100644 AGENTS.md create mode 100644 apps/AGENTS.md create mode 100644 apps/docs/AGENTS.md create mode 100644 apps/docs/src/pages/docs/TodoPage.tsx create mode 100644 apps/docs/src/prototypes/AGENTS.md create mode 100644 apps/docs/src/prototypes/existing-demos/AGENTS.md create mode 100644 apps/docs/src/prototypes/game-slice/AGENTS.md create mode 100644 apps/docs/src/prototypes/r3f/AGENTS.md create mode 100644 apps/game/AGENTS.md create mode 100644 apps/game/src/scene/AGENTS.md create mode 100644 apps/game/src/spacetime/AGENTS.md delete mode 100644 apps/game/src/spacetime/usePlayerSession.ts create mode 100644 apps/game/src/ui/AGENTS.md create mode 100644 apps/game/src/ui/MiniStarMap.tsx create mode 100644 apps/game/src/ui/NpcMarketPanel.tsx create mode 100644 apps/game/src/ui/ShipStatusPanel.tsx create mode 100644 apps/game/src/ui/useKeyboardShortcuts.ts create mode 100644 apps/site/AGENTS.md create mode 100644 archive/AGENTS.md create mode 100644 packages/AGENTS.md create mode 100644 packages/ui/AGENTS.md create mode 100644 scripts/AGENTS.md create mode 100755 scripts/dev.sh create mode 100644 services/AGENTS.md create mode 100644 services/spacetimedb/AGENTS.md diff --git a/.gitignore b/.gitignore index 1e5ffcb..d6dd2f3 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ spacetime.local.json vite.config.js vite.config.d.ts .playwright-mcp/ +.spacetime-dev/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..8eb421a --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,42 @@ +# VOID::NAV — Monorepo Root + +Open-world space exploration RPG. Windward Horizon in space with FTL-style combat. + +## Directory Map + +| Path | Purpose | +|------|---------| +| `apps/docs` | Living design docs, interactive demos, and vertical-slice prototype | +| `apps/game` | Playable game shell connected to SpacetimeDB | +| `apps/site` | Public landing page | +| `packages/ui` | Shared UI primitives and design tokens | +| `services/spacetimedb` | SpacetimeDB TypeScript backend module | +| `scripts/` | Dev tooling (dev.sh startup script) | +| `archive/` | Legacy pre-monorepo files kept for reference | +| `src/module_bindings/` | Empty; generated bindings go to `apps/game/src/module_bindings` | + +## Key Files + +- `package.json` — pnpm workspace scripts (`dev`, `build`, `check`, `generate:bindings`) +- `pnpm-workspace.yaml` — workspace glob patterns +- `spacetime.json` — SpacetimeDB module path and binding generation config +- `tsconfig.base.json` — Shared TypeScript config (ES2020, React JSX) + +## Commands + +```bash +pnpm dev # Full stack: SpacetimeDB + game (port 5175) +pnpm dev:docs # Docs only (port 5173) +pnpm dev:site # Site only (port 5174) +pnpm dev:game # Game frontend only (requires running SpacetimeDB) +pnpm build # Build all apps +pnpm check # Typecheck all packages +pnpm generate:bindings # Regenerate SpacetimeDB TypeScript bindings +``` + +## Conventions + +- SpacetimeDB is the persistence layer from day 1 — no localStorage for game state +- All UI uses Tailwind CSS v4 with custom design tokens from `packages/ui` +- 3D rendering uses React Three Fiber + Drei + Three.js +- Module bindings are auto-generated — never edit `module_bindings/` by hand diff --git a/README.md b/README.md index 3d68adb..d6bfb80 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,23 @@ # VOID::NAV -VOID::NAV is now a pnpm workspace monorepo for the game website, living design docs, browser game shell, and SpacetimeDB backend module. +**An open-world space exploration RPG** — trade, quest, fight, or simply drift and enjoy the ambiance. Inspired by the charm and freedom of Windward Horizon, set in a procedurally generated galaxy with FTL-style tactical combat. + +You are a young, unaffiliated captain with an entire galaxy open for exploration. Every campaign features a unique, procedurally generated sector map. Trade goods between stations, take on quests for various factions, mine asteroid belts, or simply cruise and enjoy the void. Prefer a quieter life? You can simply sail the stars. + +Combat is **FTL-style tactical resource management** — no dogfighting or direct piloting. Click a hostile, your ship auto-engages, and you manage reactor power between Weapons / Shields / Engines / Auxiliary. The skill is in *what* you power and *when*, not how fast you click. + +VOID::NAV is a pnpm workspace monorepo for the game website, living design docs, browser game shell, and SpacetimeDB backend module. + +## Core Pillars + +| Pillar | Description | +|--------|-------------| +| **Open-World Exploration** | Procedurally generated galaxy with unique sectors, anomalies, and hidden treasures every campaign | +| **Trade & Commerce** | Regional price differences, supply/demand, and trade routes between stations and factions | +| **FTL-Style Combat** | Tactical power management (not action). Auto-engage with reactor allocation decisions | +| **Ship Customization** | Build the ship that suits your style — tough juggernaut, nimble scout, or cargo hauler | +| **Dynamic World Impact** | Faction leaders, world events, and player actions shape the galaxy without firing a shot | +| **Solo or Co-op** | Brave the void alone or crew with friends. Built with co-op in mind. | ## Layout @@ -17,17 +34,30 @@ archive/legacy-static Legacy static prototype files kept for reference - Node.js 24+ - pnpm 9+ -- SpacetimeDB CLI for backend build, generation, and local DB runs +- SpacetimeDB CLI (`spacetime`) v2.3.0+ The SpacetimeDB CLI is not vendored. Frontend builds use the committed placeholder bindings in `apps/game/src/module_bindings`; run `pnpm generate:bindings` after installing the CLI to replace them with generated bindings. +## Quick Start + +```bash +pnpm install +pnpm dev +``` + +This single command starts a fresh SpacetimeDB instance (ephemeral, in-memory), publishes the module, generates bindings, and launches the game dev server. + +Open `http://localhost:5175` to play. + +Press `Ctrl+C` to stop everything. + ## Development ```bash -pnpm install -pnpm dev:docs -pnpm dev:site -pnpm dev:game +pnpm dev # Full stack: SpacetimeDB + game (port 5175) +pnpm dev:docs # Docs only (port 5173) +pnpm dev:site # Site only (port 5174) +pnpm dev:game # Game frontend only (requires running SpacetimeDB) ``` Default ports: @@ -35,50 +65,20 @@ Default ports: - docs: `http://localhost:5173/docs` - site: `http://localhost:5174` - game: `http://localhost:5175` - -The public site links to docs and game via: - -```text -VITE_DOCS_URL=http://localhost:5173/docs -VITE_GAME_URL=http://localhost:5175 -``` - -The game shell reads: - -```text -VITE_SPACETIME_URI=http://localhost:3000 -VITE_SPACETIME_DATABASE=void-nav-dev -``` +- SpacetimeDB: `http://127.0.0.1:3000` ## Builds ```bash -pnpm --filter @void-nav/docs build -pnpm --filter @void-nav/site build -pnpm --filter @void-nav/game build -pnpm build -pnpm check +pnpm build # Build all apps +pnpm check # Typecheck all packages +pnpm generate:bindings # Regenerate SpacetimeDB TypeScript bindings ``` ## SpacetimeDB Root `spacetime.json` points at `services/spacetimedb` and generates TypeScript bindings into `apps/game/src/module_bindings`. -With the CLI installed: +The `pnpm dev` script uses an isolated ephemeral data directory (`.spacetime-dev/`) so it starts clean every time. No stale database or auth issues. -```bash -spacetime build --module-path services/spacetimedb -pnpm generate:bindings -pnpm dev:db -``` - -`pnpm dev:db` targets the SpacetimeDB CLI server nickname `local` so development publishes to `http://127.0.0.1:3000` instead of any configured cloud default. - -Initial reducers: - -- `connectPlayer(displayName: string)` -- `renamePlayer(displayName: string)` -- `seedWorld()` -- `ping()` - -The game shell calls `connectPlayer` after connection, subscribes to starter shell rows, and displays connection, player, ship, system, station, and reducer status. When SpacetimeDB is unavailable or bindings are still placeholders, it shows a clear disconnected/error state. +Reducers: `connectPlayer`, `renamePlayer`, `seedWorld`, `ping`, `undock`, `selectTarget`, `startApproach`, `completeApproach`, `dock`, `startMining`, `completeMiningCycle`, `sellOreToNpcMarket` diff --git a/apps/AGENTS.md b/apps/AGENTS.md new file mode 100644 index 0000000..bec7e1d --- /dev/null +++ b/apps/AGENTS.md @@ -0,0 +1,16 @@ +# apps/ — Application Packages + +Three Vite + React apps, each independently buildable and dev-serveable. + +| App | Port | Package | Description | +|-----|------|---------|-------------| +| `docs` | 5173 | `@void-nav/docs` | Living design docs, 16 doc pages, 14 interactive demos, game-slice prototype | +| `game` | 5175 | `@void-nav/game` | Playable game client connected to SpacetimeDB backend | +| `site` | 5174 | `@void-nav/site` | Public landing page / marketing site | + +## Shared + +- All use Tailwind CSS v4 and `@void-nav/ui` for shared primitives +- All use Vite 7 as build tool +- All use React 18 with TypeScript 5.8 +- `docs` and `game` also use Three.js + React Three Fiber for 3D scenes diff --git a/apps/docs/AGENTS.md b/apps/docs/AGENTS.md new file mode 100644 index 0000000..463753a --- /dev/null +++ b/apps/docs/AGENTS.md @@ -0,0 +1,56 @@ +# apps/docs — Living Design Documentation + +Package: `@void-nav/docs` +Port: `http://localhost:5173/docs` + +This is the **design documentation hub** and interactive prototype playground. It contains the full GDD as 16 browsable pages, 14 interactive demos validating individual game subsystems, and a complete game-slice prototype implementing the full MVP loop in-browser with localStorage. + +## Structure + +``` +src/ + App.tsx Route definitions (16 doc pages + 14 demo routes + 1 prototype) + main.tsx Entry point + components/ Shared UI: NotFound, Sidebar, TopBar + data/ + nav.ts Sidebar navigation structure and page titles + fakeBackend.ts Mock data for demos + layouts/ + DocsLayout.tsx Sidebar + content layout wrapper + lib/ + threeHelpers.ts Shared Three.js utility functions + pages/docs/ 16 design doc page components: + OverviewPage.tsx Vision, pillars, core loop, HUD architecture, onboarding + ArchitecturePage.tsx System architecture, error handling, persistence, audio + TechStackPage.tsx Technology decisions + BackendPage.tsx 56+ database tables, movement model, ER diagram + AgentsPage.tsx Scheduled agent system (3 scheduling strategies) + GameplayPage.tsx Core loop, security, NPC pirates, combat, missions, travel, events, balancer + ShipsPage.tsx Ship classes, fitting, acquisition, AI crew + EconomyPage.tsx Trade routes, info diffusion, refining, manufacturing, NPC pricing, faucets/sinks + SocialPage.tsx XP & skills, chat, bounty, kill feed, corporations + ShipAIPage.tsx Zora companion AI with soul depth and module gating + RoadmapPage.tsx 16 phases (0-15), 2 eras, 6 integration gates + RisksPage.tsx Open risks and questions + TodoPage.tsx Living implementation status tracker (68 items across 8 phases) + DemoGalleryPage.tsx Links to all demos + GapAnalysisPage.tsx Cross-reference: specs vs demos vs readiness + VerticalSliceEvaluationPage.tsx Alignment matrix, phase readiness + DesignDocPage.tsx Original GDD reference + prototypes/ + game-slice/ Full MVP loop prototype (localStorage, not SpacetimeDB) + existing-demos/ 14 standalone interactive demo components + r3f/ Reusable R3F 3D scenes (combat, galaxy, HUD, movement, navigation, starmap, warp) + standalone-huds/ GameHudPrototype, PrototypeFrame + styles/ + tailwind.css Tailwind v4 entry point +public/ + docs/ Markdown files: vertical-slice-evaluation.md, gap-analysis.md + assets/ Design doc (.docx), 3 concept drawings (.png) +``` + +## Key Design Decisions + +- Docs are **React components**, not markdown files — this allows interactive demos inline with documentation +- The `game-slice/` prototype is a self-contained playable game that validates the full loop before backend migration +- All 14 demos in `existing-demos/` use the R3F scenes in `r3f/` diff --git a/apps/docs/src/App.tsx b/apps/docs/src/App.tsx index 05b20ab..22bf00a 100644 --- a/apps/docs/src/App.tsx +++ b/apps/docs/src/App.tsx @@ -17,6 +17,7 @@ import { DemoGalleryPage } from "./pages/docs/DemoGalleryPage"; import { GapAnalysisPage } from "./pages/docs/GapAnalysisPage"; import { VerticalSliceEvaluationPage } from "./pages/docs/VerticalSliceEvaluationPage"; import { DesignDocPage } from "./pages/docs/DesignDocPage"; +import { TodoPage } from "./pages/docs/TodoPage"; import { StarMapDemo } from "./prototypes/existing-demos/StarMapDemo"; import { ShipMovementDemo } from "./prototypes/existing-demos/ShipMovementDemo"; import { WarpTravelDemo } from "./prototypes/existing-demos/WarpTravelDemo"; @@ -56,6 +57,7 @@ export function App() { } /> } /> } /> + } /> } /> } /> diff --git a/apps/docs/src/data/nav.ts b/apps/docs/src/data/nav.ts index 59cf5bc..b523c70 100644 --- a/apps/docs/src/data/nav.ts +++ b/apps/docs/src/data/nav.ts @@ -24,6 +24,7 @@ export const navSections: NavSection[] = [ { path: "/docs/social", icon: "✧", label: "Progression & Social" }, { path: "/docs/ship-ai", icon: "◈", label: "Ship AI - Zora" }, { path: "/docs/roadmap", icon: "⊞", label: "Roadmap" }, + { path: "/docs/todo", icon: "⊡", label: "Implementation Status" }, { path: "/docs/risks", icon: "◬", label: "Risks & Questions" }, { path: "/docs/gap-analysis", icon: "□", label: "Gap Analysis" }, { path: "/docs/vertical-slice-evaluation", icon: "▤", label: "Slice Evaluation" }, diff --git a/apps/docs/src/pages/docs/EconomyPage.tsx b/apps/docs/src/pages/docs/EconomyPage.tsx index 905a560..17b8ecb 100644 --- a/apps/docs/src/pages/docs/EconomyPage.tsx +++ b/apps/docs/src/pages/docs/EconomyPage.tsx @@ -40,29 +40,29 @@ export function EconomyPage() { return (
-

Economy & Industry

+

Trade & Industry

- Player-led economy with NPC support as an underlying demand/supply buffer. Players mine, refine, - manufacture, and trade. NPCs provide a price floor and basic market liquidity so new players - always have a way to earn and spend. + Trade between stations with regional price differences. Supply what stations need and watch them grow. + Mine ore, refine into minerals, manufacture modules and ships. The economy rewards exploration — + discovering a profitable trade route is as valuable as finding a rich asteroid belt.

-
Player-led
-
Economy Model
+
Trade Routes
+
Core Mechanic
8 → 8
Ore Types → Minerals
-
8+
-
Manufacturable Items
+
Regional
+
Price Differences
-
2%
-
Market Tax
+
Dynamic
+
Station Prosperity
@@ -91,10 +91,10 @@ export function EconomyPage() {
- Exploration and commerce are the core pillars. This walkthrough shows what a new player sees, decides, - and experiences in their first 30 minutes — from undocking with an empty cargo hold to discovering their first price - difference and making a profitable trade. The goal: by minute 30, the player understands that information asymmetry - is the game, and they've profited from it at least once. + Trade and exploration are the entry point. This walkthrough shows what a new captain sees, decides, + and experiences in their first 30 minutes — from undocking with an empty cargo hold to discovering their first + trade route and making a profitable run. The goal: by minute 30, the player understands that + different stations need different things, and supplying them is how you grow — whether through mining, trading, or quests.
@@ -132,9 +132,9 @@ export function EconomyPage() {
Design intent: By minute 8, the player has discovered that prices differ between stations. - By minute 12, they\'ve acted on that discovery and profited. By minute 30, they have a mental model of the - economy as a landscape of opportunities, not a single "sell" button. This is the hook. If the price discovery - moment doesn\'t feel exciting, the entire game falls flat. + By minute 12, they've acted on that discovery and profited. By minute 30, they have a mental model of the + galaxy as a landscape of trade routes, faction needs, and opportunities — not a single "sell" button. + This is the hook. If the moment of discovering a trade route doesn't feel exciting, the trading game needs work.
)} diff --git a/apps/docs/src/pages/docs/GameplayPage.tsx b/apps/docs/src/pages/docs/GameplayPage.tsx index d58d8e7..bbdbf34 100644 --- a/apps/docs/src/pages/docs/GameplayPage.tsx +++ b/apps/docs/src/pages/docs/GameplayPage.tsx @@ -6,11 +6,11 @@ export function GameplayPage() { return (
-

MVP Gameplay Loop

+

Gameplay Systems

- The core loop: connect → spawn → navigate → mine → inventory → station → sell on the market → chat. - Steps 1–7 work in Era 1 (single-player). Step 8 (Chat) requires multiplayer and ships in Era 2 (Phase 11). - Each step is UI-driven. The player's main interface is tables, charts, and panels — not a cockpit. + The core loop: explore → trade → quest → fight (FTL-style) → upgrade → sail deeper. + Every system feeds into the others. Trade earns ISK for upgrades. Quests unlock faction influence. Combat protects trade routes. + Exploration reveals new opportunities. Steps 1–7 work in Era 1 (single-player). Multiplayer features arrive in Era 2.

@@ -20,10 +20,10 @@ export function GameplayPage() { { id: 'pirates', label: 'NPC Pirates' }, { id: 'concord', label: 'CONCORD' }, { id: 'insurance', label: 'Insurance' }, - { id: 'missions', label: 'Missions' }, - { id: 'travel', label: '🚀 Travel & Warp' }, - { id: 'events', label: 'World Events UX' }, - { id: 'balancer', label: '⚖ Balancing Agent' }, + { id: 'missions', label: 'Quests & Factions' }, + { id: 'travel', label: 'Travel & Warp' }, + { id: 'events', label: 'World Events' }, + { id: 'balancer', label: 'Balancing Agent' }, ].map(t => ( @@ -34,14 +34,14 @@ export function GameplayPage() { {/* Loop steps */}
{[ - { step: 1, title: 'Connect', desc: 'Player opens the app and connects to SpacetimeDB. Identity is established, player row loaded.', color: 'var(--cyan)' }, - { step: 2, title: 'Spawn', desc: 'A player row and ship row exist; the ship appears in the shared star system for all subscribers.', color: 'var(--cyan)' }, - { step: 3, title: 'Navigate (click & autopilot)', desc: 'Player clicks a point of interest — asteroid, station, hostile — and the ship automatically pilots there. No manual flight control at all. The player sets intent, the ship executes.', color: 'var(--green)' }, - { step: 4, title: 'Mine (activate & wait)', desc: 'Player clicks an asteroid to approach, then activates mining modules. The ship navigates on its own. Mining runs on a timer. The decision is what to mine and when, not how.', color: 'var(--accent)' }, - { step: 5, title: 'Inventory', desc: 'Ore appears in inventory panel. Quantity, type, and value visible. Player manages cargo space.', color: 'var(--purple)' }, - { step: 6, title: 'Dock at Station', desc: 'Player docks at a station. Station UI becomes available: sell, market, refit.', color: 'var(--cyan)' }, - { step: 7, title: 'Sell Ore', desc: 'Player sells ore through station UI for ISK (in-game currency). Simple fixed pricing for MVP; market orders later.', color: 'var(--accent)' }, - { step: 8, title: 'Chat', desc: 'Players send and receive messages in current system. Basic rate limiting and length validation. Requires multiplayer — ships in Phase 11 (Era 2).', color: 'var(--green)' }, + { step: 1, title: 'Spawn & Explore', desc: 'Player connects and spawns at a station in a procedurally generated sector. The map is hidden — fog of war covers unexplored regions. Nearby POIs are visible: stations, belts, signal sources. The urge to explore is immediate.', color: 'var(--cyan)' }, + { step: 2, title: 'Navigate (click & autopilot)', desc: 'Player clicks a point of interest — asteroid, station, anomaly, signal — and the ship automatically pilots there. No manual flight. The journey IS the experience: watch the stars scroll, spot distant objects, feel the void.', color: 'var(--green)' }, + { step: 3, title: 'Gather Resources', desc: 'Mine asteroids for ore. Salvage derelicts for components. Discover anomalies for rare materials. The decision is what to gather and where — different regions have different resources.', color: 'var(--accent)' }, + { step: 4, title: 'Trade Between Stations', desc: 'Buy low at one station, sell high at another. Prices vary by region and faction needs. Stations that need resources pay more. Trade routes emerge naturally from exploration.', color: 'var(--purple)' }, + { step: 5, title: 'Accept Quests', desc: 'Faction agents offer missions: deliver cargo, eliminate threats, explore sectors, escort convoys. Completing quests earns ISK, standing, and unlocks new opportunities. Helping one faction may hinder another.', color: 'var(--cyan)' }, + { step: 6, title: 'Combat (FTL-style)', desc: 'Click a hostile → ship auto-engages → manage reactor power between Weapons/Shields/Engines/Aux. No dogfighting. The skill is tactical resource allocation. PvE pirates, bounty targets, and hostile NPCs. PvP in Era 2.', color: 'var(--red)' }, + { step: 7, title: 'Upgrade & Customize Ship', desc: 'Use ISK to buy bigger ships, fit modules, upgrade weapons and defenses. Build the ship that suits your style: nimble trader, tough warship, or versatile explorer.', color: 'var(--accent)' }, + { step: 8, title: 'Sail Deeper', desc: 'Venture into unexplored sectors with better resources but greater danger. Higher risk, higher reward. Faction territory, world events, and player actions shape the galaxy. Co-op multiplayer in Era 2.', color: 'var(--green)' }, ].map((s, i) => (
diff --git a/apps/docs/src/pages/docs/OverviewPage.tsx b/apps/docs/src/pages/docs/OverviewPage.tsx index 578979d..4eb15eb 100644 --- a/apps/docs/src/pages/docs/OverviewPage.tsx +++ b/apps/docs/src/pages/docs/OverviewPage.tsx @@ -4,36 +4,36 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; export function OverviewPage() { return (
-

EVE-Inspired Multiplayer Prototype

+

Open-World Space Exploration RPG

- A browser-based spreadsheet simulator in the EVE Online tradition, - set inside a single persistent galaxy that the server simulates as a living world. - The game is played through UI panels, market tables, inventory grids, and chat channels — not by flying a ship. - Movement and combat are deliberately rudimentary; the depth lives in the economy, information diffusion, - strategic decisions, and a galaxy that evolves around you through dynamic PvE events and emergent world story. + Trade, quest, fight, or simply drift and enjoy the ambiance. A charming open-world space RPG inspired + by the freedom and charm of Windward Horizon — set in a procedurally generated galaxy where + every campaign is unique. Explore sectors, trade between stations, take on faction quests, mine asteroids, + or simply cruise the void. Combat is FTL-style tactical resource management — + no dogfighting. You are the captain who decides what to power and when, not the pilot who dodges.

-
UI-first
-
Design Pillar
+
Explore
+
Core Pillar
SpacetimeDB
Backend
-
~3 hrs
-
Session Target
+
FTL Combat
+
Combat Model
-
Player-led
-
Economy Model
-
-
-
Living Galaxy
+
Dynamic
World Model
+
+
Co-op
+
Multiplayer
+
@@ -42,10 +42,10 @@ export function OverviewPage() {
- Primary recommendation: Build the MVP as a UI-heavy browser game — a spreadsheet simulator. - The 3D scene is a strategic map layer for context, not the game itself. Players spend 90% of their time - in tables, charts, and panels: market depth, order books, cargo manifests, fitting spreadsheets, - route planners, and chat. The 3D viewport exists to give spatial awareness, not twitch gameplay. + Primary inspiration: Windward Horizon's charm and freedom in a space setting. The game is an + open-world exploration RPG where the player charts their own path — trader, mercenary, explorer, or drifter. + The 3D scene is the primary viewport for spatial immersion. Panels and UI overlays handle station services, fitting, and market. + Combat uses FTL-style power management — no dogfighting. The galaxy is procedurally generated each campaign.

Core Pillars

@@ -59,29 +59,34 @@ export function OverviewPage() { - Economy & markets - Player-led economy with NPC support. Mining → refining → manufacturing → trade. Geographic price differences, contract markets, order books, and information asymmetry between systems create emergent trade routes and speculative opportunities. This IS the game. - Era 1 NPC economy, single-player mining/refining/manufacturing
Era 2 Player-to-player market, info diffusion + Open-world exploration + Procedurally generated galaxy with unique sectors every campaign. Points of interest: stations, asteroid belts, anomalies, derelicts, signal sources. Explore to discover resources, quests, and hidden stories. The map reveals as you travel — fog of war drives the urge to see what's out there. + Era 1 Single system with POIs, procedural generation seed - Social & multiplayer - Local chat, delayed PMs, bounty system, emergent player-driven justice. Communication is range-based. Priority pillar. - Era 2 All social features require multiplayer + Trade & commerce + Buy low at one station, sell high at another. Regional price differences driven by supply/demand and faction needs. Trade routes are the economic backbone. Towns need resources to grow — supplying them leads to prosperity. + Era 1 NPC economy, regional prices, trade routes
Era 2 Player-to-player market, info diffusion - Command-based (not action) - Players issue high-level intentions — click a point of interest and the ship autopilots there. Click a hostile and the ship auto-engages. During combat the player manages reactor power allocation (FTL-style) between systems. No manual flight, no aiming, no skill shots. The skill is in what you power, not how fast you click. - Era 1 Core combat & movement loop + FTL-style combat + No dogfighting. Click hostile → ship auto-engages → manage reactor power allocation (FTL-style) between Weapons/Shields/Engines/Aux. The skill is in what you power, not how fast you click. Combat creates consequences — ship damage, loot, insurance — but it's not the focus. + Era 1 Core combat & power management - Ship fitting - CPU/Power Grid slot system. High/Med/Low slots with meaningful fitting tradeoffs. Multiple ships, AI crew (post-MVP). - Era 1 Basic fitting, single ship class + Ship customization + Build the ship that suits your style — tough juggernaut that can take a beating, nimble scout that rains fire from afar, or cargo hauler keeping trade routes supplied. Swap modules to change role. Multiple ship classes from nimble interceptors to powerful cruisers. + Era 1 Basic fitting, multiple ship classes - Emergent lore - No server has the same lore as another. The galaxy is a single persistent world with systems, planets, anomalies, and orbiting objects. A world simulation layer spawns PvE events dynamically — faction wars, space anomalies, migrations, raids — that create a living story unique to every server. Lore evolves through both player actions and server-driven world events. - Era 2 Living galaxy requires world agents + Dynamic world & factions + Stations and provinces need resources to grow. Supplying them leads to prosperity. Faction leaders involve you in their agendas — helping one may hinder another. Enough support can let a faction leader take control of a province without a single shot fired. A living galaxy that evolves through player actions and world events. + Era 1 Basic factions, NPC agents, quests
Era 2 Living galaxy, world agents, territory + + + Solo or co-op + Brave the void alone or crew with friends. The game is built with co-op in mind — bring your crew, set sail, and experience the chaotic endgame together. Solo play is fully viable; co-op makes the late game richer. + Era 1 Solo proof of concept
Era 2 Multiplayer, fleets, PvP @@ -93,11 +98,11 @@ export function OverviewPage() {
-

Spreadsheet simulator, not flight sim

+

Exploration-first, not spreadsheet-first

- 90% of gameplay happens in tables, charts, and panels: market order books, cargo manifests, fitting spreadsheets, - route planners, ship AI logs, and chat channels. The 3D viewport gives spatial awareness, not twitch gameplay. - Movement is click-to-autopilot. Combat is click-to-engage with FTL-style power management. The depth is in the economy and information. + The 3D viewport IS the game. Players spend their time flying between points of interest, discovering new sectors, + and engaging with the world. Station panels handle trading, fitting, and services — but the joy is in + the journey, not the ledger. Trade, quest, fight, or simply set sail and enjoy the ambiance.

@@ -108,20 +113,19 @@ export function OverviewPage() {

-

Information is the real currency

+

Every campaign is unique

- Market data propagates at the speed of player travel, not the speed of light. Knowing a price discrepancy exists - before other traders — that's the skill. The ship AI (Zora) is a living market intelligence tool. See the - Economy → 📡 Info Diffusion tab for the full model. + Procedurally generated galaxy maps ensure no two games are alike. Fog of war hides unexplored sectors. + Factions, resource distributions, and quest chains vary per seed. Replayability through discovery, not grind.

-

Movement & combat are not action

+

FTL combat — captain, not pilot

- The player never pilots the ship directly. Click a destination → ship autopilots. Click a hostile → ship auto-engages. + The player never directly flies or aims. Click a destination → ship autopilots. Click a hostile → ship auto-engages. During combat, the player manages reactor power allocation (FTL-style: weapons/shields/engines/aux) - and subsystem targeting. That's it. Ship destruction - is an economic event (ISK sink, insurance payout, loot drop), not a competitive action moment. ISK (symbol ₢) is the canonical in-game currency. + and subsystem targeting. You are the chief engineer deciding where + the reactor output goes — not the pilot or gunner.

@@ -133,20 +137,19 @@ export function OverviewPage() {
- ConnectSpawn Ship →{' '} - Navigate →{' '} - Mine →{' '} - Inventory →{' '} - Station →{' '} - Sell Ore →{' '} - Chat + SpawnExplore →{' '} + Trade →{' '} + Quest →{' '} + Fight →{' '} + Upgrade Ship →{' '} + Sail Deeper
- The real loop is economic. Connect → gather information → identify opportunity → act on it → profit. - The "mine → sell" cycle is the entry point. The endgame is inter-regional arbitrage, supply chain management, - manufacturing empires, and market manipulation — all driven by information diffusion between systems. + The real loop is adventure. Spawn → explore the unknown → discover opportunities → act on them → grow your ship and reputation → sail into deeper, riskier space. + The "mine → sell" cycle is the entry point. The endgame is about faction influence, rare discoveries, commanding powerful ships, + and shaping the galaxy through your choices — whether by trade, combat, or diplomacy.

Minimum Viable Screens

@@ -193,9 +196,9 @@ export function OverviewPage() {
- Decision: Hybrid diegetic + panel approach. - The game uses two distinct view modes depending on the player's state. This resolves the ambiguity - between the gamehud demo (which renders diegetic overlays on a 3D viewport) and the spec's description of traditional panels. + Decision: Hybrid immersive + panel approach. + The game uses two distinct view modes depending on the player's state. When in space, the 3D viewport + is the primary experience with diegetic HUD overlays. When docked at a station, traditional panel UI handles services. Both are correct — they apply to different contexts.
@@ -267,13 +270,13 @@ export function OverviewPage() {
- Why not all-diegetic or all-panel? - A fully diegetic HUD (Dead Space style) works for immersion but is terrible for spreadsheet gameplay — you can't read - an order book through a holographic visor. A fully panel UI (traditional MMO) loses the spatial awareness that makes - space feel like space. The hybrid approach keeps the best of both: diegetic immersion during the action, panel efficiency - during the economy game. The key insight is that the game alternates between two distinct cognitive modes — - reactive (in-space, monitoring health/modules/overview) and analytical (docked, reading tables/planning routes). - Each view mode is optimized for its cognitive mode. + Why this hybrid approach? + A fully diegetic HUD (Dead Space style) works for immersion but makes station services clunky — buying cargo through + holographic menus is painful. A fully panel UI (traditional MMO) loses the spatial immersion that makes space feel + like space. The hybrid keeps the best of both: immersive 3D exploration and combat, efficient panel-based station services. + The game alternates between two modes — + reactive (in-space, monitoring health/modules/threats) and analytical (docked, trading/fitting/planning). + Each view mode is optimized for its mode.
@@ -282,20 +285,20 @@ export function OverviewPage() {
- The first 30 minutes teach the game through doing, not reading. New players learn by playing a guided - mission sequence that introduces each system naturally. There is no separate "tutorial mode" \u2014 the tutorial IS the game. - See Economy \u2192 First 30 Minutes tab for the full moment-by-moment walkthrough. + The first 30 minutes teach the game through adventure, not reading. New players learn by playing a guided + mission sequence that introduces each system naturally — undocking, navigating, mining, trading, and their first combat encounter. + There is no separate "tutorial mode" — the tutorial IS the game.

Guided Mission Sequence

    -
  • Mission 1: "Welcome, Pilot" \u2014 undock, warp to belt, mine 100 ore, dock, sell. Teaches: navigation, mining, market.
  • -
  • Mission 2: "Armed and Ready" \u2014 accept kill mission, engage NPC frigate, manage power allocation, collect bounty. Teaches: combat, insurance.
  • -
  • Mission 3: "Supply Chain" \u2014 refine ore, manufacture a module, fit it to ship. Teaches: industry, fitting.
  • -
  • Mission 4: "Price Watcher" \u2014 fly to second station, compare prices, sell at the better one. Teaches: price discovery, trade routes.
  • -
  • Mission 5: "Your AI Companion" \u2014 Zora introduces herself, explains her modules, offers first market tip. Teaches: Zora, AI modules.
  • +
  • Mission 1: "Welcome, Captain" — undock, navigate to asteroid belt, mine 100 ore, dock, sell. Teaches: navigation, mining, market.
  • +
  • Mission 2: "First Contact" — encounter an NPC hostile, manage FTL-style power allocation, survive the fight. Teaches: combat, damage, repair.
  • +
  • Mission 3: "Supply Run" — accept a trade quest from a faction agent, deliver goods to another station, earn ISK and standing. Teaches: trade routes, factions.
  • +
  • Mission 4: "The Bigger Ship" — earn enough ISK to buy a new ship class, fit it with modules. Teaches: ship acquisition, fitting.
  • +
  • Mission 5: "Into the Unknown" — explore an uncharted sector, discover a hidden anomaly, encounter a world event. Teaches: exploration, discovery, the wider galaxy.
diff --git a/apps/docs/src/pages/docs/RoadmapPage.tsx b/apps/docs/src/pages/docs/RoadmapPage.tsx index 98f0dee..ebc148e 100644 --- a/apps/docs/src/pages/docs/RoadmapPage.tsx +++ b/apps/docs/src/pages/docs/RoadmapPage.tsx @@ -5,72 +5,72 @@ export function RoadmapPage() { const eras = [ { id: 'solo', - title: 'Era 1 — Single-Player Proof of Concept', - subtitle: 'Validate core loops locally. SpacetimeDB runs on the local machine from Phase 0 — there is no localStorage. One browser window, one player, one simulated galaxy.', + title: 'Era 1 — Single-Player Open-World RPG', + subtitle: 'Validate the core adventure loop locally. SpacetimeDB runs on the local machine from Phase 0 — there is no localStorage. One browser window, one captain, one procedurally generated galaxy to explore.', accent: 'var(--accent)', phases: [ { num: '0', - title: 'Local Skeleton', - goal: 'Vite app with local SpacetimeDB instance, game state manager, tick loop, and a single rendered star system.', - doneWhen: 'App boots, connects to local SpacetimeDB instance. Shows a star system with a station and 3 asteroids. Game state updates on a local tick (60fps render, 1Hz sim tick). All persistence through SpacetimeDB — no localStorage.', + title: 'Local Skeleton & Star System', + goal: 'Vite app with local SpacetimeDB instance, rendered star system with station and asteroids. Player can connect and see the void.', + doneWhen: 'App boots, connects to local SpacetimeDB instance. Shows a star system with a station and 3 asteroids. Ship visible in 3D viewport. Game state persists in SpacetimeDB — no localStorage.', status: 'current', }, { num: '1', - title: 'Movement & Commands', - goal: 'Click-to-move autopilot with local path resolution. Ship accelerates, cruises, decelerates.', - doneWhen: 'Click an asteroid or station. Ship plots a course and moves there with smooth interpolation. ETA display updates. Warp-to for distant objects works.', + title: 'Navigation & Exploration', + goal: 'Click-to-navigate autopilot with smooth ship movement. Warp-to for distant objects. Explore POIs within a system.', + doneWhen: 'Click an asteroid, station, or anomaly. Ship plots a course and moves there with smooth interpolation. ETA display updates. Warp-to for distant objects works. Player can freely navigate the star system.', status: 'upcoming', }, { num: '2', - title: 'Mining & Inventory', - goal: 'Asteroid mining cycle, ore extraction, cargo hold, and jettison.', - doneWhen: 'Approach asteroid, start mining. Mining cycle shows progress. Ore appears in cargo. Cargo full warning. Can jettison into a can.', + title: 'Mining, Inventory & Trade', + goal: 'Asteroid mining cycle, ore extraction, cargo hold. NPC market for buying/selling goods. Regional price differences between stations.', + doneWhen: 'Approach asteroid, start mining. Mining cycle shows progress. Ore appears in cargo. Dock at station, sell ore for ISK. Fly to another station — prices are different. Buy low / sell high works.', status: 'upcoming', }, { num: '3', title: 'Combat — FTL Power Allocation', - goal: 'Auto-engage combat with reactor power management between weapons / shields / engines.', - doneWhen: 'Target a hostile NPC. Ship auto-engages. Player shifts reactor power between 3 subsystems (FTL-style). Power allocation visibly changes combat outcome. Ship can be destroyed.', + goal: 'Auto-engage combat with reactor power management between weapons / shields / engines. Encounter NPC pirates at belts and anomalies.', + doneWhen: 'Target a hostile NPC at a belt or anomaly. Ship auto-engages. Player shifts reactor power between subsystems (FTL-style). Power allocation visibly changes combat outcome. Ship can be destroyed. NPC pirates drop loot and bounty.', status: 'upcoming', }, { num: '4', - title: 'Ship Fitting', - goal: 'CPU / Power Grid slot system. High / Med / Low racks with modules that change ship behavior.', - doneWhen: 'Dock at station. Open fitting screen. Equip weapons in high slots, shield booster in mid, cargo expander in low. Fitting affects combat and mining stats. Invalid fits rejected (insufficient CPU/PG). AI module slot type added to fitting schema.', + title: 'Ship Customization & Upgrades', + goal: 'Multiple ship classes (scout, hauler, warship). Module fitting with High/Med/Low slots. CPU/PG constraints. Buy new ships at stations.', + doneWhen: 'Dock at station. Open fitting screen. Equip weapons in high slots, shield booster in mid, cargo expander in low. Fitting affects combat and trade stats. Can buy a new ship class. Invalid fits rejected (insufficient CPU/PG).', status: 'upcoming', }, { num: '5', - title: 'Refining & Manufacturing', - goal: 'Refine ore into minerals at a station. Use minerals to manufacture modules and ammo.', - doneWhen: 'Dock with ore. Refine at station facility (with yield efficiency). Minerals stored locally. Open manufacturing tab, select a blueprint, queue a job. Job completes after sim time. Product appears in hangar.', + title: 'Faction Quests & Dynamic World', + goal: 'NPC agents offer quests (kill, courier, mining, exploration). Faction standing system. Station prosperity responds to player supply. Dynamic world events spawn.', + doneWhen: 'Talk to NPC agent at station. Accept a quest (deliver cargo, kill pirates, explore anomaly). Complete quest for ISK + standing. Standing unlocks better quests and prices. Supplying a station increases its prosperity. World events spawn in explored systems.', status: 'upcoming', }, { num: '6', - title: 'NPC Economy Sim', - goal: 'Simulated NPC market with supply/demand. Prices react to player trades. Regional price differences.', - doneWhen: 'Sell ore at a station. Price adjusts (supply increases, price drops). Fly to another system, price is different. Buy low / sell high works. Market history table shows price movement.', + title: 'Industry & Economy Depth', + goal: 'Refining ore into minerals. Manufacturing modules and items. Full NPC market with supply/demand. Price history and regional differences.', + doneWhen: 'Dock with ore. Refine at station. Minerals appear in hangar. Open manufacturing tab, select a blueprint, queue a job. Job completes. Product appears. Sell on NPC market. Prices react to trades. Different stations have different prices.', status: 'upcoming', }, { num: '7', - title: 'Single-Player Polish', - goal: 'Complete HUD, notifications, empty states, tutorial hints, and save/load.', - doneWhen: 'Full game loop is playable solo: mine → refine → manufacture → fit → fight → trade. HUD shows all relevant info. SpacetimeDB persists all state (ships, inventory, market, skills) — no localStorage. No dead-end states. Tier 0 Zora: status readouts, basic shield warnings, bare-bones soul state vector in SpacetimeDB. Lightweight exploration events spawn in visited systems.', + title: 'Single-Player Polish & Procedural Galaxy', + goal: 'Procedurally generated multi-system galaxy. Fog of war. Full HUD. Notifications. Tutorial sequence. Ship AI companion (Zora Tier 0). Save/load.', + doneWhen: 'Full game loop is playable solo: explore → mine → trade → quest → fight → upgrade → explore deeper. Procedural galaxy generates unique systems each campaign. Fog of war reveals as you travel. HUD shows all relevant info. Tutorial guides new players through first 30 minutes. SpacetimeDB persists all state.', status: 'future', }, ], }, { id: 'multi', - title: 'Era 2 — Multiplayer Environment', - subtitle: 'Promote local SpacetimeDB to a shared server. Add multiplayer networking, social systems, and the full living galaxy simulation. Multiple players, one persistent world.', + title: 'Era 2 — Multiplayer & Living Galaxy', + subtitle: 'Promote local SpacetimeDB to a shared server. Add multiplayer co-op, social systems, PvP, and the full living galaxy simulation. Multiple captains, one persistent universe.', accent: 'var(--cyan)', phases: [ { @@ -181,15 +181,15 @@ export function RoadmapPage() {

Development Roadmap

- Two eras, sixteen phases. Era 1 proves the game is fun as a single-player simulation with a local - SpacetimeDB instance — the same persistence architecture as multiplayer, just one player. Era 2 promotes - that local SpacetimeDB to a shared server and adds social systems, the living galaxy, and multiplayer combat. + Two eras, sixteen phases. Era 1 proves the game is fun as a single-player open-world space RPG + with a local SpacetimeDB instance — exploration, trading, FTL-style combat, ship customization, and faction quests. + Era 2 promotes that local SpacetimeDB to a shared server and adds multiplayer, social systems, the living galaxy, and co-op play. Each phase has a verifiable done-when condition. Integration gates between phase groups ensure every system works together before advancing.

Why single-player first with local SpacetimeDB? Networking is the biggest source of bugs and complexity. - By validating that mining, combat, fitting, and the economy are fun locally — using the same SpacetimeDB + By validating that exploration, trading, combat, fitting, and faction quests are fun locally — using the same SpacetimeDB persistence that will serve multiplayer — we de-risk the entire project. There is no localStorage; SpacetimeDB is the persistence layer from day 1. When Era 2 begins, the question is only "how do we share this server?" — not "is this game fun?" or "will the persistence migration work?" @@ -205,35 +205,37 @@ export function RoadmapPage() {

-

Gate 1 — Core Loop (after Phase 2)

+

Gate 1 — Exploration & Trade Loop (after Phase 2)

- Navigate to asteroid → mine → fill cargo → dock → sell ore. The complete economic loop runs in a single session - without errors. SpacetimeDB persists the session — closing the browser and reopening restores state. + Navigate to asteroid → mine → fill cargo → dock → sell ore → navigate to another station → discover price difference → trade. + The complete exploration and trade loop runs in a single session without errors. The galaxy feels alive and explorable. + SpacetimeDB persists the session — closing the browser and reopening restores state.

Phases covered: 0, 1, 2
-

Gate 2 — Combat + Fitting (after Phase 4)

+

Gate 2 — Combat + Customization (after Phase 4)

- Fit a ship at station → undock → encounter NPC pirate → manage power allocation → destroy or be destroyed → - insurance payout (if destroyed) → refit at station. Combat and fitting form a closed loop with economic consequences. + Customize a ship at station → undock → encounter NPC pirate → manage FTL power allocation → destroy or be destroyed → + collect loot → buy upgrades → try a different ship class. Combat and ship customization form a satisfying adventure loop.

Phases covered: 0–4
-

Gate 3 — Full Economy (after Phase 6)

+

Gate 3 — Full Adventure Loop (after Phase 6)

- Mine ore → refine → manufacture a module → fit it → use it in combat → sell excess minerals across systems at different prices. - The complete production chain and NPC market work as an integrated system. Price differences between stations are discoverable. + Explore → mine ore → refine → manufacture a module → fit it → fight pirates → accept faction quest → deliver goods → + earn standing → unlock better missions → sell excess across stations at different prices. + The complete adventure: exploration, economy, industry, and faction systems work as an integrated whole.

Phases covered: 0–6

Gate 4 — Era 1 Complete (after Phase 7)

- Full solo game loop with all systems integrated: mine → refine → manufacture → fit → fight → trade → repeat. - HUD, notifications, Zora Tier 0, exploration events, and missions all work without dead ends. A new player - can learn the game in one session. SpacetimeDB state survives restart. + Full solo game with all systems integrated: explore → mine → trade → quest → fight → customize → sail deeper. + Procedurally generated galaxy. Fog of war. HUD, notifications, Zora Tier 0, world events, tutorial sequence. + A new player can learn the game in one session and want to keep going. SpacetimeDB state survives restart.

Phases covered: 0–7 (all Era 1)
@@ -248,8 +250,9 @@ export function RoadmapPage() {

Gate 6 — Launch Ready (after Phase 15)

- Fresh player can: create account, complete tutorial, mine ore, fit ship, survive PvE, make a trade, join a corp, - participate in a world event — all without crashes or dead ends. Server handles 50 concurrent. Full game loop validated. + Fresh player can: create account, complete tutorial, explore their first system, mine and trade, survive a PvE encounter, + customize their ship, accept and complete a faction quest, join a crew with friends — all without crashes or dead ends. + Server handles 50 concurrent players. Full game loop validated.

Phases covered: 0–15 (all)
diff --git a/apps/docs/src/pages/docs/ShipsPage.tsx b/apps/docs/src/pages/docs/ShipsPage.tsx index 1a1a09c..2a9d8d2 100644 --- a/apps/docs/src/pages/docs/ShipsPage.tsx +++ b/apps/docs/src/pages/docs/ShipsPage.tsx @@ -81,11 +81,11 @@ export function ShipsPage() { return (
-

Ships & Fitting System

+

Ships & Customization

- Ships are the player's primary asset. Each ship has a slot layout with CPU and Power Grid limits - that constrain what modules can be fitted. Players own multiple ships and can assign AI crew to - pilot them on autonomous tasks. + Build the ship that suits your style — a tough juggernaut that can take a beating, a nimble scout that rains fire from afar, + or a cargo hauler keeping trade routes supplied. Swap out weapons for extra armor, or add mining lasers to become an industrial powerhouse. + Each ship has a slot layout with CPU and Power Grid limits that constrain what modules can be fitted.

{/* Tab navigation */} diff --git a/apps/docs/src/pages/docs/TodoPage.tsx b/apps/docs/src/pages/docs/TodoPage.tsx new file mode 100644 index 0000000..bd7f7b9 --- /dev/null +++ b/apps/docs/src/pages/docs/TodoPage.tsx @@ -0,0 +1,922 @@ +// @ts-nocheck +import * as React from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; + +const LAST_UPDATED = '2026-06-02'; +const BACKEND_TABLES_IMPLEMENTED = 9; +const BACKEND_TABLES_DESIGNED = 56; +const BACKEND_REDUCERS_IMPLEMENTED = 12; +const BACKEND_REDUCERS_DESIGNED = 40; + +type Phase = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7'; +type Layer = 'backend' | 'frontend' | 'scene' | 'prototype'; +type Status = 'done' | 'partial' | 'missing' | 'blocked'; + +interface TodoItem { + id: string; + title: string; + phase: Phase; + layer: Layer; + status: Status; + desc: string; + detail?: string; + files?: string; + blockedBy?: string[]; +} + +const STATUS_COLORS: Record = { + done: 'var(--green)', + partial: 'var(--accent)', + missing: 'var(--red)', + blocked: 'var(--muted)', +}; + +const STATUS_LABELS: Record = { + done: 'DONE', + partial: 'PARTIAL', + missing: 'MISSING', + blocked: 'BLOCKED', +}; + +const LAYER_LABELS: Record = { + backend: 'SpacetimeDB', + frontend: 'Game UI', + scene: '3D Scene', + prototype: 'Prototype Ready', +}; + +const items: TodoItem[] = [ + // ── PHASE 0: LOCAL SKELETON ── + { + id: 'P0-B1', + title: 'Player identity & connection', + phase: '0', + layer: 'backend', + status: 'done', + desc: 'Player table, connectPlayer, renamePlayer, ping reducers. Auth token persistence.', + files: 'services/spacetimedb/src/index.ts:143-218', + }, + { + id: 'P0-B2', + title: 'World seeding (single system)', + phase: '0', + layer: 'backend', + status: 'done', + desc: 'seedWorld creates Solace system, station, 2 POIs. Hardcoded constants.', + files: 'services/spacetimedb/src/index.ts:138-141, 458-500', + }, + { + id: 'P0-B3', + title: 'Ship state & wallet', + phase: '0', + layer: 'backend', + status: 'done', + desc: 'Ship table (position, flightMode, dockedStationId, cargoCapacity). Wallet table with ISK.', + files: 'services/spacetimedb/src/index.ts:28-45, 93-100', + }, + { + id: 'P0-B4', + title: 'Server event logging', + phase: '0', + layer: 'backend', + status: 'done', + desc: 'ServerEvent table. Every reducer writes a typed event.', + files: 'services/spacetimedb/src/index.ts:114-123, 580-588', + }, + { + id: 'P0-F1', + title: 'SpacetimeDB client connection', + phase: '0', + layer: 'frontend', + status: 'done', + desc: 'Connection lifecycle, table subscriptions, auth token, re-render triggers.', + files: 'apps/game/src/spacetime/client.ts, useSpacetimeConnection.ts, useGameSession.ts', + }, + { + id: 'P0-F2', + title: 'Game shell layout', + phase: '0', + layer: 'frontend', + status: 'partial', + desc: 'Three-column responsive layout with left/right sidebars and center 3D viewport. Missing: center panel area is empty (hidden on large screens). No loading/splash screen.', + detail: 'Middle column
is hidden lg:block with no content. No station mode panel swap. Inline "Pilot" info should be its own component.', + files: 'apps/game/src/GameShell.tsx:70', + }, + { + id: 'P0-F3', + title: 'Connection panel', + phase: '0', + layer: 'frontend', + status: 'done', + desc: 'Connection status, identity, transport/reducer messages, pilot name input, rename/ping.', + files: 'apps/game/src/ui/ConnectionPanel.tsx', + }, + { + id: 'P0-S1', + title: '3D space scene foundation', + phase: '0', + layer: 'scene', + status: 'partial', + desc: 'R3F Canvas, environment (stars, fog, lights), station mesh, asteroid mesh, ship mesh. All clickable.', + detail: 'Missing: camera controls (no orbit/zoom/pan — fixed position). No sun/star light source. No nebulae. Grid plane is debug-only.', + files: 'apps/game/src/scene/', + }, + { + id: 'P0-S2', + title: 'Ship mesh with engine glow', + phase: '0', + layer: 'scene', + status: 'partial', + desc: 'Cone+box+sphere ship. Engine glow changes by flightMode. Bob/roll animation.', + detail: 'Missing: no damage state visualization, no shield bubble, no warp animation, no mining laser beam (only flat Line), no module hardpoints visible.', + files: 'apps/game/src/scene/ShipMesh.tsx', + }, + + // ── PHASE 1: NAVIGATION & EXPLORATION ── + { + id: 'P1-B1', + title: 'Approach operation (start + complete)', + phase: '1', + layer: 'backend', + status: 'done', + desc: 'Timed approach with 5s duration. Validates undocked, target selected, target in system. Position update on complete.', + files: 'services/spacetimedb/src/index.ts:248-299', + }, + { + id: 'P1-B2', + title: 'Target selection', + phase: '1', + layer: 'backend', + status: 'done', + desc: 'selectTarget validates POI exists and is in current system.', + files: 'services/spacetimedb/src/index.ts:236-246', + }, + { + id: 'P1-B3', + title: 'Warp travel between systems', + phase: '1', + layer: 'backend', + status: 'missing', + desc: 'No stargate table, no warp sequence, no jump mechanics. Ship is locked to single system (Solace).', + blockedBy: ['P5-B3'], + }, + { + id: 'P1-B4', + title: 'Stargate network', + phase: '1', + layer: 'backend', + status: 'missing', + desc: 'No stargate table. No gate activation range, jump cooldown, gate cloak. Design describes full stargate mechanics.', + }, + { + id: 'P1-F1', + title: 'Mini star map (SVG)', + phase: '1', + layer: 'frontend', + status: 'done', + desc: '2D SVG top-down system map showing POIs, ship position, operation lines, click-to-select.', + files: 'apps/game/src/ui/MiniStarMap.tsx', + }, + { + id: 'P1-F2', + title: 'Target panel with POI list', + phase: '1', + layer: 'frontend', + status: 'partial', + desc: 'Shows selected target and clickable POI list.', + detail: 'Missing: no distance to target, no spatial grouping, no range info, no sortable overview-style list.', + files: 'apps/game/src/ui/TargetPanel.tsx', + }, + { + id: 'P1-F3', + title: 'Command rail (contextual actions)', + phase: '1', + layer: 'frontend', + status: 'partial', + desc: 'Bottom-fixed action bar with contextual buttons and progress bars for operations.', + detail: 'Missing: no abort/cancel operation, no warp/jump commands, no combat commands, no station service buttons, keyboard shortcut hints not visible.', + files: 'apps/game/src/ui/CommandRail.tsx', + }, + { + id: 'P1-F4', + title: 'Keyboard shortcuts', + phase: '1', + layer: 'frontend', + status: 'partial', + desc: 'U=undock, D=dock, A=approach, M=mine, 1-9=select target.', + detail: 'Missing: no shortcut for sell, refine, fit, combat, warp. No modifier keys. No shortcut discovery overlay.', + files: 'apps/game/src/ui/useKeyboardShortcuts.ts', + }, + { + id: 'P1-S1', + title: 'Ship position interpolation during approach', + phase: '1', + layer: 'scene', + status: 'done', + desc: 'Lerp-based smooth movement from current position to target during approach operation.', + files: 'apps/game/src/scene/GameSpaceScene.tsx', + }, + { + id: 'P1-S2', + title: 'Camera controls (orbit, zoom, pan)', + phase: '1', + layer: 'scene', + status: 'missing', + desc: 'Camera is fixed at [8,9,44] with gentle sine drift. No OrbitControls, no zoom, no user camera interaction.', + }, + { + id: 'P1-S3', + title: 'Warp/travel visual effects', + phase: '1', + layer: 'scene', + status: 'missing', + desc: 'No warp tunnel effect, no gate jump animation, no system transition. Prototype has a full warp sequence demo.', + files: 'Prototype ref: apps/docs/src/prototypes/existing-demos/WarpTravelDemo.tsx', + }, + + // ── PHASE 2: MINING, INVENTORY & TRADE ── + { + id: 'P2-B1', + title: 'Mining operation (start + complete)', + phase: '2', + layer: 'backend', + status: 'done', + desc: 'Timed mining with 6s duration. Validates undocked, at asteroid belt, cargo not full. Adds 1000 Veldspar to cargo.', + files: 'services/spacetimedb/src/index.ts:326-409', + }, + { + id: 'P2-B2', + title: 'Cargo/inventory system', + phase: '2', + layer: 'backend', + status: 'partial', + desc: 'CargoItem table with stacking. Capacity tracking.', + detail: 'Missing: only "ore" category used. No mineral, module, loot, commodity categories. No jettison. No item splitting.', + files: 'services/spacetimedb/src/index.ts:79-91', + }, + { + id: 'P2-B3', + title: 'NPC ore market (sell only)', + phase: '2', + layer: 'backend', + status: 'partial', + desc: 'sellOreToNpcMarket validates docked, ore category, quantity. Credits wallet.', + detail: 'Missing: only 1 ore type (Veldspar) at fixed 12 ISK. No NPC buy orders for minerals/modules. No sell-side NPC. No dynamic pricing. No regional variation. Prices hardcoded in both backend AND frontend.', + files: 'services/spacetimedb/src/index.ts:411-456', + }, + { + id: 'P2-B4', + title: 'Dynamic NPC pricing (supply/demand)', + phase: '2', + layer: 'backend', + status: 'missing', + desc: 'Design describes full NPC pricing algorithm with EMA, demand pressure, regional seeds, anti-arbitrage. Nothing implemented.', + }, + { + id: 'P2-B5', + title: 'Regional price differences', + phase: '2', + layer: 'backend', + status: 'missing', + desc: 'Single system only. No way for prices to differ between stations.', + blockedBy: ['P1-B3', 'P5-B3'], + }, + { + id: 'P2-F1', + title: 'Cargo panel', + phase: '2', + layer: 'frontend', + status: 'partial', + desc: 'Displays cargo items with name, quantity, category, unit price.', + detail: 'Missing: no jettison button, no item stacking/splitting, no item details, no ore-type filtering.', + files: 'apps/game/src/ui/CargoPanel.tsx', + }, + { + id: 'P2-F2', + title: 'NPC market panel', + phase: '2', + layer: 'frontend', + status: 'partial', + desc: 'Sell ore UI with quantity input, max button, payout preview. Only visible when docked.', + detail: 'Missing: only Veldspar. Prices hardcoded client-side in NPC_PRICES constant. No buy orders, no order book, no price history, no charts.', + files: 'apps/game/src/ui/NpcMarketPanel.tsx:5-7', + }, + { + id: 'P2-F3', + title: 'Wallet panel', + phase: '2', + layer: 'frontend', + status: 'partial', + desc: 'Shows ISK balance.', + detail: 'Missing: no transaction history, no income/expenditure breakdown.', + files: 'apps/game/src/ui/WalletPanel.tsx', + }, + { + id: 'P2-F4', + title: 'Ship status panel', + phase: '2', + layer: 'frontend', + status: 'partial', + desc: 'Flight mode badge, hull/shield/speed bars, position, system, operation countdown.', + detail: 'HARDCODED: hullPct=100 always (line 44), shieldPct=Math.random() (line 45), cargoPct always 0 (line 46), speed labels are hardcoded strings (lines 32-42). No armor layer. No capacitor. No power allocation. No module activation indicators.', + files: 'apps/game/src/ui/ShipStatusPanel.tsx:44-46', + }, + { + id: 'P2-S1', + title: 'Asteroid rendering (per-belt clusters)', + phase: '2', + layer: 'scene', + status: 'partial', + desc: '3 asteroids per belt with hardcoded offsets, tumble animation, clickable.', + detail: 'Missing: all identical geometry/color. No ore type differentiation. No depletion state. No mining laser beam (flat Line only).', + files: 'apps/game/src/scene/AsteroidMesh.tsx, GameSpaceScene.tsx:120-124', + }, + { + id: 'P2-S2', + title: 'Mining laser beam visual', + phase: '2', + layer: 'scene', + status: 'missing', + desc: 'Only a flat amber Line drawn during mining. No beam/particle effect connecting ship to asteroid.', + }, + + // ── PHASE 3: COMBAT — FTL POWER ALLOCATION ── + { + id: 'P3-B1', + title: 'NPC pirate entity system', + phase: '3', + layer: 'backend', + status: 'missing', + desc: 'No npc_entities, npc_class_templates, or loot_tables. Design describes full NPC spawning, AI behavior, state machine (IDLE→AGGRO→COMBAT→FLEE→DEAD).', + }, + { + id: 'P3-B2', + title: 'Combat encounter state', + phase: '3', + layer: 'backend', + status: 'missing', + desc: 'No combat session, no damage resolution, no shield/armor/hull tracking, no weapon cycling.', + }, + { + id: 'P3-B3', + title: 'FTL power allocation (weapons/shields/engines/aux)', + phase: '3', + layer: 'backend', + status: 'missing', + desc: 'Design describes 4 subsystems with powered/unpowered behavior, reroute timing (1.5-3s), and failure modes. Nothing implemented.', + }, + { + id: 'P3-B4', + title: 'Ship health model (shield/armor/hull)', + phase: '3', + layer: 'backend', + status: 'missing', + desc: 'Ship table has no health columns. Design describes 3 HP pools with different damage resistance.', + }, + { + id: 'P3-B5', + title: 'Bounty & loot drop system', + phase: '3', + layer: 'backend', + status: 'missing', + desc: 'No bounty ISK awards, no loot tables, no wreck/loot containers. Design describes full bounty tiers and module drops.', + }, + { + id: 'P3-B6', + title: 'Ship destruction & respawn', + phase: '3', + layer: 'backend', + status: 'missing', + desc: 'No death mechanic, no respawn, no insurance payout. Design describes rookie frigate respawn, loot drops, insurance.', + }, + { + id: 'P3-F1', + title: 'Combat HUD (target lock, power allocation, damage)', + phase: '3', + layer: 'frontend', + status: 'missing', + desc: 'No combat UI at all. Design describes target lock reticle, power allocation bars (W/S/E/Aux), shield/armor/hull wheel, capacitor gauge, weapon timer, combat log.', + files: 'Prototype ref: apps/docs/src/prototypes/existing-demos/CombatDemo.tsx, game-slice/ui/SliceCombatStage.tsx', + }, + { + id: 'P3-S1', + title: 'Combat 3D scene (projectiles, shields, explosions)', + phase: '3', + layer: 'scene', + status: 'missing', + desc: 'No NPC ships rendered, no projectiles, no shield effects, no explosions, no damage numbers.', + files: 'Prototype ref: apps/docs/src/prototypes/r3f/combat/CombatScene.tsx', + }, + + // ── PHASE 4: SHIP CUSTOMIZATION & UPGRADES ── + { + id: 'P4-B1', + title: 'Ship types catalog (multiple classes)', + phase: '4', + layer: 'backend', + status: 'missing', + desc: 'Only 1 hardcoded "Starter Frigate". Design describes 5 classes (Frigate through Battleship) with full stat tables.', + }, + { + id: 'P4-B2', + title: 'Module catalog & fitting system', + phase: '4', + layer: 'backend', + status: 'missing', + desc: 'No modules_catalog, no ship_fittings table. Design describes High/Med/Low slot types, CPU/PG constraints, module categories.', + }, + { + id: 'P4-B3', + title: 'Fit/unfit module reducers', + phase: '4', + layer: 'backend', + status: 'missing', + desc: 'No reducers for module management. Design validates CPU/PG budget, slot type matching.', + }, + { + id: 'P4-B4', + title: 'Ship purchase/acquisition', + phase: '4', + layer: 'backend', + status: 'missing', + desc: 'No ship buying, no hangar storage, no ship switching. Design describes NPC market, hangar, and rookie frigate policy.', + }, + { + id: 'P4-F1', + title: 'Fitting screen UI', + phase: '4', + layer: 'frontend', + status: 'missing', + desc: 'No fitting UI. Design describes slot layout with drag-and-drop, CPU/PG bars, module catalog.', + files: 'Prototype ref: apps/docs/src/prototypes/existing-demos/FittingDemo.tsx, game-slice/ui/SliceFittingService.tsx', + }, + { + id: 'P4-F2', + title: 'Ship purchase screen', + phase: '4', + layer: 'frontend', + status: 'missing', + desc: 'No ship shop, no hangar view, no ship comparison UI.', + }, + + // ── PHASE 5: FACTION QUESTS & DYNAMIC WORLD ── + { + id: 'P5-B1', + title: 'Faction & standing system', + phase: '5', + layer: 'backend', + status: 'missing', + desc: 'No factions table, no player standing, no loyalty points. Design describes -10 to +10 standing with faction consequences.', + }, + { + id: 'P5-B2', + title: 'NPC agents & mission templates', + phase: '5', + layer: 'backend', + status: 'missing', + desc: 'No npc_agents, mission_templates, active_missions tables. Design describes 6 mission types (kill, courier, mining, survey, escort, trade) with 4 reward tiers.', + }, + { + id: 'P5-B3', + title: 'Procedural galaxy generation', + phase: '5', + layer: 'backend', + status: 'missing', + desc: 'Single hardcoded system (Solace). Design describes seeded RNG galaxy with MST stargate topology, regions, constellations.', + files: 'Prototype ref: apps/docs/src/prototypes/existing-demos/GalaxyDemo.tsx', + }, + { + id: 'P5-B4', + title: 'Station prosperity (dynamic world impact)', + phase: '5', + layer: 'backend', + status: 'missing', + desc: 'No station prosperity, no resource demand model. Design describes stations growing when supplied, faction leaders taking provinces.', + }, + { + id: 'P5-B5', + title: 'World events (anomalies, faction conflicts)', + phase: '5', + layer: 'backend', + status: 'missing', + desc: 'No event spawning, no participation tracking, no galaxy story log. Design describes 6 event categories with 3 notification tiers.', + }, + { + id: 'P5-F1', + title: 'Agent/mission panel', + phase: '5', + layer: 'frontend', + status: 'missing', + desc: 'No NPC agent list, no available missions, no mission tracker, no standing display.', + }, + { + id: 'P5-F2', + title: 'Galaxy map (multi-system)', + phase: '5', + layer: 'frontend', + status: 'missing', + desc: 'Only single-system SVG map exists. No multi-system navigation, no stargate routes, no faction overlay.', + files: 'Prototype ref: apps/docs/src/prototypes/existing-demos/StarMapDemo.tsx', + }, + + // ── PHASE 6: INDUSTRY & ECONOMY DEPTH ── + { + id: 'P6-B1', + title: 'Refining (ore → minerals)', + phase: '6', + layer: 'backend', + status: 'missing', + desc: 'No refineOre reducer, no mineral item types. Design describes 8 ore types refining to 8 minerals with skill-based yield (50-95%).', + files: 'Prototype ref: apps/docs/src/prototypes/game-slice/sliceEconomy.ts', + }, + { + id: 'P6-B2', + title: 'Manufacturing (minerals → modules/ships)', + phase: '6', + layer: 'backend', + status: 'missing', + desc: 'No blueprints, no manufacturing_jobs, no production chain. Design describes 5-tier chain: Ore → Mineral → Component → Module → Ship.', + }, + { + id: 'P6-B3', + title: 'Multiple ore types', + phase: '6', + layer: 'backend', + status: 'missing', + desc: 'Only Veldspar. Design describes 8 ore types (Veldspar, Scordite, Pyroxeres, Kernite, Omber, Jaspet, Hemorphite, Arkonor).', + }, + { + id: 'P6-F1', + title: 'Refining UI', + phase: '6', + layer: 'frontend', + status: 'missing', + desc: 'No refining interface. Design describes batch processing with yield preview.', + files: 'Prototype ref: apps/docs/src/prototypes/existing-demos/RefiningDemo.tsx, game-slice/ui/SliceRefiningService.tsx', + }, + { + id: 'P6-F2', + title: 'Manufacturing UI', + phase: '6', + layer: 'frontend', + status: 'missing', + desc: 'No manufacturing tab. Design describes blueprint selection, job queue, material requirements.', + }, + { + id: 'P6-F3', + title: 'Market with order book & price history', + phase: '6', + layer: 'frontend', + status: 'missing', + desc: 'No order book, no charts, no bid/ask spread. Only flat NPC buy for ore.', + files: 'Prototype ref: apps/docs/src/prototypes/existing-demos/MarketDemo.tsx', + }, + + // ── PHASE 7: POLISH & PROCEDURAL GALAXY ── + { + id: 'P7-B1', + title: 'XP & skill progression', + phase: '7', + layer: 'backend', + status: 'missing', + desc: 'No skills_catalog, no XP awards. Design describes 5 skills (Mining, Industry, Trade, Gunnery, Navigation) with level 0-5.', + files: 'Prototype ref: apps/docs/src/prototypes/existing-demos/ProgressionDemo.tsx', + }, + { + id: 'P7-B2', + title: 'Tutorial objective chain', + phase: '7', + layer: 'backend', + status: 'missing', + desc: 'No objective tracking. Prototype has 7-objective tutorial chain.', + files: 'Prototype ref: apps/docs/src/prototypes/game-slice/sliceObjectives.ts', + }, + { + id: 'P7-B3', + title: 'Zora ship AI (Tier 0)', + phase: '7', + layer: 'backend', + status: 'missing', + desc: 'No Zora state, no soul depth, no personality axes, no module gating.', + files: 'Prototype ref: apps/docs/src/prototypes/existing-demos/ZoraDemo.tsx', + }, + { + id: 'P7-B4', + title: 'CONCORD / law enforcement', + phase: '7', + layer: 'backend', + status: 'missing', + desc: 'No security status, no CONCORD response, no criminal flagging.', + }, + { + id: 'P7-B5', + title: 'Insurance system', + phase: '7', + layer: 'backend', + status: 'missing', + desc: 'No insurance policies, no coverage tiers, no payout on death.', + }, + { + id: 'P7-F1', + title: 'Tutorial / onboarding flow', + phase: '7', + layer: 'frontend', + status: 'missing', + desc: 'No guided mission sequence, no skip option, no Zora hints.', + }, + { + id: 'P7-F2', + title: 'Full Flight Mode HUD (diegetic)', + phase: '7', + layer: 'frontend', + status: 'missing', + desc: 'No diegetic HUD overlays on 3D viewport. Design describes curved shield/armor bars, module rack, overview panel, targeting reticle, capacitor gauge.', + files: 'Prototype ref: apps/docs/src/prototypes/existing-demos/GameHudDemo.tsx', + }, + { + id: 'P7-F3', + title: 'Station Mode panel UI', + phase: '7', + layer: 'frontend', + status: 'missing', + desc: 'No station service menu. No docked-mode panel swap. GameShell shows same panels regardless of dock/flight state.', + }, + { + id: 'P7-F4', + title: 'Event feed with categories', + phase: '7', + layer: 'frontend', + status: 'partial', + desc: 'Basic event log exists (12 items, no pagination).', + detail: 'Missing: no color coding by event type, no filtering, no combat vs market vs system categorization, no click-to-zoom.', + files: 'apps/game/src/ui/EventFeed.tsx', + }, + { + id: 'P7-F5', + title: 'Fog of war / map reveal', + phase: '7', + layer: 'frontend', + status: 'missing', + desc: 'No fog of war mechanic. All POIs visible immediately in current system.', + }, + + // ── CROSS-CUTTING / INFRASTRUCTURE ── + { + id: 'X-B1', + title: 'Scheduled agents (timed server logic)', + phase: '0', + layer: 'backend', + status: 'missing', + desc: 'All game logic is reducer-call driven. No timed agents for: NPC spawning, combat ticks, economy ticks, world events, balancing.', + }, + { + id: 'X-F1', + title: 'Hardcoded system/station references', + phase: '1', + layer: 'frontend', + status: 'partial', + desc: 'Client subscribes to hardcoded "solace" system and "solace-prime" station. useGameSession hardcodes these lookups.', + detail: 'client.ts:77-78, useGameSession.ts:129-130. Must be replaced with dynamic system lookup once multi-system exists.', + files: 'apps/game/src/spacetime/client.ts:77-78', + }, + { + id: 'X-F2', + title: 'Operation auto-completion (client polling)', + phase: '1', + layer: 'frontend', + status: 'partial', + desc: 'GameSpaceScene polls Date.now() at 120ms intervals to detect operation completion and fire completeApproach/completeMiningCycle.', + detail: 'Works but is client-driven. Design recommends server-authoritative completion via scheduled reducers.', + files: 'apps/game/src/scene/GameSpaceScene.tsx', + }, + { + id: 'X-F3', + title: 'Ambient traffic / other entities', + phase: '2', + layer: 'frontend', + status: 'missing', + desc: 'No other ships visible in space. Prototype has 4 ambient entities (3 friendly + 1 hostile) with orbital movement.', + files: 'Prototype ref: apps/docs/src/prototypes/game-slice/ui/SliceFlightStage.tsx:78-92', + }, +]; + +const PHASE_LABELS: Record = { + '0': 'Phase 0 — Local Skeleton', + '1': 'Phase 1 — Navigation & Exploration', + '2': 'Phase 2 — Mining, Inventory & Trade', + '3': 'Phase 3 — Combat (FTL Power Allocation)', + '4': 'Phase 4 — Ship Customization & Upgrades', + '5': 'Phase 5 — Faction Quests & Dynamic World', + '6': 'Phase 6 — Industry & Economy Depth', + '7': 'Phase 7 — Polish & Procedural Galaxy', +}; + +export function TodoPage() { + const [filterPhase, setFilterPhase] = useState('all'); + const [filterLayer, setFilterLayer] = useState('all'); + const [filterStatus, setFilterStatus] = useState('all'); + + const filtered = useMemo(() => { + return items.filter(item => { + if (filterPhase !== 'all' && item.phase !== filterPhase) return false; + if (filterLayer !== 'all' && item.layer !== filterLayer) return false; + if (filterStatus !== 'all' && item.status !== filterStatus) return false; + return true; + }); + }, [filterPhase, filterLayer, filterStatus]); + + const counts = useMemo(() => { + const byStatus = { done: 0, partial: 0, missing: 0, blocked: 0 }; + const byPhase: Record = {}; + items.forEach(item => { + byStatus[item.status]++; + if (!byPhase[item.phase]) byPhase[item.phase] = { done: 0, partial: 0, missing: 0, blocked: 0, total: 0 }; + byPhase[item.phase][item.status]++; + byPhase[item.phase].total++; + }); + return { byStatus, byPhase }; + }, []); + + const grouped = useMemo(() => { + const groups: Record = {}; + filtered.forEach(item => { + const key = item.phase === '0' && item.id.startsWith('X-') ? 'cross' : item.phase; + if (!groups[key]) groups[key] = []; + groups[key].push(item); + }); + return groups; + }, [filtered]); + + const phaseOrder = ['0', '1', '2', '3', '4', '5', '6', '7', 'cross']; + const sortedGroups = phaseOrder.filter(p => grouped[p]).map(p => ({ + key: p, + label: p === 'cross' ? 'Cross-Cutting / Infrastructure' : PHASE_LABELS[p as Phase], + items: grouped[p], + })); + + return ( +
+

Implementation Status

+

+ Living TODO document tracking what's built vs. what's designed. Auto-generated from codebase review on {LAST_UPDATED}. + Each item maps to a specific phase, layer, and file reference. +

+ +
+
+
{counts.byStatus.done}
+
Done
+
+
+
{counts.byStatus.partial}
+
Partial
+
+
+
{counts.byStatus.missing}
+
Missing
+
+
+
{counts.byStatus.blocked}
+
Blocked
+
+
+ +
+ Backend coverage: {BACKEND_TABLES_IMPLEMENTED} / {BACKEND_TABLES_DESIGNED} tables ({Math.round(BACKEND_TABLES_IMPLEMENTED / BACKEND_TABLES_DESIGNED * 100)}%). + {BACKEND_REDUCERS_IMPLEMENTED} / {BACKEND_REDUCERS_DESIGNED} reducers ({Math.round(BACKEND_REDUCERS_IMPLEMENTED / BACKEND_REDUCERS_DESIGNED * 100)}%). + All 14 interactive demos and the full game-slice prototype live in apps/docs/src/prototypes/ using localStorage — these serve as reference implementations for migration into the SpacetimeDB backend. +
+ +
+ + + + + {filtered.length} / {items.length} items + +
+ + {sortedGroups.map(group => { + const phaseCounts = group.key !== 'cross' ? counts.byPhase[group.key] : null; + const donePct = phaseCounts ? Math.round(((phaseCounts.done + phaseCounts.partial * 0.5) / phaseCounts.total) * 100) : null; + + return ( +
+
+

{group.label}

+ {phaseCounts && ( +
+ {phaseCounts.done} done + {phaseCounts.partial} partial + {phaseCounts.missing + phaseCounts.blocked} missing +
+
70 ? 'var(--green)' : donePct > 30 ? 'var(--accent)' : 'var(--red)', + }} /> +
+ {donePct}% +
+ )} +
+ +
+ + + + + + + + + + + + {group.items.map(item => ( + + + + + + + + ))} + +
IDStatusLayerTitleDetails
{item.id} + + {STATUS_LABELS[item.status]} + + {LAYER_LABELS[item.layer]}{item.title} +
{item.desc}
+ {item.detail && ( +
+ {item.detail} +
+ )} + {item.files && ( +
+ {item.files} +
+ )} + {item.blockedBy && ( +
+ Blocked by: {item.blockedBy.join(', ')} +
+ )} +
+
+
+ ); + })} +
+ ); +} diff --git a/apps/docs/src/prototypes/AGENTS.md b/apps/docs/src/prototypes/AGENTS.md new file mode 100644 index 0000000..1341d1b --- /dev/null +++ b/apps/docs/src/prototypes/AGENTS.md @@ -0,0 +1,21 @@ +# apps/docs/src/prototypes/ — Interactive Prototypes + +Browser-based prototypes that validate game subsystems before backend migration. All run in-browser with localStorage — no SpacetimeDB connection. + +## Structure + +| Directory | Description | +|-----------|-------------| +| `game-slice/` | Full MVP loop prototype (undock → navigate → mine → dock → refine → fit → sell → combat) | +| `existing-demos/` | 14 standalone demos, each validating one game subsystem | +| `r3f/` | Reusable React Three Fiber 3D scenes shared across demos | +| `standalone-huds/` | HUD prototype frames and wrappers | + +## Relationship to Production Code + +Prototypes are **reference implementations**. When a system is ready for production, the prototype logic migrates: +1. State management (localStorage) → SpacetimeDB tables +2. Event sourcing → SpacetimeDB reducers +3. UI components → `apps/game/src/ui/` components + +The game-slice prototype has the richest implementation of combat, fitting, refining, skills, objectives, and Zora — all of which need backend tables before they can migrate. diff --git a/apps/docs/src/prototypes/existing-demos/AGENTS.md b/apps/docs/src/prototypes/existing-demos/AGENTS.md new file mode 100644 index 0000000..ab22b32 --- /dev/null +++ b/apps/docs/src/prototypes/existing-demos/AGENTS.md @@ -0,0 +1,22 @@ +# apps/docs/src/prototypes/existing-demos/ — Standalone Game Demos + +14 interactive demo components, each validating one game subsystem. Linked from the docs Demo Gallery page. + +| Demo | File | Validates | +|------|------|-----------| +| MVP Loop Slice | `GameLoopSliceDemo.tsx` | Full playable game loop via game-slice prototype | +| Star Map | `StarMapDemo.tsx` | Era 2 galaxy map with multi-system view, warp routes | +| Ship Movement | `ShipMovementDemo.tsx` | Sub-warp movement model | +| Warp Travel | `WarpTravelDemo.tsx` | Warp sequence (align → accelerate → cruise → exit) | +| Combat | `CombatDemo.tsx` | 3D combat with power allocation, projectiles, subsystem damage | +| Market | `MarketDemo.tsx` | Full order book with bid/ask spread, price history, ~20 commodities | +| Ship Fitting | `FittingDemo.tsx` | Module fitting with CPU/PG constraints, slot types | +| Refining | `RefiningDemo.tsx` | Ore-to-mineral refining + manufacturing recipes | +| Skill Progression | `ProgressionDemo.tsx` | XP curve, skill levels, training queue | +| Bounty | `BountyDemo.tsx` | Bounty tiers, kill feed, player-placed bounties | +| Game HUD | `GameHudDemo.tsx` | Flight mode diegetic overlays on 3D scene | +| Chat | `ChatDemo.tsx` | 4 channels with light-speed delay simulation | +| Zora Tier 0 | `ZoraDemo.tsx` | Deterministic template engine with soul depth progression | +| Galaxy Generator | `GalaxyDemo.tsx` | Seeded RNG galaxy generation with MST stargate topology | + +All demos use the shared R3F scenes in `../r3f/`. diff --git a/apps/docs/src/prototypes/game-slice/AGENTS.md b/apps/docs/src/prototypes/game-slice/AGENTS.md new file mode 100644 index 0000000..8f8ce21 --- /dev/null +++ b/apps/docs/src/prototypes/game-slice/AGENTS.md @@ -0,0 +1,42 @@ +# apps/docs/src/prototypes/game-slice/ — MVP Loop Prototype + +A self-contained, playable implementation of the full MVP game loop using localStorage for persistence. This is the **most complete gameplay implementation** in the repo — every other game system prototype feeds into or from this slice. + +## Core Files + +| File | Purpose | +|------|---------| +| `types.ts` | Full type system: 8 flight modes, 19 event types, combat modules, skills, objectives, Zora state | +| `gameSliceState.ts` | Event sourcing engine: `applySliceEvent()` handles 19 event types, localStorage persistence, XP awards | +| `sliceController.ts` | Action gating: `getContextualSliceActions()` returns enabled/disabled actions with reasons | +| `sliceObjectives.ts` | 7-objective tutorial chain: undock → navigate → mine → dock → refine → fit → sell (+ optional combat) | +| `sliceWorld.ts` | World constants: 2 POIs (station + belt), travel durations, ship stats | +| `sliceEconomy.ts` | Cargo management (upsert/remove/clamp), refining (Veldspar → Tritanium + Pyerite), selling | +| `sliceNavigationBridge.ts` | Bridge between game-slice state and warp travel sessions | +| `useGameSliceSession.ts` | React hook: reads/writes localStorage, exposes session + event dispatcher | +| `useSliceController.ts` | React hook: derives contextual actions and computed facts from session | +| `SeamlessGameLoopSlice.tsx` | Root component: composes all stages with mode-based rendering | + +## UI Components (`ui/`) + +22 components covering every game state: +- Station: `SliceStationStage`, `SliceStationPanel` +- Flight: `SliceFlightStage` (with ambient traffic entities, context menus) +- Travel: `SliceTravelStage` +- Mining: `SliceMiningStage` +- Combat: `SliceCombatStage` +- Services: `SliceServicesStage`, `SliceRefiningService`, `SliceFittingService`, `SliceMarketService` +- Shell: `SliceShell`, `SliceTopBar`, `SliceActionRail`, `SliceObjectiveTracker` +- Shared: `SliceModuleRack`, `SliceShipStatus`, `SliceCargoPanel`, `SliceEventLog`, `SliceProgressBar`, `SliceStage`, `SliceDemoLinks`, `sliceStyles` + +## Systems Implemented (not yet in production) + +| System | Status | Production Target | +|--------|--------|-------------------| +| Refining (ore → minerals) | Working | Phase 6 | +| Fitting (module slots + CPU/PG) | Working | Phase 4 | +| Combat (FTL power allocation) | Working | Phase 3 | +| XP/Skills (5 skills, level 0-5) | Working | Phase 7 | +| Objective chain (tutorial) | Working | Phase 7 | +| Zora modules (personality + soul depth) | Partial | Phase 7 | +| Multi-system travel (5 systems) | Working | Phase 5 | diff --git a/apps/docs/src/prototypes/r3f/AGENTS.md b/apps/docs/src/prototypes/r3f/AGENTS.md new file mode 100644 index 0000000..4fd5b20 --- /dev/null +++ b/apps/docs/src/prototypes/r3f/AGENTS.md @@ -0,0 +1,14 @@ +# apps/docs/src/prototypes/r3f/ — Reusable 3D Scenes + +Shared React Three Fiber scenes used by the demos in `../existing-demos/` and the game-slice prototype. + +| Directory | Contains | +|-----------|----------| +| `combat/` | Combat scene, combat state machine, combat math (damage, range, tracking) | +| `galaxy/` | Galaxy generation and visualization | +| `hud/` | Diegetic HUD elements for flight mode | +| `movement/` | Ship movement and interpolation | +| `navigation/` | Travel session management, route planning | +| `shared/` | Common utilities, camera controls, lighting | +| `starmap/` | Star map 3D visualization | +| `warp/` | Warp tunnel and warp sequence effects | diff --git a/apps/game/AGENTS.md b/apps/game/AGENTS.md new file mode 100644 index 0000000..a89d22f --- /dev/null +++ b/apps/game/AGENTS.md @@ -0,0 +1,58 @@ +# apps/game — Playable Game Client + +Package: `@void-nav/game` +Port: `http://localhost:5175` + +The **standalone playable game** connected to a SpacetimeDB backend. This is the production game client — all other apps are documentation or marketing. + +## Current State (Phase 0-2) + +The game implements the basic loop: **undock → select target → approach asteroid → mine ore → dock → sell ore**. Keyboard shortcuts (U/D/A/M/1-9) and 3D click selection work. SVG mini-map and 3D scene stay synchronized through SpacetimeDB subscriptions. + +## Structure + +``` +src/ + main.tsx Entry point, renders GameShell into #root + GameShell.tsx Root layout: 3-column grid (left sidebar, center 3D scene, right sidebar, bottom command rail) + module_bindings/ Auto-generated SpacetimeDB TypeScript bindings (24 files) + types.ts All table row types and reducer call types + ... One file per table + connection builder + scene/ 3D rendering (React Three Fiber) + SpaceCanvas.tsx R3F Canvas wrapper (camera at [8,9,44], FOV 48) + SpaceEnvironment.tsx Background, fog, lights, star field + GameSpaceScene.tsx Main orchestrator: places stations/asteroids/ship, manages operations, interpolates movement + StationMesh.tsx Torus + cylinder station, clickable, selection ring + AsteroidMesh.tsx Icosahedron asteroid, tumble animation, clickable + ShipMesh.tsx Cone + box + sphere ship, engine glow by mode, bob animation + spacetime/ SpacetimeDB connection layer + client.ts Connection factory, table subscriptions, auth token storage + useSpacetimeConnection.ts React hook: connection state, identity, revision polling + useGameSession.ts React hook: reads all tables into GameSession object, exposes action callbacks for all reducers + ui/ UI panels + CommandRail.tsx Bottom-fixed contextual action bar with progress bars + ConnectionPanel.tsx Dev diagnostic: connection status, identity, rename/ping + CargoPanel.tsx Cargo hold items with quantity and price + NpcMarketPanel.tsx Sell ore at fixed NPC price (docked only) + WalletPanel.tsx ISK balance display + ShipStatusPanel.tsx Hull/shield/speed bars, position, operation timer + TargetPanel.tsx Selected target info, clickable POI list + MiniStarMap.tsx SVG 2D system map with ship position and operation lines + EventFeed.tsx Server event log (last 12 events) + useKeyboardShortcuts.ts U=undock, D=dock, A=approach, M=mine, 1-9=select target + styles/ + tailwind.css Tailwind v4 entry point +``` + +## SpacetimeDB Tables (9) and Reducers (12) + +See `services/spacetimedb/AGENTS.md` for full backend details. + +## Known Hardcoded Values + +- `client.ts:77-78` — subscriptions hardcoded to `solace` system and `solace-prime` station +- `useGameSession.ts:129-130` — system/station lookup hardcoded to specific IDs +- `ShipStatusPanel.tsx:44` — hullPct always 100 (no damage model) +- `ShipStatusPanel.tsx:45` — shieldPct uses Math.random() (not server data) +- `NpcMarketPanel.tsx:5-7` — NPC_PRICES hardcoded client-side (only Veldspar at 12 ISK) +- `GameSpaceScene.tsx:120-124` — asteroid cluster offsets hardcoded (3 rocks per belt) diff --git a/apps/game/src/GameShell.tsx b/apps/game/src/GameShell.tsx index 31f19bb..e0c7195 100644 --- a/apps/game/src/GameShell.tsx +++ b/apps/game/src/GameShell.tsx @@ -5,7 +5,11 @@ import { CommandRail } from "./ui/CommandRail"; import { CargoPanel } from "./ui/CargoPanel"; import { ConnectionPanel } from "./ui/ConnectionPanel"; import { EventFeed } from "./ui/EventFeed"; +import { MiniStarMap } from "./ui/MiniStarMap"; +import { NpcMarketPanel } from "./ui/NpcMarketPanel"; +import { ShipStatusPanel } from "./ui/ShipStatusPanel"; import { TargetPanel } from "./ui/TargetPanel"; +import { useKeyboardShortcuts } from "./ui/useKeyboardShortcuts"; import { WalletPanel } from "./ui/WalletPanel"; import { useGameSession } from "./spacetime/useGameSession"; import { useSpacetimeConnection } from "./spacetime/useSpacetimeConnection"; @@ -33,6 +37,22 @@ export function GameShell() { const selectedPoi = session.pois.find((poi) => poi.poiId === session.ship?.selectedPoiId); const pilotName = session.player?.displayName ?? displayName; + const keyboardActions = useMemo( + () => ({ + onUndock: session.actions.undock, + onStartApproach: session.actions.startApproach, + onDock: session.actions.dock, + onStartMining: session.actions.startMining, + onSelectTarget: (index: number) => { + const poi = session.pois[index]; + if (poi) session.actions.selectTarget(poi.poiId); + }, + }), + [session.actions, session.pois], + ); + + useKeyboardShortcuts(session.ship, session.pois, session.operation, keyboardActions); + return (
@@ -72,6 +92,7 @@ export function GameShell() { + @@ -79,7 +100,14 @@ export function GameShell() {
diff --git a/apps/game/src/scene/AGENTS.md b/apps/game/src/scene/AGENTS.md new file mode 100644 index 0000000..45733fd --- /dev/null +++ b/apps/game/src/scene/AGENTS.md @@ -0,0 +1,24 @@ +# apps/game/src/scene/ — 3D Rendering (React Three Fiber) + +The 3D viewport for the game client. Renders the player's ship, surrounding space environment, stations, asteroids, and visual effects. + +## Files + +| File | Purpose | +|------|---------| +| `SpaceCanvas.tsx` | R3F Canvas wrapper. Fixed camera at [8,9,44], FOV 48. No user camera controls yet. | +| `SpaceEnvironment.tsx` | Background (#040712), fog, ambient light, two point lights (cyan + amber), animated star field (3600 stars) | +| `GameSpaceScene.tsx` | Main orchestrator. Renders stations, asteroid clusters, ship. Manages approach/mining operations. Interpolates ship position. Draws approach line (dashed cyan) and mining line (solid amber). Auto-completes operations via 120ms polling interval. | +| `StationMesh.tsx` | Torus ring + cylinder core + crossbar. Clickable. Selection ring when targeted. | +| `AsteroidMesh.tsx` | Icosahedron with tumble animation. Clickable. Selection ring when targeted. | +| `ShipMesh.tsx` | Cone (hull) + box (wings) + sphere (engine glow). Engine glow changes by flight mode. Subtle bob + roll animation. | + +## Visual Gaps + +- No mining laser beam effect (flat Line only) +- No warp/travel animation +- No combat visuals (projectiles, shields, explosions) +- No NPC/other player ships +- No camera controls (orbit, zoom, pan) +- No sun/star light source +- No nebulae or system-specific skybox diff --git a/apps/game/src/spacetime/AGENTS.md b/apps/game/src/spacetime/AGENTS.md new file mode 100644 index 0000000..e94f1b0 --- /dev/null +++ b/apps/game/src/spacetime/AGENTS.md @@ -0,0 +1,32 @@ +# apps/game/src/spacetime/ — SpacetimeDB Connection Layer + +Bridge between the game client and the SpacetimeDB backend. Manages connection lifecycle, table subscriptions, and exposes a typed React hook for the game session. + +## Files + +| File | Purpose | +|------|---------| +| `client.ts` | Connection factory. Creates `DbConnection`, subscribes to all 9 tables (hardcoded to `solace` system), registers table-change listeners for React re-renders, manages auth token in localStorage. Auto-calls `seedWorld` and `connectPlayer` on every connection. | +| `useSpacetimeConnection.ts` | React hook wrapping `client.ts`. Exposes connection state (idle/connecting/connected/error), identity, and a 1.5s revision-poll trigger. | +| `useGameSession.ts` | Main data/action bridge. Reads all 9 SpacetimeDB tables into a typed `GameSession` object. Exposes action callbacks for all 12 reducers. Hardcodes system/station lookups to `solace`/`solace-prime`. | + +## Subscriptions (hardcoded) + +```sql +SELECT * FROM player +SELECT * FROM ship +SELECT * FROM system WHERE system_id = 'solace' +SELECT * FROM station WHERE station_id = 'solace-prime' +SELECT * FROM point_of_interest WHERE system_id = 'solace' +SELECT * FROM cargo_item +SELECT * FROM wallet +SELECT * FROM ship_operation +SELECT * FROM server_event +``` + +## Known Issues + +- System/station subscriptions hardcoded to `solace`/`solace-prime` — must be dynamic for multi-system +- No reconnection logic (no exponential backoff) +- Auth token stored in localStorage (acceptable for auth, but design says no localStorage for game state) +- Events capped at 12 in useGameSession (no pagination) diff --git a/apps/game/src/spacetime/usePlayerSession.ts b/apps/game/src/spacetime/usePlayerSession.ts deleted file mode 100644 index 44b2229..0000000 --- a/apps/game/src/spacetime/usePlayerSession.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { useMemo } from "react"; -import type { DbConnection } from "../module_bindings"; -import type { Player, ServerEvent, Ship, Station, System } from "../module_bindings/types"; -import { invokeReducer } from "./client"; - -type ReducerReporter = (message: string, isError?: boolean) => void; - -export function usePlayerSession(connection: DbConnection | null, revision: number, onReducerStatus: ReducerReporter) { - const rows = useMemo(() => readRows(connection), [connection, revision]); - - return { - ...rows, - renamePlayer(displayName: string) { - if (!connection) { - onReducerStatus("renamePlayer unavailable until connected", true); - return; - } - invokeReducer({ onReducerStatus }, "renamePlayer", () => connection.reducers.renamePlayer({ displayName })); - }, - ping() { - if (!connection) { - onReducerStatus("ping unavailable until connected", true); - return; - } - invokeReducer({ onReducerStatus }, "ping", () => connection.reducers.ping({})); - }, - }; -} - -function readRows(connection: DbConnection | null) { - const players = readTable(connection?.db.player); - const ships = readTable(connection?.db.ship); - const systems = readTable(connection?.db.system); - const stations = readTable(connection?.db.station); - const events = readTable(connection?.db.server_event).sort((a, b) => compareBigintsDesc(a.eventId, b.eventId)); - - return { - player: players[0], - ship: ships[0], - system: systems.find((row) => row.systemId === "solace") ?? systems[0], - station: stations.find((row) => row.stationId === "solace-prime") ?? stations[0], - events: events.slice(0, 6), - }; -} - -function readTable(table?: { iter?: () => Iterable }): Row[] { - if (!table?.iter) return []; - try { - return Array.from(table.iter()); - } catch { - return []; - } -} - -function compareBigintsDesc(left: bigint, right: bigint) { - if (left === right) return 0; - return left > right ? -1 : 1; -} diff --git a/apps/game/src/spacetime/useSpacetimeConnection.ts b/apps/game/src/spacetime/useSpacetimeConnection.ts index d3a5be6..2e3b2aa 100644 --- a/apps/game/src/spacetime/useSpacetimeConnection.ts +++ b/apps/game/src/spacetime/useSpacetimeConnection.ts @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import type { DbConnection } from "../module_bindings"; import { createSpacetimeConnection, type ConnectionStatus, type IdentityLike } from "./client"; @@ -22,10 +22,13 @@ export function useSpacetimeConnection(displayName: string) { [], ); + const displayNameRef = useRef(displayName); + displayNameRef.current = displayName; + useEffect(() => { const conn = createSpacetimeConnection({ ...config, - displayName, + displayName: displayNameRef.current, onStatus: (nextStatus, nextMessage) => { setStatus(nextStatus); setMessage(nextMessage); @@ -45,7 +48,7 @@ export function useSpacetimeConnection(displayName: string) { window.clearInterval(refresh); conn?.disconnect?.(); }; - }, [config, displayName]); + }, [config]); return { connection, diff --git a/apps/game/src/ui/AGENTS.md b/apps/game/src/ui/AGENTS.md new file mode 100644 index 0000000..a3a885b --- /dev/null +++ b/apps/game/src/ui/AGENTS.md @@ -0,0 +1,18 @@ +# apps/game/src/ui/ — Game UI Panels + +All UI components for the game client. Composed by `GameShell.tsx` into a three-column layout with a bottom command rail. + +## Files + +| File | Purpose | Status | +|------|---------|--------| +| `CommandRail.tsx` | Bottom-fixed contextual action bar. Shows progress bars during operations, context-appropriate buttons otherwise. Covers: undock, approach, dock, mine, sell. | Partial — missing abort, warp, combat, station service buttons | +| `ConnectionPanel.tsx` | Dev diagnostic panel: connection status, identity, endpoint, pilot name input, rename/ping buttons. | Done — but this is a dev tool, not player-facing | +| `CargoPanel.tsx` | Cargo hold display: fill bar + item list with name, quantity, category, unit price. | Partial — no jettison, no stacking/splitting | +| `NpcMarketPanel.tsx` | Sell ore to NPCs. Quantity input, max button, payout preview. Only visible when docked. Prices hardcoded client-side. | Partial — only Veldspar, hardcoded prices, sell-only | +| `WalletPanel.tsx` | ISK balance display. | Partial — no transaction history | +| `ShipStatusPanel.tsx` | Flight mode badge, hull/shield/speed bars, position, operation countdown. | Partial — hull always 100%, shields random, speed hardcoded | +| `TargetPanel.tsx` | Selected target info, clickable POI list. | Partial — no distance, no sorting | +| `MiniStarMap.tsx` | SVG 2D system map: POIs, ship position, operation lines, click-to-select. | Done — single system only | +| `EventFeed.tsx` | Server event log (last 12 events, type + timestamp + message). | Partial — no filtering, no pagination, no color coding | +| `useKeyboardShortcuts.ts` | U=undock, D=dock, A=approach, M=mine, 1-9=select target. | Partial — no sell/refine/fit/combat/warp shortcuts | diff --git a/apps/game/src/ui/CargoPanel.tsx b/apps/game/src/ui/CargoPanel.tsx index f337e37..b2d99be 100644 --- a/apps/game/src/ui/CargoPanel.tsx +++ b/apps/game/src/ui/CargoPanel.tsx @@ -4,15 +4,23 @@ import type { CargoItem, Ship } from "../module_bindings/types"; export function CargoPanel({ cargo, ship }: { cargo: CargoItem[]; ship?: Ship }) { const used = cargo.reduce((total, item) => total + item.quantity, 0n); const capacity = ship?.cargoCapacity ?? 0n; + const fillPct = capacity > 0n ? Math.min(100, Math.round((Number(used) / Number(capacity)) * 100)) : 0; + const barColor = fillPct >= 90 ? "#ef4444" : fillPct >= 60 ? "#f0a030" : "#22d3ee"; return (

Cargo

- {used.toString()} / {capacity.toString()} + {used.toString()} / {capacity.toString()} m³
+
+
+
{cargo.length > 0 ? ( cargo.map((item) => ( @@ -22,7 +30,7 @@ export function CargoPanel({ cargo, ship }: { cargo: CargoItem[]; ship?: Ship }) {item.quantity.toString()}
- {item.category} / {item.unitPrice.toString()} ISK + {item.category} / {item.unitPrice.toString()} ISK per unit
)) diff --git a/apps/game/src/ui/CommandRail.tsx b/apps/game/src/ui/CommandRail.tsx index 6396834..ae90cde 100644 --- a/apps/game/src/ui/CommandRail.tsx +++ b/apps/game/src/ui/CommandRail.tsx @@ -1,3 +1,4 @@ +import { useEffect, useState } from "react"; import { Button } from "@void-nav/ui"; import type { CargoItem, PointOfInterest, Ship, ShipOperation } from "../module_bindings/types"; @@ -25,14 +26,39 @@ export function CommandRail({ const selected = pois.find((poi) => poi.poiId === ship?.selectedPoiId); const current = pois.find((poi) => poi.poiId === ship?.currentPoiId); const ore = cargo.find((item) => item.category === "ore" && item.quantity > 0n); + const [nowMs, setNowMs] = useState(() => Date.now()); + + useEffect(() => { + if (!operation) return; + const timer = window.setInterval(() => setNowMs(Date.now()), 80); + return () => window.clearInterval(timer); + }, [operation]); let content = Connect to SpacetimeDB to initialize the starter ship.; if (ship) { if (operation?.operationType === "approach") { - content = ; + const progress = getOperationProgress(operation, nowMs); + content = ( +
+
+ Approaching target... + {Math.round(progress * 100)}% +
+ +
+ ); } else if (operation?.operationType === "mining") { - content = ; + const progress = getOperationProgress(operation, nowMs); + content = ( +
+
+ Mining in progress... + {Math.round(progress * 100)}% +
+ +
+ ); } else if (ship.dockedStationId.length > 0) { content = ( <> @@ -72,6 +98,24 @@ export function CommandRail({ ); } +function ProgressBar({ progress, color }: { progress: number; color: string }) { + return ( +
+
+
+ ); +} + +function getOperationProgress(operation: ShipOperation, nowMs: number) { + const startedAt = Number(operation.startedAt.toMillis()); + const duration = Number(operation.durationMs); + if (duration <= 0) return 1; + return Math.max(0, Math.min(1, (nowMs - startedAt) / duration)); +} + function cargoUsed(cargo: CargoItem[]) { return cargo.reduce((total, item) => total + item.quantity, 0n); } diff --git a/apps/game/src/ui/MiniStarMap.tsx b/apps/game/src/ui/MiniStarMap.tsx new file mode 100644 index 0000000..dba8882 --- /dev/null +++ b/apps/game/src/ui/MiniStarMap.tsx @@ -0,0 +1,149 @@ +import { Panel } from "@void-nav/ui"; +import type { PointOfInterest, Ship, ShipOperation } from "../module_bindings/types"; + +export function MiniStarMap({ + ship, + pois, + operation, + onSelectPoi, +}: { + ship?: Ship; + pois: PointOfInterest[]; + operation?: ShipOperation; + onSelectPoi: (poiId: string) => void; +}) { + const bounds = getBounds(pois); + const mapSize = 280; + const padding = 24; + + return ( + +

System Map

+ + + {pois.map((poi) => { + const pos = projectPoi(poi, bounds, mapSize, padding); + const isCurrent = poi.poiId === ship?.currentPoiId; + const isSelected = poi.poiId === ship?.selectedPoiId; + return ( + onSelectPoi(poi.poiId)} style={{ cursor: "pointer" }}> + {isSelected ? ( + + ) : null} + {isCurrent ? ( + + ) : null} + {poi.poiType === "station" ? ( + + ) : ( + + )} + + {poi.name} + + + ); + })} + {ship ? (() => { + const pos = projectXY(ship.x, ship.y, bounds, mapSize, padding); + return ( + + + + {operation && (() => { + const target = pois.find((p) => p.poiId === operation.targetPoiId); + if (!target) return null; + const tpos = projectPoi(target, bounds, mapSize, padding); + return ( + + ); + })()} + + ); + })() : null} + +
+ + + Ship + + + + Station + + + + Belt + +
+
+ ); +} + +function StationIcon({ x, y }: { x: number; y: number }) { + return ; +} + +function BeltIcon({ x, y }: { x: number; y: number }) { + return ; +} + +function GridOverlay({ size }: { size: number }) { + const lines = []; + const step = size / 6; + for (let i = 1; i < 6; i++) { + lines.push( + , + , + ); + } + return <>{lines}; +} + +type Bounds = { minX: number; maxX: number; minY: number; maxY: number }; + +function getBounds(pois: PointOfInterest[]): Bounds { + if (pois.length === 0) return { minX: -50, maxX: 50, minY: -50, maxY: 50 }; + let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity; + for (const poi of pois) { + if (poi.x < minX) minX = poi.x; + if (poi.x > maxX) maxX = poi.x; + if (poi.y < minY) minY = poi.y; + if (poi.y > maxY) maxY = poi.y; + } + const padX = Math.max(5, (maxX - minX) * 0.15); + const padY = Math.max(5, (maxY - minY) * 0.15); + return { minX: minX - padX, maxX: maxX + padX, minY: minY - padY, maxY: maxY + padY }; +} + +function projectPoi(poi: PointOfInterest, bounds: Bounds, size: number, padding: number) { + return projectXY(poi.x, poi.y, bounds, size, padding); +} + +function projectXY(x: number, y: number, bounds: Bounds, size: number, padding: number) { + const rangeX = bounds.maxX - bounds.minX || 1; + const rangeY = bounds.maxY - bounds.minY || 1; + const drawSize = size - padding * 2; + return { + x: padding + ((x - bounds.minX) / rangeX) * drawSize, + y: padding + drawSize - ((y - bounds.minY) / rangeY) * drawSize, + }; +} diff --git a/apps/game/src/ui/NpcMarketPanel.tsx b/apps/game/src/ui/NpcMarketPanel.tsx new file mode 100644 index 0000000..98127cf --- /dev/null +++ b/apps/game/src/ui/NpcMarketPanel.tsx @@ -0,0 +1,110 @@ +import { useState } from "react"; +import { Button, Panel } from "@void-nav/ui"; +import type { CargoItem, Ship, Wallet } from "../module_bindings/types"; + +const NPC_PRICES: Record = { + "ore-veldspar": { name: "Veldspar", buyPrice: 12n }, +}; + +export function NpcMarketPanel({ + ship, + cargo, + wallet, + onSellOre, +}: { + ship?: Ship; + cargo: CargoItem[]; + wallet?: Wallet; + onSellOre: (itemId: string, quantity: bigint) => void; +}) { + const [sellAmounts, setSellAmounts] = useState>({}); + + if (!ship || ship.dockedStationId.length === 0) { + return null; + } + + const oreItems = cargo.filter((item) => item.category === "ore" && item.quantity > 0n); + if (oreItems.length === 0) { + return ( + +

NPC Market

+

No ore to sell. Go mine some asteroids first.

+
+ ); + } + + return ( + +
+

NPC Market

+ Buy Orders +
+
+ {oreItems.map((item) => { + const pricing = NPC_PRICES[item.itemId]; + const unitPrice = pricing?.buyPrice ?? item.unitPrice; + const rawAmount = sellAmounts[item.itemId] ?? ""; + const parsedAmount = parseBigint(rawAmount); + const sellQty = parsedAmount > 0n ? minBigint(parsedAmount, item.quantity) : 0n; + const payout = sellQty * unitPrice; + + return ( +
+
+ {item.itemName} + {item.quantity.toString()} units +
+
+ setSellAmounts((prev) => ({ ...prev, [item.itemId]: e.target.value }))} + className="min-h-8 w-24 rounded border border-border bg-surface-raised px-2 py-1 text-sm text-fg outline-none focus:border-cyan" + /> + + + {payout > 0n ? `+${payout.toLocaleString()} ISK` : `${unitPrice}/u`} + +
+ {sellQty > 0n ? ( + + ) : null} +
+ ); + })} +
+
+ ); +} + +function parseBigint(value: string): bigint { + try { + const n = BigInt(value); + return n >= 0n ? n : 0n; + } catch { + return 0n; + } +} + +function minBigint(left: bigint, right: bigint) { + return left < right ? left : right; +} diff --git a/apps/game/src/ui/ShipStatusPanel.tsx b/apps/game/src/ui/ShipStatusPanel.tsx new file mode 100644 index 0000000..3e03f10 --- /dev/null +++ b/apps/game/src/ui/ShipStatusPanel.tsx @@ -0,0 +1,130 @@ +import { useEffect, useState } from "react"; +import { Panel } from "@void-nav/ui"; +import type { Ship, ShipOperation } from "../module_bindings/types"; + +export function ShipStatusPanel({ + ship, + operation, +}: { + ship?: Ship; + operation?: ShipOperation; +}) { + const [nowMs, setNowMs] = useState(() => Date.now()); + + useEffect(() => { + const timer = window.setInterval(() => setNowMs(Date.now()), 200); + return () => window.clearInterval(timer); + }, []); + + if (!ship) { + return ( + +

Ship Status

+

Awaiting ship data...

+
+ ); + } + + const flightModeLabel = formatFlightMode(ship.flightMode); + const isOperating = !!operation; + + let speedLabel = "0 m/s"; + let speedPct = 0; + if (operation?.operationType === "approach") { + speedPct = 75; + speedLabel = "~2,450 m/s"; + } else if (ship.flightMode === "flight") { + speedPct = 15; + speedLabel = "~180 m/s"; + } else if (ship.flightMode === "mining") { + speedPct = 0; + speedLabel = "0 m/s"; + } + + const hullPct = 100; + const shieldPct = isOperating ? Math.max(0, 80 - Math.random() * 5) : 100; + const cargoPct = ship.cargoCapacity > 0n ? 0 : 0; + + return ( + +
+

Ship Status

+ + {flightModeLabel} + +
+ +
+ + + +
+ +
+ + + + {isOperating && operation ? ( + <> + + + + ) : null} +
+
+ ); +} + +function StatusBar({ label, value, color }: { label: string; value: number; color: string }) { + return ( +
+ {label} +
+
+
+
+ {value}% +
+
+ ); +} + +function InfoRow({ label, value }: { label: string; value: string }) { + return ( +
+
{label}
+
{value}
+
+ ); +} + +function formatFlightMode(mode: string) { + switch (mode) { + case "docked": return "Docked"; + case "flight": return "In Flight"; + case "approaching": return "Approaching"; + case "mining": return "Mining"; + default: return mode || "Offline"; + } +} + +function flightModeColor(mode: string) { + switch (mode) { + case "docked": return "border-green text-green"; + case "approaching": return "border-cyan text-cyan"; + case "mining": return "border-accent text-accent"; + case "flight": return "border-fg-dim text-fg-dim"; + default: return "border-muted text-muted"; + } +} + +function formatCountdown(operation: ShipOperation, nowMs: number) { + const completesAt = Number(operation.completesAtMs); + const remaining = completesAt - nowMs; + if (remaining <= 0) return "Completing..."; + const seconds = Math.ceil(remaining / 1000); + return `${seconds}s`; +} diff --git a/apps/game/src/ui/useKeyboardShortcuts.ts b/apps/game/src/ui/useKeyboardShortcuts.ts new file mode 100644 index 0000000..7ba6b83 --- /dev/null +++ b/apps/game/src/ui/useKeyboardShortcuts.ts @@ -0,0 +1,68 @@ +import { useEffect } from "react"; + +export type ShipActions = { + onUndock: () => void; + onStartApproach: () => void; + onDock: () => void; + onStartMining: () => void; + onSelectTarget?: (index: number) => void; +}; + +export function useKeyboardShortcuts( + ship: { + dockedStationId: string; + flightMode: string; + selectedPoiId: string; + currentPoiId: string; + } | undefined, + pois: Array<{ poiId: string; poiType: string }>, + operation: { operationType: string } | undefined, + actions: ShipActions, +) { + useEffect(() => { + function handleKeyDown(event: KeyboardEvent) { + if (event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement) return; + + const key = event.key.toLowerCase(); + + if (key === "u" && !operation) { + if (ship && ship.dockedStationId.length > 0) { + event.preventDefault(); + actions.onUndock(); + } + } + + if (key === "d" && !operation) { + if (ship && ship.dockedStationId.length === 0 && ship.flightMode === "flight") { + event.preventDefault(); + actions.onDock(); + } + } + + if (key === "a" && !operation) { + if (ship && ship.dockedStationId.length === 0 && ship.selectedPoiId.length > 0 && ship.selectedPoiId !== ship.currentPoiId) { + event.preventDefault(); + actions.onStartApproach(); + } + } + + if (key === "m" && !operation) { + if (ship && ship.dockedStationId.length === 0 && ship.flightMode === "flight") { + event.preventDefault(); + actions.onStartMining(); + } + } + + if (key >= "1" && key <= "9") { + const index = parseInt(key) - 1; + if (index < pois.length && actions.onSelectTarget) { + event.preventDefault(); + actions.onSelectTarget(index); + } + } + } + + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [ship, pois, operation, actions]); +} diff --git a/apps/game/vite.config.ts b/apps/game/vite.config.ts index b0044df..6ed633d 100644 --- a/apps/game/vite.config.ts +++ b/apps/game/vite.config.ts @@ -7,4 +7,14 @@ export default defineConfig({ jsx: "automatic", jsxImportSource: "react", }, + build: { + rollupOptions: { + output: { + manualChunks: { + three: ["three", "@react-three/fiber", "@react-three/drei"], + spacetimedb: ["spacetimedb"], + }, + }, + }, + }, }); diff --git a/apps/site/AGENTS.md b/apps/site/AGENTS.md new file mode 100644 index 0000000..af4a392 --- /dev/null +++ b/apps/site/AGENTS.md @@ -0,0 +1,20 @@ +# apps/site — Public Landing Page + +Package: `@void-nav/site` +Port: `http://localhost:5174` + +Minimal marketing/landing page for VOID::NAV. Uses `@void-nav/ui` for shared primitives. + +## Structure + +``` +src/ + main.tsx Entry point + pages/ + LandingPage.tsx Landing page with hero section and links to game + docs + NotFoundPage.tsx 404 page + styles/ + tailwind.css Tailwind v4 entry point +``` + +This is the simplest app in the monorepo. It exists to provide a public entry point that links to the game and docs. diff --git a/archive/AGENTS.md b/archive/AGENTS.md new file mode 100644 index 0000000..2a333d1 --- /dev/null +++ b/archive/AGENTS.md @@ -0,0 +1,18 @@ +# archive/ — Legacy Reference Files + +Pre-monorepo static files kept for reference. Not part of the build. Not imported by any app. + +## Structure + +``` +legacy-static/ + gap-analysis.html Old gap analysis page + game-hud.html Old game HUD prototype + gdd-docs-hub.html.artifact.json GDD hub artifact + mpg7uppn-eve_like_multiplayer_prototype_design_doc.docx Original GDD document + *.png 3 concept art drawings + css/ tokens.css, base.css, components.css, layout.css + js/ Legacy vanilla JS: demos/, pages/, components/ +``` + +These files document the project's evolution from a static HTML prototype to the current React + SpacetimeDB monorepo. Do not modify — they exist as historical reference only. diff --git a/package.json b/package.json index b1e0c40..678e93f 100644 --- a/package.json +++ b/package.json @@ -4,14 +4,13 @@ "version": "0.1.0", "type": "module", "scripts": { + "dev": "bash scripts/dev.sh", "dev:docs": "pnpm --filter @void-nav/docs dev --port 5173", "dev:site": "pnpm --filter @void-nav/site dev --port 5174", "dev:game": "pnpm --filter @void-nav/game dev --port 5175", - "dev:db": "spacetime dev --server local", "build": "pnpm -r --if-present build", "check": "pnpm -r --if-present check", - "dev:standalone": "pnpm run dev:db & pnpm run dev:game", -"generate:bindings": "spacetime generate" + "generate:bindings": "spacetime generate" }, "packageManager": "pnpm@9.15.0" } diff --git a/packages/AGENTS.md b/packages/AGENTS.md new file mode 100644 index 0000000..c7e1927 --- /dev/null +++ b/packages/AGENTS.md @@ -0,0 +1,9 @@ +# packages/ — Shared Libraries + +Shared packages consumed by one or more apps in the monorepo. + +| Package | Export | Used By | +|---------|--------|---------| +| `ui` | `@void-nav/ui` | `apps/game`, `apps/site` | + +All packages use TypeScript and export directly from source (no build step — consumers handle bundling). diff --git a/packages/ui/AGENTS.md b/packages/ui/AGENTS.md new file mode 100644 index 0000000..debe000 --- /dev/null +++ b/packages/ui/AGENTS.md @@ -0,0 +1,31 @@ +# packages/ui — Shared UI Primitives & Design Tokens + +Package: `@void-nav/ui` +Exports: `Button`, `Panel` components and a CSS design token system. + +## Structure + +``` +src/ + index.ts Barrel export: Button, Panel + primitives/ + Button.tsx Button component with variant support + Panel.tsx Panel/card container component + styles.css Design tokens (dark space theme) +``` + +## Design Tokens + +The CSS file in `src/styles.css` defines the visual identity of the entire project: + +- **15 color tokens** — dark space palette (bg, fg, surface, accent, cyan, green, purple, red, muted, border) +- **3 font stacks** — display (Space Grotesk), body (Inter), mono (JetBrains Mono) +- **Border radius**, spacing, and shadow scales + +These tokens are imported by `apps/game` and `apps/site` via Tailwind CSS `@import`. + +## Conventions + +- Components are minimal — they define structure, not complex behavior +- No external dependencies beyond React peer dependency +- Styles use CSS custom properties, not Tailwind classes directly (consumers apply Tailwind) diff --git a/scripts/AGENTS.md b/scripts/AGENTS.md new file mode 100644 index 0000000..287708f --- /dev/null +++ b/scripts/AGENTS.md @@ -0,0 +1,11 @@ +# scripts/ — Dev Tooling + +Shell scripts for development workflow automation. + +## Files + +| Script | Purpose | +|--------|---------| +| `dev.sh` | Full-stack dev startup: starts fresh SpacetimeDB instance, publishes module, generates bindings, launches game dev server | + +Run via `pnpm dev` from the repo root. Uses an isolated ephemeral data directory (`.spacetime-dev/`) so it starts clean every time. diff --git a/scripts/dev.sh b/scripts/dev.sh new file mode 100755 index 0000000..e28c250 --- /dev/null +++ b/scripts/dev.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +set -euo pipefail + +STDB_DATA_DIR=".spacetime-dev" +STDB_HOST="http://127.0.0.1:3000" +STDB_DATABASE="void-nav-dev" +MAX_WAIT=30 +PIDS=() + +cleanup() { + echo "" + echo "Shutting down..." + for pid in "${PIDS[@]}"; do + kill "$pid" 2>/dev/null || true + done + wait 2>/dev/null + echo "Done." + exit 0 +} +trap cleanup INT TERM + +rm -rf "$STDB_DATA_DIR" +mkdir -p "$STDB_DATA_DIR" + +echo "[dev] Starting SpacetimeDB (data: $STDB_DATA_DIR)..." +spacetime start --data-dir "$STDB_DATA_DIR" --in-memory & +PIDS+=($!) + +echo "[dev] Waiting for SpacetimeDB at $STDB_HOST ..." +waited=0 +until curl -sf "$STDB_HOST/v1/health" >/dev/null 2>&1; do + waited=$((waited + 1)) + if [ "$waited" -ge "$MAX_WAIT" ]; then + echo "[dev] ERROR: SpacetimeDB did not start within ${MAX_WAIT}s" + cleanup + exit 1 + fi + sleep 1 +done +echo "[dev] SpacetimeDB is ready." + +echo "[dev] Publishing module and generating bindings..." +spacetime dev --server local --delete-data -y --server-only & +PIDS+=($!) + +sleep 3 + +echo "[dev] Starting game dev server on http://localhost:5175 ..." +pnpm --filter @void-nav/game dev --port 5175 & +PIDS+=($!) + +echo "" +echo "=== VOID::NAV Dev Environment ===" +echo " SpacetimeDB: $STDB_HOST" +echo " Game: http://localhost:5175" +echo " Data dir: $STDB_DATA_DIR (ephemeral)" +echo " Press Ctrl+C to stop" +echo "==================================" +echo "" + +wait diff --git a/services/AGENTS.md b/services/AGENTS.md new file mode 100644 index 0000000..30e0044 --- /dev/null +++ b/services/AGENTS.md @@ -0,0 +1,12 @@ +# services/ — Backend Services + +Server-side modules that own authoritative game state. + +| Service | Protocol | Description | +|---------|----------|-------------| +| `spacetimedb` | SpacetimeDB TS module | Authoritative game backend: all tables, reducers, validation | + +## Key Files + +- `spacetime.json` (repo root) — points `module-path` at `./services/spacetimedb` +- `spacetime.local.json` — local database name: `void-nav-dev` diff --git a/services/spacetimedb/AGENTS.md b/services/spacetimedb/AGENTS.md new file mode 100644 index 0000000..940cc54 --- /dev/null +++ b/services/spacetimedb/AGENTS.md @@ -0,0 +1,67 @@ +# services/spacetimedb — Authoritative Game Backend + +Package: `@void-nav/spacetimedb` +Single file: `src/index.ts` (~595 lines) + +This is the **entire server-side game logic** — all tables, all reducers, all validation. SpacetimeDB runs this as a hosted module; clients subscribe to tables and call reducers. + +## Tables (9) + +| Table | PK | Purpose | +|-------|-----|---------| +| `player` | identity | Player identity, connection state, display name | +| `ship` | ship_id (u64) | Ship position, flight mode, docking state, cargo capacity | +| `system` | system_id | Star system (currently only "solace") | +| `station` | station_id | Station within a system | +| `point_of_interest` | poi_id | Navigable locations: stations, asteroid belts | +| `cargo_item` | cargo_item_id (u64) | Ship cargo: item type, category, quantity, unit price | +| `wallet` | owner_identity | ISK balance | +| `ship_operation` | ship_id | Active timed operation (approach/mining) with start time and duration | +| `server_event` | event_id (u64) | Server-side event log | + +## Reducers (12) + +| Reducer | Purpose | +|---------|---------| +| `seedWorld` | Creates starter system, station, POIs | +| `connectPlayer` | Player onboarding: creates player + ship + wallet | +| `renamePlayer` | Updates display name | +| `ping` | Connection test | +| `undock` | Transitions ship from docked to flight | +| `selectTarget` | Sets ship's selected POI target | +| `startApproach` | Begins 5s approach operation to selected target | +| `completeApproach` | Finalizes approach, updates ship position | +| `dock` | Transitions ship to docked at current station | +| `startMining` | Begins 6s mining cycle at asteroid belt | +| `completeMiningCycle` | Finalizes mining, adds 1000 Veldspar to cargo | +| `sellOreToNpcMarket` | Sells ore from cargo, credits ISK wallet | + +## Constants (all hardcoded, top of file) + +| Constant | Value | Should become | +|----------|-------|---------------| +| `STARTER_SYSTEM_ID` | `"solace"` | Procedurally generated | +| `APPROACH_DURATION_MS` | `5000n` | Ship-class dependent | +| `MINING_DURATION_MS` | `6000n` | Module + skill dependent | +| `STARTER_WALLET_ISK` | `25000n` | Tunable | +| `STARTER_CARGO_CAPACITY` | `2500n` | Ship-class dependent | +| `MINING_YIELD_QUANTITY` | `1000n` | Module + skill dependent | +| Veldspar price | `12n` ISK | Dynamic NPC pricing | + +## Validation + +Every reducer has guard clauses that throw on invalid state (wrong flight mode, missing target, already operating, etc.). Timing is server-authoritative — `completeApproach` and `completeMiningCycle` check `ctx.timestamp` against `completes_at_ms`. + +## What's Not Here Yet + +The design docs describe 56+ tables. This module implements 9. Missing systems: +- Combat (NPC entities, damage, power allocation, bounties, loot) +- Ship fitting (module catalog, CPU/PG, slot types) +- Multiple ship classes +- Refining and manufacturing +- Missions / NPC agents / faction standing +- Dynamic NPC pricing (EMA, demand pressure) +- Multi-system travel (stargates, warp) +- Skills / XP progression +- Insurance, CONCORD, world events +- Zora ship AI