970 lines
52 KiB
HTML
970 lines
52 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<title>GDD::DOCS — EVE-Inspired Multiplayer Prototype</title>
|
||
|
||
<!-- Styles -->
|
||
<link rel="stylesheet" href="css/tokens.css" />
|
||
<link rel="stylesheet" href="css/base.css" />
|
||
<link rel="stylesheet" href="css/layout.css" />
|
||
<link rel="stylesheet" href="css/components.css" />
|
||
|
||
<!-- Three.js (r160 is last version with UMD global build) -->
|
||
<script src="https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js"></script>
|
||
|
||
<!-- Three.js helpers (plain JS — no Babel needed, must run before Babel demos) -->
|
||
<script src="js/lib/three-helpers.js"></script>
|
||
|
||
<!-- React + Babel -->
|
||
<script src="https://unpkg.com/react@18.3.1/umd/react.development.js"
|
||
integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L"
|
||
crossorigin="anonymous"></script>
|
||
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js"
|
||
integrity="sha384-u6aeetuaXnQ38mYT8rp6sbXaQe3NL9t+IBXmnYxwkUI2Hw4bsp2Wvmx4yRQF1uAm"
|
||
crossorigin="anonymous"></script>
|
||
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js"
|
||
integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y"
|
||
crossorigin="anonymous"></script>
|
||
|
||
<!-- On-demand page loader (plain JS, before Babel scripts) -->
|
||
<script src="js/loader.js"></script>
|
||
</head>
|
||
<body>
|
||
<div id="root"></div>
|
||
|
||
<!--
|
||
All core scripts are inlined so the app works when opened as file://.
|
||
Babel Standalone cannot fetch external text/babel scripts on file://
|
||
(XHR/fetch is blocked), but it CAN transform inline text/babel blocks.
|
||
-->
|
||
|
||
<!-- ── State store ─────────────────────────────────────────────── -->
|
||
<script type="text/babel">
|
||
window.GDD = window.GDD || {};
|
||
|
||
/* Simple pub/sub state store — Zustand-like for prototype */
|
||
window.GDD.createStore = function createStore(initialState) {
|
||
let state = { ...initialState };
|
||
const listeners = new Set();
|
||
|
||
return {
|
||
getState: () => ({ ...state }),
|
||
setState: (partial) => {
|
||
const next = typeof partial === 'function' ? partial(state) : partial;
|
||
state = { ...state, ...next };
|
||
listeners.forEach(fn => fn(state));
|
||
},
|
||
subscribe: (fn) => {
|
||
listeners.add(fn);
|
||
return () => listeners.delete(fn);
|
||
}
|
||
};
|
||
};
|
||
|
||
/* App-level store */
|
||
window.GDD.appStore = window.GDD.createStore({
|
||
currentPage: 'overview',
|
||
sidebarCollapsed: false,
|
||
sidebarOpen: false, // mobile
|
||
connected: true,
|
||
playerName: 'Captain Riker',
|
||
playerCredits: 125000,
|
||
});
|
||
</script>
|
||
|
||
<!-- ── Fake backend ────────────────────────────────────────────── -->
|
||
<script type="text/babel">
|
||
window.GDD = window.GDD || {};
|
||
|
||
/* ---- Fake Data ---- */
|
||
const SYSTEMS = [
|
||
{ id: 'sol', name: 'Sol', security: 1.0, x: 400, y: 300, type: 'G2V Star', planets: 8, stations: ['Jita IV - Moon 4', 'Amarr VIII'], color: '#fbbf24' },
|
||
{ id: 'amarr', name: 'Amarr', security: 0.9, x: 550, y: 220, type: 'K4V Star', planets: 6, stations: ['Amarr Prime'], color: '#f59e0b' },
|
||
{ id: 'heinoo', name: 'Hek', security: 0.7, x: 320, y: 180, type: 'M3V Star', planets: 4, stations: ['Hek VII'], color: '#22c55e' },
|
||
{ id: 'rens', name: 'Rens', security: 0.6, x: 250, y: 350, type: 'G9V Star', planets: 7, stations: ['Rens VI'], color: '#22d3ee' },
|
||
{ id: 'dodixie', name: 'Dodixie', security: 0.8, x: 480, y: 400, type: 'F7V Star', planets: 5, stations: ['Dodixie IX'], color: '#a78bfa' },
|
||
{ id: 'u-irtyr', name: 'U-IRTYR', security: 0.3, x: 150, y: 250, type: 'M7V Star', planets: 3, stations: [], color: '#ef4444' },
|
||
{ id: 'pf-346', name: 'PF-346', security: 0.2, x: 600, y: 350, type: 'K7V Star', planets: 2, stations: ['PF-346 II'], color: '#ef4444' },
|
||
{ id: 'huzzah', name: 'YZ-LQL', security: 0.1, x: 100, y: 400, type: 'M2V Star', planets: 1, stations: [], color: '#dc2626' },
|
||
{ id: 'pirates', name: 'O-WAMW', security: 0.0, x: 650, y: 150, type: 'M5V Red Giant', planets: 9, stations: [], color: '#991b1b' },
|
||
];
|
||
|
||
const CONNECTIONS = [
|
||
['sol', 'amarr'], ['sol', 'heinoo'], ['sol', 'rens'],
|
||
['amarr', 'dodixie'], ['amarr', 'pf-346'], ['amarr', 'pirates'],
|
||
['heinoo', 'u-irtyr'], ['heinoo', 'rens'],
|
||
['rens', 'u-irtyr'], ['rens', 'huzzah'],
|
||
['dodixie', 'pf-346'], ['dodixie', 'sol'],
|
||
['u-irtyr', 'huzzah'], ['pf-346', 'pirates'],
|
||
];
|
||
|
||
const ASTEROID_TYPES = ['Veldspar', 'Scordite', 'Pyroxeres', 'Kernite', 'Omber', 'Jaspet', 'Hemorphite', 'Arkonor'];
|
||
const ORE_PRICES = { Veldspar: 12, Scordite: 28, Pyroxeres: 45, Kernite: 85, Omber: 120, Jaspet: 190, Hemorphite: 340, Arkonor: 620 };
|
||
const MODULES = [
|
||
{ id: 'laser1', name: 'Mining Laser I', type: 'mining', slot: 'high', power: 40, cpu: 30, cycle: 10, active: false },
|
||
{ id: 'laser2', name: 'Mining Laser II', type: 'mining', slot: 'high', power: 50, cpu: 40, cycle: 8, active: false },
|
||
{ id: 'shield1', name: 'Shield Booster I', type: 'shield', slot: 'med', power: 30, cpu: 30, cycle: 5, active: false },
|
||
{ id: 'turret1', name: '150mm Railgun', type: 'weapon', slot: 'high', power: 50, cpu: 35, damage: 25, cycle: 3, active: false },
|
||
{ id: 'turret2', name: '200mm Autocannon', type: 'weapon', slot: 'high', power: 45, cpu: 30, damage: 35, cycle: 2.5, active: false },
|
||
{ id: 'warp1', name: '1MN Afterburner', type: 'propulsion', slot: 'med', power: 20, cpu: 25, speed: 1.5, cycle: 0, active: false },
|
||
{ id: 'scram1', name: 'Warp Scrambler I', type: 'ewar', slot: 'med', power: 25, cpu: 25, range: 20, cycle: 0, active: false },
|
||
{ id: 'armor1', name: 'Armor Plate I', type: 'armor', slot: 'low', power: 20, cpu: 10, cycle: 0, active: false },
|
||
{ id: 'magstab1', name: 'Magnetic Field Stabilizer', type: 'damage_mod', slot: 'low', power: 5, cpu: 15, cycle: 0, active: false },
|
||
{ id: 'cargo1', name: 'Cargo Expander I', type: 'cargo', slot: 'low', power: 0, cpu: 15, cycle: 0, active: false },
|
||
];
|
||
|
||
const MARKET_ORDERS = [
|
||
{ id: 1, station: 'Jita IV - Moon 4', type: 'sell', item: 'Veldspar', price: 14, quantity: 45000, seller: 'MinerKing42' },
|
||
{ id: 2, station: 'Jita IV - Moon 4', type: 'sell', item: 'Scordite', price: 32, quantity: 12000, seller: 'RockHound' },
|
||
{ id: 3, station: 'Jita IV - Moon 4', type: 'buy', item: 'Arkonor', price: 580, quantity: 500, seller: 'IndustrialMega' },
|
||
{ id: 4, station: 'Jita IV - Moon 4', type: 'sell', item: 'Kernite', price: 90, quantity: 8000, seller: 'DeepMiner' },
|
||
{ id: 5, station: 'Jita IV - Moon 4', type: 'buy', item: 'Pyroxeres', price: 42, quantity: 20000, seller: 'RefineryCorp' },
|
||
{ id: 6, station: 'Amarr Prime', type: 'sell', item: 'Omber', price: 135, quantity: 6000, seller: 'AmarrTrader' },
|
||
{ id: 7, station: 'Amarr Prime', type: 'buy', item: 'Jaspet', price: 180, quantity: 3000, seller: 'HighSecOps' },
|
||
{ id: 8, station: 'Rens VI', type: 'sell', item: 'Hemorphite', price: 360, quantity: 1200, seller: 'NullRunner' },
|
||
{ id: 9, station: 'Rens VI', type: 'sell', item: 'Veldspar', price: 11, quantity: 90000, seller: 'BulkMiner' },
|
||
{ id: 10, station: 'Dodixie IX', type: 'buy', item: 'Scordite', price: 30, quantity: 15000, seller: 'GallenteForge' },
|
||
];
|
||
|
||
const PLAYER_INVENTORY = [
|
||
{ item: 'Veldspar', quantity: 8500, unitPrice: 12 },
|
||
{ item: 'Scordite', quantity: 2300, unitPrice: 28 },
|
||
{ item: 'Kernite', quantity: 400, unitPrice: 85 },
|
||
{ item: 'Pyroxeres', quantity: 1200, unitPrice: 45 },
|
||
];
|
||
|
||
const PLAYER_SHIPS = [
|
||
{ id: 'ship1', name: 'Merlin', class: 'Frigate', system: 'Sol', status: 'active', highSlots: 3, medSlots: 3, lowSlots: 2, cpu: 120, powerGrid: 40, fitted: ['laser1', 'turret1', 'warp1'] },
|
||
{ id: 'ship2', name: 'Thrasher', class: 'Destroyer', system: 'Amarr', status: 'docked', highSlots: 7, medSlots: 3, lowSlots: 3, cpu: 180, powerGrid: 65, fitted: ['turret1', 'turret2', 'scram1'] },
|
||
];
|
||
|
||
const PLAYER_BOUNTIES = [
|
||
{ target: 'PirateKing99', pool: 125000, tier: 'Dangerous', lastHostile: '2h ago' },
|
||
{ target: 'NullSecWarlord', pool: 520000, tier: 'Most Wanted', lastHostile: '30m ago' },
|
||
];
|
||
|
||
const PLAYER_SKILLS = [
|
||
{ name: 'Gunnery', level: 2, xp: 380, nextLevel: 500, category: 'Combat' },
|
||
{ name: 'Mining', level: 3, xp: 1850, nextLevel: 2000, category: 'Industry' },
|
||
{ name: 'Refining', level: 2, xp: 420, nextLevel: 500, category: 'Industry' },
|
||
{ name: 'Navigation', level: 1, xp: 80, nextLevel: 100, category: 'Navigation' },
|
||
{ name: 'Broker Relations', level: 1, xp: 45, nextLevel: 100, category: 'Trade' },
|
||
];
|
||
|
||
const KILL_FEED = [
|
||
{ victim: 'MinerBob', killer: 'PirateKing99', ship: 'Rifter', system: 'U-IRTYR', bounty: 5000, time: '5m ago' },
|
||
{ victim: 'TraderAlice', killer: 'CMDR Worf', ship: 'Hauler', system: 'Hek', bounty: 0, time: '12m ago' },
|
||
{ victim: 'PirateScout', killer: 'CMDR Picard', ship: 'Merlin', system: 'Sol', bounty: 2000, time: '25m ago' },
|
||
];
|
||
|
||
/* ---- Simulated API ---- */
|
||
const delay = (ms) => new Promise(r => setTimeout(r, ms + Math.random() * 50));
|
||
|
||
window.GDD.api = {
|
||
getSystems: async () => { await delay(80); return SYSTEMS.map(s => ({ ...s })); },
|
||
getConnections: async () => { await delay(60); return CONNECTIONS.map(c => [...c]); },
|
||
getSystemDetail: async (id) => { await delay(100); return SYSTEMS.find(s => s.id === id) || null; },
|
||
setDestination: async (systemId) => { await delay(150); return { success: true, destination: systemId, eta: '3m 42s' }; },
|
||
|
||
getShipStatus: async () => {
|
||
await delay(100);
|
||
return {
|
||
name: 'Merlin', class: 'Frigate', system: 'Sol',
|
||
x: 400, y: 300, status: 'idle', speed: 0, maxSpeed: 250,
|
||
shields: 100, armor: 100, hull: 100, capacitor: 85,
|
||
cargo: { used: 12400, total: 25000 },
|
||
target: null,
|
||
};
|
||
},
|
||
|
||
getModules: async () => { await delay(80); return MODULES.map(m => ({ ...m, active: false })); },
|
||
toggleModule: async (moduleId) => { await delay(100); return { success: true, moduleId, active: true }; },
|
||
|
||
getNearbyEntities: async () => {
|
||
await delay(120);
|
||
return [
|
||
{ id: 'npc1', name: 'Guristas Pirate', type: 'hostile', x: 430, y: 280, shields: 100, distance: 45 },
|
||
{ id: 'asteroid1', name: 'Veldspar Asteroid', type: 'asteroid', x: 370, y: 320, resource: 'Veldspar', quantity: 8500, distance: 12 },
|
||
{ id: 'station1', name: 'Jita IV - Moon 4', type: 'station', x: 410, y: 310, distance: 8 },
|
||
{ id: 'player2', name: 'CMDR LaForge', type: 'player', x: 390, y: 260, distance: 55 },
|
||
];
|
||
},
|
||
|
||
getMarketOrders: async (stationId) => { await delay(150); return MARKET_ORDERS.filter(o => !stationId || o.station === stationId); },
|
||
getPlayerInventory: async () => { await delay(80); return PLAYER_INVENTORY.map(i => ({ ...i })); },
|
||
placeOrder: async (type, item, price, quantity) => { await delay(200); return { success: true, orderId: Date.now() }; },
|
||
sellItem: async (item, quantity, stationId) => { await delay(250); return { success: true, isk: quantity * (ORE_PRICES[item] || 10) }; },
|
||
|
||
getChatMessages: async () => {
|
||
await delay(60);
|
||
return [
|
||
{ id: 1, sender: 'CMDR Picard', body: 'Heading to Jita with a cargo of Kernite.', time: '14:22' },
|
||
{ id: 2, sender: 'CMDR Worf', body: 'Pirates spotted near U-IRTYR gate. Stay alert.', time: '14:25' },
|
||
{ id: 3, sender: 'CMDR Data', body: 'Scordite prices up 12% in Amarr this hour.', time: '14:28' },
|
||
{ id: 4, sender: 'CMDR Troi', body: 'Anyone want to form a mining fleet in Sol?', time: '14:31' },
|
||
];
|
||
},
|
||
sendMessage: async (body) => { await delay(50); return { success: true, id: Date.now() }; },
|
||
|
||
getOrePrices: async () => { await delay(80); return { ...ORE_PRICES }; },
|
||
|
||
getShipFittings: async (shipId) => { await delay(100); const ship = PLAYER_SHIPS.find(s => s.id === shipId); return ship ? ship.fitted.map(id => MODULES.find(m => m.id === id)).filter(Boolean) : []; },
|
||
getPlayerShips: async () => { await delay(100); return PLAYER_SHIPS.map(s => ({ ...s })); },
|
||
getAvailableModules: async () => { await delay(80); return MODULES.map(m => ({ ...m })); },
|
||
fitModule: async (shipId, moduleId) => { await delay(150); return { success: true, shipId, moduleId }; },
|
||
unfitModule: async (shipId, slotIndex) => { await delay(100); return { success: true, shipId, slotIndex }; },
|
||
|
||
refineOre: async (oreType, quantity) => {
|
||
await delay(200);
|
||
const prices = ORE_PRICES[oreType] || 10;
|
||
return { success: true, ore: oreType, quantity, iskEarned: Math.floor(quantity * prices * 0.7), efficiency: 0.7 };
|
||
},
|
||
manufactureItem: async (blueprintId, stationId) => { await delay(300); return { success: true, jobId: Date.now(), eta: '5m 00s' }; },
|
||
|
||
getBounties: async () => { await delay(100); return PLAYER_BOUNTIES.map(b => ({ ...b })); },
|
||
placeBounty: async (targetPlayer, amount) => { await delay(150); return { success: true, target: targetPlayer, amount }; },
|
||
getKillFeed: async () => { await delay(80); return KILL_FEED.map(k => ({ ...k })); },
|
||
|
||
getPlayerSkills: async () => { await delay(100); return PLAYER_SKILLS.map(s => ({ ...s })); },
|
||
|
||
sendPrivateMessage: async (recipient, body) => {
|
||
const dist = Math.floor(Math.random() * 20);
|
||
const lightDelay = Math.floor(Math.sqrt(dist) * 2);
|
||
await delay(50);
|
||
return { success: true, id: Date.now(), recipient, lightDelay };
|
||
},
|
||
|
||
getBookmarks: async () => {
|
||
await delay(80);
|
||
return [
|
||
{ id: 1, name: 'Safe spot Alpha', system: 'Sol', x: 380, y: 290, type: 'safe' },
|
||
{ id: 2, name: 'Good Veldspar belt', system: 'Amarr', x: 560, y: 210, type: 'mining' },
|
||
{ id: 3, name: 'Ambush point', system: 'U-IRTYR', x: 160, y: 260, type: 'tactical' },
|
||
];
|
||
},
|
||
};
|
||
|
||
const CELESTIAL_BODIES = {
|
||
'sol': {
|
||
description: 'The cradle of humanity. A stable G2V main-sequence star hosting the busiest trade hub in known space.',
|
||
faction: 'CONCORD',
|
||
population: '12.4 billion',
|
||
resources: ['Veldspar', 'Scordite', 'Pyroxeres', 'Kernite'],
|
||
bodies: [
|
||
{ name: 'Mercury', type: 'rocky', orbit: 6, period: 4, size: 0.3, color: '#a0a0a0', ecc: 0.2, inc: 0.12, moons: [] },
|
||
{ name: 'Venus', type: 'rocky', orbit: 9, period: 6.5, size: 0.5, color: '#e8c56d', ecc: 0.01, inc: 0.06, moons: [], atmosphere: '#e8c56d' },
|
||
{ name: 'Earth', type: 'terrestrial', orbit: 13, period: 10, size: 0.55, color: '#4a90d9', ecc: 0.017, inc: 0, atmosphere: '#6ac0ff', moons: [{ name: 'Luna', orbit: 2, period: 2, size: 0.15, color: '#c0c0c0' }] },
|
||
{ name: 'Mars', type: 'rocky', orbit: 17, period: 14, size: 0.4, color: '#c1440e', ecc: 0.09, inc: 0.03, moons: [{ name: 'Phobos', orbit: 1.2, period: 0.8, size: 0.08, color: '#8a7a6a' }, { name: 'Deimos', orbit: 1.8, period: 1.5, size: 0.06, color: '#7a6a5a' }] },
|
||
{ name: 'Asteroid Belt', type: 'belt', innerOrbit: 22, outerOrbit: 26, count: 80 },
|
||
{ name: 'Jupiter', type: 'gas', orbit: 32, period: 35, size: 1.2, color: '#c88b3a', ecc: 0.048, inc: 0.02 },
|
||
{ name: 'Saturn', type: 'gas', orbit: 42, period: 50, size: 1.0, color: '#d4a560', ecc: 0.054, inc: 0.04, hasRings: true },
|
||
],
|
||
},
|
||
'amarr': {
|
||
description: 'Seat of the Amarr Empire. A warm K4V star surrounded by heavily industrialized worlds.',
|
||
faction: 'Amarr Empire',
|
||
population: '9.1 billion',
|
||
resources: ['Kernite', 'Omber', 'Pyroxeres'],
|
||
bodies: [
|
||
{ name: 'Amarr I', type: 'rocky', orbit: 5, period: 3, size: 0.35, color: '#d4a040', ecc: 0.02, inc: 0.01, moons: [] },
|
||
{ name: 'Amarr II', type: 'terrestrial', orbit: 10, period: 8, size: 0.65, color: '#c06030', ecc: 0.01, inc: 0.05, atmosphere: '#c08050', moons: [] },
|
||
{ name: 'Amarr III', type: 'gas', orbit: 18, period: 22, size: 0.9, color: '#8b6914', ecc: 0.03, inc: 0.02, moons: [{ name: 'Amarr III-a', orbit: 2, period: 1.5, size: 0.12, color: '#a08040' }] },
|
||
{ name: 'Asteroid Belt', type: 'belt', innerOrbit: 24, outerOrbit: 27, count: 60 },
|
||
],
|
||
},
|
||
'heinoo': {
|
||
description: 'A frontier system popular with independent miners. Rich in common ores and frequently trafficked by haulers.',
|
||
faction: 'Minmatar Republic',
|
||
population: '340 million',
|
||
resources: ['Veldspar', 'Scordite', 'Plagioclase'],
|
||
bodies: [
|
||
{ name: 'Hek I', type: 'rocky', orbit: 5, period: 3.5, size: 0.3, color: '#7a8a6a', ecc: 0.05, inc: 0.02, moons: [] },
|
||
{ name: 'Hek II', type: 'terrestrial', orbit: 11, period: 9, size: 0.55, color: '#4a8a5a', ecc: 0.03, inc: 0.01, atmosphere: '#6aaa7a', moons: [{ name: 'Hek II-a', orbit: 2, period: 1.8, size: 0.1, color: '#8a8a7a' }] },
|
||
{ name: 'Hek III', type: 'gas', orbit: 20, period: 25, size: 0.8, color: '#5a7a9a', ecc: 0.02, inc: 0.03 },
|
||
],
|
||
},
|
||
'rens': {
|
||
description: 'Major trade hub in the Minmatar Republic. Bustling commerce and a strong naval presence keep pirates at bay.',
|
||
faction: 'Minmatar Republic',
|
||
population: '2.1 billion',
|
||
resources: ['Scordite', 'Pyroxeres', 'Kernite'],
|
||
bodies: [
|
||
{ name: 'Rens I', type: 'rocky', orbit: 6, period: 4, size: 0.35, color: '#9a7a5a', ecc: 0.03, inc: 0.01, moons: [] },
|
||
{ name: 'Rens II', type: 'terrestrial', orbit: 12, period: 10, size: 0.6, color: '#5a7aaa', ecc: 0.02, inc: 0.04, atmosphere: '#7a9acc', moons: [{ name: 'Rens II-a', orbit: 1.8, period: 1.4, size: 0.12, color: '#aaaaaa' }] },
|
||
{ name: 'Asteroid Belt', type: 'belt', innerOrbit: 18, outerOrbit: 22, count: 70 },
|
||
{ name: 'Rens III', type: 'gas', orbit: 28, period: 35, size: 1.1, color: '#aa7a3a', ecc: 0.04, inc: 0.02 },
|
||
],
|
||
},
|
||
'dodixie': {
|
||
description: 'Federation commerce hub. High-tech industry and cutting-edge research facilities orbit its F7V star.',
|
||
faction: 'Gallente Federation',
|
||
population: '3.8 billion',
|
||
resources: ['Omber', 'Kernite', 'Jaspet'],
|
||
bodies: [
|
||
{ name: 'Dodixie I', type: 'rocky', orbit: 4, period: 2.5, size: 0.25, color: '#6a5a7a', ecc: 0.01, inc: 0, moons: [] },
|
||
{ name: 'Dodixie II', type: 'terrestrial', orbit: 9, period: 7, size: 0.5, color: '#5a8aaa', ecc: 0.015, inc: 0.02, atmosphere: '#7aaacc', moons: [] },
|
||
{ name: 'Dodixie III', type: 'gas', orbit: 16, period: 18, size: 0.85, color: '#7a6aaa', ecc: 0.02, inc: 0.01, hasRings: true, moons: [{ name: 'Dodixie III-a', orbit: 2.5, period: 2, size: 0.1, color: '#9a8aba' }] },
|
||
],
|
||
},
|
||
'u-irtyr': {
|
||
description: 'Dangerous low-security system. Pirate activity is rampant and uncharted asteroid fields hide valuable ores.',
|
||
faction: 'Unclaimed',
|
||
population: '~2,000',
|
||
resources: ['Hemorphite', 'Jaspet', 'Arkonor'],
|
||
bodies: [
|
||
{ name: 'U-IRTYR I', type: 'rocky', orbit: 5, period: 3, size: 0.35, color: '#6a4a3a', ecc: 0.15, inc: 0.08, moons: [] },
|
||
{ name: 'U-IRTYR II', type: 'rocky', orbit: 10, period: 8, size: 0.3, color: '#5a3a2a', ecc: 0.12, inc: 0.1, moons: [] },
|
||
{ name: 'Asteroid Belt', type: 'belt', innerOrbit: 15, outerOrbit: 22, count: 100 },
|
||
],
|
||
},
|
||
'pf-346': {
|
||
description: 'Low-sec border system. Contested territory with valuable resources and frequent skirmishes.',
|
||
faction: 'Contested',
|
||
population: '~15,000',
|
||
resources: ['Jaspet', 'Hemorphite', 'Kernite'],
|
||
bodies: [
|
||
{ name: 'PF-346 I', type: 'rocky', orbit: 6, period: 4, size: 0.3, color: '#7a5a4a', ecc: 0.08, inc: 0.04, moons: [] },
|
||
{ name: 'PF-346 II', type: 'terrestrial', orbit: 12, period: 10, size: 0.5, color: '#4a6a5a', ecc: 0.05, inc: 0.03, moons: [{ name: 'PF-346 II-a', orbit: 1.5, period: 1.2, size: 0.08, color: '#8a8a7a' }] },
|
||
],
|
||
},
|
||
'huzzah': {
|
||
description: 'Null-sec wasteland. No law, no stations, no mercy. Rare ores attract the desperate and the bold.',
|
||
faction: 'Unclaimed',
|
||
population: '< 100',
|
||
resources: ['Arkonor', 'Bistot', 'Crokmite'],
|
||
bodies: [
|
||
{ name: 'YZ-LQL I', type: 'rocky', orbit: 5, period: 3.5, size: 0.25, color: '#4a3a2a', ecc: 0.2, inc: 0.15, moons: [] },
|
||
{ name: 'Asteroid Belt', type: 'belt', innerOrbit: 8, outerOrbit: 15, count: 120 },
|
||
],
|
||
},
|
||
'pirates': {
|
||
description: 'Deep null-sec. Pirate stronghold with hidden bases and rich, unexploited asteroid belts.',
|
||
faction: 'Guristas Pirates',
|
||
population: 'Unknown',
|
||
resources: ['Arkonor', 'Mercoxit', 'Dark Ochre'],
|
||
bodies: [
|
||
{ name: 'O-WAMW I', type: 'gas', orbit: 8, period: 6, size: 0.7, color: '#8a3a3a', ecc: 0.06, inc: 0.03, moons: [{ name: 'O-WAMW I-a', orbit: 2, period: 1.5, size: 0.12, color: '#6a4a4a' }] },
|
||
{ name: 'O-WAMW II', type: 'gas', orbit: 18, period: 20, size: 1.0, color: '#5a2a5a', ecc: 0.04, inc: 0.05, hasRings: true },
|
||
{ name: 'Asteroid Belt', type: 'belt', innerOrbit: 25, outerOrbit: 32, count: 90 },
|
||
{ name: 'O-WAMW III', type: 'rocky', orbit: 38, period: 45, size: 0.4, color: '#3a2a3a', ecc: 0.1, inc: 0.08, moons: [] },
|
||
],
|
||
},
|
||
};
|
||
|
||
window.GDD.CONSTANTS = { SYSTEMS, CONNECTIONS, ASTEROID_TYPES, ORE_PRICES, MODULES, MARKET_ORDERS, CELESTIAL_BODIES };
|
||
</script>
|
||
|
||
<!-- ── Router ───────────────────────────────────────────────────── -->
|
||
<script type="text/babel">
|
||
window.GDD = window.GDD || {};
|
||
|
||
const { useState, useEffect, useCallback } = React;
|
||
|
||
/* Hash-based router */
|
||
window.GDD.useRouter = function() {
|
||
const [page, setPage] = useState(getPage());
|
||
|
||
function getPage() {
|
||
const hash = window.location.hash.slice(1) || 'overview';
|
||
return hash;
|
||
}
|
||
|
||
useEffect(() => {
|
||
const handler = () => setPage(getPage());
|
||
window.addEventListener('hashchange', handler);
|
||
return () => window.removeEventListener('hashchange', handler);
|
||
}, []);
|
||
|
||
const navigate = useCallback((path) => {
|
||
window.location.hash = path;
|
||
}, []);
|
||
|
||
return { page, navigate };
|
||
};
|
||
</script>
|
||
|
||
<!-- ── Sidebar ──────────────────────────────────────────────────── -->
|
||
<script type="text/babel">
|
||
window.GDD = window.GDD || {};
|
||
|
||
const GDD = window.GDD;
|
||
const { useState, useEffect } = React;
|
||
|
||
const NAV_SECTIONS = [
|
||
{
|
||
title: 'Documentation',
|
||
items: [
|
||
{ id: 'overview', icon: '◈', label: 'Overview' },
|
||
{ id: 'architecture', icon: '⬡', label: 'Architecture' },
|
||
{ id: 'techstack', icon: '⟐', label: 'Tech Stack' },
|
||
{ id: 'backend', icon: '⊞', label: 'Backend Model' },
|
||
{ id: 'agents', icon: '⏣', label: 'Agent Lifecycle' },
|
||
{ id: 'gameplay', icon: '◉', label: 'Gameplay Loop' },
|
||
{ id: 'ships', icon: '◇', label: 'Ships & Fitting' },
|
||
{ id: 'economy', icon: '⇄', label: 'Economy & Industry' },
|
||
{ id: 'social', icon: '✧', label: 'Progression & Social' },
|
||
{ id: 'ship-ai', icon: '◈', label: 'Ship AI — Zora' },
|
||
{ id: 'roadmap', icon: '⊞', label: 'Roadmap' },
|
||
{ id: 'risks', icon: '◬', label: 'Risks & Questions' },
|
||
{ id: 'demo-gallery', icon: '◈', label: 'Demo Gallery' },
|
||
]
|
||
},
|
||
{
|
||
title: 'Interactive Demos',
|
||
items: [
|
||
{ id: 'demo-starmap', icon: '✦', label: 'Star Map' },
|
||
{ id: 'demo-movement', icon: '→', label: 'Ship Movement' },
|
||
{ id: 'demo-combat', icon: '✸', label: 'Combat System' },
|
||
{ id: 'demo-market', icon: '⇄', label: 'Market' },
|
||
{ id: 'demo-fitting', icon: '⊞', label: 'Ship Fitting' },
|
||
{ id: 'demo-refining', icon: '⚗', label: 'Refining & MFG' },
|
||
{ id: 'demo-progression', icon: '▲', label: 'Skill Progression' },
|
||
{ id: 'demo-bounty', icon: '✸', label: 'Bounty & Kill Feed' },
|
||
{ id: 'demo-gamehud', icon: '◉', label: 'Game HUD' },
|
||
{ id: 'demo-chat', icon: '💬', label: 'Chat & Comms' },
|
||
{ id: 'demo-zora', icon: '🤖', label: 'Zora Tier 0' },
|
||
{ id: 'demo-galaxy', icon: '🌌', label: 'Galaxy Gen' },
|
||
]
|
||
}
|
||
];
|
||
|
||
function Sidebar({ collapsed, currentPage, onNavigate, onToggle }) {
|
||
const [hoveredItem, setHoveredItem] = useState(null);
|
||
|
||
return (
|
||
<aside className={`sidebar${collapsed ? ' collapsed' : ''}`}>
|
||
<div className="sidebar-header">
|
||
<div className="sidebar-logo">
|
||
GDD<span className="logo-dot">::</span>DOCS
|
||
</div>
|
||
</div>
|
||
<nav className="sidebar-nav">
|
||
{NAV_SECTIONS.map((section, si) => (
|
||
<div key={si}>
|
||
<div className="nav-section-title">{section.title}</div>
|
||
{section.items.map((item) => (
|
||
<a
|
||
key={item.id}
|
||
className={`nav-item${currentPage === item.id ? ' active' : ''}`}
|
||
href={`#${item.id}`}
|
||
onClick={(e) => { e.preventDefault(); onNavigate(item.id); }}
|
||
onMouseEnter={() => setHoveredItem(item.id)}
|
||
onMouseLeave={() => setHoveredItem(null)}
|
||
>
|
||
<span className="nav-icon">{item.icon}</span>
|
||
<span className="nav-label">{item.label}</span>
|
||
|
||
</a>
|
||
))}
|
||
</div>
|
||
))}
|
||
</nav>
|
||
</aside>
|
||
);
|
||
}
|
||
|
||
GDD.Sidebar = Sidebar;
|
||
</script>
|
||
|
||
<!-- ── TopBar ───────────────────────────────────────────────────── -->
|
||
<script type="text/babel">
|
||
window.GDD = window.GDD || {};
|
||
|
||
const GDD = window.GDD;
|
||
|
||
const PAGE_TITLES = {
|
||
'overview': 'Overview',
|
||
'architecture': 'Architecture',
|
||
'techstack': 'Tech Stack',
|
||
'backend': 'Backend Model',
|
||
'agents': 'Agent Lifecycle & Scheduling',
|
||
'gameplay': 'Gameplay Loop',
|
||
'roadmap': 'Roadmap',
|
||
'risks': 'Risks & Questions',
|
||
'demo-starmap': 'Star Map',
|
||
'demo-movement': 'Ship Movement',
|
||
'demo-combat': 'Combat System',
|
||
'demo-market': 'Market Interface',
|
||
};
|
||
|
||
function TopBar({ collapsed, currentPage, onToggle }) {
|
||
const isDemo = currentPage.startsWith('demo-');
|
||
const section = isDemo ? 'Demos' : 'Docs';
|
||
const title = PAGE_TITLES[currentPage] || currentPage;
|
||
|
||
return (
|
||
<div className="topbar">
|
||
<button className="topbar-toggle" onClick={onToggle}>
|
||
{collapsed ? '→' : '←'}
|
||
</button>
|
||
<div className="topbar-breadcrumb">
|
||
<span className="bc-root">GDD</span>
|
||
<span className="bc-sep">/</span>
|
||
<span>{section}</span>
|
||
<span className="bc-sep">/</span>
|
||
<span className="bc-current">{title}</span>
|
||
</div>
|
||
<div className="topbar-status">
|
||
<span><span className="status-dot online"></span> Connected</span>
|
||
<span style={{ color: 'var(--accent)' }}>●</span>
|
||
<span>Prototype v0.1.0</span>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
GDD.TopBar = TopBar;
|
||
</script>
|
||
|
||
<!-- ── Overview page ────────────────────────────────────────────── -->
|
||
<script type="text/babel">
|
||
window.GDD = window.GDD || {};
|
||
|
||
function OverviewPage() {
|
||
return (
|
||
<div className="content-inner">
|
||
<h1 style={{ marginBottom: '8px' }}>EVE-Inspired Multiplayer Prototype</h1>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '1.1rem', maxWidth: '680px', marginBottom: 'var(--sp-6)' }}>
|
||
A browser-based <strong style={{ color: 'var(--fg-bright)' }}>spreadsheet simulator</strong> in the EVE Online tradition,
|
||
set inside a <strong style={{ color: 'var(--fg-bright)' }}>single persistent galaxy</strong> 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 <strong style={{ color: 'var(--fg-bright)' }}>dynamic PvE events and emergent world story</strong>.
|
||
</p>
|
||
|
||
<div className="stat-grid">
|
||
<div className="stat-card">
|
||
<div className="stat-value" style={{ color: 'var(--accent)' }}>UI-first</div>
|
||
<div className="stat-label">Design Pillar</div>
|
||
</div>
|
||
<div className="stat-card">
|
||
<div className="stat-value" style={{ color: 'var(--cyan)' }}>SpacetimeDB</div>
|
||
<div className="stat-label">Backend</div>
|
||
</div>
|
||
<div className="stat-card">
|
||
<div className="stat-value" style={{ color: 'var(--green)' }}>~3 hrs</div>
|
||
<div className="stat-label">Session Target</div>
|
||
</div>
|
||
<div className="stat-card">
|
||
<div className="stat-value" style={{ color: 'var(--purple)' }}>Player-led</div>
|
||
<div className="stat-label">Economy Model</div>
|
||
</div>
|
||
<div className="stat-card">
|
||
<div className="stat-value" style={{ color: 'var(--red)' }}>Living Galaxy</div>
|
||
<div className="stat-label">World Model</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="section-header">
|
||
<span className="section-num">OV-01</span>
|
||
<h2 style={{ margin: 0 }}>Product Vision</h2>
|
||
</div>
|
||
|
||
<div className="callout callout-info">
|
||
<strong>Primary recommendation:</strong> Build the MVP as a UI-heavy browser game — a <em>spreadsheet simulator</em>.
|
||
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.
|
||
</div>
|
||
|
||
<h3>Core Pillars</h3>
|
||
<table className="data-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Pillar</th>
|
||
<th>Prototype Interpretation</th>
|
||
<th>MVP Scope</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td><span className="pill pill-amber">Economy & markets</span></td>
|
||
<td style={{ color: 'var(--fg-dim)' }}>Player-led economy with NPC support. Mining → refining → manufacturing → trade. Geographic price differences, contract markets, order books, and <strong style={{ color: 'var(--accent)' }}>information asymmetry between systems</strong> create emergent trade routes and speculative opportunities. <strong style={{ color: 'var(--accent)' }}>This IS the game.</strong></td>
|
||
<td><span className="pill pill-green">Era 1</span> <span style={{ fontSize: '0.75rem', color: 'var(--fg-dim)' }}>NPC economy, single-player mining/refining/manufacturing</span><br/><span className="pill pill-cyan" style={{ fontSize: '0.65rem' }}>Era 2</span> <span style={{ fontSize: '0.75rem', color: 'var(--fg-dim)' }}>Player-to-player market, info diffusion</span></td>
|
||
</tr>
|
||
<tr>
|
||
<td><span className="pill pill-cyan">Social & multiplayer</span></td>
|
||
<td style={{ color: 'var(--fg-dim)' }}>Local chat, delayed PMs, bounty system, emergent player-driven justice. Communication is range-based. <strong style={{ color: 'var(--accent)' }}>Priority pillar.</strong></td>
|
||
<td><span className="pill pill-cyan" style={{ fontSize: '0.65rem' }}>Era 2</span> <span style={{ fontSize: '0.75rem', color: 'var(--fg-dim)' }}>All social features require multiplayer</span></td>
|
||
</tr>
|
||
<tr>
|
||
<td><span className="pill pill-green">Command-based (not action)</span></td>
|
||
<td style={{ color: 'var(--fg-dim)' }}>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 <strong style={{ color: 'var(--fg)' }}>reactor power allocation</strong> (FTL-style) between systems. No manual flight, no aiming, no skill shots. The skill is in <strong style={{ color: 'var(--fg)' }}>what</strong> you power, not <strong style={{ color: 'var(--fg)' }}>how fast</strong> you click.</td>
|
||
<td><span className="pill pill-green">Era 1</span> <span style={{ fontSize: '0.75rem', color: 'var(--fg-dim)' }}>Core combat & movement loop</span></td>
|
||
</tr>
|
||
<tr>
|
||
<td><span className="pill pill-purple">Ship fitting</span></td>
|
||
<td style={{ color: 'var(--fg-dim)' }}>CPU/Power Grid slot system. High/Med/Low slots with meaningful fitting tradeoffs. Multiple ships, AI crew (post-MVP).</td>
|
||
<td><span className="pill pill-green">Era 1</span> <span style={{ fontSize: '0.75rem', color: 'var(--fg-dim)' }}>Basic fitting, single ship class</span></td>
|
||
</tr>
|
||
<tr>
|
||
<td><span className="pill pill-amber">Emergent lore</span></td>
|
||
<td style={{ color: 'var(--fg-dim)' }}>No server has the same lore as another. The galaxy is a <strong style={{ color: 'var(--fg)' }}>single persistent world</strong> with systems, planets, anomalies, and orbiting objects. A world simulation layer spawns PvE events dynamically — faction wars, space anomalies, migrations, raids — that create a <strong style={{ color: 'var(--fg)' }}>living story unique to every server</strong>. Lore evolves through both player actions and server-driven world events.</td>
|
||
<td><span className="pill pill-cyan" style={{ fontSize: '0.65rem' }}>Era 2</span> <span style={{ fontSize: '0.75rem', color: 'var(--fg-dim)' }}>Living galaxy requires world agents</span></td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
|
||
<div className="section-header" style={{ marginTop: 'var(--sp-8)' }}>
|
||
<span className="section-num">OV-02</span>
|
||
<h2 style={{ margin: 0 }}>Design Principles</h2>
|
||
</div>
|
||
|
||
<div className="grid-2">
|
||
<div className="card" style={{ borderLeft: '3px solid var(--accent)' }}>
|
||
<h4 style={{ color: 'var(--accent)' }}>Spreadsheet simulator, not flight sim</h4>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: 0 }}>
|
||
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. <strong style={{ color: 'var(--fg)' }}>The depth is in the economy and information.</strong>
|
||
</p>
|
||
</div>
|
||
<div className="card">
|
||
<h4 style={{ color: 'var(--cyan)' }}>Authoritative backend</h4>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: 0 }}>
|
||
SpacetimeDB owns authoritative game state. The browser renders state and sends player
|
||
intentions. The renderer should never become the source of truth.
|
||
</p>
|
||
</div>
|
||
<div className="card">
|
||
<h4 style={{ color: 'var(--green)' }}>Information is the real currency</h4>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: 0 }}>
|
||
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
|
||
<em> Economy → 📡 Info Diffusion</em> tab for the full model.
|
||
</p>
|
||
</div>
|
||
<div className="card">
|
||
<h4 style={{ color: 'var(--purple)' }}>Movement & combat are not action</h4>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: 0 }}>
|
||
The player never pilots the ship directly. Click a destination → ship autopilots. Click a hostile → ship auto-engages.
|
||
During combat, the player manages <strong style={{ color: 'var(--fg)' }}>reactor power allocation</strong> (FTL-style: weapons/shields/engines/aux)
|
||
and <strong style={{ color: 'var(--fg)' }}>subsystem targeting</strong>. 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.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="section-header" style={{ marginTop: 'var(--sp-8)' }}>
|
||
<span className="section-num">OV-03</span>
|
||
<h2 style={{ margin: 0 }}>Core MVP Loop</h2>
|
||
</div>
|
||
|
||
<div className="card card-accent" style={{ padding: 'var(--sp-6) var(--sp-8)' }}>
|
||
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.85rem', color: 'var(--fg-dim)', lineHeight: 2 }}>
|
||
<span style={{ color: 'var(--cyan)' }}>Connect</span> → <span style={{ color: 'var(--accent)' }}>Spawn Ship</span> →{' '}
|
||
<span style={{ color: 'var(--green)' }}>Navigate</span> →{' '}
|
||
<span style={{ color: 'var(--purple)' }}>Mine</span> →{' '}
|
||
<span style={{ color: 'var(--fg-bright)' }}>Inventory</span> →{' '}
|
||
<span style={{ color: 'var(--cyan)' }}>Station</span> →{' '}
|
||
<span style={{ color: 'var(--accent)' }}>Sell Ore</span> →{' '}
|
||
<span style={{ color: 'var(--green)' }}>Chat</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="callout callout-info" style={{ marginBottom: 'var(--sp-5)' }}>
|
||
<strong>The real loop is economic.</strong> 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 <strong>information diffusion between systems</strong>.
|
||
</div>
|
||
|
||
<h3 style={{ marginTop: 'var(--sp-6)' }}>Minimum Viable Screens</h3>
|
||
|
||
<div className="callout callout-info" style={{ marginBottom: 'var(--sp-4)' }}>
|
||
<strong>Era 1 screens</strong> ship in the single-player proof of concept (Roadmap phases 0–7). These are the minimum screens needed to validate the core loop is fun.
|
||
</div>
|
||
<table className="data-table">
|
||
<thead>
|
||
<tr><th>Screen / Panel</th><th>Minimum Functionality</th></tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr><td>Login / Connect</td><td>Display current identity and connection status.</td></tr>
|
||
<tr><td>3D Star-System Map</td><td>Strategic overview — ships, asteroids, station, click-to-set-waypoint. <strong>Not a flight sim.</strong></td></tr>
|
||
<tr><td>Ship Status Panel</td><td>Name, owner, status, cargo, current action, location.</td></tr>
|
||
<tr><td>Inventory Panel</td><td>Item type + quantity grid. Sell button when docked. Cargo capacity bar.</td></tr>
|
||
<tr><td>Station Panel</td><td>Dock/undock state, sell ore, view market, refit ship.</td></tr>
|
||
<tr><td>Market Panel</td><td>Order book, price per unit, place sell order from inventory. NPC-only economy in Era 1.</td></tr>
|
||
<tr><td>Combat HUD</td><td>Target selection, module activation, reactor power allocation bars (FTL-style).</td></tr>
|
||
<tr><td>Debug Panel</td><td>Reducer call log, error display, connection metrics, entity count.</td></tr>
|
||
</tbody>
|
||
</table>
|
||
|
||
<div className="callout callout-info" style={{ marginTop: 'var(--sp-6)', marginBottom: 'var(--sp-4)' }}>
|
||
<strong>Era 2 screens</strong> require SpacetimeDB multiplayer infrastructure (Roadmap phases 8–15).
|
||
</div>
|
||
<table className="data-table">
|
||
<thead>
|
||
<tr><th>Screen / Panel</th><th>Minimum Functionality</th></tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr><td style={{ fontWeight: 600, color: 'var(--accent)' }}>Market Panel ★</td><td><strong>Primary game surface.</strong> Order book with depth, price history charts, contract specifications, bid/ask spread, long/short positions, margin account, place orders (market/limit/stop). See the Interactive Demos → Market demo.</td></tr>
|
||
<tr><td style={{ fontWeight: 600, color: 'var(--cyan)' }}>Commodity Ticker</td><td>Scrolling price ticker across all contracts. Real-time price updates. Category filters. Sparkline charts.</td></tr>
|
||
<tr><td>Chat Panel</td><td>Send and receive local/system messages. Range-based propagation.</td></tr>
|
||
<tr><td>Bounty Board</td><td>Active bounties by tier, place bounty on player, kill feed.</td></tr>
|
||
<tr><td>Galaxy Map</td><td>Region/constellation/system hierarchy, faction territory overlay, active world events.</td></tr>
|
||
<tr><td>World Event Panel</td><td>Active events in current region, countdown timers, story log access.</td></tr>
|
||
</tbody>
|
||
</table>
|
||
|
||
<div className="section-header" style={{ marginTop: 'var(--sp-8)' }}>
|
||
<span className="section-num">OV-04</span>
|
||
<h2 style={{ margin: 0 }}>HUD & View Mode Architecture</h2>
|
||
</div>
|
||
|
||
<div className="callout callout-info" style={{ marginBottom: 'var(--sp-5)' }}>
|
||
<strong>Decision: Hybrid diegetic + panel approach.</strong>
|
||
The game uses <strong>two distinct view modes</strong> 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.
|
||
Both are correct — they apply to different contexts.
|
||
</div>
|
||
|
||
<div className="grid-2" style={{ marginBottom: 'var(--sp-6)' }}>
|
||
<div className="card" style={{ borderLeft: '3px solid var(--cyan)' }}>
|
||
<h4 style={{ color: 'var(--cyan)' }}>🚀 Flight Mode (Undocked)</h4>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
|
||
When the player's ship is in space (undocked), the primary view is a <strong style={{ color: 'var(--fg)' }}>3D viewport
|
||
with diegetic HUD overlays</strong>. This is what the Game HUD demo validates. The 3D scene shows the ship's
|
||
surroundings (asteroids, stations, other ships, celestials) while the HUD overlays provide:
|
||
</p>
|
||
<ul style={{ color: 'var(--fg-dim)', fontSize: '0.82rem', margin: 0, paddingLeft: 'var(--sp-5)' }}>
|
||
<li><strong style={{ color: 'var(--fg)' }}>Shield/armor/hull bars</strong> — curved around the viewport center, not flat rectangles</li>
|
||
<li><strong style={{ color: 'var(--fg)' }}>Module activation buttons</strong> — arranged in a bottom rack, grouped by slot type</li>
|
||
<li><strong style={{ color: 'var(--fg)' }}>Overview panel</strong> — collapsible sidebar listing all on-grid entities with type, distance, and hostile/friendly status</li>
|
||
<li><strong style={{ color: 'var(--fg)' }}>Target lock indicator</strong> — centered targeting reticle with lock timer</li>
|
||
<li><strong style={{ color: 'var(--fg)' }}>Capacitor gauge</strong> — circular arc display</li>
|
||
<li><strong style={{ color: 'var(--fg)' }}>Speed/distance HUD</strong> — current speed, target distance, ETA</li>
|
||
<li><strong style={{ color: 'var(--fg)' }}>Chat stub</strong> — minimized chat bubble, expands to full chat on click</li>
|
||
</ul>
|
||
<div style={{ marginTop: 'var(--sp-3)', fontFamily: 'var(--font-mono)', fontSize: '0.72rem', color: 'var(--muted)' }}>
|
||
Validated by: Game HUD demo · Covers: movement, combat, mining interactions
|
||
</div>
|
||
</div>
|
||
<div className="card" style={{ borderLeft: '3px solid var(--accent)' }}>
|
||
<h4 style={{ color: 'var(--accent)' }}>🏢 Station Mode (Docked)</h4>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
|
||
When the player is docked at a station, the 3D viewport is replaced by a <strong style={{ color: 'var(--fg)' }}>traditional
|
||
panel-based UI</strong> — the "spreadsheet simulator" surface. This is the Overview spec's "tables, charts, and panels"
|
||
description. Station mode panels include:
|
||
</p>
|
||
<ul style={{ color: 'var(--fg-dim)', fontSize: '0.82rem', margin: 0, paddingLeft: 'var(--sp-5)' }}>
|
||
<li><strong style={{ color: 'var(--fg)' }}>Market Panel</strong> — order book, price history charts, contract specifications</li>
|
||
<li><strong style={{ color: 'var(--fg)' }}>Inventory Panel</strong> — item grid with quantity, type, value estimation</li>
|
||
<li><strong style={{ color: 'var(--fg)' }}>Fitting Screen</strong> — ship slot layout with drag-and-drop module fitting</li>
|
||
<li><strong style={{ color: 'var(--fg)' }}>Refining Interface</strong> — batch processing with yield preview</li>
|
||
<li><strong style={{ color: 'var(--fg)' }}>Manufacturing Tab</strong> — blueprint selection, job queue, material requirements</li>
|
||
<li><strong style={{ color: 'var(--fg)' }}>Insurance Panel</strong> — coverage tiers, premium calculator, active policies</li>
|
||
<li><strong style={{ color: 'var(--fg)' }}>Agent/Mission Panel</strong> — NPC agent list, available missions, standings</li>
|
||
</ul>
|
||
<div style={{ marginTop: 'var(--sp-3)', fontFamily: 'var(--font-mono)', fontSize: '0.72rem', color: 'var(--muted)' }}>
|
||
Validated by: Market, Fitting, Refining demos · Covers: all station-based gameplay
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="grid-2" style={{ marginBottom: 'var(--sp-6)' }}>
|
||
<div className="card" style={{ borderLeft: '3px solid var(--green)' }}>
|
||
<h4 style={{ color: 'var(--green)' }}>🗺 Map Mode (Both)</h4>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
|
||
Accessible from either view mode, the map replaces the main viewport with a full-screen 3D strategic map.
|
||
<strong style={{ color: 'var(--fg)' }}>Era 1</strong> shows the current star system (single system with celestials, belts, stations).
|
||
<strong style={{ color: 'var(--fg)' }}>Era 2</strong> adds the Galaxy Map (multi-system with region/constellation hierarchy, faction overlay, world events).
|
||
</p>
|
||
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.72rem', color: 'var(--muted)' }}>
|
||
Era 1: System Map needed · Era 2: Validated by Star Map demo
|
||
</div>
|
||
</div>
|
||
<div className="card" style={{ borderLeft: '3px solid var(--purple)' }}>
|
||
<h4 style={{ color: 'var(--purple)' }}>Transition Rules</h4>
|
||
<ul style={{ color: 'var(--fg-dim)', fontSize: '0.82rem', margin: 0, paddingLeft: 'var(--sp-5)' }}>
|
||
<li><strong style={{ color: 'var(--fg)' }}>Undock:</strong> Flight Mode fades in with 3D viewport. HUD elements animate in sequence (shields → modules → overview).</li>
|
||
<li><strong style={{ color: 'var(--fg)' }}>Dock:</strong> 3D viewport zooms toward station. Fades to Station Mode panel layout.</li>
|
||
<li><strong style={{ color: 'var(--fg)' }}>Open Map:</strong> Current viewport shrinks into a corner (minimap). Full map overlay fades in.</li>
|
||
<li><strong style={{ color: 'var(--fg)' }}>Close Map:</strong> Map fades out. Previous viewport mode restores.</li>
|
||
<li><strong style={{ color: 'var(--fg)' }}>Combat ambush:</strong> If attacked while docked, player auto-undocks into Flight Mode (no safe space abuse).</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="callout callout-warn">
|
||
<strong>Why not all-diegetic or all-panel?</strong>
|
||
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 <strong>the game alternates between two distinct cognitive modes</strong> —
|
||
reactive (in-space, monitoring health/modules/overview) and analytical (docked, reading tables/planning routes).
|
||
Each view mode is optimized for its cognitive mode.
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
window.GDD.OverviewPage = OverviewPage;
|
||
</script>
|
||
|
||
<!-- ── App bootstrap (must be last) ─────────────────────────────── -->
|
||
<script type="text/babel">
|
||
window.GDD = window.GDD || {};
|
||
const GDD = window.GDD;
|
||
|
||
const FULLSCREEN_PAGES = new Set([
|
||
'demo-starmap',
|
||
'demo-movement',
|
||
'demo-combat',
|
||
'demo-market',
|
||
'demo-fitting',
|
||
'demo-refining',
|
||
'demo-progression',
|
||
'demo-bounty',
|
||
'demo-gamehud',
|
||
'demo-chat',
|
||
'demo-zora',
|
||
'demo-galaxy',
|
||
]);
|
||
|
||
// Map pageId -> component name on GDD
|
||
const PAGE_COMPONENTS = {
|
||
'overview': 'OverviewPage',
|
||
'architecture': 'ArchitecturePage',
|
||
'techstack': 'TechStackPage',
|
||
'backend': 'BackendPage',
|
||
'agents': 'AgentsPage',
|
||
'gameplay': 'GameplayPage',
|
||
'ships': 'ShipsPage',
|
||
'economy': 'EconomyPage',
|
||
'social': 'SocialPage',
|
||
'ship-ai': 'ShipAIPage',
|
||
'roadmap': 'RoadmapPage',
|
||
'risks': 'RisksPage',
|
||
'demo-gallery': 'DemoGalleryPage',
|
||
'demo-starmap': 'StarMapDemo',
|
||
'demo-movement': 'ShipMovementDemo',
|
||
'demo-combat': 'CombatDemo',
|
||
'demo-market': 'MarketDemo',
|
||
'demo-fitting': 'FittingDemo',
|
||
'demo-refining': 'RefiningDemo',
|
||
'demo-progression':'ProgressionDemo',
|
||
'demo-bounty': 'BountyDemo',
|
||
'demo-gamehud': 'GameHudDemo',
|
||
'demo-chat': 'ChatDemo',
|
||
'demo-zora': 'ZoraDemo',
|
||
'demo-galaxy': 'GalaxyDemo',
|
||
};
|
||
|
||
function App() {
|
||
const { page, navigate } = GDD.useRouter();
|
||
const [collapsed, setCollapsed] = React.useState(false);
|
||
const [loading, setLoading] = React.useState(false);
|
||
const [loadError, setLoadError] = React.useState(null);
|
||
|
||
// Load the page component on demand when navigation changes
|
||
React.useEffect(() => {
|
||
// Check if component already available (inline loaded pages like overview)
|
||
const compName = PAGE_COMPONENTS[page];
|
||
if (compName && GDD[compName]) {
|
||
GDD._loadedPages = GDD._loadedPages || {};
|
||
GDD._loadedPages[page] = true;
|
||
return;
|
||
}
|
||
setLoading(true);
|
||
setLoadError(null);
|
||
GDD.loadPage(page)
|
||
.then(() => setLoading(false))
|
||
.catch((err) => {
|
||
setLoadError(err.message || 'Failed to load page');
|
||
setLoading(false);
|
||
});
|
||
}, [page]);
|
||
|
||
// Preload adjacent pages in the background after current page loads
|
||
React.useEffect(() => {
|
||
if (!loading && !loadError) {
|
||
// Preload the next/prev pages after a short delay
|
||
const allPageIds = Object.keys(PAGE_COMPONENTS);
|
||
const idx = allPageIds.indexOf(page);
|
||
if (idx >= 0 && idx < allPageIds.length - 1) {
|
||
GDD.preloadPage(allPageIds[idx + 1]);
|
||
}
|
||
if (idx > 0) {
|
||
GDD.preloadPage(allPageIds[idx - 1]);
|
||
}
|
||
}
|
||
}, [page, loading, loadError]);
|
||
|
||
const renderPage = () => {
|
||
if (loading) {
|
||
return (
|
||
<div style={{
|
||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||
height: '60vh', flexDirection: 'column', gap: 'var(--sp-4)',
|
||
color: 'var(--muted)', fontFamily: 'var(--font-mono)', fontSize: '0.85rem',
|
||
}}>
|
||
<div style={{ animation: 'pulse 1.5s ease-in-out infinite' }}>Loading module…</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
if (loadError) {
|
||
return (
|
||
<div style={{
|
||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||
height: '60vh', flexDirection: 'column', gap: 'var(--sp-4)',
|
||
color: 'var(--red)', fontFamily: 'var(--font-mono)', fontSize: '0.85rem',
|
||
}}>
|
||
<div>Failed to load page: {loadError}</div>
|
||
<button className="btn btn-sm" onClick={() => window.location.reload()}>Reload</button>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
const compName = PAGE_COMPONENTS[page];
|
||
const Comp = compName && GDD[compName];
|
||
if (!Comp) return <GDD.OverviewPage />;
|
||
return <Comp />;
|
||
};
|
||
|
||
const isFullscreen = FULLSCREEN_PAGES.has(page);
|
||
|
||
if (isFullscreen) {
|
||
return (
|
||
<div className="app-shell fullscreen-demo">
|
||
<div className="main-area">
|
||
<div className="content content--flush">
|
||
{renderPage()}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div className="app-shell">
|
||
<GDD.Sidebar
|
||
collapsed={collapsed}
|
||
currentPage={page}
|
||
onNavigate={navigate}
|
||
onToggle={() => setCollapsed(c => !c)}
|
||
/>
|
||
<div className="main-area">
|
||
<GDD.TopBar
|
||
collapsed={collapsed}
|
||
currentPage={page}
|
||
onToggle={() => setCollapsed(c => !c)}
|
||
/>
|
||
<div className="content">
|
||
{renderPage()}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||
root.render(<App />);
|
||
</script>
|
||
|
||
<!-- All other pages & demos are loaded on-demand by js/loader.js -->
|
||
</body>
|
||
</html>
|