Files
Space-Game/archive/legacy-static/js/pages/backend.js
francy51 316a44661b Restructure into pnpm monorepo with game shell, docs, and SpacetimeDB backend
- Restructure flat static prototype into pnpm workspace monorepo
- apps/game: playable shell with R3F 3D scene, HUD, SpacetimeDB connection
- apps/docs: design docs and prototypes
- apps/site: landing page
- packages/ui: shared Button and Panel primitives
- services/spacetimedb: backend module (9 tables, 11 reducers)
- Archive legacy static files to archive/legacy-static/
- Game loop: connect, undock, target, approach, dock, mine, sell
- Add pnpm-workspace.yaml, tsconfig.base.json, spacetime.json
2026-05-31 17:56:56 -04:00

677 lines
58 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
window.GDD = window.GDD || {};
function BackendPage() {
const [activeSection, setActiveSection] = React.useState('tables');
const [galaxySubSection, setGalaxySubSection] = React.useState('galaxy-overview');
return (
<div className="content-inner">
<h1 style={{ marginBottom: '8px' }}>SpacetimeDB Backend Model</h1>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.95rem', maxWidth: '680px' }}>
The backend holds persistent, authoritative state and exposes server-side reducers for game
actions. Clients subscribe to the rows they need and update reactively.
</p>
<div style={{ display: 'flex', gap: 'var(--sp-2)', marginBottom: 'var(--sp-6)' }}>
{[
{ id: 'tables', label: 'Tables' },
{ id: 'reducers', label: 'Reducers' },
{ id: 'movement', label: 'Movement Model' },
{ id: 'galaxy', label: 'Galaxy Simulation' },
{ id: 'er', label: 'ER Diagram' },
].map(t => (
<button key={t.id} className={`btn btn-sm${activeSection === t.id ? ' btn-primary' : ''}`}
onClick={() => setActiveSection(t.id)}>{t.label}</button>
))}
</div>
{activeSection === 'tables' && (
<>
<h3>Data Tables</h3>
<div style={{ overflowX: 'auto' }}>
<table className="data-table">
<thead>
<tr><th>Table</th><th>Purpose</th><th>Key Fields</th></tr>
</thead>
<tbody>
{[
{ name: 'players', purpose: 'Player account/session profile', fields: 'player_id, identity, display_name, current_system_id, created_at' },
{ name: 'ships', purpose: 'Active ship state', fields: 'ship_id, owner_player_id, system_id, x/y/z, destination, speed, status' },
{ name: 'regions', purpose: 'Galaxy regions (core, frontier, deep null)', fields: 'region_id, name, description, faction_id, security_profile, x/y/z (galaxy coords)' },
{ name: 'constellations', purpose: 'Star clusters within regions', fields: 'constellation_id, region_id, name, gate_connections (json), x/y/z (region coords)' },
{ name: 'factions', purpose: 'NPC factions with territory, goals, and military/economic strength', fields: 'faction_id, name, ideology, territory_region_ids (json), military_strength (f64), economy_strength (f64), diplomatic_stance (enum)' },
{ name: 'systems', purpose: 'Star systems within the single galaxy', fields: 'system_id, name, region_id, constellation_id, security_level, x/y/z (galaxy coords), star_type, description' },
{ name: 'planets', purpose: 'Planets orbiting stars', fields: 'planet_id, system_id, name, planet_type, orbit_radius, orbit_period, resources' },
{ name: 'moons', purpose: 'Moons orbiting planets', fields: 'moon_id, planet_id, name, moon_type, orbit_radius, resources' },
{ name: 'orbiting_objects', purpose: 'Stations, belts, anomalies in orbital slots', fields: 'object_id, parent_body_id, object_type, orbit_radius, name, state' },
{ name: 'stations', purpose: 'Docking and trading locations', fields: 'station_id, system_id, name, x/y/z, services' },
{ name: 'asteroids', purpose: 'Mineable resource nodes', fields: 'asteroid_id, system_id, resource_type, quantity, x/y/z' },
{ name: 'inventory_items', purpose: 'Player/station item storage', fields: 'item_id, owner_player_id, location, item_type, quantity' },
{ name: 'market_orders', purpose: 'Buy/sell orders', fields: 'order_id, station_id, seller_id, item_type, price, quantity, status' },
{ name: 'chat_messages', purpose: 'Local/system chat stream', fields: 'message_id, channel_id, sender_id, body, created_at' },
{ name: 'active_actions', purpose: 'Long-running actions', fields: 'action_id, player_id, action_type, target_id, started_at, completes_at' },
{ name: 'faction_relations', purpose: 'Dynamic NPC faction relationship matrix', fields: 'faction_a_id, faction_b_id, standing (-10 to +10), trend (rising/falling/stable), last_event_id' },
{ name: 'world_events', purpose: 'Active world simulation events', fields: 'event_id, event_type, system_id, severity, started_at, expires_at, state, participants' },
{ name: 'galaxy_story_log', purpose: 'Persistent server story timeline', fields: 'log_id, event_id, chapter, headline, body, timestamp' },
{ name: 'bookmarks', purpose: 'Player-saved locations', fields: 'bookmark_id, player_id, system_id, x/y/z, name, created_at' },
{ name: 'waypoints', purpose: 'Multi-stop navigation routes', fields: 'route_id, player_id, stops (ordered system list), name, shared' },
{ name: 'bounties', purpose: 'Bounty pool per target', fields: 'target_player_id, total_pool, tier, last_hostile_act' },
{ name: 'bounty_contributions', purpose: 'Individual bounty payments', fields: 'contribution_id, target_id, contributor_id, amount, timestamp' },
{ name: 'kill_feed', purpose: 'Ship destruction events', fields: 'kill_id, victim_id, killer_id, ship_type, system_id, bounty_collected, timestamp' },
{ name: 'player_skills', purpose: 'XP and levels per skill', fields: 'player_id, skill_name, xp, level, last_action_at' },
{ name: 'fleet_beacons', purpose: 'Temporary fleet rally points (post-MVP)', fields: 'beacon_id, fleet_id, creator_id, system_id, x/y/z, expires_at' },
{ name: 'ship_ai_soul', purpose: 'Soul document and personality state per ship', fields: 'ship_id, soul_md (text), growth_vectors (json), personality_state (json), soul_depth (u32), created_at, last_updated_at' },
{ name: 'ship_ai_modules', purpose: 'Installed AI modules and fitting state', fields: 'module_id, ship_id, module_type (enum), tier, slot (med/low), cpu_cost, grid_cost, active, fitted_at' },
{ name: 'ship_ai_tools', purpose: 'Tool registry derived from fitted modules', fields: 'tool_id, ship_id, tool_name, source_module_id, parameters_schema (json), return_schema (json)' },
{ name: 'ship_ai_memory', purpose: 'Event log and conversation history', fields: 'memory_id, ship_id, category, content, related_event_id, timestamp, importance_score' },
{ name: 'ship_ai_directives', purpose: 'Player-set goals for autonomous mode', fields: 'directive_id, ship_id, description, priority, deadline, status, created_at' },
{ name: 'ship_ai_agent_runtime', purpose: 'Per-ship agent loop state and tick schedule', fields: 'ship_id, implementation_tier (0/1/2), tick_interval_ms, next_tick_at, token_budget, status' },
{ name: 'ship_ai_soul_events', purpose: 'Audit log of soul-shaping events', fields: 'event_id, ship_id, event_type, soul_md_delta, applied_at' },
{ name: 'ship_types', purpose: 'Ship class definitions (stats, slot layout)', fields: 'type_id, name, class, hull, armor, shield, high_slots, med_slots, low_slots, cpu, power_grid, cargo, speed, mass, base_hull_value' },
{ name: 'modules_catalog', purpose: 'Module definitions (stats, slot type, costs)', fields: 'module_id, name, slot_type (high/med/low), cpu_cost, grid_cost, category, tier, effects_json' },
{ name: 'ship_fittings', purpose: 'Which modules are fitted to which ship slots', fields: 'fitting_id, ship_id, slot_index, module_id, online (bool)' },
{ name: 'npc_entities', purpose: 'Active NPC pirates and hostiles', fields: 'npc_id, system_id, class_id, behavior_template, x/y/z, hull/armor/shield, target_id, state, spawn_location, spawn_time' },
{ name: 'npc_class_templates', purpose: 'NPC type definitions and stats', fields: 'class_id, name, tier, hull_base, armor_base, shield_base, speed, damage, behavior, loot_table_id, bounty' },
{ name: 'loot_tables', purpose: 'Drop tables for NPC kills and wrecks', fields: 'table_id, entries (item_type, min_qty, max_qty, drop_chance), security_band' },
{ name: 'blueprints', purpose: 'Manufacturing recipes', fields: 'bp_id, product_type, product_qty, materials_json, time_seconds, skill_requirements' },
{ name: 'manufacturing_jobs', purpose: 'Active manufacturing queues', fields: 'job_id, player_id, station_id, bp_id, started_at, completes_at, status, output_location' },
{ name: 'skills_catalog', purpose: 'Skill definitions and XP curves', fields: 'skill_id, name, category, xp_curve (array), unlocks_json, max_level' },
{ name: 'chat_channels', purpose: 'Channel definitions and properties', fields: 'channel_id, name, type (local/trade/private/corp), scope, owner_id, created_at' },
{ name: 'insurance_policies', purpose: 'Active ship insurance contracts', fields: 'policy_id, player_id, ship_id, tier, premium_paid, payout_value, purchased_at, expires_at, active' },
{ name: 'ship_type_base_values', purpose: 'Base hull values for insurance', fields: 'ship_type_id, base_hull_value, insurance_premium_mult' },
{ name: 'station_commodity_demand', purpose: 'Per-station per-commodity demand state for NPC pricing', fields: 'station_id, commodity_id, flow_ema (f64), demand_pressure (f64, [0.81.4]), volume_sold_to_npc, volume_bought_from_npc, npc_stock_remaining, last_tick' },
{ name: 'commodity_price_params', purpose: 'Base prices and adjustment parameters per commodity', fields: 'commodity_id, base_price (f64), buy_spread (f64, [0.650.85]), sell_spread (f64, [1.101.35]), ema_alpha (f64), pressure_beta (f64), decay_gamma (f64)' },
{ name: 'regional_price_seeds', purpose: 'Static regional modifiers set at galaxy generation', fields: 'region_id, commodity_id, modifier (f64, [0.61.5])' },
{ name: 'npc_agents', purpose: 'NPC agents at stations offering missions', fields: 'agent_id, name, faction_id, station_id, specialty (kill/courier/mining/survey/trade/escort), quality (u32), mission_levels_offered, dialogue_seed' },
{ name: 'mission_templates', purpose: 'Mission type definitions and objectives', fields: 'template_id, type (enum), level (14), title, description_template, objectives_json, reward_base, time_limit_seconds, security_band_min, skill_requirements_json, faction_id' },
{ name: 'active_missions', purpose: 'Currently active player missions', fields: 'mission_id, player_id, agent_id, template_id, objectives_state_json, status (active/completed/failed/expired), accepted_at, expires_at, completed_at' },
{ name: 'player_standing', purpose: 'Player standing with agents and factions', fields: 'player_id, entity_id, entity_type (agent/faction), standing (f64, 10 to +10), last_mission_at' },
{ name: 'player_loyalty_points', purpose: 'Faction loyalty point balances', fields: 'player_id, faction_id, lp_balance (u64), lifetime_earned (u64)' },
{ name: 'mission_offers', purpose: 'Current mission offerings at stations', fields: 'offer_id, agent_id, station_id, template_id, reward_modifier, expires_at, generated_at' },
{ name: 'balance_metrics', purpose: 'Balancing Agent metric tracking', fields: 'metric_name, current_value (f64), healthy_min, healthy_max, last_updated, trend (rising/falling/stable)' },
{ name: 'balance_levers', purpose: 'Balancing Agent control levers', fields: 'lever_name, current_multiplier (f64), target_multiplier (f64), clamp_min, clamp_max, last_adjusted_at' },
{ name: 'balance_audit', purpose: 'Balancing Agent intervention log', fields: 'audit_id, tick_time, metrics_snapshot_json, adjustments_json, reason' },
].map((row, i) => (
<tr key={i}>
<td><code>{row.name}</code></td>
<td style={{ color: 'var(--fg-dim)' }}>{row.purpose}</td>
<td style={{ fontSize: '0.75rem', color: 'var(--muted)' }}>{row.fields}</td>
</tr>
))}
</tbody>
</table>
</div>
</>
)}
{activeSection === 'reducers' && (
<>
<h3>Reducers (Server Commands)</h3>
<div style={{ overflowX: 'auto' }}>
<table className="data-table">
<thead>
<tr><th>Reducer</th><th>Client Trigger</th><th>Server Responsibility</th></tr>
</thead>
<tbody>
{[
{ name: 'connect_player(display_name)', trigger: 'Player opens app', resp: 'Create/update player row, spawn initial ship if needed.' },
{ name: 'set_destination(ship_id, x, y, z)', trigger: 'Click in space', resp: 'Validate ownership/status, update destination/vector.' },
{ name: 'dock(station_id)', trigger: 'Click dock', resp: 'Check distance, set ship docked, update location/state.' },
{ name: 'start_mining(asteroid_id)', trigger: 'Click mine', resp: 'Check range, asteroid quantity, ship status, create active action.' },
{ name: 'complete_mining(action_id)', trigger: 'Timer/event', resp: 'Transfer ore into inventory, reduce asteroid quantity.' },
{ name: 'sell_item(item_type, qty, station)', trigger: 'Sell from UI', resp: 'Validate inventory and station, exchange ore for ISK.' },
{ name: 'place_market_order(...)', trigger: 'Market UI', resp: 'Reserve inventory, create sell order.' },
{ name: 'send_chat(channel_id, body)', trigger: 'Chat box', resp: 'Validate/rate-limit, append chat message row.' },
{ name: 'world_tick(ctx)', trigger: 'Server timer (5 min)', resp: 'Evaluate galaxy state, player density, faction matrix. Conditionally spawn PvE events (faction conflicts, anomalies, migrations, raids). Propagate active events.' },
{ name: 'spawn_world_event(event_type, system_id, params)', trigger: 'World tick evaluation', resp: 'Create world_event row, generate story log entry, notify nearby players via sensors, set expiration timer.' },
{ name: 'resolve_world_event(event_id, outcome)', trigger: 'Event timer or player action', resp: 'Update galaxy state based on outcome, write story log chapter, adjust faction relations, trigger cascading events.' },
].map((row, i) => (
<tr key={i}>
<td><code style={{ fontSize: '0.75rem' }}>{row.name}</code></td>
<td style={{ color: 'var(--fg-dim)' }}>{row.trigger}</td>
<td style={{ color: 'var(--fg-dim)' }}>{row.resp}</td>
</tr>
))}
</tbody>
</table>
</div>
</>
)}
{activeSection === 'movement' && (
<>
<h3>Movement Model</h3>
<div className="callout callout-info" style={{ marginBottom: 'var(--sp-5)' }}>
Avoid sending per-frame movement. Store destination and speed. Clients interpolate visually;
the backend periodically updates authoritative positions.
</div>
<div className="card card-accent">
<h4>Movement Flow</h4>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.82rem', color: 'var(--fg-dim)', lineHeight: 2 }}>
1. Client calls <code>set_destination(ship_id, x, y, z)</code><br/>
2. Server validates ownership + ship status<br/>
3. Server updates <code>ships.destination</code> + calculates velocity vector<br/>
4. Server broadcasts updated ship state to subscribers<br/>
5. Client interpolates visual position between last known + destination<br/>
6. Server periodically updates authoritative <code>x/y/z</code> position
</div>
</div>
<div className="grid-2" style={{ marginTop: 'var(--sp-5)' }}>
<div className="card">
<h4 style={{ color: 'var(--green)' }}>Client-side interpolation</h4>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: 0 }}>
Smooth visual movement between authoritative position updates. Uses dead reckoning
with periodic server correction. Handles latency spikes gracefully.
</p>
</div>
<div className="card">
<h4 style={{ color: 'var(--cyan)' }}>Server authority</h4>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: 0 }}>
Backend is the source of truth for all positions. Clients never modify their own
position directly they submit intentions and wait for confirmation.
</p>
</div>
</div>
</>
)}
{activeSection === 'galaxy' && (
<>
<div style={{ display: 'flex', gap: 'var(--sp-2)', marginBottom: 'var(--sp-5)' }}>
{[
{ id: 'galaxy-overview', label: 'Overview' },
{ id: 'galaxy-gen', label: 'Galaxy Generation' },
{ id: 'galaxy-events', label: 'World Events' },
].map(g => (
<button key={g.id} className={`btn btn-sm${galaxySubSection === g.id ? ' btn-primary' : ''}`}
onClick={() => setGalaxySubSection(g.id)}>{g.label}</button>
))}
</div>
{galaxySubSection === 'galaxy-overview' && (<>
<h3>Galaxy Simulation Layer</h3>
<div className="callout callout-info" style={{ marginBottom: 'var(--sp-5)' }}>
<strong>Single galaxy, simulated world.</strong> The server maintains one persistent galaxy with a connected graph of star systems,
each containing planets, moons, asteroid belts, and stations. A world simulation layer runs on top, spawning dynamic PvE events
that create a unique story per server. This is not instanced content every player in the galaxy shares the same world state.
</div>
<div className="card card-accent" style={{ padding: 'var(--sp-6) var(--sp-8)', marginBottom: 'var(--sp-5)' }}>
<h4 style={{ marginBottom: 'var(--sp-4)' }}>Galaxy Topology</h4>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.82rem', color: 'var(--fg-dim)', lineHeight: 2.2 }}>
<span style={{ color: 'var(--accent)' }}>Galaxy</span> contains <span style={{ color: 'var(--cyan)' }}>Regions</span> (48 regions, each with distinct character)<br/>
<span style={{ color: 'var(--cyan)' }}>Region</span> contains <span style={{ color: 'var(--green)' }}>Constellations</span> (36 per region, connected clusters)<br/>
<span style={{ color: 'var(--green)' }}>Constellation</span> contains <span style={{ color: 'var(--purple)' }}>Systems</span> (28 per constellation, gate-connected)<br/>
<span style={{ color: 'var(--purple)' }}>System</span> contains <span style={{ color: 'var(--fg)' }}>Star + Planets + Moons + Belts + Stations + Anomalies</span><br/>
<span style={{ color: 'var(--fg)' }}>Planet</span> has <span style={{ color: 'var(--accent)' }}>orbiting_objects</span> (stations, moon mining outposts, customs offices)
</div>
</div>
<h3 style={{ marginBottom: 'var(--sp-4)' }}>World Simulation Tables</h3>
<div className="callout callout-info" style={{ marginBottom: 'var(--sp-5)', fontSize: '0.82rem' }}>
<strong>Overlap note:</strong> <code>faction_relations</code>, <code>world_events</code>, and <code>galaxy_story_log</code> also appear in the Tables tab with abbreviated field descriptions. The definitions below are the expanded versions with full field detail. Both tabs describe the same underlying tables.
</div>
<div style={{ overflowX: 'auto' }}>
<table className="data-table">
<thead>
<tr><th>Table</th><th>Purpose</th><th>Key Fields</th></tr>
</thead>
<tbody>
{[
{ name: 'regions', purpose: 'Galaxy regions (core, frontier, deep null)', fields: 'region_id, name, description, faction_id, security_profile' },
{ name: 'constellations', purpose: 'Star clusters within regions', fields: 'constellation_id, region_id, name, gate_connections' },
{ name: 'factions', purpose: 'NPC factions with territory and goals', fields: 'faction_id, name, ideology, territory_region_ids, military_strength, economy_strength' },
{ name: 'faction_relations', purpose: 'Dynamic relationship matrix', fields: 'faction_a_id, faction_b_id, standing (-10 to +10), trend (rising/falling/stable), last_event_id' },
{ name: 'world_events', purpose: 'Active PvE events in the galaxy', fields: 'event_id, event_type, system_id, severity (15), started_at, expires_at, state, participants_json, params_json' },
{ name: 'world_event_templates', purpose: 'Event blueprints with spawn conditions', fields: 'template_id, type, name, min_severity, max_severity, spawn_weight, required_faction_state, cooldown_hours' },
{ name: 'galaxy_story_log', purpose: 'Persistent server timeline — the “history” of this galaxy', fields: 'log_id, event_id, chapter_index, headline, body, affected_systems, timestamp' },
{ name: 'space_fauna', purpose: 'Migrating space creatures', fields: 'fauna_id, species, current_system_id, migration_route (json), next_waypoint, arrival_at, cycle_phase' },
{ name: 'anomalies', purpose: 'Temporary spatial phenomena', fields: 'anomaly_id, type (wormhole/nebula/storm/void), system_id, x/y/z, severity, expires_at, loot_table' },
].map((row, i) => (
<tr key={i}>
<td><code>{row.name}</code></td>
<td style={{ color: 'var(--fg-dim)' }}>{row.purpose}</td>
<td style={{ fontSize: '0.75rem', color: 'var(--muted)' }}>{row.fields}</td>
</tr>
))}
</tbody>
</table>
</div>
<h3 style={{ marginTop: 'var(--sp-6)', marginBottom: 'var(--sp-4)' }}>Event Spawn Logic</h3>
<div className="card" style={{ padding: 0, overflow: 'hidden' }}>
<div style={{ padding: 'var(--sp-3) var(--sp-4)', background: 'var(--surface-raised)', borderBottom: '1px solid var(--border)' }}>
<span style={{ fontFamily: 'var(--font-mono)', fontSize: '0.7rem', color: 'var(--accent)', textTransform: 'uppercase', letterSpacing: '0.05em' }}>
World Tick Reducer Pseudocode
</span>
</div>
<div className="code-block" style={{ margin: 0, borderRadius: 0 }}>
<code>
<span className="cm">// Runs every 5 minutes on the server</span><br/>
<span className="kw">reducer</span> <span className="fn">world_tick</span>(ctx) {'{'}<br/>
&nbsp;&nbsp;<span className="cm">// 1. Evaluate faction tension matrix</span><br/>
&nbsp;&nbsp;<span className="kw">for</span> each faction_pair {'{'}<br/>
&nbsp;&nbsp;&nbsp;&nbsp;<span className="kw">if</span> standing {'<'} -5 && random {'<'} tension_weight {'{'}<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span className="fn">spawn_event</span>(<span className="str">"faction_skirmish"</span>, contested_system);<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span className="fn">log_story</span>({'"'}Hostilities erupt between {'{'}A{'}'} and {'{'}B{'}'} in {'{'}system{'}'}{'"'});<br/>
&nbsp;&nbsp;&nbsp;&nbsp;{'}'}<br/>
&nbsp;&nbsp;{'}'}<br/>
<br/>
&nbsp;&nbsp;<span className="cm">// 2. Check anomaly spawn slots</span><br/>
&nbsp;&nbsp;<span className="kw">if</span> active_anomalies {'<'} max_anomalies {'{'}<br/>
&nbsp;&nbsp;&nbsp;&nbsp;pick random system weighted by distance_from_hub;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;<span className="fn">spawn_anomaly</span>(system, random_type);<br/>
&nbsp;&nbsp;{'}'}<br/>
<br/>
&nbsp;&nbsp;<span className="cm">// 3. Advance fauna migration routes</span><br/>
&nbsp;&nbsp;<span className="kw">for</span> each fauna {'{'}<br/>
&nbsp;&nbsp;&nbsp;&nbsp;<span className="kw">if</span> now {'>='} next_waypoint.arrival_at {'{'}<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;advance fauna to next system in route;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span className="fn">log_story</span>({'"'}{'{'}species{'}'} migration enters {'{'}system{'}'}{'"'});<br/>
&nbsp;&nbsp;&nbsp;&nbsp;{'}'}<br/>
&nbsp;&nbsp;{'}'}<br/>
<br/>
&nbsp;&nbsp;<span className="cm">// 4. Cascade: check if any active events should trigger follow-ons</span><br/>
&nbsp;&nbsp;<span className="kw">for</span> each active_event near expiry {'{'}<br/>
&nbsp;&nbsp;&nbsp;&nbsp;evaluate_outcome(participants, event_state);<br/>
&nbsp;&nbsp;&nbsp;&nbsp;<span className="fn">resolve_event</span>(event_id, outcome);<br/>
&nbsp;&nbsp;&nbsp;&nbsp;<span className="cm">// outcome may shift faction relations → future events</span><br/>
&nbsp;&nbsp;{'}'}<br/>
{'}'}
</code>
</div>
</div>
</>)}
{galaxySubSection === 'galaxy-gen' && (<>
<div className="section-header">
<span className="section-num">GALAXY-GEN</span>
<h2 style={{ margin: 0 }}>Galaxy Generation Seeded Parameters & Algorithm</h2>
</div>
<div className="callout callout-info" style={{ marginBottom: 'var(--sp-5)' }}>
<strong>Every galaxy begins with a seed.</strong> The galaxy generation algorithm is deterministic: the same seed always produces
the same galaxy. This means server operators can share a seed for a known-good galaxy layout, or generate a unique one.
Generation runs once at server bootstrap and writes immutable topology tables (regions, constellations, systems, stargates,
planets, moons, stations, asteroid belts). Faction territories and NPC agent placement are also seeded at this stage.
</div>
<h3 style={{ marginBottom: 'var(--sp-4)' }}>Galaxy Parameters</h3>
<div style={{ overflowX: 'auto', marginBottom: 'var(--sp-6)' }}>
<table className="data-table">
<thead>
<tr><th>Parameter</th><th>MVP Value</th><th>Full Launch</th><th>Rationale</th></tr>
</thead>
<tbody>
{[
{ param: 'Regions', mvp: '4', full: '68', reason: 'Core, Frontier, Null, Deep Null for MVP. Add 24 faction-specific regions at launch.' },
{ param: 'Constellations per region', mvp: '34', full: '48', reason: 'Minimum 3 for gate connectivity. More in Core region, fewer in Deep Null.' },
{ param: 'Systems per constellation', mvp: '25', full: '38', reason: 'Density varies by region type. Core constellations are denser (trade hubs).' },
{ param: 'Total systems (MVP)', mvp: '~50', full: '~300500', reason: '4 regions × 3.5 const × 3.5 sys ≈ 49 systems. Enough for economic loops without barren stretches.' },
{ param: 'Stargates per system', mvp: '14', full: '16', reason: 'Minimum 1 (no dead ends). Hub systems get 4+. Frontier gets 23.' },
{ param: 'Stations per system', mvp: '13', full: '16', reason: 'High-sec: 23 stations. Low-sec: 12. Null: 01 NPC stations. Stations are where the economy lives.' },
{ param: 'Planets per system', mvp: '15', full: '28', reason: 'Aesthetic + resource variety. Orbital mechanics run on a slow tick (no gameplay impact in MVP).' },
{ param: 'Asteroid belts per system', mvp: '13', full: '15', reason: 'Belts are where mining happens. More belts in lower-sec = better ore = risk/reward.' },
{ param: 'Factions', mvp: '4', full: '46', reason: 'One per region in MVP. Each has territory, ideology, and agent networks.' },
{ param: 'NPC agents per station', mvp: '12', full: '14', reason: 'Mission-givers. Specialty and quality randomized from seed.' },
].map((row, i) => (
<tr key={i}>
<td style={{ fontWeight: 600, color: 'var(--accent)' }}>{row.param}</td>
<td className="mono" style={{ color: 'var(--cyan)' }}>{row.mvp}</td>
<td className="mono" style={{ color: 'var(--fg-dim)' }}>{row.full}</td>
<td style={{ color: 'var(--fg-dim)', fontSize: '0.85rem' }}>{row.reason}</td>
</tr>
))}
</tbody>
</table>
</div>
<h3 style={{ marginBottom: 'var(--sp-4)' }}>Galaxy Shape & Layout</h3>
<div className="callout callout-info" style={{ marginBottom: 'var(--sp-5)' }}>
<strong>Spiral galaxy with 4 arms.</strong> The galaxy is rendered as a top-down 2D map (with a Z-depth dimension for 3D system
coordinates within each system). Regions are assigned to spiral arm segments:
Core (center), Frontier (inner arms), Null (outer arms), Deep Null (tips and gaps between arms).
Systems within a region are placed using a Poisson disk distribution to ensure minimum spacing while maintaining natural clustering.
</div>
<div className="grid-2" style={{ marginBottom: 'var(--sp-6)' }}>
<div className="card" style={{ borderLeft: '3px solid var(--accent)' }}>
<h4 style={{ color: 'var(--accent)' }}>Region Assignment Rules</h4>
<ul style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: 0, paddingLeft: 'var(--sp-5)' }}>
<li><strong style={{ color: 'var(--fg)' }}>Core (sec +0.8 +1.0):</strong> Center of galaxy map. Dense, high station count, trade hub. Starter systems live here. 1 region.</li>
<li><strong style={{ color: 'var(--fg)' }}>Frontier (sec +0.1 +0.7):</strong> Inner spiral arms. Moderate density. Faction border zones. Mission territory. 12 regions.</li>
<li><strong style={{ color: 'var(--fg)' }}>Null (sec 0.0 0.4):</strong> Outer spiral arms. Sparse stations. Rich belts. PvP-free. 1 region.</li>
<li><strong style={{ color: 'var(--fg)' }}>Deep Null (sec 0.5 1.0):</strong> Tips and gaps. Very sparse. Wormhole connections only. Elite content. 1 region.</li>
</ul>
</div>
<div className="card" style={{ borderLeft: '3px solid var(--cyan)' }}>
<h4 style={{ color: 'var(--cyan)' }}>System Placement Algorithm</h4>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.82rem', color: 'var(--fg-dim)', lineHeight: 2 }}>
1. Place constellation centroids via Poisson disk (min 40px apart on map)<br/>
2. For each centroid, place 25 systems in a cluster (Gaussian offset from centroid, σ = 15px)<br/>
3. Assign security level band based on region assignment<br/>
4. Add jitter to security within band (±0.1 random) for natural variation<br/>
5. Place star type (O/B/A/F/G/K/M) weighted by frequency G/K most common<br/>
6. System name generated from faction language + sequential number
</div>
</div>
</div>
<h3 style={{ marginBottom: 'var(--sp-4)' }}>Stargate Topology</h3>
<div className="callout callout-warn" style={{ marginBottom: 'var(--sp-5)' }}>
<strong>No disconnected components. Every system must be reachable from every other system.</strong>
The stargate graph is the transportation backbone. If a system has only one gate, it\'s a dead-end —
risky because you can\'t flee without going back through the same gate. The algorithm ensures minimum 2 gates
per system (MVP) and creates "choke point" systems with 4+ gates that become natural trade hubs and conflict zones.
</div>
<div className="card card-accent" style={{ marginBottom: 'var(--sp-5)' }}>
<h4 style={{ marginBottom: 'var(--sp-4)' }}>Stargate Connectivity Algorithm</h4>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.82rem', color: 'var(--fg-dim)', lineHeight: 2.2 }}>
<span style={{ color: 'var(--green)' }}>Phase 1 — Minimum Spanning Tree:</span> Compute MST over all systems using Euclidean distance. This guarantees full connectivity with minimum total gate length.<br/>
<span style={{ color: 'var(--cyan)' }}>Phase 2 — Intra-constellation edges:</span> For each constellation, add 12 extra gates between systems within the constellation. This creates local redundancy and multiple routes within a cluster.<br/>
<span style={{ color: 'var(--accent)' }}>Phase 3 — Inter-region choke points:</span> Identify 24 systems on region boundaries. Add gates between them to create known choke points. These become strategic PvP locations.<br/>
<span style={{ color: 'var(--purple)' }}>Phase 4 — Shortcut edges:</span> Add 1015% extra gates weighted toward connecting high-sec systems to create trade route variety. Never add shortcuts into/out of Deep Null (wormhole-only access preserved).<br/>
<span style={{ color: 'var(--red)' }}>Validation:</span> After generation, verify: (a) graph is fully connected (BFS from any node reaches all), (b) no system has &lt;2 gates, (c) Deep Null systems have no direct high-sec gates, (d) average path length &lt;15 jumps for MVP galaxy.
</div>
</div>
<h3 style={{ marginBottom: 'var(--sp-4)' }}>Starter System Template</h3>
<div className="grid-2" style={{ marginBottom: 'var(--sp-6)' }}>
<div className="card" style={{ borderLeft: '3px solid var(--green)' }}>
<h4 style={{ color: 'var(--green)' }}>Starter System Layout</h4>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.82rem', color: 'var(--fg-dim)', lineHeight: 2 }}>
<strong style={{ color: 'var(--fg)' }}>Security:</strong> +1.0 (maximum safety)<br/>
<strong style={{ color: 'var(--fg)' }}>Stations:</strong> 3 — Home Station, Trade Hub, Factory<br/>
<strong style={{ color: 'var(--fg)' }}>Belts:</strong> 3 — Veldspar/Scordite (easy ore)<br/>
<strong style={{ color: 'var(--fg)' }}>NPC agents:</strong> 2 — Tutorial agent + Level 1 kill agent<br/>
<strong style={{ color: 'var(--fg)' }}>Gates:</strong> 3 — connects to 3 adjacent high-sec systems<br/>
<strong style={{ color: 'var(--fg)' }}>NPC pirates:</strong> None (CONCORD-protected + no belt spawns in 1.0)<br/>
<strong style={{ color: 'var(--fg)' }}>Services:</strong> Refinery, Factory, Market, Fitting, Insurance, Medical
</div>
</div>
<div className="card" style={{ borderLeft: '3px solid var(--cyan)' }}>
<h4 style={{ color: 'var(--cyan)' }}>New Player Spawn Rules</h4>
<ul style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: 0, paddingLeft: 'var(--sp-5)' }}>
<li>New players always spawn in a starter system (sec 1.0)</li>
<li>Each faction has exactly 1 starter system in their Core region</li>
<li>Player receives a <strong style={{ color: 'var(--fg)' }}>Rookie Frigate</strong> (free, uninsurable, untradeable)</li>
<li>Player receives the tutorial mission sequence from the tutorial agent</li>
<li>Starter system is guaranteed to have Veldspar and Scordite at NPC buy prices that make the first-30-minute walkthrough viable</li>
<li>Multiple new players can share the same starter system (no instancing)</li>
</ul>
</div>
</div>
<h3 style={{ marginBottom: 'var(--sp-4)' }}>Station & Belt Placement Rules</h3>
<div style={{ overflowX: 'auto', marginBottom: 'var(--sp-6)' }}>
<table className="data-table">
<thead>
<tr><th>Entity</th><th>Placement Rule</th><th>Density by Sec</th><th>Notes</th></tr>
</thead>
<tbody>
{[
{ entity: 'Station', rule: 'Orbiting a planet or moon. Placed at galaxy gen, never moved.', density: 'High-sec: 23/station. Low: 12. Null: 01 NPC. Player stations (post-MVP) can be anchored.', notes: 'Stations define where economy happens. Every station has a Market. Refinery and Factory depend on station size.' },
{ entity: 'Asteroid Belt', rule: 'Circular orbit around star. 35 asteroids per belt.', density: 'High-sec: 12. Low: 23. Null: 24. Deep Null: 35.', notes: 'Belt ore quality scales with sec level. High-sec: Veldspar/Scordite only. Null: adds Arkonor/Megacyte.' },
{ entity: 'Moon', rule: 'Orbiting planets. 03 moons per planet.', density: '13 moons per planet (uniform distribution).', notes: 'Moons have moon minerals (post-MVP). MVP: moons exist for visual flavor and as station anchors.' },
{ entity: 'Stargate', rule: 'At system edge, paired with gate in target system. Placed at fixed (x,y) = system edge toward destination.', density: 'Matches graph topology. 24 per system typically.', notes: 'Gates are always paired. Jumping gate AB always works. No fuel cost for jumping (MVP).' },
{ entity: 'NPC Agent', rule: 'Located at a station. 12 per station in MVP.', density: 'High-sec stations: 2. Low-sec: 1. Null: 1 or 0.', notes: 'Agent specialty drawn from faction pool. Quality randomized from seed.' },
].map((row, i) => (
<tr key={i}>
<td style={{ fontWeight: 600, color: 'var(--accent)' }}>{row.entity}</td>
<td style={{ color: 'var(--fg-dim)', fontSize: '0.85rem' }}>{row.rule}</td>
<td style={{ color: 'var(--fg-dim)', fontSize: '0.85rem' }}>{row.density}</td>
<td style={{ color: 'var(--muted)', fontSize: '0.82rem' }}>{row.notes}</td>
</tr>
))}
</tbody>
</table>
</div>
<h3 style={{ marginBottom: 'var(--sp-4)' }}>Faction Territory Seeding</h3>
<div className="card card-accent" style={{ marginBottom: 'var(--sp-5)' }}>
<h4 style={{ marginBottom: 'var(--sp-4)' }}>Faction Assignment at Galaxy Gen</h4>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.82rem', color: 'var(--fg-dim)', lineHeight: 2.2 }}>
<span style={{ color: 'var(--green)' }}>1. Assign regions:</span> Each faction claims 1 region as home territory. The Core region is shared (contested).<br/>
<span style={{ color: 'var(--cyan)' }}>2. Place capitals:</span> Each faction gets a capital station in their home region — largest station, most agents, best services.<br/>
<span style={{ color: 'var(--accent)' }}>3. Seed diplomatic stance:</span> Initial faction relations set to baseline matrix (allies: +5, neutral: 0, rivals: 3). This drifts via world simulation.<br/>
<span style={{ color: 'var(--purple)' }}>4. Distribute agents:</span> NPC agents placed at stations within faction territory. Specialty weighted by faction ideology (militarist → kill agents, trader → trade/escort agents).<br/>
<span style={{ color: 'var(--fg)' }}>5. Set regional price seeds:</span> <code>regional_price_seeds</code> table populated at gen time. Each region gets commodity modifiers (0.61.5) that create baseline price differences for traders to discover.<br/>
<span style={{ color: 'var(--red)' }}>6. Faction military/economy:</span> Initial <code>military_strength</code> and <code>economy_strength</code> set from faction template. These are the starting values the world simulation modifies.
</div>
</div>
<h3 style={{ marginBottom: 'var(--sp-4)' }}>Generation Pseudocode</h3>
<div className="card" style={{ padding: 0, overflow: 'hidden', marginBottom: 'var(--sp-5)' }}>
<div style={{ padding: 'var(--sp-3) var(--sp-4)', background: 'var(--surface-raised)', borderBottom: '1px solid var(--border)' }}>
<span style={{ fontFamily: 'var(--font-mono)', fontSize: '0.7rem', color: 'var(--accent)', textTransform: 'uppercase', letterSpacing: '0.05em' }}>
Galaxy Generation — Pseudocode
</span>
</div>
<div className="code-block" style={{ margin: 0, borderRadius: 0 }}>
<code>
<span className="kw">fn</span> <span className="fn">generate_galaxy</span>(seed: u64) {'{'}<br/>
&nbsp;&nbsp;<span className="kw">let</span> rng = SeededRng::new(seed);<br/>
<br/>
&nbsp;&nbsp;<span className="cm">// 1. Create regions</span><br/>
&nbsp;&nbsp;<span className="kw">let</span> regions = ['{'Core, Frontier_A, Null, Deep_Null'}'];<br/>
&nbsp;&nbsp;<span className="kw">for</span> region <span className="kw">in</span> regions {'{'}<br/>
&nbsp;&nbsp;&nbsp;&nbsp;insert region row (id, name, faction_id, security_profile);<br/>
&nbsp;&nbsp;{'}'}<br/>
<br/>
&nbsp;&nbsp;<span className="cm">// 2. Place constellation centroids (Poisson disk)</span><br/>
&nbsp;&nbsp;<span className="kw">let</span> centroids = poisson_disk(min_dist=40, rng);<br/>
&nbsp;&nbsp;<span className="kw">for</span> centroid <span className="kw">in</span> centroids {'{'}<br/>
&nbsp;&nbsp;&nbsp;&nbsp;<span className="kw">let</span> region = assign_region(centroid.position);<br/>
&nbsp;&nbsp;&nbsp;&nbsp;insert constellation row (region_id, centroid.x, centroid.y);<br/>
&nbsp;&nbsp;{'}'}<br/>
<br/>
&nbsp;&nbsp;<span className="cm">// 3. Place systems within constellations (Gaussian cluster)</span><br/>
&nbsp;&nbsp;<span className="kw">for</span> constellation <span className="kw">in</span> constellations {'{'}<br/>
&nbsp;&nbsp;&nbsp;&nbsp;<span className="kw">let</span> count = rng.range(2..5); <span className="cm">// MVP: 25 systems per constellation</span><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<span className="kw">for</span> i <span className="kw">in</span> 0..count {'{'}<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span className="kw">let</span> offset = gaussian(σ=15, rng);<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span className="kw">let</span> sec = assign_security(constellation.region) + rng.range(-0.1..+0.1);<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;insert system row (name, constellation_id, sec, star_type, x, y);<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;place_planets(system, rng);<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;place_stations(system, sec, rng);<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;place_belts(system, sec, rng);<br/>
&nbsp;&nbsp;&nbsp;&nbsp;{'}'}<br/>
&nbsp;&nbsp;{'}'}<br/>
<br/>
&nbsp;&nbsp;<span className="cm">// 4. Stargate topology</span><br/>
&nbsp;&nbsp;<span className="kw">let</span> mst = minimum_spanning_tree(all_systems, euclidean_distance);<br/>
&nbsp;&nbsp;<span className="kw">for</span> edge <span className="kw">in</span> mst {'{'} insert_gate(edge.a, edge.b); {'}'}<br/>
&nbsp;&nbsp;add_intra_constellation_edges(rng); <span className="cm">// +12 per constellation</span><br/>
&nbsp;&nbsp;add_region_chokepoints(rng); <span className="cm">// 24 cross-region gates</span><br/>
&nbsp;&nbsp;add_shortcut_edges(percentage=0.12, rng); <span className="cm">// 12% extra high-sec shortcuts</span><br/>
&nbsp;&nbsp;validate_connectivity(); <span className="cm">// BFS from node 0 reaches all?</span><br/>
<br/>
&nbsp;&nbsp;<span className="cm">// 5. Faction seeding</span><br/>
&nbsp;&nbsp;seed_faction_territories(factions, regions);<br/>
&nbsp;&nbsp;seed_capital_stations(factions);<br/>
&nbsp;&nbsp;seed_diplomatic_matrix(factions);<br/>
&nbsp;&nbsp;seed_npc_agents(stations, factions, rng);<br/>
&nbsp;&nbsp;seed_regional_prices(regions, commodities, rng);<br/>
{'}'}
</code>
</div>
</div>
<div className="callout callout-info" style={{ marginBottom: 'var(--sp-5)' }}>
<strong>Determinism guarantee:</strong> Given the same seed, <code>generate_galaxy</code> always produces identical topology.
This enables: (1) shared "known-good" galaxy seeds for competitive servers, (2) reproducible bug reports with exact galaxy layout,
(3) automated testing against fixed galaxy configurations. The seed is stored in a single <code>galaxy_meta</code> table row:
<code>{'{'} seed: u64, generated_at: timestamp, system_count: u32, total_gates: u32 {'}'}</code>.
</div>
<div className="callout callout-warn">
<strong>MVP scope note:</strong> For Phase 0, the galaxy can be hand-authored (a 510 system "mini galaxy") as long as
it follows these rules. The procedural generator ships when the galaxy needs to scale beyond ~50 systems (Phase 7+).
Hand-authored galaxies must still pass the same connectivity validation.
</div>
</>)}
{galaxySubSection === 'galaxy-events' && (<>
<h3>World Simulation Tables</h3>
<div className="callout callout-info" style={{ marginBottom: 'var(--sp-5)', fontSize: '0.82rem' }}>
<strong>Overlap note:</strong> <code>faction_relations</code>, <code>world_events</code>, and <code>galaxy_story_log</code> also appear in the Tables tab with abbreviated field descriptions. The definitions below are the expanded versions with full field detail.
</div>
<div style={{ overflowX: 'auto' }}>
<table className="data-table">
<thead>
<tr><th>Table</th><th>Purpose</th><th>Key Fields</th></tr>
</thead>
<tbody>
{[
{ name: 'faction_relations', purpose: 'Dynamic relationship matrix', fields: 'faction_a_id, faction_b_id, standing (-10 to +10), trend, last_event_id' },
{ name: 'world_events', purpose: 'Active PvE events in the galaxy', fields: 'event_id, event_type, system_id, severity (15), started_at, expires_at, state, participants_json' },
{ name: 'world_event_templates', purpose: 'Event blueprints with spawn conditions', fields: 'template_id, type, name, spawn_weight, required_faction_state, cooldown_hours' },
{ name: 'galaxy_story_log', purpose: 'Persistent server timeline', fields: 'log_id, event_id, chapter_index, headline, body, affected_systems, timestamp' },
{ name: 'space_fauna', purpose: 'Migrating space creatures', fields: 'fauna_id, species, current_system_id, migration_route (json), cycle_phase' },
{ name: 'anomalies', purpose: 'Temporary spatial phenomena', fields: 'anomaly_id, type, system_id, x/y/z, severity, expires_at, loot_table' },
].map((row, i) => (
<tr key={i}>
<td><code>{row.name}</code></td>
<td style={{ color: 'var(--fg-dim)' }}>{row.purpose}</td>
<td style={{ fontSize: '0.75rem', color: 'var(--muted)' }}>{row.fields}</td>
</tr>
))}
</tbody>
</table>
</div>
</>)}
</>
)}
{activeSection === 'er' && (
<>
<div className="section-header">
<span className="section-num">BACKEND-ER</span>
<h2 style={{ margin: 0 }}>Entity-Relationship Diagram</h2>
</div>
<div className="callout callout-info" style={{ marginBottom: 'var(--sp-5)' }}>
<strong>50+ tables organized into 5 clusters.</strong> This diagram shows the core entity relationships
and how data flows between clusters. Each cluster is color-coded. Foreign key relationships shown as arrows.
Subscription patterns indicate which tables clients subscribe to for reactive updates.
</div>
{[
{
name: 'Player & Identity',
color: 'var(--cyan)',
desc: 'Core player data, ships, inventory, skills, and session state.',
tables: [
{ name: 'players', pk: 'player_id', fk: '', note: 'Root entity. One row per identity.' },
{ name: 'ships', pk: 'ship_id', fk: '\u2192 players.player_id', note: 'Multiple ships per player. owner_player_id.' },
{ name: 'ship_fittings', pk: 'fitting_id', fk: '\u2192 ships.ship_id, \u2192 modules_catalog.module_id', note: 'Many-to-many: ships \u2194 modules.' },
{ name: 'inventory_items', pk: 'item_id', fk: '\u2192 players.player_id', note: 'Location field: ship cargo or station hangar.' },
{ name: 'player_skills', pk: 'player_id + skill_name', fk: '\u2192 players.player_id', note: 'XP and level per skill per player.' },
{ name: 'player_standing', pk: 'player_id + entity_id', fk: '\u2192 players.player_id', note: 'Standing with agents and factions.' },
{ name: 'player_loyalty_points', pk: 'player_id + faction_id', fk: '\u2192 players.player_id', note: 'LP balance per faction.' },
],
},
{
name: 'Economy & Industry',
color: 'var(--green)',
desc: 'Market, manufacturing, blueprints, and NPC pricing.',
tables: [
{ name: 'market_orders', pk: 'order_id', fk: '\u2192 stations.station_id, \u2192 players.player_id', note: 'Buy/sell orders. Core market table.' },
{ name: 'blueprints', pk: 'bp_id', fk: '', note: 'Manufacturing recipes. Materials JSON.' },
{ name: 'manufacturing_jobs', pk: 'job_id', fk: '\u2192 players.player_id, \u2192 stations.station_id, \u2192 blueprints.bp_id', note: 'Active production queues.' },
{ name: 'station_commodity_demand', pk: 'station_id + commodity_id', fk: '\u2192 stations.station_id', note: 'Per-station demand state for NPC pricing.' },
{ name: 'commodity_price_params', pk: 'commodity_id', fk: '', note: 'Base prices and EMA parameters.' },
{ name: 'regional_price_seeds', pk: 'region_id + commodity_id', fk: '', note: 'Static modifiers from galaxy gen.' },
{ name: 'insurance_policies', pk: 'policy_id', fk: '\u2192 players.player_id, \u2192 ships.ship_id', note: 'Active insurance contracts.' },
],
},
{
name: 'World & Galaxy',
color: 'var(--accent)',
desc: 'Galaxy topology, world events, factions, anomalies, and fauna.',
tables: [
{ name: 'systems', pk: 'system_id', fk: '\u2192 regions.region_id', note: 'Star systems. Security level immutable.' },
{ name: 'stations', pk: 'station_id', fk: '\u2192 systems.system_id', note: 'Docking locations with services.' },
{ name: 'asteroids', pk: 'asteroid_id', fk: '\u2192 systems.system_id', note: 'Mineable resource nodes.' },
{ name: 'factions', pk: 'faction_id', fk: '', note: 'NPC factions with territory.' },
{ name: 'faction_relations', pk: 'faction_a_id + faction_b_id', fk: '\u2192 factions.faction_id (\u00d72)', note: 'Dynamic relationship matrix.' },
{ name: 'world_events', pk: 'event_id', fk: '\u2192 systems.system_id', note: 'Active PvE events. Severity, state, params.' },
{ name: 'galaxy_story_log', pk: 'log_id', fk: '\u2192 world_events.event_id', note: 'Persistent server timeline.' },
{ name: 'anomalies', pk: 'anomaly_id', fk: '\u2192 systems.system_id', note: 'Temporary spatial phenomena.' },
{ name: 'space_fauna', pk: 'fauna_id', fk: '\u2192 systems.system_id', note: 'Migrating creatures. Route JSON.' },
],
},
{
name: 'Social & PvP',
color: 'var(--red)',
desc: 'Chat, bounty, kill feed, waypoints, and missions.',
tables: [
{ name: 'chat_channels', pk: 'channel_id', fk: '', note: 'Channel definitions: local, trade, private.' },
{ name: 'chat_messages', pk: 'message_id', fk: '\u2192 chat_channels.channel_id, \u2192 players.player_id', note: 'Message stream with delay.' },
{ name: 'bounties', pk: 'target_player_id', fk: '\u2192 players.player_id', note: 'Bounty pool per target.' },
{ name: 'bounty_contributions', pk: 'contribution_id', fk: '\u2192 bounties.target_player_id, \u2192 players.player_id', note: 'Individual payments into pools.' },
{ name: 'kill_feed', pk: 'kill_id', fk: '\u2192 players.player_id (victim + killer)', note: 'Ship destruction events.' },
{ name: 'npc_agents', pk: 'agent_id', fk: '\u2192 factions.faction_id, \u2192 stations.station_id', note: 'NPC agents at stations.' },
{ name: 'active_missions', pk: 'mission_id', fk: '\u2192 players.player_id, \u2192 npc_agents.agent_id', note: 'Player mission state.' },
{ name: 'waypoints', pk: 'route_id', fk: '\u2192 players.player_id', note: 'Multi-stop routes.' },
],
},
{
name: 'Ship AI (Zora)',
color: 'var(--purple)',
desc: 'Soul state, modules, tools, memory, directives, and runtime.',
tables: [
{ name: 'ship_ai_soul', pk: 'ship_id', fk: '\u2192 ships.ship_id', note: 'One-to-one with ships. Soul document + personality.' },
{ name: 'ship_ai_modules', pk: 'module_id', fk: '\u2192 ships.ship_id', note: 'Installed AI modules. Medium/low slots.' },
{ name: 'ship_ai_tools', pk: 'tool_id', fk: '\u2192 ship_ai_modules.module_id', note: 'Derived from modules. Tool registry.' },
{ name: 'ship_ai_memory', pk: 'memory_id', fk: '\u2192 ships.ship_id', note: 'Event log. Category + importance scoring.' },
{ name: 'ship_ai_directives', pk: 'directive_id', fk: '\u2192 ships.ship_id', note: 'Player-set goals for autonomous mode.' },
{ name: 'ship_ai_agent_runtime', pk: 'ship_id', fk: '\u2192 ships.ship_id', note: 'Per-ship agent loop state. One-to-one.' },
],
},
].map((cluster, ci) => (
<div key={ci} style={{ marginBottom: 'var(--sp-6)' }}>
<h3 style={{ color: cluster.color, marginBottom: 'var(--sp-4)' }}>
{cluster.name}
<span style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', fontWeight: 400, marginLeft: 'var(--sp-3)' }}>{cluster.desc}</span>
</h3>
<div style={{ overflowX: 'auto' }}>
<table className="data-table">
<thead>
<tr><th>Table</th><th>PK</th><th>FK Relationships</th><th>Notes</th></tr>
</thead>
<tbody>
{cluster.tables.map((t, ti) => (
<tr key={ti}>
<td><code style={{ color: cluster.color }}>{t.name}</code></td>
<td className="mono" style={{ fontSize: '0.75rem' }}>{t.pk}</td>
<td style={{ fontSize: '0.75rem', color: 'var(--fg-dim)' }}>{t.fk || '\u2014'}</td>
<td style={{ fontSize: '0.75rem', color: 'var(--muted)' }}>{t.note}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
))}
<div className="callout callout-info" style={{ marginTop: 'var(--sp-4)' }}>
<strong>Cross-cluster flows:</strong> The most important cross-cluster relationships are:
(1) <code style={{ color: 'var(--cyan)' }}>players</code> \u2192 <code style={{ color: 'var(--green)' }}>market_orders</code> \u2192 <code style={{ color: 'var(--accent)' }}>stations</code> (the economic loop),
(2) <code style={{ color: 'var(--cyan)' }}>ships</code> \u2192 <code style={{ color: 'var(--accent)' }}>systems</code> \u2192 <code style={{ color: 'var(--red)' }}>chat_messages</code> (the spatial-social loop),
(3) <code style={{ color: 'var(--purple)' }}>ship_ai_soul</code> \u2192 <code style={{ color: 'var(--green)' }}>market_orders</code> (Zora reads market data for intelligence).
Every reducer call touches at least two clusters.
</div>
</>
)}
</div>
);
}
window.GDD.BackendPage = BackendPage;