304 lines
18 KiB
JavaScript
304 lines
18 KiB
JavaScript
window.GDD = window.GDD || {};
|
||
|
||
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.',
|
||
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.',
|
||
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.',
|
||
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.',
|
||
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.',
|
||
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.',
|
||
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.',
|
||
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.',
|
||
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.',
|
||
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.',
|
||
accent: 'var(--cyan)',
|
||
phases: [
|
||
{
|
||
num: '8',
|
||
title: 'SpacetimeDB Skeleton',
|
||
goal: 'Replace local game state with SpacetimeDB tables and reducers. Client subscribes to state.',
|
||
doneWhen: 'Two browser windows connect to the same SpacetimeDB instance. Both see the same star system state. One client issues a move command, the other sees it. Connection status indicator works.',
|
||
status: 'future',
|
||
},
|
||
{
|
||
num: '9',
|
||
title: 'Presence & Movement Sync',
|
||
goal: 'Players see each other in real time. Movement is server-authoritative with client-side interpolation.',
|
||
doneWhen: 'Two players in the same system. Each sees the other\'s ship. Click-to-move sends reducer, server validates, all clients interpolate movement. No desync under normal latency.',
|
||
status: 'future',
|
||
},
|
||
{
|
||
num: '10',
|
||
title: 'Shared Economy',
|
||
goal: 'Player-to-player market. Buy/sell orders, contracts, and real price discovery.',
|
||
doneWhen: 'Player A places a sell order. Player B sees it in the market table and buys. ISK and items transfer atomically. Order book shows depth. Market history is shared.',
|
||
status: 'future',
|
||
},
|
||
{
|
||
num: '11',
|
||
title: 'Social — Chat & Bounty',
|
||
goal: 'Local chat (system-range), delayed PMs, and player-posted bounty system.',
|
||
doneWhen: 'Players in the same system see local chat in real time. PMs arrive with configurable delay (light-speed). Any player can post a bounty on another. Bounty board is visible galaxy-wide.',
|
||
status: 'future',
|
||
},
|
||
{
|
||
num: '12',
|
||
title: 'Living Galaxy — World Agents + Ship AI Tier 1',
|
||
goal: 'Background agent scheduler (BitCraft model). NPC trade convoys, faction skirmishes, anomaly spawns, migration routes. Ship AI promoted to LLM-assisted dialogue.',
|
||
doneWhen: 'Server spawns world events without player input. Events appear in the galaxy story log. Faction borders shift over time. Anomalies appear and expire. A returning player sees the galaxy has changed. Tier 1 Zora: soul.md as real system prompt, LLM-generated dialogue, comms module enables natural language responses.',
|
||
status: 'future',
|
||
},
|
||
{
|
||
num: '13',
|
||
title: 'Multiplayer Combat',
|
||
goal: 'PvP combat with FTL power allocation. Multiple ships in an engagement. Target calling, range bands.',
|
||
doneWhen: 'Two players engage each other. Both manage power allocation. Server resolves combat ticks authoritatively. Both clients see damage applied. Loser\'s ship drops loot (or wreck). Kill log records the event.',
|
||
status: 'future',
|
||
},
|
||
{
|
||
num: '14',
|
||
title: 'Corporations & Territory',
|
||
goal: 'Player corps, structure anchoring, system sovereignty claims.',
|
||
doneWhen: 'Players form a corp. Corp can anchor a structure in a system. Structure provides bonuses (refining yield, market tax). Sovereignty map shows corp-held systems. Rival corps can contest.',
|
||
status: 'future',
|
||
},
|
||
{
|
||
num: '15',
|
||
title: 'Full MVP — Launch Candidate',
|
||
goal: 'Polish pass on all systems. Error handling, reconnection, scaling tests, and onboarding flow.',
|
||
doneWhen: 'Fresh player can create account, complete a guided tutorial, mine their first ore, fit their first ship, survive a PvE encounter, make their first trade, and join a corp — all without hitting a dead-end or a crash. Server handles 50 concurrent players.',
|
||
status: 'future',
|
||
},
|
||
],
|
||
},
|
||
];
|
||
|
||
function PhaseItem({ phase, isLast }) {
|
||
const statusStyle = phase.status === 'current'
|
||
? { background: 'var(--accent-bg)', color: 'var(--accent)', border: '1px solid var(--accent-border)' }
|
||
: phase.status === 'upcoming'
|
||
? { background: 'var(--cyan-bg)', color: 'var(--cyan)', border: '1px solid rgba(34,211,238,0.25)' }
|
||
: { background: 'var(--surface-raised)', color: 'var(--muted)', border: '1px solid var(--border)' };
|
||
|
||
return (
|
||
<div className="phase-item">
|
||
<div className="phase-marker">
|
||
<div
|
||
className={`phase-dot${phase.status === 'future' ? ' future' : ''}`}
|
||
style={phase.status === 'current' ? {
|
||
background: 'var(--accent)',
|
||
boxShadow: '0 0 12px rgba(240,160,48,0.4)',
|
||
animation: 'pulse 2s infinite',
|
||
} : {}}
|
||
/>
|
||
{!isLast && <div className="phase-line" />}
|
||
</div>
|
||
<div className="phase-content">
|
||
<div style={{ display: 'flex', alignItems: 'center', gap: 'var(--sp-3)', marginBottom: 'var(--sp-2)' }}>
|
||
<span style={{
|
||
fontFamily: 'var(--font-mono)',
|
||
fontSize: '0.7rem',
|
||
padding: '2px 8px',
|
||
borderRadius: 'var(--radius-pill)',
|
||
...statusStyle,
|
||
}}>
|
||
PHASE {phase.num}
|
||
</span>
|
||
<h4 style={{ margin: 0 }}>{phase.title}</h4>
|
||
{phase.status === 'current' && <span className="pill pill-amber">IN PROGRESS</span>}
|
||
</div>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-2) 0' }}>{phase.goal}</p>
|
||
<div style={{ background: 'var(--surface-raised)', borderRadius: 'var(--radius-md)', padding: 'var(--sp-2) var(--sp-3)', fontSize: '0.8rem' }}>
|
||
<span style={{ color: 'var(--muted)', fontFamily: 'var(--font-mono)', fontSize: '0.7rem' }}>DONE WHEN: </span>
|
||
<span style={{ color: 'var(--fg-dim)' }}>{phase.doneWhen}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div className="content-inner">
|
||
<h1 style={{ marginBottom: '8px' }}>Development Roadmap</h1>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.95rem', maxWidth: '720px' }}>
|
||
Two eras, sixteen phases. <strong style={{ color: 'var(--fg)' }}>Era 1</strong> proves the game is fun as a single-player simulation with a local
|
||
SpacetimeDB instance — the same persistence architecture as multiplayer, just one player. <strong style={{ color: 'var(--fg)' }}>Era 2</strong> promotes
|
||
that local SpacetimeDB to a shared server and adds social systems, the living galaxy, and multiplayer combat.
|
||
Each phase has a verifiable done-when condition. Integration gates between phase groups ensure every system works together before advancing.
|
||
</p>
|
||
|
||
<div className="callout callout-info" style={{ marginTop: 'var(--sp-4)', maxWidth: '720px' }}>
|
||
<strong>Why single-player first with local SpacetimeDB?</strong> Networking is the biggest source of bugs and complexity.
|
||
By validating that mining, combat, fitting, and the economy are fun locally — using the <em>same</em> 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?"
|
||
</div>
|
||
|
||
<div className="section-header" style={{ marginTop: 'var(--sp-8)' }}>
|
||
<span className="section-num">IG</span>
|
||
<h2 style={{ margin: 0 }}>Integration Gates</h2>
|
||
</div>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.95rem', maxWidth: '720px', marginBottom: 'var(--sp-4)' }}>
|
||
Between phase groups, an integration gate ensures all systems work together before new ones are added.
|
||
A gate is a focused playtest that exercises every previously-built feature end-to-end.
|
||
</p>
|
||
<div className="grid-2" style={{ marginBottom: 'var(--sp-6)' }}>
|
||
<div className="card" style={{ borderLeft: '3px solid var(--accent)' }}>
|
||
<h4 style={{ color: 'var(--accent)' }}>Gate 1 — Core Loop (after Phase 2)</h4>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-2) 0' }}>
|
||
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.
|
||
</p>
|
||
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.72rem', color: 'var(--muted)' }}>Phases covered: 0, 1, 2</div>
|
||
</div>
|
||
<div className="card" style={{ borderLeft: '3px solid var(--red)' }}>
|
||
<h4 style={{ color: 'var(--red)' }}>Gate 2 — Combat + Fitting (after Phase 4)</h4>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-2) 0' }}>
|
||
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.
|
||
</p>
|
||
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.72rem', color: 'var(--muted)' }}>Phases covered: 0–4</div>
|
||
</div>
|
||
<div className="card" style={{ borderLeft: '3px solid var(--green)' }}>
|
||
<h4 style={{ color: 'var(--green)' }}>Gate 3 — Full Economy (after Phase 6)</h4>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-2) 0' }}>
|
||
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.
|
||
</p>
|
||
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.72rem', color: 'var(--muted)' }}>Phases covered: 0–6</div>
|
||
</div>
|
||
<div className="card" style={{ borderLeft: '3px solid var(--cyan)' }}>
|
||
<h4 style={{ color: 'var(--cyan)' }}>Gate 4 — Era 1 Complete (after Phase 7)</h4>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-2) 0' }}>
|
||
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.
|
||
</p>
|
||
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.72rem', color: 'var(--muted)' }}>Phases covered: 0–7 (all Era 1)</div>
|
||
</div>
|
||
<div className="card" style={{ borderLeft: '3px solid var(--purple)' }}>
|
||
<h4 style={{ color: 'var(--purple)' }}>Gate 5 — Multiplayer Core (after Phase 10)</h4>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-2) 0' }}>
|
||
Two players in the same galaxy. Both see each other. Both can trade on the shared market. ISK and items
|
||
transfer atomically. Movement is synced. Connection loss and reconnection work. No desync under normal latency.
|
||
</p>
|
||
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.72rem', color: 'var(--muted)' }}>Phases covered: 8–10</div>
|
||
</div>
|
||
<div className="card" style={{ borderLeft: '3px solid var(--fg-dim)' }}>
|
||
<h4 style={{ color: 'var(--fg-dim)' }}>Gate 6 — Launch Ready (after Phase 15)</h4>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-2) 0' }}>
|
||
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.
|
||
</p>
|
||
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.72rem', color: 'var(--muted)' }}>Phases covered: 0–15 (all)</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div style={{ marginTop: 'var(--sp-8)' }}>
|
||
{eras.map((era, ei) => (
|
||
<div key={era.id} style={{ marginBottom: ei < eras.length - 1 ? 'var(--sp-8)' : 0 }}>
|
||
{/* Era header */}
|
||
<div style={{
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
gap: 'var(--sp-4)',
|
||
marginBottom: 'var(--sp-5)',
|
||
paddingBottom: 'var(--sp-3)',
|
||
borderBottom: `2px solid ${era.accent}`,
|
||
}}>
|
||
<span style={{
|
||
fontFamily: 'var(--font-mono)',
|
||
fontSize: '0.7rem',
|
||
fontWeight: 700,
|
||
letterSpacing: '0.08em',
|
||
padding: '4px 12px',
|
||
borderRadius: 'var(--radius-pill)',
|
||
background: era.accent === 'var(--accent)' ? 'var(--accent-bg)' : 'var(--cyan-bg)',
|
||
color: era.accent,
|
||
border: `1px solid ${era.accent === 'var(--accent)' ? 'var(--accent-border)' : 'rgba(34,211,238,0.3)'}`,
|
||
}}>
|
||
ERA {ei + 1}
|
||
</span>
|
||
<div>
|
||
<h2 style={{ margin: 0, fontSize: '1.1rem' }}>{era.title}</h2>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.82rem', margin: '4px 0 0 0' }}>{era.subtitle}</p>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Phase list */}
|
||
{era.phases.map((phase, pi) => (
|
||
<PhaseItem
|
||
key={phase.num}
|
||
phase={phase}
|
||
isLast={pi === era.phases.length - 1}
|
||
/>
|
||
))}
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
window.GDD.RoadmapPage = RoadmapPage;
|