diff --git a/.gitignore b/.gitignore index d6dd2f3..cac5382 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ vite.config.js vite.config.d.ts .playwright-mcp/ .spacetime-dev/ +target/ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..b69c772 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "rust-analyzer.linkedProjects": ["apps/game/Cargo.toml"] +} diff --git a/AGENTS.md b/AGENTS.md index 8eb421a..7082189 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ # VOID::NAV — Monorepo Root -Open-world space exploration RPG. Windward Horizon in space with FTL-style combat. +Single-player open-world space exploration RPG. Windward Horizon in space with FTL-style combat. ## Directory Map diff --git a/README.md b/README.md index d6bfb80..deb9a9f 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # VOID::NAV -**An open-world space exploration RPG** — trade, quest, fight, or simply drift and enjoy the ambiance. Inspired by the charm and freedom of Windward Horizon, set in a procedurally generated galaxy with FTL-style tactical combat. +**A single-player open-world space exploration RPG** — trade, quest, fight, or simply drift and enjoy the ambiance. Inspired by the charm and freedom of Windward Horizon, set in a procedurally generated galaxy with FTL-style tactical combat. You are a young, unaffiliated captain with an entire galaxy open for exploration. Every campaign features a unique, procedurally generated sector map. Trade goods between stations, take on quests for various factions, mine asteroid belts, or simply cruise and enjoy the void. Prefer a quieter life? You can simply sail the stars. Combat is **FTL-style tactical resource management** — no dogfighting or direct piloting. Click a hostile, your ship auto-engages, and you manage reactor power between Weapons / Shields / Engines / Auxiliary. The skill is in *what* you power and *when*, not how fast you click. -VOID::NAV is a pnpm workspace monorepo for the game website, living design docs, browser game shell, and SpacetimeDB backend module. +VOID::NAV is a pnpm workspace monorepo for the game website, living design docs, browser game shell, and SpacetimeDB backend module. The architecture supports optional co-op servers in the future, but the core experience is designed as a solo adventure. ## Core Pillars @@ -17,7 +17,7 @@ VOID::NAV is a pnpm workspace monorepo for the game website, living design docs, | **FTL-Style Combat** | Tactical power management (not action). Auto-engage with reactor allocation decisions | | **Ship Customization** | Build the ship that suits your style — tough juggernaut, nimble scout, or cargo hauler | | **Dynamic World Impact** | Faction leaders, world events, and player actions shape the galaxy without firing a shot | -| **Solo or Co-op** | Brave the void alone or crew with friends. Built with co-op in mind. | +| **Solo Adventure** | A personal galaxy to explore at your own pace. The architecture supports optional co-op servers later. | ## Layout diff --git a/apps/AGENTS.md b/apps/AGENTS.md index bec7e1d..e3c8364 100644 --- a/apps/AGENTS.md +++ b/apps/AGENTS.md @@ -5,7 +5,7 @@ Three Vite + React apps, each independently buildable and dev-serveable. | App | Port | Package | Description | |-----|------|---------|-------------| | `docs` | 5173 | `@void-nav/docs` | Living design docs, 16 doc pages, 14 interactive demos, game-slice prototype | -| `game` | 5175 | `@void-nav/game` | Playable game client connected to SpacetimeDB backend | +| `game` | 5175 | `@void-nav/game` | Playable single-player game client connected to SpacetimeDB backend | | `site` | 5174 | `@void-nav/site` | Public landing page / marketing site | ## Shared diff --git a/apps/docs/AGENTS.md b/apps/docs/AGENTS.md index 463753a..6511f57 100644 --- a/apps/docs/AGENTS.md +++ b/apps/docs/AGENTS.md @@ -3,7 +3,7 @@ Package: `@void-nav/docs` Port: `http://localhost:5173/docs` -This is the **design documentation hub** and interactive prototype playground. It contains the full GDD as 16 browsable pages, 14 interactive demos validating individual game subsystems, and a complete game-slice prototype implementing the full MVP loop in-browser with localStorage. +This is the **design documentation hub** and interactive prototype playground for a single-player space exploration RPG. It contains the full GDD as 16 browsable pages, 14 interactive demos validating individual game subsystems, and a complete game-slice prototype implementing the full MVP loop in-browser with localStorage. ## Structure diff --git a/apps/docs/public/docs/gap-analysis.md b/apps/docs/public/docs/gap-analysis.md index 1c4732c..b21f543 100644 --- a/apps/docs/public/docs/gap-analysis.md +++ b/apps/docs/public/docs/gap-analysis.md @@ -1,4 +1,4 @@ -# Gap Analysis — EVE-Inspired Multiplayer Prototype GDD +# Gap Analysis — VOID::NAV Single-Player Space RPG **Date:** 2026-05-24 **Last Updated:** 2026-05-25 (Session 7) @@ -24,9 +24,9 @@ Comprehensive fresh gap analysis identified 20 missing specs. The 3 critical blo | 🟠 Important | Module Activation & Cycle System | Not started | | 🟠 Important | Damage Types & Resistances | Not started | | 🟠 Important | Stargate Mechanics | **Resolved** — covered in Travel & Warp tab | -| 🟠 Important | Multiplayer Combat (Phase 13) | Not started | +| 🟠 Important | Multiplayer Combat (co-op) | Not started | | 🟠 Important | Scanning / Exploration System | Not started | -| 🟠 Important | Fleet System | Not started | +| 🟠 Important | Fleet System (co-op) | Not started | | 🟡 Nice-to-have | Contract system | Not started | | 🟡 Nice-to-have | Cloaking/stealth | Not started | | 🟡 Nice-to-have | Server admin/GM tools | Not started | diff --git a/apps/docs/public/docs/vertical-slice-evaluation.md b/apps/docs/public/docs/vertical-slice-evaluation.md index 47a5c08..e4e5378 100644 --- a/apps/docs/public/docs/vertical-slice-evaluation.md +++ b/apps/docs/public/docs/vertical-slice-evaluation.md @@ -1,7 +1,7 @@ # Vertical Slice Evaluation **Date:** 2026-05-31 -**Scope:** Compare the current playable game implementation against the MVP Loop Slice demo and the Era 1 roadmap goals. +**Scope:** Compare the current playable game implementation against the MVP Loop Slice demo and the core single-player roadmap goals. --- @@ -20,9 +20,9 @@ In practical terms: ## Baselines Compared -### Intended Era 1 Vertical Slice +### Intended Core Game Vertical Slice -The roadmap defines Era 1 as a single-player proof of concept using local SpacetimeDB from day one. The minimum full loop is: +The roadmap defines the core game as a single-player adventure using local SpacetimeDB from day one. The minimum full loop is: 1. Boot the game and connect to local SpacetimeDB. 2. Render a single star system with station and asteroids. @@ -260,7 +260,7 @@ The fitting demo validates module constraints and UI feel. The game app needs mo **Status:** Refining prototyped; manufacturing not in slice runtime. -The slice supports ore-to-mineral refining. Manufacturing remains a later implementation gap for the full Era 1 economy loop. +The slice supports ore-to-mineral refining. Manufacturing remains a later implementation gap for the full economy loop. ### Phase 6 - NPC Economy Sim diff --git a/apps/docs/src/data/nav.ts b/apps/docs/src/data/nav.ts index b523c70..d5026d2 100644 --- a/apps/docs/src/data/nav.ts +++ b/apps/docs/src/data/nav.ts @@ -21,7 +21,7 @@ export const navSections: NavSection[] = [ { path: "/docs/gameplay", icon: "◉", label: "Gameplay Loop" }, { path: "/docs/ships", icon: "◇", label: "Ships & Fitting" }, { path: "/docs/economy", icon: "⇄", label: "Economy & Industry" }, - { path: "/docs/social", icon: "✧", label: "Progression & Social" }, + { path: "/docs/social", icon: "✧", label: "Progression & World" }, { path: "/docs/ship-ai", icon: "◈", label: "Ship AI - Zora" }, { path: "/docs/roadmap", icon: "⊞", label: "Roadmap" }, { path: "/docs/todo", icon: "⊡", label: "Implementation Status" }, diff --git a/apps/docs/src/pages/docs/AgentsPage.tsx b/apps/docs/src/pages/docs/AgentsPage.tsx index c4d4b20..b2098de 100644 --- a/apps/docs/src/pages/docs/AgentsPage.tsx +++ b/apps/docs/src/pages/docs/AgentsPage.tsx @@ -517,7 +517,7 @@ export function AgentsPage() { { type: 'pirate_combat_tick', strat: 'fixed', interval: '1s', max: '1 per engaged NPC', desc: 'Executes NPC behavior template per tick: orbit/approach/kite/flee. Applies damage to target, regenerates shields/armor. Only runs for NPCs in combat state.' }, { type: 'pirate_loot_drop', strat: 'one-shot', interval: '0s', max: '∞', desc: 'Generates loot and bounty from destroyed NPC. Rolls loot table, awards ISK bounty to top damage contributor, creates wreck with items.' }, { type: 'loot_decay', strat: 'fixed', interval: '120s', max: '500', desc: 'Counts down loot container lifetime. On expiry, removes container row and frees the slot.' }, - { type: 'pvp_session_timer', strat: 'one-shot', interval: 'varies', max: '∞', desc: 'Tracks PvP engagement timers (combat flags, weapons timers). Fires on expiry to clear flags.' }, + { type: 'pvp_session_timer', strat: 'one-shot', interval: 'varies', max: '∞', desc: 'Tracks PvP engagement timers on co-op servers (combat flags, weapons timers). Fires on expiry to clear flags.' }, ].map((row, i) => ( {row.type} @@ -568,7 +568,7 @@ export function AgentsPage() { { type: 'building_decay', strat: 'fixed', interval: '60s', max: '∞', desc: 'Reduces building condition per tick (0.1–2% depending on material). At 0%, marks for demolition.' }, { type: 'fuel_consumption', strat: 'fixed', interval: '300s', max: '1 per structure', desc: 'Deducts fuel from powered structures. If fuel hits 0, disables structure services.' }, { type: 'production_cycle', strat: 'fixed', interval: 'varies', max: '1 per structure', desc: 'Advances manufacturing jobs. On completion, moves output to structure inventory.' }, - { type: 'reinforcement_timer', strat: 'one-shot', interval: '24–72h', max: '1 per structure', desc: 'Delays structure destruction in PvP. Allows owners time to defend. Fires → structure becomes vulnerable.' }, + { type: 'reinforcement_timer', strat: 'one-shot', interval: '24–72h', max: '1 per structure', desc: 'Delays structure destruction on co-op servers. Allows owners time to defend. Fires → structure becomes vulnerable.' }, ].map((row, i) => ( {row.type} @@ -597,7 +597,7 @@ export function AgentsPage() { { type: 'npc_trade_route', strat: 'conditional', interval: '600s', max: '50', desc: 'Evaluates NPC trade route viability. Condition: market activity > threshold AND at least one player docked. Moves goods between stations.' }, { type: 'market_price_adjust', strat: 'fixed', interval: '300s', max: '1 per item_type', desc: 'Ticks NPC demand pressure algorithm: updates flow_ema, recomputes demand_pressure, applies idle decay. See Economy → NPC Pricing tab for full algorithm spec.' }, { type: 'bounty_pool_refresh', strat: 'fixed', interval: '3600s', max: '1', desc: 'Refreshes the global bounty pool based on faction military budgets. Allocates bounty value to active threats.' }, - { type: 'insurance_payout', strat: 'one-shot', interval: '30–120s', max: '∞', desc: 'Delays ship insurance payout. Fires once, credits ISK to player, deletes self. Prevents instant-PvP-profit loops.' }, + { type: 'insurance_payout', strat: 'one-shot', interval: '30–120s', max: '∞', desc: 'Delays ship insurance payout. Fires once, credits ISK to player, deletes self. Prevents instant-profit loops on co-op servers.' }, ].map((row, i) => ( {row.type} diff --git a/apps/docs/src/pages/docs/ArchitecturePage.tsx b/apps/docs/src/pages/docs/ArchitecturePage.tsx index 7b9ba52..51c1330 100644 --- a/apps/docs/src/pages/docs/ArchitecturePage.tsx +++ b/apps/docs/src/pages/docs/ArchitecturePage.tsx @@ -8,7 +8,7 @@ export function ArchitecturePage() {

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

{/* Architecture diagram */} @@ -175,7 +175,7 @@ export function ArchitecturePage() { { scene: 'Mid-warp', server: 'Warp completes normally. Ship arrives at destination.', reconnect: 'Ship at destination. No interruption to warp.', impact: 'None.' }, { scene: 'Docked at station', server: 'No risk. Station is safe. Player remains docked.', reconnect: 'Restored to station. All panels and inventory intact.', impact: 'None.' }, { scene: 'Mid-market order', server: 'If reducer was received, order is placed. If not, no order.', reconnect: 'Check market panel for order state. ISK and inventory reflect server truth.', impact: 'At worst, order was not placed. No double-spend possible (atomic reducers).' }, - { scene: 'Mid-combat (PvP)', server: 'Ship continues with last power allocation. Enemy continues attacking.', reconnect: 'Same as PvE: ship may be destroyed. No special protection for PvP disconnect.', impact: 'Ship may be destroyed. Disconnecting during PvP carries the same risk as staying.' }, + { scene: 'Mid-combat (PvP — co-op)', server: 'Ship continues with last power allocation. Enemy continues attacking.', reconnect: 'Same as PvE: ship may be destroyed. No special protection for PvP disconnect.', impact: 'Ship may be destroyed. Disconnecting during PvP carries the same risk as staying.' }, ].map((row, i) => ( {row.scene} @@ -205,22 +205,22 @@ export function ArchitecturePage() {

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

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

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

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

- Anti-exploit: combat disconnect. Players who disconnect during PvP combat receive no special protection. + Anti-exploit: combat disconnect. On co-op servers, players who disconnect during PvP combat receive no special protection. Their ship remains in the world, continuing to fight with last-known power allocation. If destroyed, normal death penalties apply (loot drop, insurance). This prevents "combat logging" as an escape mechanism. Zora may send a message on reconnect: "I sustained damage while you were away. Shields at 40%. The enemy disengaged." @@ -289,9 +289,9 @@ export function ArchitecturePage() {
- Era 1 vs Era 2 persistence: Both eras use SpacetimeDB exclusively — there is no localStorage. - In Era 1, SpacetimeDB runs locally on the player's machine. "Persistence" means the local database file survives browser restart. - In Era 2, SpacetimeDB runs on a server. Persistence is permanent and shared. The only difference is where the database + Persistence model: The core game and co-op servers both use SpacetimeDB exclusively — there is no localStorage. + In the core game, SpacetimeDB runs locally on the player's machine. "Persistence" means the local database file survives browser restart. + On co-op servers, SpacetimeDB runs on a remote server. Persistence is permanent and shared. The only difference is where the database process runs; the persistence model is identical.
@@ -447,7 +447,7 @@ export function ArchitecturePage() { { area: 'Reduced Motion', req: 'Respect prefers-reduced-motion. No animations that could cause discomfort.', impl: 'CSS media query: disable particle effects, smooth scrolling, and HUD animations. Red Alert uses static red border instead of pulsing. Power allocation bars snap instead of animating. Mining cycle uses progress bar, not spinning animation.', phase: '7' }, { area: 'Contrast', req: 'All text meets WCAG AA contrast ratio (4.5:1 for normal text, 3:1 for large text).', impl: 'Var(--fg) on var(--bg) already exceeds 7:1. Dim text (--fg-dim) checked to meet 4.5:1. Interactive elements have visible focus indicators with 3:1 contrast against adjacent colors. Red Alert border is high-contrast red (#ff0000) against dark background.', phase: '7' }, { area: 'Cognitive Load', req: 'Information density is manageable. Players can hide complexity.', impl: 'Collapsible panels. Summary view vs. detail view toggle. Zora provides guided assistance. Red Alert collapses non-essential HUD elements. Tutorial hints can be disabled. Settings persist per player.', phase: '7' }, - { area: 'Input Timing', req: 'No time-critical inputs required for core gameplay. Combat is manageable at any APM.', impl: "Power allocation has no per-second requirements — the skill is in choosing distribution, not speed. Mining is click-and-wait. Market orders don't expire mid-interaction. Only exception: PvP combat in Era 2, which is inherently competitive.", phase: '7' }, + { area: 'Input Timing', req: 'No time-critical inputs required for core gameplay. Combat is manageable at any APM.', impl: "Power allocation has no per-second requirements — the skill is in choosing distribution, not speed. Mining is click-and-wait. Market orders don't expire mid-interaction. Only exception: PvP combat on co-op servers, which is inherently competitive.", phase: '7' }, ].map((row, i) => ( {row.area} @@ -461,7 +461,7 @@ export function ArchitecturePage() {
- PvP accessibility note: PvP combat in Era 2 inherently involves time pressure — rerouting power, + PvP accessibility note: PvP combat on co-op servers inherently involves time pressure — rerouting power, selecting targets, and reacting to damage. These cannot be fully de-timed without removing the competitive element. Players with motor impairments can (1) stay in high-sec where PvP is punished, (2) focus on PvE, industry, and market gameplay which are fully accessible, or (3) use fleet roles that require less real-time input @@ -469,7 +469,7 @@ export function ArchitecturePage() {
- Testing: Accessibility validation is part of Gate 4 (Era 1 Complete). The acceptance test is: + Testing: Accessibility validation is part of Gate 4 (Core Game Complete). The acceptance test is: (1) navigate all panels via keyboard only, (2) complete a mining-sell cycle with screen reader enabled, (3) verify all color-coded info has secondary indicators in grayscale. Automated: run axe-core or Lighthouse accessibility audit as part of CI. diff --git a/apps/docs/src/pages/docs/BackendPage.tsx b/apps/docs/src/pages/docs/BackendPage.tsx index ce396a3..c2cdcca 100644 --- a/apps/docs/src/pages/docs/BackendPage.tsx +++ b/apps/docs/src/pages/docs/BackendPage.tsx @@ -344,7 +344,7 @@ export function BackendPage() {
@@ -374,7 +374,7 @@ export function BackendPage() {
Phase 1 — Minimum Spanning Tree: Compute MST over all systems using Euclidean distance. This guarantees full connectivity with minimum total gate length.
Phase 2 — Intra-constellation edges: For each constellation, add 1–2 extra gates between systems within the constellation. This creates local redundancy and multiple routes within a cluster.
- Phase 3 — Inter-region choke points: Identify 2–4 systems on region boundaries. Add gates between them to create known choke points. These become strategic PvP locations.
+ Phase 3 — Inter-region choke points: Identify 2–4 systems on region boundaries. Add gates between them to create known choke points with tougher NPCs.
Phase 4 — Shortcut edges: Add 10–15% 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).
Validation: After generation, verify: (a) graph is fully connected (BFS from any node reaches all), (b) no system has <2 gates, (c) Deep Null systems have no direct high-sec gates, (d) average path length <15 jumps for MVP galaxy.
@@ -608,7 +608,7 @@ export function BackendPage() { ], }, { - name: 'Social & PvP', + name: 'Social & Co-op', color: 'var(--red)', desc: 'Chat, bounty, kill feed, waypoints, and missions.', tables: [ diff --git a/apps/docs/src/pages/docs/DemoGalleryPage.tsx b/apps/docs/src/pages/docs/DemoGalleryPage.tsx index 943541c..1e4eec7 100644 --- a/apps/docs/src/pages/docs/DemoGalleryPage.tsx +++ b/apps/docs/src/pages/docs/DemoGalleryPage.tsx @@ -8,17 +8,17 @@ export function DemoGalleryPage() { name: 'MVP Loop Slice', color: 'var(--accent)', icon: '◎', - validates: 'Connected Era 1 horizontal slice: undock, navigate to belt, mine ore, dock, refine, fit, sell on market, and optionally run NPC combat. Uses a shared localStorage slice session across integrated demos.', - limitations: 'Local browser persistence only. Deterministic fake economy and combat payouts. No backend, multiplayer authority, or SpacetimeDB integration.', - docPage: 'Gameplay → Era 1 MVP Loop', + validates: 'Connected horizontal slice: undock, navigate to belt, mine ore, dock, refine, fit, sell on market, and optionally run NPC combat. Uses a shared localStorage slice session across integrated demos.', + limitations: 'Local browser persistence only. Deterministic fake economy and combat payouts. No backend or SpacetimeDB integration.', + docPage: 'Gameplay → MVP Loop', }, { id: 'starmap', - name: 'Star Map (Era 2 Galaxy Map)', + name: 'Star Map (Galaxy Map)', color: 'var(--accent)', icon: '🗺', - validates: 'Multi-system galaxy view with system connections, faction territories, click-to-navigate interaction, warp routes. Validates the Era 2 Galaxy Map (region/constellation/system hierarchy). Each system has celestial body rendering.', - limitations: 'This is the Era 2 Galaxy Map, NOT the Era 1 System Map. No single-system detail view showing belts, stations, and orbital bodies at navigation scale. Era 1 needs a separate System Map demo. Static galaxy topology — systems don\'t shift. No faction territory animation. World events shown as static icons, not animated effects. No waypoint creation or route planning integration.', + validates: 'Multi-system galaxy view with system connections, faction territories, click-to-navigate interaction, warp routes. Validates the Galaxy Map (region/constellation/system hierarchy). Each system has celestial body rendering.', + limitations: 'This is the Galaxy Map, NOT the System Map. No single-system detail view showing belts, stations, and orbital bodies at navigation scale. A separate System Map demo is needed. Static galaxy topology — systems don\'t shift. No faction territory animation. World events shown as static icons, not animated effects. No waypoint creation or route planning integration.', docPage: 'Overview → HUD & View Mode Architecture → Map Mode', }, { @@ -45,7 +45,7 @@ export function DemoGalleryPage() { color: 'var(--red)', icon: '⚔', validates: 'FTL-style reactor power allocation (weapons/shields/engines/aux), target selection, auto-engage, module auto-cycling, capacitor drain, shield/armor/hull layers.', - limitations: 'Solo PvE only — no PvP. Single opponent at a time. No subsystem targeting. No weapon type differentiation (all deal generic damage). Power allocation is instant, not gradual.', + limitations: 'Solo PvE only. Single opponent at a time. No subsystem targeting. No weapon type differentiation (all deal generic damage). Power allocation is instant, not gradual.', docPage: 'Gameplay → Combat Model', }, { @@ -129,7 +129,7 @@ export function DemoGalleryPage() { Thirteen interactive demos that validate specific game systems. Each demo is a standalone prototype focused on one aspect of the design unless opened through the MVP Loop Slice. They use fake data and simulated APIs — not connected to SpacetimeDB or a real server. Use these to evaluate feel and UX, not performance or scale. - Note: The Star Map demo covers the Era 2 Galaxy Map. A separate Era 1 System Map demo (single-system + Note: The Star Map demo covers the Galaxy Map. A separate System Map demo (single-system navigation view) is still needed before Phase 1.

diff --git a/apps/docs/src/pages/docs/EconomyPage.tsx b/apps/docs/src/pages/docs/EconomyPage.tsx index 17b8ecb..7d25e2f 100644 --- a/apps/docs/src/pages/docs/EconomyPage.tsx +++ b/apps/docs/src/pages/docs/EconomyPage.tsx @@ -394,7 +394,7 @@ export function EconomyPage() {

Margin Accounts & Positions

- Era 2 feature. Players can open long and short positions + Future co-op feature. Players on shared servers could open long and short positions on commodity contracts using a margin account. Margin requires collateral (ISK + assets). Maintenance margin enforced — if position moves against the player beyond the margin, the position is force-liquidated. This adds speculative depth for advanced traders. @@ -428,7 +428,7 @@ export function EconomyPage() {

- Priority callout: Economy feel + social/multiplayer feel are the two most + Priority callout: Economy feel is one of the most important things to get right. If the economy doesn't feel rewarding and dynamic, the game falls flat regardless of how good combat or graphics are.
diff --git a/apps/docs/src/pages/docs/GameplayPage.tsx b/apps/docs/src/pages/docs/GameplayPage.tsx index bbdbf34..721fed0 100644 --- a/apps/docs/src/pages/docs/GameplayPage.tsx +++ b/apps/docs/src/pages/docs/GameplayPage.tsx @@ -10,7 +10,7 @@ export function GameplayPage() {

The core loop: explore → trade → quest → fight (FTL-style) → upgrade → sail deeper. Every system feeds into the others. Trade earns ISK for upgrades. Quests unlock faction influence. Combat protects trade routes. - Exploration reveals new opportunities. Steps 1–7 work in Era 1 (single-player). Multiplayer features arrive in Era 2. + Exploration reveals new opportunities. Steps 1–8 work as a single-player adventure. Co-op is an optional future enhancement.

@@ -39,9 +39,9 @@ export function GameplayPage() { { step: 3, title: 'Gather Resources', desc: 'Mine asteroids for ore. Salvage derelicts for components. Discover anomalies for rare materials. The decision is what to gather and where — different regions have different resources.', color: 'var(--accent)' }, { step: 4, title: 'Trade Between Stations', desc: 'Buy low at one station, sell high at another. Prices vary by region and faction needs. Stations that need resources pay more. Trade routes emerge naturally from exploration.', color: 'var(--purple)' }, { step: 5, title: 'Accept Quests', desc: 'Faction agents offer missions: deliver cargo, eliminate threats, explore sectors, escort convoys. Completing quests earns ISK, standing, and unlocks new opportunities. Helping one faction may hinder another.', color: 'var(--cyan)' }, - { step: 6, title: 'Combat (FTL-style)', desc: 'Click a hostile → ship auto-engages → manage reactor power between Weapons/Shields/Engines/Aux. No dogfighting. The skill is tactical resource allocation. PvE pirates, bounty targets, and hostile NPCs. PvP in Era 2.', color: 'var(--red)' }, + { step: 6, title: 'Combat (FTL-style)', desc: 'Click a hostile → ship auto-engages → manage reactor power between Weapons/Shields/Engines/Aux. No dogfighting. The skill is tactical resource allocation. PvE pirates, bounty targets, and hostile NPCs.', color: 'var(--red)' }, { step: 7, title: 'Upgrade & Customize Ship', desc: 'Use ISK to buy bigger ships, fit modules, upgrade weapons and defenses. Build the ship that suits your style: nimble trader, tough warship, or versatile explorer.', color: 'var(--accent)' }, - { step: 8, title: 'Sail Deeper', desc: 'Venture into unexplored sectors with better resources but greater danger. Higher risk, higher reward. Faction territory, world events, and player actions shape the galaxy. Co-op multiplayer in Era 2.', color: 'var(--green)' }, + { step: 8, title: 'Sail Deeper', desc: 'Venture into unexplored sectors with better resources but greater danger. Higher risk, higher reward. Faction territory, world events, and your actions shape the galaxy. Your adventure, your galaxy.', color: 'var(--green)' }, ].map((s, i) => (
@@ -119,7 +119,7 @@ export function GameplayPage() {

Player Pirating

  • Players can attack other players in low-sec and null-sec
  • -
  • High-sec attacks trigger CONCORD response (ship destruction)
  • +
  • High-sec has weaker NPC pirates — CONCORD presence keeps them at bay
  • Pirates can loot cargo from destroyed ships
  • Piracy lowers security status → eventually locked out of high-sec
  • Bounty system creates natural consequences
  • @@ -379,7 +379,7 @@ export function GameplayPage() {
    Security levels define the rules of engagement. Every star system has a security status from +1.0 (safest) to −1.0 (lawless). - Security 0.0 is explicitly null-sec — there is no border case. Systems at exactly 0.0 have the same rules as systems at −0.4: no CONCORD, no gate guns, full PvP freedom. + Security 0.0 is explicitly null-sec — there is no border case. Systems at exactly 0.0 have the same rules as systems at −0.4: no CONCORD, no gate guns, the most dangerous NPC pirates and anomalies. This single number controls CONCORD response, NPC pirate spawns, PvE difficulty, and the economic risk/reward balance. Inspired by EVE Online's sec-space but simplified for the prototype's scope.
    @@ -392,8 +392,8 @@ export function GameplayPage() { {[ - { band: 'High-Sec', range: '+0.5 → +1.0', color: 'var(--green)', pvp: 'Aggression triggers CONCORD. PvP possible but punished.', concord: 'Immediate (3–8s). Lethal.', pirates: 'Weak frigates. Easy kills, low bounties.', risk: 'Very low risk. Baseline ore prices. Starter systems.' }, - { band: 'Low-Sec', range: '+0.1 → +0.4', color: 'var(--accent)', pvp: 'No CONCORD. Gate/station guns fire on aggressors. PvP is free.', concord: 'None. Gate/station guns only.', pirates: 'Mixed frigates/destroyers. Moderate bounties.', risk: 'Medium risk. Better ore. Faction missions. Trade route choke points.' }, + { band: 'High-Sec', range: '+0.5 → +1.0', color: 'var(--green)', pvp: 'Safe zone. CONCORD protects. Co-op PvP punished.', concord: 'Immediate (3–8s). Lethal.', pirates: 'Weak frigates. Easy kills, low bounties.', risk: 'Very low risk. Baseline ore prices. Starter systems.' }, + { band: 'Low-Sec', range: '+0.1 → +0.4', color: 'var(--accent)', pvp: 'No CONCORD. Gate/station guns only. Co-op PvP unrestricted.', concord: 'None. Gate/station guns only.', pirates: 'Mixed frigates/destroyers. Moderate bounties.', risk: 'Medium risk. Better ore. Faction missions. Trade route choke points.' }, { band: 'Null-Sec', range: '0.0 → −0.4', color: 'var(--red)', pvp: 'No rules. No guns. Anything goes.', concord: 'None.', pirates: 'Cruiser+ NPCs. Dangerous. High bounties + rare loot.', risk: 'High risk. Richest ore. Best anomalies. Faction conflict zones.' }, { band: 'Deep Null', range: '−0.5 → −1.0', color: 'var(--purple)', pvp: 'No rules. Wormhole connections only.', concord: 'None.', pirates: 'Battlecruiser/battleship NPCs. Elite loot tables.', risk: 'Extreme risk. Unique resources. Story event spawning grounds.' }, ].map((row, i) => ( @@ -427,7 +427,7 @@ export function GameplayPage() {
  • Attacking a player in high-sec: −0.5 to −2.0 (scaled by victim's ship value)
  • Destroying a player ship in high-sec: −2.0 to −5.0
  • Podding (if applicable): −5.0 (extreme penalty)
  • -
  • Below −2.0: barred from +0.8+ systems (CONCORD patrols eject)
  • +
  • Below −2.0: barred from +0.8+ systems (CONCORD patrols eject) — co-op server feature
  • Below −5.0: freely attackable anywhere ("outlaw")
@@ -508,7 +508,7 @@ export function GameplayPage() { { band: 'High-Sec', classes: 'Frigate (T1)', hp: '200–400', bounty: '500–2,000 ₢', behavior: 'Passive orbit. Low aggression. Flees below 30% HP.', loot: 'Basic modules, small ammo, common ore' }, { band: 'Low-Sec', classes: 'Frigate (T2), Destroyer', hp: '400–800', bounty: '2,000–8,000 ₢', behavior: 'Aggressive orbit. Target priority (weakest ship). Kites if outgunned.', loot: 'T2 modules, blueprint fragments, mid-tier ore' }, { band: 'Null-Sec', classes: 'Destroyer, Cruiser', hp: '800–2,500', bounty: '8,000–30,000 ₢', behavior: 'Coordinated packs. Spider tanking (remote reps). Escorts target jamming.', loot: 'Rare modules, full blueprints, rare minerals' }, - { band: 'Deep Null', classes: 'Cruiser, Battlecruiser, Boss', hp: '2,500–12,000', bounty: '30,000–200,000 ₢', behavior: 'Boss mechanics. Phase triggers. Spawns reinforcements. Requires fleet tactics.', loot: 'Officer modules, ship BPCs, unique cosmetics, story items' }, + { band: 'Deep Null', classes: 'Cruiser, Battlecruiser, Boss', hp: '2,500–12,000', bounty: '30,000–200,000 ₢', behavior: 'Boss mechanics. Phase triggers. Spawns reinforcements. Requires careful power management and loadout.', loot: 'Officer modules, ship BPCs, unique cosmetics, story items' }, ].map((row, i) => ( {row.band} @@ -596,9 +596,7 @@ export function GameplayPage() {
CONCORD is the NPC police force in high-security space. - They do not prevent crime — they punish it. High-sec is not safe; it is consequential. - A determined player can destroy a target in high-sec, but they will lose their ship to CONCORD. - This creates a cost equation: is the target's loot worth more than the attacker's ship? Most of the time, it isn't. + In the core single-player game, CONCORD provides world flavor and protects the player in high-sec systems — NPC pirates spawn less frequently and CONCORD ships respond to attacks. In optional co-op servers, CONCORD also enforces rules between players, making high-sec aggression consequential.

Response Model

@@ -631,10 +629,10 @@ export function GameplayPage() {

How CONCORD Works

- 1. Aggression — Player A attacks Player B in high-sec. combat_flag set to criminal.
+ 1. Aggression — NPC pirate or (on co-op servers) a hostile player attacks in high-sec. combat_flag set to criminal.
2. CONCORD Spawn — Server spawns CONCORD NPCs at attacker's location. Response time based on system sec.
3. Point + Web — CONCORD warp-scrambles and stasis-webs the attacker. No escape.
- 4. Destruction — CONCORD applies overwhelming damage. Ship destroyed. No survival possible.
+ 4. Destruction — CONCORD applies overwhelming damage. Attacker destroyed. No survival possible.
5. Status Penalty — Attacker's security status reduced. Large bounty placed automatically at low status.
6. Kill Log — Event logged in kill feed. Galaxy-wide notification if bounty collected.
@@ -662,10 +660,7 @@ export function GameplayPage() {
- Design intent: High-sec is not safe — it is punitively expensive to attack there. - The gank-vs-cost equation is the core balance lever. If ganking becomes too easy, reduce CONCORD response time. - If high-sec is too safe, increase response time slightly or reduce CONCORD force. - The goal is a living ecosystem where piracy exists but is a strategic choice, not a griefing tool. + Design intent: CONCORD creates meaningful security bands in the galaxy. High-sec is safer for new players — NPC pirates are weaker, CONCORD provides backup. Lower-security space offers better rewards but greater danger. On optional co-op servers, CONCORD also enforces rules between players, making high-sec aggression a costly strategic choice rather than a griefing tool.
)} @@ -1220,7 +1215,7 @@ export function GameplayPage() {

Event Map Integration

-

System Map (Era 1)

+

System Map

Events in the current system appear as pulsing icons at their location. Clicking an event icon on the map opens the Event Detail Panel. Active events have a glowing radius showing their area of effect. @@ -1228,7 +1223,7 @@ export function GameplayPage() {

-

Galaxy Map (Era 2)

+

Galaxy Map

Events across all systems appear as icons on the galaxy map, sized by severity and colored by type. A filter sidebar lets players show/hide by type (faction conflict, anomaly, migration, catastrophe). diff --git a/apps/docs/src/pages/docs/OverviewPage.tsx b/apps/docs/src/pages/docs/OverviewPage.tsx index 4eb15eb..4a176ba 100644 --- a/apps/docs/src/pages/docs/OverviewPage.tsx +++ b/apps/docs/src/pages/docs/OverviewPage.tsx @@ -4,13 +4,14 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; export function OverviewPage() { return (

-

Open-World Space Exploration RPG

+

Single-Player Space Exploration RPG

- Trade, quest, fight, or simply drift and enjoy the ambiance. A charming open-world space RPG inspired + Trade, quest, fight, or simply drift and enjoy the ambiance. A charming single-player open-world space RPG inspired by the freedom and charm of Windward Horizon — set in a procedurally generated galaxy where every campaign is unique. Explore sectors, trade between stations, take on faction quests, mine asteroids, or simply cruise the void. Combat is FTL-style tactical resource management — no dogfighting. You are the captain who decides what to power and when, not the pilot who dodges. + The architecture supports optional co-op servers in the future, but the core experience is your solo adventure.

@@ -31,8 +32,8 @@ export function OverviewPage() {
World Model
-
Co-op
-
Multiplayer
+
Solo
+
Adventure
@@ -61,32 +62,32 @@ export function OverviewPage() { Open-world exploration Procedurally generated galaxy with unique sectors every campaign. Points of interest: stations, asteroid belts, anomalies, derelicts, signal sources. Explore to discover resources, quests, and hidden stories. The map reveals as you travel — fog of war drives the urge to see what's out there. - Era 1 Single system with POIs, procedural generation seed + Core Single system with POIs, procedural generation seed Trade & commerce Buy low at one station, sell high at another. Regional price differences driven by supply/demand and faction needs. Trade routes are the economic backbone. Towns need resources to grow — supplying them leads to prosperity. - Era 1 NPC economy, regional prices, trade routes
Era 2 Player-to-player market, info diffusion + Core NPC economy, regional prices, trade routes
Future Optional player-to-player market on co-op servers FTL-style combat No dogfighting. Click hostile → ship auto-engages → manage reactor power allocation (FTL-style) between Weapons/Shields/Engines/Aux. The skill is in what you power, not how fast you click. Combat creates consequences — ship damage, loot, insurance — but it's not the focus. - Era 1 Core combat & power management + Core Core combat & power management Ship customization Build the ship that suits your style — tough juggernaut that can take a beating, nimble scout that rains fire from afar, or cargo hauler keeping trade routes supplied. Swap modules to change role. Multiple ship classes from nimble interceptors to powerful cruisers. - Era 1 Basic fitting, multiple ship classes + Core Basic fitting, multiple ship classes Dynamic world & factions Stations and provinces need resources to grow. Supplying them leads to prosperity. Faction leaders involve you in their agendas — helping one may hinder another. Enough support can let a faction leader take control of a province without a single shot fired. A living galaxy that evolves through player actions and world events. - Era 1 Basic factions, NPC agents, quests
Era 2 Living galaxy, world agents, territory + Core Factions, NPC agents, quests, territory influence
Future Living galaxy with world agents on co-op servers - Solo or co-op - Brave the void alone or crew with friends. The game is built with co-op in mind — bring your crew, set sail, and experience the chaotic endgame together. Solo play is fully viable; co-op makes the late game richer. - Era 1 Solo proof of concept
Era 2 Multiplayer, fleets, PvP + Solo Adventure + Your personal galaxy to explore at your own pace. The game is designed as a solo adventure first — every system works for one captain. The SpacetimeDB architecture naturally supports optional co-op servers later, but that's an enhancement, not the core. + Core Solo adventure with full game loop
Future Optional co-op servers @@ -155,7 +156,7 @@ export function OverviewPage() {

Minimum Viable Screens

- Era 1 screens 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. + Core screens are the single-player game. These are the minimum screens needed to validate the core adventure loop is fun.
@@ -167,14 +168,14 @@ export function OverviewPage() { - +
Ship Status PanelName, owner, status, cargo, current action, location.
Inventory PanelItem type + quantity grid. Sell button when docked. Cargo capacity bar.
Station PanelDock/undock state, sell ore, view market, refit ship.
Market PanelOrder book, price per unit, place sell order from inventory. NPC-only economy in Era 1.
Market PanelOrder book, price per unit, place sell order from inventory. NPC-driven economy with regional price differences.
Combat HUDTarget selection, module activation, reactor power allocation bars (FTL-style).
Debug PanelReducer call log, error display, connection metrics, entity count.
- Era 2 screens require SpacetimeDB multiplayer infrastructure (Roadmap phases 8–15). + Future screens would be needed if optional co-op servers are added later. These are not part of the core game.
@@ -250,11 +251,11 @@ export function OverviewPage() {

🗺 Map Mode (Both)

Accessible from either view mode, the map replaces the main viewport with a full-screen 3D strategic map. - Era 1 shows the current star system (single system with celestials, belts, stations). - Era 2 adds the Galaxy Map (multi-system with region/constellation hierarchy, faction overlay, world events). + System Map shows the current star system (single system with celestials, belts, stations). + The Galaxy Map shows the full multi-system view with region/constellation hierarchy, faction overlay, and world events.

- Era 1: System Map needed · Era 2: Validated by Star Map demo + System Map needed (core) · Galaxy Map: Validated by Star Map demo
diff --git a/apps/docs/src/pages/docs/RisksPage.tsx b/apps/docs/src/pages/docs/RisksPage.tsx index d6f2a14..3f0521f 100644 --- a/apps/docs/src/pages/docs/RisksPage.tsx +++ b/apps/docs/src/pages/docs/RisksPage.tsx @@ -33,7 +33,7 @@ export function RisksPage() { risk: 'Movement update frequency', severity: 'medium', mitigation: 'Use destination-based movement, not frame-based syncing. Server updates positions periodically, not per-frame.', - impact: 'Per-frame state writes would overwhelm SpacetimeDB and make multiplayer unreliable.', + impact: 'Per-frame state writes would overwhelm SpacetimeDB and make co-op servers unreliable.', }, { risk: 'Economy complexity explosion', @@ -51,7 +51,7 @@ export function RisksPage() { risk: 'Authentication complexity', severity: 'low', mitigation: 'Use SpacetimeDB identity for the MVP. Add proper account/auth only when persistence is proven.', - impact: 'OAuth/session management is a distraction until the game actually works multiplayer.', + impact: 'OAuth/session management is a distraction until co-op servers are actually needed.', }, { risk: 'World simulation tuning', diff --git a/apps/docs/src/pages/docs/RoadmapPage.tsx b/apps/docs/src/pages/docs/RoadmapPage.tsx index ebc148e..6a344c0 100644 --- a/apps/docs/src/pages/docs/RoadmapPage.tsx +++ b/apps/docs/src/pages/docs/RoadmapPage.tsx @@ -5,8 +5,8 @@ export function RoadmapPage() { const eras = [ { id: 'solo', - title: 'Era 1 — Single-Player Open-World RPG', - subtitle: 'Validate the core adventure loop locally. SpacetimeDB runs on the local machine from Phase 0 — there is no localStorage. One browser window, one captain, one procedurally generated galaxy to explore.', + title: 'VOID::NAV — Single-Player Open-World RPG', + subtitle: 'Build the core adventure game locally. SpacetimeDB runs on the local machine from Phase 0 — there is no localStorage. One browser window, one captain, one procedurally generated galaxy to explore. Every feature is designed to be fun solo first.', accent: 'var(--accent)', phases: [ { @@ -69,36 +69,36 @@ export function RoadmapPage() { }, { id: 'multi', - title: 'Era 2 — Multiplayer & Living Galaxy', - subtitle: 'Promote local SpacetimeDB to a shared server. Add multiplayer co-op, social systems, PvP, and the full living galaxy simulation. Multiple captains, one persistent universe.', + title: 'Optional — Co-op Servers', + subtitle: 'Promote local SpacetimeDB to a shared server for small co-op crews. Same adventure, shared with friends. Entirely optional — the core game is complete without this.', accent: 'var(--cyan)', phases: [ { num: '8', - title: 'SpacetimeDB Skeleton', - goal: 'Replace local game state with SpacetimeDB tables and reducers. Client subscribes to state.', + title: 'Shared Server Skeleton', + goal: 'Promote local SpacetimeDB to a shared server. Multiple clients connect and see the same state.', doneWhen: 'Two browser windows connect to the same SpacetimeDB instance. Both see the same star system state. One client issues a move command, the other sees it. Connection status indicator works.', status: 'future', }, { num: '9', - title: 'Presence & Movement Sync', - goal: 'Players see each other in real time. Movement is server-authoritative with client-side interpolation.', + title: 'Co-op Presence & Movement', + goal: 'Players see each other in real time. Movement is server-authoritative with client-side interpolation. Small crew exploration.', doneWhen: 'Two players in the same system. Each sees the other\'s ship. Click-to-move sends reducer, server validates, all clients interpolate movement. No desync under normal latency.', status: 'future', }, { num: '10', - title: 'Shared Economy', - goal: 'Player-to-player market. Buy/sell orders, contracts, and real price discovery.', + title: 'Shared Market & Trading', + goal: 'Player-to-player trading. Buy/sell orders between co-op crew members. Shared price discovery.', doneWhen: 'Player A places a sell order. Player B sees it in the market table and buys. ISK and items transfer atomically. Order book shows depth. Market history is shared.', status: 'future', }, { num: '11', - title: 'Social — Chat & Bounty', - goal: 'Local chat (system-range), delayed PMs, and player-posted bounty system.', - doneWhen: 'Players in the same system see local chat in real time. PMs arrive with configurable delay (light-speed). Any player can post a bounty on another. Bounty board is visible galaxy-wide.', + title: 'Chat & Communication', + goal: 'Local chat for co-op crew, delayed comms for immersion.', + doneWhen: 'Players in the same system see chat in real time. PMs arrive with light-speed delay. Crew can coordinate exploration.', status: 'future', }, { @@ -110,23 +110,23 @@ export function RoadmapPage() { }, { num: '13', - title: 'Multiplayer Combat', - goal: 'PvP combat with FTL power allocation. Multiple ships in an engagement. Target calling, range bands.', + title: 'Co-op Combat', + goal: 'Crew members fight together. Multiple ships in an engagement. Coordinate against tough NPCs.', doneWhen: 'Two players engage each other. Both manage power allocation. Server resolves combat ticks authoritatively. Both clients see damage applied. Loser\'s ship drops loot (or wreck). Kill log records the event.', status: 'future', }, { num: '14', - title: 'Corporations & Territory', - goal: 'Player corps, structure anchoring, system sovereignty claims.', + title: 'Crews & Shared Progress', + goal: 'Form a persistent crew. Shared wallet, coordinated objectives, crew-level progression.', doneWhen: 'Players form a corp. Corp can anchor a structure in a system. Structure provides bonuses (refining yield, market tax). Sovereignty map shows corp-held systems. Rival corps can contest.', status: 'future', }, { num: '15', - title: 'Full MVP — Launch Candidate', - goal: 'Polish pass on all systems. Error handling, reconnection, scaling tests, and onboarding flow.', - doneWhen: 'Fresh player can create account, complete a guided tutorial, mine their first ore, fit their first ship, survive a PvE encounter, make their first trade, and join a corp — all without hitting a dead-end or a crash. Server handles 50 concurrent players.', + title: 'Co-op Polish', + goal: 'Polish pass on co-op features. Error handling, reconnection, scaling tests for small crews.', + doneWhen: 'Fresh player can create account, complete the tutorial, explore, mine, trade, fight, and customize their ship — all without hitting a dead-end or a crash. Optionally, invite a friend to join the same server for co-op. Server handles a small crew of friends.', status: 'future', }, ], @@ -181,17 +181,17 @@ export function RoadmapPage() {

Development Roadmap

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

- Why single-player first with local SpacetimeDB? Networking is the biggest source of bugs and complexity. + Why single-player with local SpacetimeDB? The game is a solo adventure — Windward Horizon in space. Networking is the biggest source of bugs and complexity. By validating that exploration, trading, combat, fitting, and faction quests are fun locally — using the same SpacetimeDB - persistence that will serve multiplayer — we de-risk the entire project. There is no localStorage; SpacetimeDB is the - persistence layer from day 1. When Era 2 begins, the question is only "how do we share this server?" — not + persistence that would serve co-op — we de-risk the entire project. There is no localStorage; SpacetimeDB is the + persistence layer from day 1. If co-op is added later, the question is only "how do we share this server?" — not "is this game fun?" or "will the persistence migration work?"
@@ -231,28 +231,28 @@ export function RoadmapPage() {
Phases covered: 0–6
-

Gate 4 — Era 1 Complete (after Phase 7)

+

Gate 4 — Core Game Complete (after Phase 7)

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

-
Phases covered: 0–7 (all Era 1)
+
Phases covered: 0–7 (core game)
-

Gate 5 — Multiplayer Core (after Phase 10)

+

Gate 5 — Co-op Core (after Phase 10)

- Two players in the same galaxy. Both see each other. Both can trade on the shared market. ISK and items - transfer atomically. Movement is synced. Connection loss and reconnection work. No desync under normal latency. + Two players on the same server. Both see each other. Both can trade on the shared market. ISK and items + transfer atomically. Movement is synced. Connection loss and reconnection work. The same solo adventure, shared with a friend.

Phases covered: 8–10
-

Gate 6 — Launch Ready (after Phase 15)

+

Gate 6 — Co-op Ready (after Phase 15)

Fresh player can: create account, complete tutorial, explore their first system, mine and trade, survive a PvE encounter, - customize their ship, accept and complete a faction quest, join a crew with friends — all without crashes or dead ends. - Server handles 50 concurrent players. Full game loop validated. + customize their ship, accept and complete a faction quest — and optionally invite a friend to share the adventure — all without crashes or dead ends. + Server handles a small crew of friends.

Phases covered: 0–15 (all)
diff --git a/apps/docs/src/pages/docs/SocialPage.tsx b/apps/docs/src/pages/docs/SocialPage.tsx index c33964d..dbd7cac 100644 --- a/apps/docs/src/pages/docs/SocialPage.tsx +++ b/apps/docs/src/pages/docs/SocialPage.tsx @@ -32,8 +32,8 @@ export function SocialPage() { { name: 'Leadership', color: 'var(--purple)', - skills: ['Fleet Command', 'Wing Command', 'AI Coordination', 'Crew Management'], - xpSource: 'Fleet actions, AI crew task completions, group mission success (post-MVP)', + skills: ['Leadership', 'Navigation Mastery', 'AI Coordination', 'Crew Management'], + xpSource: 'Solo exploration, AI crew task completions, challenging encounters', }, ]; @@ -52,21 +52,19 @@ export function SocialPage() { return (
-

Progression & Social

+

Progression & World

- XP-based progression across five skill categories. Chat ranges from instant local to - light-speed-delayed private messages. Bounty system creates emergent player-driven justice - and piracy consequences. Designed for ~3-hour play sessions with meaningful progression per session. + XP-based progression across five skill categories gives your captain measurable growth. Chat, bounty, and corporation features are designed for optional co-op servers — they enrich the multiplayer experience but are not part of the core solo adventure. Designed for ~3-hour play sessions with meaningful progression per session.

{/* Tab navigation */}
{[ { id: 'progression', label: 'XP & Skills' }, - { id: 'chat', label: 'Chat & Comms' }, - { id: 'bounty', label: 'Bounty System' }, + { id: 'chat', label: 'Chat (Co-op)' }, + { id: 'bounty', label: 'Bounty (Co-op)' }, { id: 'waypoints', label: 'Waypoints' }, - { id: 'corps', label: 'Corporations' }, + { id: 'corps', label: 'Crews (Co-op)' }, ].map(t => ( @@ -267,8 +265,12 @@ export function SocialPage() {

Bounty System

+
+ Co-op server feature. The bounty system adds emergent player-driven dynamics when playing on shared servers. Not part of the solo adventure. +
+

- Any player can place a bounty on another player. Bounties create consequences for piracy + On co-op servers, any player can place a bounty on another player. Bounties create consequences for piracy and emergent "space justice" dynamics. Higher bounties attract more bounty hunters and increase visibility across the galaxy.

@@ -446,11 +448,11 @@ export function SocialPage() { {activeSection === 'corps' && (<>
SOC-CORP -

Corporations & Territory

+

Crews & Shared Progress

- Era 2 feature — designed now, implemented in Phase 14. + Optional co-op feature — designed now, implemented if co-op servers are added. Corporations ("corps") are player organizations: persistent groups with shared wallets, hangars, roles, and territory claims. They are the endgame social structure — the reason players stay for years. This spec establishes the design direction so the backend schema can accommodate it without breaking changes. @@ -564,7 +566,7 @@ export function SocialPage() {

Backend Impact

-

New Tables (Phase 14)

+

New Tables (co-op phase)

corporationscorp_id, name, ticker, ceo_player_id, founded_at, wallet_balance, tax_rate_bounty, tax_rate_market, tax_rate_refining, member_count, hq_station_id, vulnerability_window_start, vulnerability_window_hours, dissolved_at
corp_memberscorp_id, player_id, role (enum), joined_at, tax_contribution_lifetime
diff --git a/apps/docs/src/pages/docs/TechStackPage.tsx b/apps/docs/src/pages/docs/TechStackPage.tsx index 98e23a5..9dc2268 100644 --- a/apps/docs/src/pages/docs/TechStackPage.tsx +++ b/apps/docs/src/pages/docs/TechStackPage.tsx @@ -31,7 +31,7 @@ export function TechStackPage() { }, backend: { choice: 'SpacetimeDB', - reason: 'Real-time backend/database with server-side reducers and live client subscriptions. Authoritative state, persistence, multiplayer all in one.', + reason: 'Real-time backend/database with server-side reducers and live client subscriptions. Authoritative state, persistence, and optional co-op all in one.', whyNot: "A custom Node.js + PostgreSQL backend would require building real-time sync, subscriptions, and authoritative game logic from scratch.", }, styling: { diff --git a/apps/docs/src/pages/docs/VerticalSliceEvaluationPage.tsx b/apps/docs/src/pages/docs/VerticalSliceEvaluationPage.tsx index 08c197b..b9a267b 100644 --- a/apps/docs/src/pages/docs/VerticalSliceEvaluationPage.tsx +++ b/apps/docs/src/pages/docs/VerticalSliceEvaluationPage.tsx @@ -5,7 +5,7 @@ export function VerticalSliceEvaluationPage() {

Vertical Slice Evaluation

Current assessment of how the standalone game app lines up against the - MVP Loop Slice demo and Era 1 roadmap, including implementation gaps + MVP Loop Slice demo and core single-player roadmap, including implementation gaps and the recommended next work.

diff --git a/apps/docs/src/prototypes/existing-demos/AGENTS.md b/apps/docs/src/prototypes/existing-demos/AGENTS.md index ab22b32..82a99ad 100644 --- a/apps/docs/src/prototypes/existing-demos/AGENTS.md +++ b/apps/docs/src/prototypes/existing-demos/AGENTS.md @@ -5,7 +5,7 @@ | Demo | File | Validates | |------|------|-----------| | MVP Loop Slice | `GameLoopSliceDemo.tsx` | Full playable game loop via game-slice prototype | -| Star Map | `StarMapDemo.tsx` | Era 2 galaxy map with multi-system view, warp routes | +| Star Map | `StarMapDemo.tsx` | Galaxy map with multi-system view, warp routes | | Ship Movement | `ShipMovementDemo.tsx` | Sub-warp movement model | | Warp Travel | `WarpTravelDemo.tsx` | Warp sequence (align → accelerate → cruise → exit) | | Combat | `CombatDemo.tsx` | 3D combat with power allocation, projectiles, subsystem damage | diff --git a/apps/docs/src/prototypes/game-slice/AGENTS.md b/apps/docs/src/prototypes/game-slice/AGENTS.md index 8f8ce21..924611a 100644 --- a/apps/docs/src/prototypes/game-slice/AGENTS.md +++ b/apps/docs/src/prototypes/game-slice/AGENTS.md @@ -39,4 +39,4 @@ A self-contained, playable implementation of the full MVP game loop using localS | XP/Skills (5 skills, level 0-5) | Working | Phase 7 | | Objective chain (tutorial) | Working | Phase 7 | | Zora modules (personality + soul depth) | Partial | Phase 7 | -| Multi-system travel (5 systems) | Working | Phase 5 | +| Multi-system travel (5 systems) | Working | Phase 7 | diff --git a/apps/docs/src/prototypes/game-slice/SeamlessGameLoopSlice.tsx b/apps/docs/src/prototypes/game-slice/SeamlessGameLoopSlice.tsx index d7e72a1..b302911 100644 --- a/apps/docs/src/prototypes/game-slice/SeamlessGameLoopSlice.tsx +++ b/apps/docs/src/prototypes/game-slice/SeamlessGameLoopSlice.tsx @@ -16,7 +16,7 @@ function LoadingSlice({ message, onReset }: { message: string; onReset: () => vo return (
-

Era 1 Playable Loop

+

Playable Loop

{message}

diff --git a/apps/docs/src/prototypes/r3f/shared/StarSystemNode.tsx b/apps/docs/src/prototypes/r3f/shared/StarSystemNode.tsx index bfe1ce1..ffd3fd4 100644 --- a/apps/docs/src/prototypes/r3f/shared/StarSystemNode.tsx +++ b/apps/docs/src/prototypes/r3f/shared/StarSystemNode.tsx @@ -44,7 +44,7 @@ export function StarSystemNode({ const color = destination ? "#22d3ee" : selected ? "#f0a030" : waypoint ? "#a78bfa" : current ? "#22c55e" : system.color; const size = selected ? 2.4 : hovered || destination || current ? 1.9 : 1.35; const showLabel = selected || hovered || destination || current || waypoint; - const expanded = selected || hovered; + const expanded = !!(selected || hovered); const contentScale = expanded ? 1.1 : 0.72; return ( diff --git a/apps/game/AGENTS.md b/apps/game/AGENTS.md deleted file mode 100644 index a89d22f..0000000 --- a/apps/game/AGENTS.md +++ /dev/null @@ -1,58 +0,0 @@ -# apps/game — Playable Game Client - -Package: `@void-nav/game` -Port: `http://localhost:5175` - -The **standalone playable game** connected to a SpacetimeDB backend. This is the production game client — all other apps are documentation or marketing. - -## Current State (Phase 0-2) - -The game implements the basic loop: **undock → select target → approach asteroid → mine ore → dock → sell ore**. Keyboard shortcuts (U/D/A/M/1-9) and 3D click selection work. SVG mini-map and 3D scene stay synchronized through SpacetimeDB subscriptions. - -## Structure - -``` -src/ - main.tsx Entry point, renders GameShell into #root - GameShell.tsx Root layout: 3-column grid (left sidebar, center 3D scene, right sidebar, bottom command rail) - module_bindings/ Auto-generated SpacetimeDB TypeScript bindings (24 files) - types.ts All table row types and reducer call types - ... One file per table + connection builder - scene/ 3D rendering (React Three Fiber) - SpaceCanvas.tsx R3F Canvas wrapper (camera at [8,9,44], FOV 48) - SpaceEnvironment.tsx Background, fog, lights, star field - GameSpaceScene.tsx Main orchestrator: places stations/asteroids/ship, manages operations, interpolates movement - StationMesh.tsx Torus + cylinder station, clickable, selection ring - AsteroidMesh.tsx Icosahedron asteroid, tumble animation, clickable - ShipMesh.tsx Cone + box + sphere ship, engine glow by mode, bob animation - spacetime/ SpacetimeDB connection layer - client.ts Connection factory, table subscriptions, auth token storage - useSpacetimeConnection.ts React hook: connection state, identity, revision polling - useGameSession.ts React hook: reads all tables into GameSession object, exposes action callbacks for all reducers - ui/ UI panels - CommandRail.tsx Bottom-fixed contextual action bar with progress bars - ConnectionPanel.tsx Dev diagnostic: connection status, identity, rename/ping - CargoPanel.tsx Cargo hold items with quantity and price - NpcMarketPanel.tsx Sell ore at fixed NPC price (docked only) - WalletPanel.tsx ISK balance display - ShipStatusPanel.tsx Hull/shield/speed bars, position, operation timer - TargetPanel.tsx Selected target info, clickable POI list - MiniStarMap.tsx SVG 2D system map with ship position and operation lines - EventFeed.tsx Server event log (last 12 events) - useKeyboardShortcuts.ts U=undock, D=dock, A=approach, M=mine, 1-9=select target - styles/ - tailwind.css Tailwind v4 entry point -``` - -## SpacetimeDB Tables (9) and Reducers (12) - -See `services/spacetimedb/AGENTS.md` for full backend details. - -## Known Hardcoded Values - -- `client.ts:77-78` — subscriptions hardcoded to `solace` system and `solace-prime` station -- `useGameSession.ts:129-130` — system/station lookup hardcoded to specific IDs -- `ShipStatusPanel.tsx:44` — hullPct always 100 (no damage model) -- `ShipStatusPanel.tsx:45` — shieldPct uses Math.random() (not server data) -- `NpcMarketPanel.tsx:5-7` — NPC_PRICES hardcoded client-side (only Veldspar at 12 ISK) -- `GameSpaceScene.tsx:120-124` — asteroid cluster offsets hardcoded (3 rocks per belt) diff --git a/apps/game/Cargo.lock b/apps/game/Cargo.lock new file mode 100644 index 0000000..da71c14 --- /dev/null +++ b/apps/game/Cargo.lock @@ -0,0 +1,5384 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "accesskit" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "becf0eb5215b6ecb0a739c31c21bd83c4f326524c9b46b7e882d77559b60a529" + +[[package]] +name = "accesskit_consumer" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0bf66a7bf0b7ea4fd7742d50b64782a88f99217cf246b3f93b4162528dde520" +dependencies = [ + "accesskit", + "hashbrown 0.15.5", + "immutable-chunkmap", +] + +[[package]] +name = "accesskit_macos" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09e230718177753b4e4ad9e1d9f6cfc2f4921212d4c1c480b253f526babb258d" +dependencies = [ + "accesskit", + "accesskit_consumer", + "hashbrown 0.15.5", + "objc2 0.5.2", + "objc2-app-kit", + "objc2-foundation", +] + +[[package]] +name = "accesskit_windows" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65178f3df98a51e4238e584fcb255cb1a4f9111820848eeddd37663be40a625f" +dependencies = [ + "accesskit", + "accesskit_consumer", + "hashbrown 0.15.5", + "paste", + "static_assertions", + "windows 0.58.0", + "windows-core 0.58.0", +] + +[[package]] +name = "accesskit_winit" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34d941bb8c414caba6e206de669c7dc0dbeb305640ea890772ee422a40e6b89f" +dependencies = [ + "accesskit", + "accesskit_macos", + "accesskit_windows", + "raw-window-handle", + "winit", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "alsa" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43" +dependencies = [ + "alsa-sys", + "bitflags 2.12.1", + "cfg-if", + "libc", +] + +[[package]] +name = "alsa-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "android-activity" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f2a1bb052857d5dd49572219344a7332b31b76405648eabac5bc68978251bcd" +dependencies = [ + "android-properties", + "bitflags 2.12.1", + "cc", + "jni 0.22.4", + "libc", + "log", + "ndk 0.9.0", + "ndk-context", + "ndk-sys 0.6.0+11769913", + "num_enum", + "simd_cesu8", + "thiserror 2.0.18", +] + +[[package]] +name = "android-properties" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" + +[[package]] +name = "android_log-sys" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84521a3cf562bc62942e294181d9eef17eb38ceb8c68677bc49f144e4c3d4f8d" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" + +[[package]] +name = "ash" +version = "0.38.0+1.3.281" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" +dependencies = [ + "libloading", +] + +[[package]] +name = "assert_type_match" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f548ad2c4031f2902e3edc1f29c29e835829437de49562d8eb5dc5584d3a1043" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-fs" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8034a681df4aed8b8edbd7fbe472401ecf009251c8b40556b304567052e294c5" +dependencies = [ + "async-lock", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "atomicow" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301801c08259e328a1c7da556608c0c22687708831b22024dbd3a57ea741e6de" +dependencies = [ + "portable-atomic", + "portable-atomic-util", +] + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bevy" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b8369c16b7c017437021341521f8b4a0d98e1c70113fb358c3258ae7d661d79" +dependencies = [ + "bevy_internal", +] + +[[package]] +name = "bevy_a11y" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3561712cf49074d89e9989bfc2e6c6add5d33288f689db9a0c333300d2d004" +dependencies = [ + "accesskit", + "bevy_app", + "bevy_derive", + "bevy_ecs", + "bevy_reflect", +] + +[[package]] +name = "bevy_animation" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49796627726d0b9a722ad9a0127719e7c1868f474d6575ec0f411e8299c4d7bb" +dependencies = [ + "bevy_app", + "bevy_asset", + "bevy_color", + "bevy_derive", + "bevy_ecs", + "bevy_log", + "bevy_math", + "bevy_mesh", + "bevy_platform", + "bevy_reflect", + "bevy_render", + "bevy_time", + "bevy_transform", + "bevy_utils", + "blake3", + "derive_more", + "downcast-rs", + "either", + "petgraph", + "ron", + "serde", + "smallvec", + "thiserror 2.0.18", + "thread_local", + "tracing", + "uuid", +] + +[[package]] +name = "bevy_app" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4491cc4c718ae76b4c6883df58b94cc88b32dcd894ea8d5b603c7c7da72ca967" +dependencies = [ + "bevy_derive", + "bevy_ecs", + "bevy_platform", + "bevy_reflect", + "bevy_tasks", + "bevy_utils", + "cfg-if", + "console_error_panic_hook", + "ctrlc", + "downcast-rs", + "log", + "thiserror 2.0.18", + "variadics_please", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "bevy_asset" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f56111d9b88d8649f331a667d9d72163fb26bd09518ca16476d238653823db1e" +dependencies = [ + "async-broadcast", + "async-fs", + "async-lock", + "atomicow", + "bevy_app", + "bevy_asset_macros", + "bevy_ecs", + "bevy_platform", + "bevy_reflect", + "bevy_tasks", + "bevy_utils", + "bevy_window", + "bitflags 2.12.1", + "blake3", + "crossbeam-channel", + "derive_more", + "disqualified", + "downcast-rs", + "either", + "futures-io", + "futures-lite", + "js-sys", + "parking_lot", + "ron", + "serde", + "stackfuture", + "thiserror 2.0.18", + "tracing", + "uuid", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "bevy_asset_macros" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4cca3e67c0ec760d8889d42293d987ce5da92eaf9c592bf5d503728a63b276d" +dependencies = [ + "bevy_macro_utils", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bevy_audio" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b4f6f2a5c6c0e7c6825e791d2a061c76c2d6784f114c8f24382163fabbfaaa" +dependencies = [ + "bevy_app", + "bevy_asset", + "bevy_derive", + "bevy_ecs", + "bevy_math", + "bevy_reflect", + "bevy_transform", + "cpal", + "rodio", + "tracing", +] + +[[package]] +name = "bevy_color" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c101cbe1e26b8d701eb77263b14346e2e0cbbd2a6e254b9b1aead814e5ca8d3" +dependencies = [ + "bevy_math", + "bevy_reflect", + "bytemuck", + "derive_more", + "encase", + "serde", + "thiserror 2.0.18", + "wgpu-types", +] + +[[package]] +name = "bevy_core_pipeline" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed46363cad80dc00f08254c3015232bd6f640738403961c6d63e7ecfc61625" +dependencies = [ + "bevy_app", + "bevy_asset", + "bevy_color", + "bevy_derive", + "bevy_diagnostic", + "bevy_ecs", + "bevy_image", + "bevy_math", + "bevy_platform", + "bevy_reflect", + "bevy_render", + "bevy_transform", + "bevy_utils", + "bevy_window", + "bitflags 2.12.1", + "bytemuck", + "nonmax", + "radsort", + "serde", + "smallvec", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "bevy_derive" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b837bf6c51806b10ebfa9edf1844ad80a3a0760d6c5fac4e90761df91a8901a" +dependencies = [ + "bevy_macro_utils", + "quote", + "syn", +] + +[[package]] +name = "bevy_diagnostic" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48797366f312a8f31e237d08ce3ee70162591282d2bfe7c5ad8be196fb263e55" +dependencies = [ + "bevy_app", + "bevy_ecs", + "bevy_platform", + "bevy_tasks", + "bevy_time", + "bevy_utils", + "const-fnv1a-hash", + "log", + "serde", + "sysinfo", +] + +[[package]] +name = "bevy_ecs" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c2bf6521aae57a0ec3487c4bfb59e36c4a378e834b626a4bea6a885af2fdfe7" +dependencies = [ + "arrayvec", + "bevy_ecs_macros", + "bevy_platform", + "bevy_ptr", + "bevy_reflect", + "bevy_tasks", + "bevy_utils", + "bitflags 2.12.1", + "bumpalo", + "concurrent-queue", + "derive_more", + "disqualified", + "fixedbitset", + "indexmap", + "log", + "nonmax", + "serde", + "smallvec", + "thiserror 2.0.18", + "variadics_please", +] + +[[package]] +name = "bevy_ecs_macros" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38748d6f3339175c582d751f410fb60a93baf2286c3deb7efebb0878dce7f413" +dependencies = [ + "bevy_macro_utils", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bevy_encase_derive" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8148f4edee470a2ea5cad010184c492a4c94c36d7a7158ea28e134ea87f274ab" +dependencies = [ + "bevy_macro_utils", + "encase_derive_impl", +] + +[[package]] +name = "bevy_gilrs" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97efef87c631949e67d06bb5d7dfd2a5f936b3b379afb6b1485b08edbb219b87" +dependencies = [ + "bevy_app", + "bevy_ecs", + "bevy_input", + "bevy_platform", + "bevy_time", + "bevy_utils", + "gilrs", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "bevy_gizmos" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7823154a9682128c261d8bddb3a4d7192a188490075c527af04520c2f0f8aad6" +dependencies = [ + "bevy_app", + "bevy_asset", + "bevy_color", + "bevy_core_pipeline", + "bevy_ecs", + "bevy_gizmos_macros", + "bevy_image", + "bevy_math", + "bevy_pbr", + "bevy_reflect", + "bevy_render", + "bevy_sprite", + "bevy_time", + "bevy_transform", + "bevy_utils", + "bytemuck", + "tracing", +] + +[[package]] +name = "bevy_gizmos_macros" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f378f3b513218ddc78254bbe76536d9de59c1429ebd0c14f5d8f2a25812131ad" +dependencies = [ + "bevy_macro_utils", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bevy_gltf" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a080237c0b8842ccc15a06d3379302c68580eeea4497b1c7387e470eda1f07" +dependencies = [ + "base64 0.22.1", + "bevy_animation", + "bevy_app", + "bevy_asset", + "bevy_color", + "bevy_core_pipeline", + "bevy_ecs", + "bevy_image", + "bevy_math", + "bevy_mesh", + "bevy_pbr", + "bevy_platform", + "bevy_reflect", + "bevy_render", + "bevy_scene", + "bevy_tasks", + "bevy_transform", + "bevy_utils", + "fixedbitset", + "gltf", + "itertools 0.14.0", + "percent-encoding", + "serde", + "serde_json", + "smallvec", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "bevy_image" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65e6e900cfecadbc3149953169e36b9e26f922ed8b002d62339d8a9dc6129328" +dependencies = [ + "bevy_app", + "bevy_asset", + "bevy_color", + "bevy_math", + "bevy_platform", + "bevy_reflect", + "bevy_utils", + "bitflags 2.12.1", + "bytemuck", + "futures-lite", + "guillotiere", + "half", + "image", + "ktx2", + "rectangle-pack", + "ruzstd", + "serde", + "thiserror 2.0.18", + "tracing", + "wgpu-types", +] + +[[package]] +name = "bevy_input" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d6b6516433f6f7d680f648d04eb1866bb3927a1782d52f74831b62042f3cd1" +dependencies = [ + "bevy_app", + "bevy_ecs", + "bevy_math", + "bevy_platform", + "bevy_reflect", + "bevy_utils", + "derive_more", + "log", + "smol_str", + "thiserror 2.0.18", +] + +[[package]] +name = "bevy_input_focus" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e2d079fda74d1416e0a57dac29ea2b79ff77f420cd6b87f833d3aa29a46bc4d" +dependencies = [ + "bevy_app", + "bevy_ecs", + "bevy_input", + "bevy_math", + "bevy_reflect", + "bevy_window", + "log", + "thiserror 2.0.18", +] + +[[package]] +name = "bevy_internal" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "857da8785678fde537d02944cd20dec9cafb7d4c447efe15f898dc60e733cacd" +dependencies = [ + "bevy_a11y", + "bevy_animation", + "bevy_app", + "bevy_asset", + "bevy_audio", + "bevy_color", + "bevy_core_pipeline", + "bevy_derive", + "bevy_diagnostic", + "bevy_ecs", + "bevy_gilrs", + "bevy_gizmos", + "bevy_gltf", + "bevy_image", + "bevy_input", + "bevy_input_focus", + "bevy_log", + "bevy_math", + "bevy_pbr", + "bevy_picking", + "bevy_platform", + "bevy_ptr", + "bevy_reflect", + "bevy_render", + "bevy_scene", + "bevy_sprite", + "bevy_state", + "bevy_tasks", + "bevy_text", + "bevy_time", + "bevy_transform", + "bevy_ui", + "bevy_utils", + "bevy_window", + "bevy_winit", +] + +[[package]] +name = "bevy_log" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a61ee8aef17a974f5ca481dcedf0c2bd52670e231d4c4bc9ddef58328865f9" +dependencies = [ + "android_log-sys", + "bevy_app", + "bevy_ecs", + "bevy_utils", + "tracing", + "tracing-log", + "tracing-oslog", + "tracing-subscriber", + "tracing-wasm", +] + +[[package]] +name = "bevy_macro_utils" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "052eeebcb8e7e072beea5031b227d9a290f8a7fbbb947573ab6ec81df0fb94be" +dependencies = [ + "parking_lot", + "proc-macro2", + "quote", + "syn", + "toml_edit 0.22.27", +] + +[[package]] +name = "bevy_math" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68553e0090fe9c3ba066c65629f636bd58e4ebd9444fdba097b91af6cd3e243f" +dependencies = [ + "approx", + "bevy_reflect", + "derive_more", + "glam", + "itertools 0.14.0", + "libm", + "rand", + "rand_distr", + "serde", + "smallvec", + "thiserror 2.0.18", + "variadics_please", +] + +[[package]] +name = "bevy_mesh" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b10399c7027001edbc0406d7d0198596b1f07206c1aae715274106ba5bdcac40" +dependencies = [ + "bevy_asset", + "bevy_derive", + "bevy_ecs", + "bevy_image", + "bevy_math", + "bevy_mikktspace", + "bevy_platform", + "bevy_reflect", + "bevy_transform", + "bevy_utils", + "bitflags 2.12.1", + "bytemuck", + "hexasphere", + "serde", + "thiserror 2.0.18", + "tracing", + "wgpu-types", +] + +[[package]] +name = "bevy_mikktspace" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bb60c753b968a2de0fd279b76a3d19517695e771edb4c23575c7f92156315de" +dependencies = [ + "glam", +] + +[[package]] +name = "bevy_pbr" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5e0b4eb871f364a0d217f70f6c41d7fdc6f9f931fa1abbf222180c03d0ae410" +dependencies = [ + "bevy_app", + "bevy_asset", + "bevy_color", + "bevy_core_pipeline", + "bevy_derive", + "bevy_diagnostic", + "bevy_ecs", + "bevy_image", + "bevy_math", + "bevy_platform", + "bevy_reflect", + "bevy_render", + "bevy_transform", + "bevy_utils", + "bevy_window", + "bitflags 2.12.1", + "bytemuck", + "derive_more", + "fixedbitset", + "nonmax", + "offset-allocator", + "radsort", + "smallvec", + "static_assertions", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "bevy_picking" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed04757938655ed8094ea1efb533f99063a8b22abffc22010c694d291522850" +dependencies = [ + "bevy_app", + "bevy_asset", + "bevy_derive", + "bevy_ecs", + "bevy_input", + "bevy_math", + "bevy_mesh", + "bevy_platform", + "bevy_reflect", + "bevy_render", + "bevy_time", + "bevy_transform", + "bevy_utils", + "bevy_window", + "crossbeam-channel", + "tracing", + "uuid", +] + +[[package]] +name = "bevy_platform" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7573dc824a1b08b4c93fdbe421c53e1e8188e9ca1dd74a414455fe571facb47" +dependencies = [ + "cfg-if", + "critical-section", + "foldhash", + "getrandom 0.2.17", + "hashbrown 0.15.5", + "portable-atomic", + "portable-atomic-util", + "serde", + "spin", + "web-time", +] + +[[package]] +name = "bevy_ptr" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df7370d0e46b60e071917711d0860721f5347bc958bf325975ae6913a5dfcf01" + +[[package]] +name = "bevy_reflect" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daeb91a63a1a4df00aa58da8cc4ddbd4b9f16ab8bb647c5553eb156ce36fa8c2" +dependencies = [ + "assert_type_match", + "bevy_platform", + "bevy_ptr", + "bevy_reflect_derive", + "bevy_utils", + "derive_more", + "disqualified", + "downcast-rs", + "erased-serde", + "foldhash", + "glam", + "petgraph", + "serde", + "smallvec", + "smol_str", + "thiserror 2.0.18", + "uuid", + "variadics_please", + "wgpu-types", +] + +[[package]] +name = "bevy_reflect_derive" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ddadc55fe16b45faaa54ab2f9cb00548013c74812e8b018aa172387103cce6" +dependencies = [ + "bevy_macro_utils", + "proc-macro2", + "quote", + "syn", + "uuid", +] + +[[package]] +name = "bevy_render" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef91fed1f09405769214b99ebe4390d69c1af5cdd27967deae9135c550eb1667" +dependencies = [ + "async-channel", + "bevy_app", + "bevy_asset", + "bevy_color", + "bevy_derive", + "bevy_diagnostic", + "bevy_ecs", + "bevy_encase_derive", + "bevy_image", + "bevy_math", + "bevy_mesh", + "bevy_platform", + "bevy_reflect", + "bevy_render_macros", + "bevy_tasks", + "bevy_time", + "bevy_transform", + "bevy_utils", + "bevy_window", + "bitflags 2.12.1", + "bytemuck", + "codespan-reporting", + "derive_more", + "downcast-rs", + "encase", + "fixedbitset", + "futures-lite", + "image", + "indexmap", + "js-sys", + "ktx2", + "naga", + "naga_oil", + "nonmax", + "offset-allocator", + "send_wrapper", + "serde", + "smallvec", + "thiserror 2.0.18", + "tracing", + "variadics_please", + "wasm-bindgen", + "web-sys", + "wgpu", +] + +[[package]] +name = "bevy_render_macros" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abd42cf6c875bcf38da859f8e731e119a6aff190d41dd0a1b6000ad57cf2ed3d" +dependencies = [ + "bevy_macro_utils", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bevy_scene" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c52ca165200995fe8afd2a1a6c03e4ffee49198a1d4653d32240ea7f217d4ab" +dependencies = [ + "bevy_app", + "bevy_asset", + "bevy_derive", + "bevy_ecs", + "bevy_platform", + "bevy_reflect", + "bevy_render", + "bevy_transform", + "bevy_utils", + "derive_more", + "serde", + "thiserror 2.0.18", + "uuid", +] + +[[package]] +name = "bevy_sprite" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ccae7bab2cb956fb0434004c359e432a3a1a074a6ef4eb471f1fb099f0b620b" +dependencies = [ + "bevy_app", + "bevy_asset", + "bevy_color", + "bevy_core_pipeline", + "bevy_derive", + "bevy_ecs", + "bevy_image", + "bevy_math", + "bevy_picking", + "bevy_platform", + "bevy_reflect", + "bevy_render", + "bevy_transform", + "bevy_utils", + "bevy_window", + "bitflags 2.12.1", + "bytemuck", + "derive_more", + "fixedbitset", + "nonmax", + "radsort", + "tracing", +] + +[[package]] +name = "bevy_state" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "155d3cd97b900539008cdcaa702f88b724d94b08977b8e591a32536ce66faa8c" +dependencies = [ + "bevy_app", + "bevy_ecs", + "bevy_platform", + "bevy_reflect", + "bevy_state_macros", + "bevy_utils", + "log", + "variadics_please", +] + +[[package]] +name = "bevy_state_macros" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2481c1304fd2a1851a0d4cb63a1ce6421ae40f3f0117cbc9882963ee4c9bb609" +dependencies = [ + "bevy_macro_utils", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bevy_tasks" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b674242641cab680688fc3b850243b351c1af49d4f3417a576debd6cca8dcf5" +dependencies = [ + "async-channel", + "async-executor", + "async-task", + "atomic-waker", + "bevy_platform", + "cfg-if", + "concurrent-queue", + "crossbeam-queue", + "derive_more", + "futures-channel", + "futures-lite", + "heapless", + "pin-project", + "wasm-bindgen-futures", +] + +[[package]] +name = "bevy_text" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d76c85366159f5f54110f33321c76d8429cfd8f39638f26793a305dae568b60" +dependencies = [ + "bevy_app", + "bevy_asset", + "bevy_color", + "bevy_derive", + "bevy_ecs", + "bevy_image", + "bevy_log", + "bevy_math", + "bevy_platform", + "bevy_reflect", + "bevy_render", + "bevy_sprite", + "bevy_transform", + "bevy_utils", + "bevy_window", + "cosmic-text", + "serde", + "smallvec", + "sys-locale", + "thiserror 2.0.18", + "tracing", + "unicode-bidi", +] + +[[package]] +name = "bevy_time" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc98eb356c75be04fbbc77bb3d8ffa24c8bacd99f76111cee23d444be6ac8c9c" +dependencies = [ + "bevy_app", + "bevy_ecs", + "bevy_platform", + "bevy_reflect", + "crossbeam-channel", + "log", + "serde", +] + +[[package]] +name = "bevy_transform" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df218e440bb9a19058e1b80a68a031c887bcf7bd3a145b55f361359a2fa3100d" +dependencies = [ + "bevy_app", + "bevy_ecs", + "bevy_log", + "bevy_math", + "bevy_reflect", + "bevy_tasks", + "bevy_utils", + "derive_more", + "serde", + "thiserror 2.0.18", +] + +[[package]] +name = "bevy_ui" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea4a4d2ba51865bc3039af29a26b4f52c48b54cc758369f52004caf4b6f03770" +dependencies = [ + "accesskit", + "bevy_a11y", + "bevy_app", + "bevy_asset", + "bevy_color", + "bevy_core_pipeline", + "bevy_derive", + "bevy_ecs", + "bevy_image", + "bevy_input", + "bevy_math", + "bevy_picking", + "bevy_platform", + "bevy_reflect", + "bevy_render", + "bevy_sprite", + "bevy_text", + "bevy_transform", + "bevy_utils", + "bevy_window", + "bytemuck", + "derive_more", + "nonmax", + "smallvec", + "taffy", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "bevy_utils" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f7a8905a125d2017e8561beefb7f2f5e67e93ff6324f072ad87c5fd6ec3b99" +dependencies = [ + "bevy_platform", + "thread_local", +] + +[[package]] +name = "bevy_window" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df7e8ad0c17c3cc23ff5566ae2905c255e6986037fb041f74c446216f5c38431" +dependencies = [ + "android-activity", + "bevy_app", + "bevy_ecs", + "bevy_input", + "bevy_math", + "bevy_platform", + "bevy_reflect", + "bevy_utils", + "log", + "raw-window-handle", + "serde", + "smol_str", +] + +[[package]] +name = "bevy_winit" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a5e7f00c6b3b6823df5ec2a5e9067273607208919bc8c211773ebb9643c87f0" +dependencies = [ + "accesskit", + "accesskit_winit", + "approx", + "bevy_a11y", + "bevy_app", + "bevy_asset", + "bevy_derive", + "bevy_ecs", + "bevy_image", + "bevy_input", + "bevy_input_focus", + "bevy_log", + "bevy_math", + "bevy_platform", + "bevy_reflect", + "bevy_tasks", + "bevy_utils", + "bevy_window", + "bytemuck", + "cfg-if", + "crossbeam-channel", + "raw-window-handle", + "tracing", + "wasm-bindgen", + "web-sys", + "wgpu-types", + "winit", +] + +[[package]] +name = "bindgen" +version = "0.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" +dependencies = [ + "bitflags 2.12.1", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex 1.3.0", + "syn", +] + +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.12.1", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "proc-macro2", + "quote", + "regex", + "rustc-hash 2.1.2", + "shlex 1.3.0", + "syn", +] + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec 0.6.3", +] + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec 0.8.0", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84d7ced0ae9557296835c32bf1b1e02b44c746701f898460fb000d7eaa84f00a" +dependencies = [ + "serde_core", +] + +[[package]] +name = "blake3" +version = "1.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures", +] + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2 0.5.2", +] + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2 0.6.4", +] + +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "calloop" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" +dependencies = [ + "bitflags 2.12.1", + "log", + "polling", + "rustix 0.38.44", + "slab", + "thiserror 1.0.69", +] + +[[package]] +name = "cc" +version = "1.2.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex 2.0.1", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", + "portable-atomic", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "const-fnv1a-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b13ea120a812beba79e34316b3942a857c86ec1593cb34f27bb28272ce2cca" + +[[package]] +name = "const_panic" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e262cdaac42494e3ae34c43969f9cdeb7da178bdb4b66fa6a1ea2edb4c8ae652" +dependencies = [ + "typewit", +] + +[[package]] +name = "const_soft_float" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ca1caa64ef4ed453e68bb3db612e51cf1b2f5b871337f0fcab1c8f87cc3dff" + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "constgebra" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1aaf9b65849a68662ac6c0810c8893a765c960b907dd7cfab9c4a50bf764fbc" +dependencies = [ + "const_soft_float", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "libc", +] + +[[package]] +name = "coreaudio-rs" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "321077172d79c662f64f5071a03120748d5bb652f5231570141be24cfcd2bace" +dependencies = [ + "bitflags 1.3.2", + "core-foundation-sys", + "coreaudio-sys", +] + +[[package]] +name = "coreaudio-sys" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ceec7a6067e62d6f931a2baf6f3a751f4a892595bcec1461a3c94ef9949864b6" +dependencies = [ + "bindgen 0.72.1", +] + +[[package]] +name = "cosmic-text" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e418dd4f5128c3e93eab12246391c54a20c496811131f85754dc8152ee207892" +dependencies = [ + "bitflags 2.12.1", + "fontdb", + "log", + "rangemap", + "rustc-hash 1.1.0", + "rustybuzz", + "self_cell", + "smol_str", + "swash", + "sys-locale", + "ttf-parser 0.21.1", + "unicode-bidi", + "unicode-linebreak", + "unicode-script", + "unicode-segmentation", +] + +[[package]] +name = "cpal" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "873dab07c8f743075e57f524c583985fbaf745602acbe916a01539364369a779" +dependencies = [ + "alsa", + "core-foundation-sys", + "coreaudio-rs", + "dasp_sample", + "jni 0.21.1", + "js-sys", + "libc", + "mach2", + "ndk 0.8.0", + "ndk-context", + "oboe", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows 0.54.0", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "ctrlc" +version = "3.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0b1fab2ae45819af2d0731d60f2afe17227ebb1a1538a236da84c93e9a60162" +dependencies = [ + "dispatch2", + "nix", + "windows-sys 0.61.2", +] + +[[package]] +name = "cursor-icon" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" + +[[package]] +name = "dasp_sample" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" + +[[package]] +name = "data-encoding" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" + +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags 2.12.1", + "block2 0.6.2", + "libc", + "objc2 0.6.4", +] + +[[package]] +name = "disqualified" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9c272297e804878a2a4b707cfcfc6d2328b5bb936944613b4fdf2b9269afdfd" + +[[package]] +name = "dlib" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" +dependencies = [ + "libloading", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "downcast-rs" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117240f60069e65410b3ae1bb213295bd828f707b5bec6596a1afc8793ce0cbc" + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" + +[[package]] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" + +[[package]] +name = "encase" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0a05902cf601ed11d564128448097b98ebe3c6574bd7b6a653a3d56d54aa020" +dependencies = [ + "const_panic", + "encase_derive", + "glam", + "thiserror 1.0.69", +] + +[[package]] +name = "encase_derive" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "181d475b694e2dd56ae919ce7699d344d1fd259292d590c723a50d1189a2ea85" +dependencies = [ + "encase_derive_impl", +] + +[[package]] +name = "encase_derive_impl" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f97b51c5cc57ef7c5f7a0c57c250251c49ee4c28f819f87ac32f4aceabc36792" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased-serde" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "euclid" +version = "0.22.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06" +dependencies = [ + "num-traits", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "font-types" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b38ad915f6dadd993ced50848a8291a543bd41ca62bc10740d5e64e2ab4cfd7" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "fontconfig-parser" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646" +dependencies = [ + "roxmltree", +] + +[[package]] +name = "fontdb" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0299020c3ef3f60f526a4f64ab4a3d4ce116b1acbf24cdd22da0068e5d81dc3" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2", + "slotmap", + "tinyvec", + "ttf-parser 0.20.0", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "gethostname" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" +dependencies = [ + "rustix 1.1.4", + "windows-link", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + +[[package]] +name = "gilrs" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "902fb00d3f6398e635be22e5c837b303c501835cca7ac11a47bba138f7aafdd8" +dependencies = [ + "fnv", + "gilrs-core", + "log", + "uuid", + "vec_map", +] + +[[package]] +name = "gilrs-core" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc7f0ce6237abcc0523f2a5502b1e3fe5802daaae47ac14e166fe49551301ea9" +dependencies = [ + "inotify", + "js-sys", + "libc", + "libudev-sys", + "log", + "nix", + "objc2-core-foundation", + "objc2-io-kit", + "uuid", + "vec_map", + "wasm-bindgen", + "web-sys", + "windows 0.62.2", +] + +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + +[[package]] +name = "glam" +version = "0.29.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8babf46d4c1c9d92deac9f7be466f76dfc4482b6452fc5024b5e8daf6ffeb3ee" +dependencies = [ + "bytemuck", + "libm", + "rand", + "serde", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "glow" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e5ea60d70410161c8bf5da3fdfeaa1c72ed2c15f8bbb9d19fe3a4fad085f08" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gltf" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ce1918195723ce6ac74e80542c5a96a40c2b26162c1957a5cd70799b8cacf7" +dependencies = [ + "byteorder", + "gltf-json", + "lazy_static", + "serde_json", +] + +[[package]] +name = "gltf-derive" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14070e711538afba5d6c807edb74bcb84e5dbb9211a3bf5dea0dfab5b24f4c51" +dependencies = [ + "inflections", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "gltf-json" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6176f9d60a7eab0a877e8e96548605dedbde9190a7ae1e80bbcc1c9af03ab14" +dependencies = [ + "gltf-derive", + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "gpu-alloc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" +dependencies = [ + "bitflags 2.12.1", + "gpu-alloc-types", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" +dependencies = [ + "bitflags 2.12.1", +] + +[[package]] +name = "gpu-allocator" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c151a2a5ef800297b4e79efa4f4bec035c5f51d5ae587287c9b952bdf734cacd" +dependencies = [ + "log", + "presser", + "thiserror 1.0.69", + "windows 0.58.0", +] + +[[package]] +name = "gpu-descriptor" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" +dependencies = [ + "bitflags 2.12.1", + "gpu-descriptor-types", + "hashbrown 0.15.5", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" +dependencies = [ + "bitflags 2.12.1", +] + +[[package]] +name = "grid" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36119f3a540b086b4e436bb2b588cf98a68863470e0e880f4d0842f112a3183a" + +[[package]] +name = "guillotiere" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62d5865c036cb1393e23c50693df631d3f5d7bcca4c04fe4cc0fd592e74a782" +dependencies = [ + "euclid", + "svg_fmt", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "equivalent", + "foldhash", + "serde", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "portable-atomic", + "stable_deref_trait", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hexasphere" +version = "15.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c9e718d32b6e6b2b32354e1b0367025efdd0b11d6a740b905ddf5db1074679" +dependencies = [ + "constgebra", + "glam", + "tinyvec", +] + +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" +dependencies = [ + "bytemuck", + "byteorder-lite", + "moxcms", + "num-traits", + "png", +] + +[[package]] +name = "immutable-chunkmap" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3e98b1520e49e252237edc238a39869da9f3241f2ec19dc788c1d24694d1e4" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "inflections" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a257582fdcde896fd96463bf2d40eefea0580021c0712a0e2b028b60b47a837a" + +[[package]] +name = "inotify" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "533e68a5842e734946fe159fb03fc9bbbb254f590dd0d8ad321ae5ff7beca2c1" +dependencies = [ + "bitflags 2.12.1", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys 0.3.1", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys 0.4.1", + "log", + "simd_cesu8", + "thiserror 2.0.18", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "libloading", + "pkg-config", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "ktx2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87d65e08a9ec02e409d27a0139eaa6b9756b4d81fe7cde71f6941a83730ce838" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "lewton" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "777b48df9aaab155475a83a7df3070395ea1ac6902f5cd062b8f2b028075c030" +dependencies = [ + "byteorder", + "ogg", + "tinyvec", +] + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" +dependencies = [ + "bitflags 2.12.1", + "libc", + "plain", + "redox_syscall 0.8.1", +] + +[[package]] +name = "libudev-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "113b30b4cd05f7c06868fdb2854f66a7b9fece9a48425351cd532e810d74024f" + +[[package]] +name = "mach2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +dependencies = [ + "libc", +] + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" + +[[package]] +name = "memmap2" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +dependencies = [ + "libc", +] + +[[package]] +name = "metal" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f569fb946490b5743ad69813cb19629130ce9374034abe31614a36402d18f99e" +dependencies = [ + "bitflags 2.12.1", + "block", + "core-graphics-types", + "foreign-types", + "log", + "objc", + "paste", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "moxcms" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +dependencies = [ + "num-traits", + "pxfm", +] + +[[package]] +name = "naga" +version = "24.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e380993072e52eef724eddfcde0ed013b0c023c3f0417336ed041aa9f076994e" +dependencies = [ + "arrayvec", + "bit-set 0.8.0", + "bitflags 2.12.1", + "cfg_aliases", + "codespan-reporting", + "hexf-parse", + "indexmap", + "log", + "pp-rs", + "rustc-hash 1.1.0", + "spirv", + "strum", + "termcolor", + "thiserror 2.0.18", + "unicode-xid", +] + +[[package]] +name = "naga_oil" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2464f7395decfd16bb4c33fb0cb3b2c645cc60d051bc7fb652d3720bfb20f18" +dependencies = [ + "bit-set 0.5.3", + "codespan-reporting", + "data-encoding", + "indexmap", + "naga", + "once_cell", + "regex", + "regex-syntax", + "rustc-hash 1.1.0", + "thiserror 1.0.69", + "tracing", + "unicode-ident", +] + +[[package]] +name = "ndk" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" +dependencies = [ + "bitflags 2.12.1", + "jni-sys 0.3.1", + "log", + "ndk-sys 0.5.0+25.2.9519653", + "num_enum", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.12.1", + "jni-sys 0.3.1", + "log", + "ndk-sys 0.6.0+11769913", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.5.0+25.2.9519653" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" +dependencies = [ + "jni-sys 0.3.1", +] + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys 0.3.1", +] + +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags 2.12.1", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nonmax" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "610a5acd306ec67f907abe5567859a3c693fb9886eb1f012ab8f2a47bef3db51" + +[[package]] +name = "ntapi" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" +dependencies = [ + "winapi", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.12.1", + "block2 0.5.1", + "libc", + "objc2 0.5.2", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +dependencies = [ + "bitflags 2.12.1", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-core-location", + "objc2-foundation", +] + +[[package]] +name = "objc2-contacts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.12.1", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.12.1", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-core-location" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-contacts", + "objc2-foundation", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.12.1", + "block2 0.5.1", + "dispatch", + "libc", + "objc2 0.5.2", +] + +[[package]] +name = "objc2-io-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" +dependencies = [ + "bitflags 2.12.1", + "libc", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-link-presentation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-app-kit", + "objc2-foundation", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.12.1", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.12.1", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-symbols" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" +dependencies = [ + "objc2 0.5.2", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +dependencies = [ + "bitflags 2.12.1", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-image", + "objc2-core-location", + "objc2-foundation", + "objc2-link-presentation", + "objc2-quartz-core", + "objc2-symbols", + "objc2-uniform-type-identifiers", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-uniform-type-identifiers" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" +dependencies = [ + "bitflags 2.12.1", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-core-location", + "objc2-foundation", +] + +[[package]] +name = "oboe" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8b61bebd49e5d43f5f8cc7ee2891c16e0f41ec7954d36bcb6c14c5e0de867fb" +dependencies = [ + "jni 0.21.1", + "ndk 0.8.0", + "ndk-context", + "num-derive", + "num-traits", + "oboe-sys", +] + +[[package]] +name = "oboe-sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8bb09a4a2b1d668170cfe0a7d5bc103f8999fb316c98099b6a9939c9f2e79d" +dependencies = [ + "cc", +] + +[[package]] +name = "offset-allocator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e234d535da3521eb95106f40f0b73483d80bfb3aacf27c40d7e2b72f1a3e00a2" +dependencies = [ + "log", + "nonmax", +] + +[[package]] +name = "ogg" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6951b4e8bf21c8193da321bcce9c9dd2e13c858fe078bf9054a288b419ae5d6e" +dependencies = [ + "byteorder", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "orbclient" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5df339f526ea9a60e371768d50efc2f2508c7203290731565d1f7a6f71d21747" +dependencies = [ + "libc", + "libredox", +] + +[[package]] +name = "ordered-float" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" +dependencies = [ + "num-traits", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.18", + "smallvec", + "windows-link", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap", + "serde", + "serde_derive", +] + +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "piper" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "png" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags 2.12.1", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "portable-atomic-util" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "pp-rs" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb458bb7f6e250e6eb79d5026badc10a3ebb8f9a15d1fff0f13d17c71f4d6dee" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit 0.25.12+spec-1.1.0", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" + +[[package]] +name = "pxfm" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f" + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "radsort" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "019b4b213425016d7d84a153c4c73afb0946fbb4840e4eece7ba8848b9d6da22" + +[[package]] +name = "rand" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_distr" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "range-alloc" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca45419789ae5a7899559e9512e58ca889e41f04f1f2445e9f4b290ceccd1d08" + +[[package]] +name = "rangemap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "973443cf09a9c8656b574a866ab68dfa19f0867d0340648c7d2f6a71b8a8ea68" + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "read-fonts" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b634fabf032fab15307ffd272149b622260f55974d9fad689292a5d33df02e5" +dependencies = [ + "bytemuck", + "font-types", +] + +[[package]] +name = "rectangle-pack" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d463f2884048e7153449a55166f91028d5b0ea53c79377099ce4e8cf0cf9bb" + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.12.1", +] + +[[package]] +name = "redox_syscall" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b44b894f2a6e36457d665d1e08c3866add6ed5e70050c1b4ba8a8ddedb02ce7" +dependencies = [ + "bitflags 2.12.1", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + +[[package]] +name = "rodio" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ceb6607dd738c99bc8cb28eff249b7cd5c8ec88b9db96c0608c1480d140fb1" +dependencies = [ + "cpal", + "lewton", +] + +[[package]] +name = "ron" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" +dependencies = [ + "base64 0.21.7", + "bitflags 2.12.1", + "serde", + "serde_derive", +] + +[[package]] +name = "roxmltree" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.12.1", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.12.1", + "errno", + "libc", + "linux-raw-sys 0.12.1", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "rustybuzz" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfb9cf8877777222e4a3bc7eb247e398b56baba500c38c1c46842431adc8b55c" +dependencies = [ + "bitflags 2.12.1", + "bytemuck", + "libm", + "smallvec", + "ttf-parser 0.21.1", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-properties", + "unicode-script", +] + +[[package]] +name = "ruzstd" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7c1c839d570d835527c9a5e4db7cb2198683a988cb9d7293fc8674e6bd58fc8" +dependencies = [ + "twox-hash", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "self_cell" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b12e76d157a900eb52e81bc6e9f3069344290341720e9178cde2407113ac8d89" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "send_wrapper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "skrifa" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fbdfe3d2475fbd7ddd1f3e5cf8288a30eb3e5f95832829570cd88115a7434ac" +dependencies = [ + "bytemuck", + "read-fonts", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "spirv" +version = "0.3.0+sdk-1.3.268.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" +dependencies = [ + "bitflags 2.12.1", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "stackfuture" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115beb9c69db2393ff10b75a1b8587a51716e5551d015001e55320ed279d32f9" +dependencies = [ + "const_panic", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "svg_fmt" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb" + +[[package]] +name = "swash" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "842f3cd369c2ba38966204f983eaa5e54a8e84a7d7159ed36ade2b6c335aae64" +dependencies = [ + "skrifa", + "yazi", + "zeno", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sys-locale" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4" +dependencies = [ + "libc", +] + +[[package]] +name = "sysinfo" +version = "0.34.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4b93974b3d3aeaa036504b8eefd4c039dced109171c1ae973f1dc63b2c7e4b2" +dependencies = [ + "libc", + "memchr", + "ntapi", + "objc2-core-foundation", + "windows 0.57.0", +] + +[[package]] +name = "taffy" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab4f4d046dd956a47a7e1a2947083d7ac3e6aa3cfaaead36173ceaa5ab11878c" +dependencies = [ + "arrayvec", + "grid", + "serde", + "slotmap", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "toml_datetime 0.6.11", + "winnow 0.7.15", +] + +[[package]] +name = "toml_edit" +version = "0.25.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" +dependencies = [ + "indexmap", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.3", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow 1.0.3", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-oslog" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528bdd1f0e27b5dd9a4ededf154e824b0532731e4af73bb531de46276e0aab1e" +dependencies = [ + "bindgen 0.70.1", + "cc", + "cfg-if", + "once_cell", + "parking_lot", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "tracing-wasm" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4575c663a174420fa2d78f4108ff68f65bf2fbb7dd89f33749b6e826b3626e07" +dependencies = [ + "tracing", + "tracing-subscriber", + "wasm-bindgen", +] + +[[package]] +name = "ttf-parser" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" + +[[package]] +name = "ttf-parser" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8" + +[[package]] +name = "twox-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "typewit" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "214ca0b2191785cbc06209b9ca1861e048e39b5ba33574b3cedd58363d5bb5f6" + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-bidi-mirroring" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cb788ffebc92c5948d0e997106233eeb1d8b9512f93f41651f52b6c5f5af86" + +[[package]] +name = "unicode-ccc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df77b101bcc4ea3d78dafc5ad7e4f58ceffe0b2b16bf446aeb50b6cb4157656" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" + +[[package]] +name = "unicode-script" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "383ad40bb927465ec0ce7720e033cb4ca06912855fc35db31b5755d0de75b1ee" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "uuid" +version = "1.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "variadics_please" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41b6d82be61465f97d42bd1d15bf20f3b0a3a0905018f38f9d6f6962055b0b5c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "void-nav" +version = "0.1.0" +dependencies = [ + "bevy", + "rand", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen 0.57.1", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.12.1", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wgpu" +version = "24.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b0b3436f0729f6cdf2e6e9201f3d39dc95813fad61d826c1ed07918b4539353" +dependencies = [ + "arrayvec", + "bitflags 2.12.1", + "cfg_aliases", + "document-features", + "js-sys", + "log", + "naga", + "parking_lot", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "24.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f0aa306497a238d169b9dc70659105b4a096859a34894544ca81719242e1499" +dependencies = [ + "arrayvec", + "bit-vec 0.8.0", + "bitflags 2.12.1", + "cfg_aliases", + "document-features", + "indexmap", + "log", + "naga", + "once_cell", + "parking_lot", + "profiling", + "raw-window-handle", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 2.0.18", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-hal" +version = "24.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f112f464674ca69f3533248508ee30cb84c67cf06c25ff6800685f5e0294e259" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bit-set 0.8.0", + "bitflags 2.12.1", + "block", + "bytemuck", + "cfg_aliases", + "core-graphics-types", + "glow", + "glutin_wgl_sys", + "gpu-alloc", + "gpu-allocator", + "gpu-descriptor", + "js-sys", + "khronos-egl", + "libc", + "libloading", + "log", + "metal", + "naga", + "ndk-sys 0.5.0+25.2.9519653", + "objc", + "once_cell", + "ordered-float", + "parking_lot", + "profiling", + "range-alloc", + "raw-window-handle", + "renderdoc-sys", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 2.0.18", + "wasm-bindgen", + "web-sys", + "wgpu-types", + "windows 0.58.0", + "windows-core 0.58.0", +] + +[[package]] +name = "wgpu-types" +version = "24.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50ac044c0e76c03a0378e7786ac505d010a873665e2d51383dcff8dd227dc69c" +dependencies = [ + "bitflags 2.12.1", + "js-sys", + "log", + "serde", + "web-sys", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" +dependencies = [ + "windows-core 0.54.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +dependencies = [ + "windows-core 0.57.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core 0.58.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections", + "windows-core 0.62.2", + "windows-future", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core 0.62.2", +] + +[[package]] +name = "windows-core" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" +dependencies = [ + "windows-result 0.1.2", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +dependencies = [ + "windows-implement 0.57.0", + "windows-interface 0.57.0", + "windows-result 0.1.2", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement 0.58.0", + "windows-interface 0.58.0", + "windows-result 0.2.0", + "windows-strings 0.1.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core 0.62.2", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core 0.62.2", + "windows-link", +] + +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result 0.2.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winit" +version = "0.30.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6755fa58a9f8350bd1e472d4c3fcc25f824ec358933bba33306d0b63df5978d" +dependencies = [ + "android-activity", + "atomic-waker", + "bitflags 2.12.1", + "block2 0.5.1", + "bytemuck", + "calloop", + "cfg_aliases", + "concurrent-queue", + "core-foundation", + "core-graphics", + "cursor-icon", + "dpi", + "js-sys", + "libc", + "ndk 0.9.0", + "objc2 0.5.2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "orbclient", + "percent-encoding", + "pin-project", + "raw-window-handle", + "redox_syscall 0.4.1", + "rustix 0.38.44", + "smol_str", + "tracing", + "unicode-segmentation", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "web-time", + "windows-sys 0.52.0", + "x11-dl", + "x11rb", + "xkbcommon-dl", +] + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.12.1", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "x11rb" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" +dependencies = [ + "as-raw-xcb-connection", + "gethostname", + "libc", + "libloading", + "once_cell", + "rustix 1.1.4", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" + +[[package]] +name = "xkbcommon-dl" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" +dependencies = [ + "bitflags 2.12.1", + "dlib", + "log", + "once_cell", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + +[[package]] +name = "xml-rs" +version = "0.8.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" + +[[package]] +name = "yazi" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01738255b5a16e78bbb83e7fbba0a1e7dd506905cfc53f4622d89015a03fbb5" + +[[package]] +name = "zeno" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524" + +[[package]] +name = "zerocopy" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/apps/game/Cargo.toml b/apps/game/Cargo.toml new file mode 100644 index 0000000..06276b0 --- /dev/null +++ b/apps/game/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "void-nav" +version = "0.1.0" +edition = "2021" +description = "VOID::NAV — Space exploration RPG built with Bevy" + +[dependencies] +bevy = "0.16" +rand = "0.8" + +# Optimize dependencies in dev for better FPS +[profile.dev.package."*"] +opt-level = 3 diff --git a/apps/game/index.html b/apps/game/index.html deleted file mode 100644 index 7ce33fe..0000000 --- a/apps/game/index.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - VOID::NAV Game - - -
- - - diff --git a/apps/game/package.json b/apps/game/package.json deleted file mode 100644 index 18cb6dd..0000000 --- a/apps/game/package.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "@void-nav/game", - "private": true, - "version": "0.1.0", - "type": "module", - "scripts": { - "dev": "vite --host 0.0.0.0", - "build": "tsc --noEmit && vite build", - "check": "tsc --noEmit", - "preview": "vite preview --host 0.0.0.0" - }, - "dependencies": { - "@react-three/drei": "^9.122.0", - "@react-three/fiber": "^8.17.10", - "@void-nav/ui": "workspace:*", - "react": "^18.3.1", - "react-dom": "^18.3.1", - "spacetimedb": "^2.3.0", - "three": "^0.160.0" - }, - "devDependencies": { - "@tailwindcss/vite": "^4.3.0", - "@types/react": "^18.3.23", - "@types/react-dom": "^18.3.7", - "@types/three": "^0.160.0", - "tailwindcss": "^4.3.0", - "typescript": "^5.8.3", - "vite": "^7.0.0" - } -} diff --git a/apps/game/src/GameShell.tsx b/apps/game/src/GameShell.tsx deleted file mode 100644 index e0c7195..0000000 --- a/apps/game/src/GameShell.tsx +++ /dev/null @@ -1,139 +0,0 @@ -import { useMemo, useState } from "react"; -import { Panel } from "@void-nav/ui"; -import { GameSpaceScene } from "./scene/GameSpaceScene"; -import { CommandRail } from "./ui/CommandRail"; -import { CargoPanel } from "./ui/CargoPanel"; -import { ConnectionPanel } from "./ui/ConnectionPanel"; -import { EventFeed } from "./ui/EventFeed"; -import { MiniStarMap } from "./ui/MiniStarMap"; -import { NpcMarketPanel } from "./ui/NpcMarketPanel"; -import { ShipStatusPanel } from "./ui/ShipStatusPanel"; -import { TargetPanel } from "./ui/TargetPanel"; -import { useKeyboardShortcuts } from "./ui/useKeyboardShortcuts"; -import { WalletPanel } from "./ui/WalletPanel"; -import { useGameSession } from "./spacetime/useGameSession"; -import { useSpacetimeConnection } from "./spacetime/useSpacetimeConnection"; - -const defaultPilotName = "New Pilot"; - -export function GameShell() { - const [displayName, setDisplayName] = useState(defaultPilotName); - const [manualReducerStatus, setManualReducerStatus] = useState<{ message: string; isError: boolean }>(); - const connectionState = useSpacetimeConnection(displayName); - const reducerStatus = manualReducerStatus ?? connectionState.reducerStatus; - const session = useGameSession( - connectionState.connection, - connectionState.revision, - (message, isError = false) => setManualReducerStatus({ message, isError }), - connectionState.identity, - ); - - const connectionLabel = useMemo(() => { - if (connectionState.status === "error") return "Disconnected"; - if (connectionState.status === "connected") return "Connected"; - return connectionState.status.charAt(0).toUpperCase() + connectionState.status.slice(1); - }, [connectionState.status]); - - const selectedPoi = session.pois.find((poi) => poi.poiId === session.ship?.selectedPoiId); - const pilotName = session.player?.displayName ?? displayName; - - const keyboardActions = useMemo( - () => ({ - onUndock: session.actions.undock, - onStartApproach: session.actions.startApproach, - onDock: session.actions.dock, - onStartMining: session.actions.startMining, - onSelectTarget: (index: number) => { - const poi = session.pois[index]; - if (poi) session.actions.selectTarget(poi.poiId); - }, - }), - [session.actions, session.pois], - ); - - useKeyboardShortcuts(session.ship, session.pois, session.operation, keyboardActions); - - return ( -
-
- -
- -
- - -
- - -
- -
- session.actions.sellOreToNpcMarket(item.itemId, item.quantity)} - /> -
-
- ); -} - -function InfoRow({ label, value }: { label: string; value: string }) { - return ( -
-
{label}
-
{value}
-
- ); -} diff --git a/apps/game/src/main.rs b/apps/game/src/main.rs new file mode 100644 index 0000000..d7544a9 --- /dev/null +++ b/apps/game/src/main.rs @@ -0,0 +1,176 @@ +use bevy::prelude::*; + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .insert_resource(ClearColor(Color::srgb(0.02, 0.02, 0.06))) + .init_state::() + .add_systems(Startup, spawn_camera) + .add_systems(OnEnter(AppState::MainMenu), setup_main_menu) + .add_systems(OnExit(AppState::MainMenu), despawn_main_menu) + .add_systems(Update, main_menu_buttons) + .run(); +} + +// ── States ────────────────────────────────────────────────────────────────── + +#[derive(Clone, Eq, PartialEq, Debug, Hash, States, Default)] +enum AppState { + #[default] + MainMenu, + InGame, + Options, +} + +// ── Markers ───────────────────────────────────────────────────────────────── + +#[derive(Component)] +struct MainMenuUi; + +#[derive(Component)] +enum MenuButton { + Start, + Options, + Exit, +} + +// ── Camera ────────────────────────────────────────────────────────────────── + +fn spawn_camera(mut commands: Commands) { + commands.spawn(Camera2d); +} + +// ── Main Menu ─────────────────────────────────────────────────────────────── + +fn setup_main_menu(mut commands: Commands) { + let button_style = Style { + width: Val::Px(280.0), + height: Val::Px(64.0), + justify_content: JustifyContent::Center, + align_items: AlignItems::Center, + ..default() + }; + + let button_text_font = TextFont { + font_size: 28.0, + ..default() + }; + + commands.spawn(( + Node { + width: Val::Percent(100.0), + height: Val::Percent(100.0), + display: Display::Flex, + flex_direction: FlexDirection::Column, + justify_content: JustifyContent::Center, + align_items: AlignItems::Center, + row_gap: Val::Px(24.0), + ..default() + }, + BackgroundColor(Color::srgb(0.02, 0.02, 0.06)), + MainMenuUi, + )).with_children(|parent| { + // Title + parent.spawn(( + Text::new("VOID::NAV"), + TextFont { + font_size: 72.0, + ..default() + }, + TextColor(Color::srgb(0.7, 0.85, 1.0)), + Node { + margin: UiRect::bottom(Val::Px(48.0)), + ..default() + }, + )); + + // Subtitle + parent.spawn(( + Text::new("A space exploration RPG"), + TextFont { + font_size: 18.0, + ..default() + }, + TextColor(Color::srgb(0.4, 0.5, 0.6)), + Node { + margin: UiRect::bottom(Val::Px(32.0)), + ..default() + }, + )); + + // Start Game button + spawn_menu_button( + &mut parent.spawn_empty(), + "Start Game", + MenuButton::Start, + &button_style, + &button_text_font, + ); + + // Options button + spawn_menu_button( + &mut parent.spawn_empty(), + "Options", + MenuButton::Options, + &button_style, + &button_text_font, + ); + + // Exit button + spawn_menu_button( + &mut parent.spawn_empty(), + "Exit", + MenuButton::Exit, + &button_style, + &button_text_font, + ); + }); +} + +fn spawn_menu_button( + cmd: &mut EntityCommands, + label: &str, + marker: MenuButton, + style: &Style, + text_font: &TextFont, +) { + cmd.insert(( + Button, + style.clone(), + BackgroundColor(Color::srgb(0.08, 0.1, 0.18)), + BorderColor(Color::srgb(0.3, 0.45, 0.7)), + BorderRadius::all(Val::Px(8.0)), + marker, + )) + .with_children(|btn| { + btn.spawn(( + Text::new(label), + text_font.clone(), + TextColor(Color::srgb(0.75, 0.85, 1.0)), + )); + }); +} + +fn despawn_main_menu(mut commands: Commands, query: Query>) { + for entity in &query { + commands.entity(entity).despawn_recursive(); + } +} + +// ── Button Interaction ────────────────────────────────────────────────────── + +fn main_menu_buttons( + mut next_state: ResMut>, + mut exit: EventWriter, + interaction_query: Query<(&Interaction, &MenuButton), Changed>, +) { + for (interaction, button) in &interaction_query { + if *interaction == Interaction::Pressed { + match button { + MenuButton::Start => next_state.set(AppState::InGame), + MenuButton::Options => next_state.set(AppState::Options), + MenuButton::Exit => exit.send(AppExit::Success), + } + } + } +} diff --git a/apps/game/src/main.tsx b/apps/game/src/main.tsx deleted file mode 100644 index 36cd823..0000000 --- a/apps/game/src/main.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from "react"; -import ReactDOM from "react-dom/client"; -import { GameShell } from "./GameShell"; -import "./styles/tailwind.css"; - -ReactDOM.createRoot(document.getElementById("root")!).render( - - - , -); diff --git a/apps/game/src/module_bindings/cargo_item_table.ts b/apps/game/src/module_bindings/cargo_item_table.ts deleted file mode 100644 index d86d4b3..0000000 --- a/apps/game/src/module_bindings/cargo_item_table.ts +++ /dev/null @@ -1,22 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -/* eslint-disable */ -/* tslint:disable */ -import { - TypeBuilder as __TypeBuilder, - t as __t, - type AlgebraicTypeType as __AlgebraicTypeType, - type Infer as __Infer, -} from "spacetimedb"; - -export default __t.row({ - cargoItemId: __t.u64().primaryKey().name("cargo_item_id"), - ownerIdentity: __t.identity().name("owner_identity"), - shipId: __t.u64().name("ship_id"), - itemId: __t.string().name("item_id"), - itemName: __t.string().name("item_name"), - category: __t.string(), - quantity: __t.u64(), - unitPrice: __t.u64().name("unit_price"), -}); diff --git a/apps/game/src/module_bindings/complete_approach_reducer.ts b/apps/game/src/module_bindings/complete_approach_reducer.ts deleted file mode 100644 index e18fbc0..0000000 --- a/apps/game/src/module_bindings/complete_approach_reducer.ts +++ /dev/null @@ -1,13 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -/* eslint-disable */ -/* tslint:disable */ -import { - TypeBuilder as __TypeBuilder, - t as __t, - type AlgebraicTypeType as __AlgebraicTypeType, - type Infer as __Infer, -} from "spacetimedb"; - -export default {}; diff --git a/apps/game/src/module_bindings/complete_mining_cycle_reducer.ts b/apps/game/src/module_bindings/complete_mining_cycle_reducer.ts deleted file mode 100644 index e18fbc0..0000000 --- a/apps/game/src/module_bindings/complete_mining_cycle_reducer.ts +++ /dev/null @@ -1,13 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -/* eslint-disable */ -/* tslint:disable */ -import { - TypeBuilder as __TypeBuilder, - t as __t, - type AlgebraicTypeType as __AlgebraicTypeType, - type Infer as __Infer, -} from "spacetimedb"; - -export default {}; diff --git a/apps/game/src/module_bindings/connect_player_reducer.ts b/apps/game/src/module_bindings/connect_player_reducer.ts deleted file mode 100644 index 547493e..0000000 --- a/apps/game/src/module_bindings/connect_player_reducer.ts +++ /dev/null @@ -1,15 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -/* eslint-disable */ -/* tslint:disable */ -import { - TypeBuilder as __TypeBuilder, - t as __t, - type AlgebraicTypeType as __AlgebraicTypeType, - type Infer as __Infer, -} from "spacetimedb"; - -export default { - displayName: __t.string(), -}; diff --git a/apps/game/src/module_bindings/dock_reducer.ts b/apps/game/src/module_bindings/dock_reducer.ts deleted file mode 100644 index e18fbc0..0000000 --- a/apps/game/src/module_bindings/dock_reducer.ts +++ /dev/null @@ -1,13 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -/* eslint-disable */ -/* tslint:disable */ -import { - TypeBuilder as __TypeBuilder, - t as __t, - type AlgebraicTypeType as __AlgebraicTypeType, - type Infer as __Infer, -} from "spacetimedb"; - -export default {}; diff --git a/apps/game/src/module_bindings/index.ts b/apps/game/src/module_bindings/index.ts deleted file mode 100644 index d977f8f..0000000 --- a/apps/game/src/module_bindings/index.ts +++ /dev/null @@ -1,258 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -// This was generated using spacetimedb cli version 2.3.0 (commit aa73d1c35b4b346b98eeba10a3d756b4ae72162f). - -/* eslint-disable */ -/* tslint:disable */ -import { - DbConnectionBuilder as __DbConnectionBuilder, - DbConnectionImpl as __DbConnectionImpl, - SubscriptionBuilderImpl as __SubscriptionBuilderImpl, - TypeBuilder as __TypeBuilder, - Uuid as __Uuid, - convertToAccessorMap as __convertToAccessorMap, - makeQueryBuilder as __makeQueryBuilder, - procedureSchema as __procedureSchema, - procedures as __procedures, - reducerSchema as __reducerSchema, - reducers as __reducers, - schema as __schema, - t as __t, - table as __table, - type AlgebraicTypeType as __AlgebraicTypeType, - type DbConnectionConfig as __DbConnectionConfig, - type ErrorContextInterface as __ErrorContextInterface, - type Event as __Event, - type EventContextInterface as __EventContextInterface, - type Infer as __Infer, - type QueryBuilder as __QueryBuilder, - type ReducerEventContextInterface as __ReducerEventContextInterface, - type RemoteModule as __RemoteModule, - type SubscriptionEventContextInterface as __SubscriptionEventContextInterface, - type SubscriptionHandleImpl as __SubscriptionHandleImpl, -} from "spacetimedb"; - -// Import all reducer arg schemas -import CompleteApproachReducer from "./complete_approach_reducer"; -import CompleteMiningCycleReducer from "./complete_mining_cycle_reducer"; -import ConnectPlayerReducer from "./connect_player_reducer"; -import DockReducer from "./dock_reducer"; -import PingReducer from "./ping_reducer"; -import RenamePlayerReducer from "./rename_player_reducer"; -import SeedWorldReducer from "./seed_world_reducer"; -import SelectTargetReducer from "./select_target_reducer"; -import SellOreToNpcMarketReducer from "./sell_ore_to_npc_market_reducer"; -import StartApproachReducer from "./start_approach_reducer"; -import StartMiningReducer from "./start_mining_reducer"; -import UndockReducer from "./undock_reducer"; - -// Import all procedure arg schemas - -// Import all table schema definitions -import CargoItemRow from "./cargo_item_table"; -import PlayerRow from "./player_table"; -import PointOfInterestRow from "./point_of_interest_table"; -import ServerEventRow from "./server_event_table"; -import ShipRow from "./ship_table"; -import ShipOperationRow from "./ship_operation_table"; -import StationRow from "./station_table"; -import SystemRow from "./system_table"; -import WalletRow from "./wallet_table"; - -/** Type-only namespace exports for generated type groups. */ - -/** The schema information for all tables in this module. This is defined the same was as the tables would have been defined in the server. */ -const tablesSchema = __schema({ - cargo_item: __table({ - name: 'cargo_item', - indexes: [ - { accessor: 'cargo_item_id', name: 'cargo_item_cargo_item_id_idx_btree', algorithm: 'btree', columns: [ - 'cargoItemId', - ] }, - { accessor: 'owner_identity', name: 'cargo_item_owner_identity_idx_btree', algorithm: 'btree', columns: [ - 'ownerIdentity', - ] }, - { accessor: 'ship_id', name: 'cargo_item_ship_id_idx_btree', algorithm: 'btree', columns: [ - 'shipId', - ] }, - ], - constraints: [ - { name: 'cargo_item_cargo_item_id_key', constraint: 'unique', columns: ['cargoItemId'] }, - ], - }, CargoItemRow), - player: __table({ - name: 'player', - indexes: [ - { accessor: 'identity', name: 'player_identity_idx_btree', algorithm: 'btree', columns: [ - 'identity', - ] }, - ], - constraints: [ - { name: 'player_identity_key', constraint: 'unique', columns: ['identity'] }, - ], - }, PlayerRow), - point_of_interest: __table({ - name: 'point_of_interest', - indexes: [ - { accessor: 'poi_id', name: 'point_of_interest_poi_id_idx_btree', algorithm: 'btree', columns: [ - 'poiId', - ] }, - { accessor: 'system_id', name: 'point_of_interest_system_id_idx_btree', algorithm: 'btree', columns: [ - 'systemId', - ] }, - ], - constraints: [ - { name: 'point_of_interest_poi_id_key', constraint: 'unique', columns: ['poiId'] }, - ], - }, PointOfInterestRow), - server_event: __table({ - name: 'server_event', - indexes: [ - { accessor: 'actor_identity', name: 'server_event_actor_identity_idx_btree', algorithm: 'btree', columns: [ - 'actorIdentity', - ] }, - { accessor: 'event_id', name: 'server_event_event_id_idx_btree', algorithm: 'btree', columns: [ - 'eventId', - ] }, - ], - constraints: [ - { name: 'server_event_event_id_key', constraint: 'unique', columns: ['eventId'] }, - ], - }, ServerEventRow), - ship: __table({ - name: 'ship', - indexes: [ - { accessor: 'owner_identity', name: 'ship_owner_identity_idx_btree', algorithm: 'btree', columns: [ - 'ownerIdentity', - ] }, - { accessor: 'ship_id', name: 'ship_ship_id_idx_btree', algorithm: 'btree', columns: [ - 'shipId', - ] }, - ], - constraints: [ - { name: 'ship_ship_id_key', constraint: 'unique', columns: ['shipId'] }, - ], - }, ShipRow), - ship_operation: __table({ - name: 'ship_operation', - indexes: [ - { accessor: 'ship_id', name: 'ship_operation_ship_id_idx_btree', algorithm: 'btree', columns: [ - 'shipId', - ] }, - ], - constraints: [ - { name: 'ship_operation_ship_id_key', constraint: 'unique', columns: ['shipId'] }, - ], - }, ShipOperationRow), - station: __table({ - name: 'station', - indexes: [ - { accessor: 'station_id', name: 'station_station_id_idx_btree', algorithm: 'btree', columns: [ - 'stationId', - ] }, - { accessor: 'system_id', name: 'station_system_id_idx_btree', algorithm: 'btree', columns: [ - 'systemId', - ] }, - ], - constraints: [ - { name: 'station_station_id_key', constraint: 'unique', columns: ['stationId'] }, - ], - }, StationRow), - system: __table({ - name: 'system', - indexes: [ - { accessor: 'system_id', name: 'system_system_id_idx_btree', algorithm: 'btree', columns: [ - 'systemId', - ] }, - ], - constraints: [ - { name: 'system_system_id_key', constraint: 'unique', columns: ['systemId'] }, - ], - }, SystemRow), - wallet: __table({ - name: 'wallet', - indexes: [ - { accessor: 'owner_identity', name: 'wallet_owner_identity_idx_btree', algorithm: 'btree', columns: [ - 'ownerIdentity', - ] }, - ], - constraints: [ - { name: 'wallet_owner_identity_key', constraint: 'unique', columns: ['ownerIdentity'] }, - ], - }, WalletRow), -}); - -/** The schema information for all reducers in this module. This is defined the same way as the reducers would have been defined in the server, except the body of the reducer is omitted in code generation. */ -const reducersSchema = __reducers( - __reducerSchema("complete_approach", CompleteApproachReducer), - __reducerSchema("complete_mining_cycle", CompleteMiningCycleReducer), - __reducerSchema("connect_player", ConnectPlayerReducer), - __reducerSchema("dock", DockReducer), - __reducerSchema("ping", PingReducer), - __reducerSchema("rename_player", RenamePlayerReducer), - __reducerSchema("seed_world", SeedWorldReducer), - __reducerSchema("select_target", SelectTargetReducer), - __reducerSchema("sell_ore_to_npc_market", SellOreToNpcMarketReducer), - __reducerSchema("start_approach", StartApproachReducer), - __reducerSchema("start_mining", StartMiningReducer), - __reducerSchema("undock", UndockReducer), -); - -/** The schema information for all procedures in this module. This is defined the same way as the procedures would have been defined in the server. */ -const proceduresSchema = __procedures( -); - -/** The remote SpacetimeDB module schema, both runtime and type information. */ -const REMOTE_MODULE = { - versionInfo: { - cliVersion: "2.3.0" as const, - }, - tables: tablesSchema.schemaType.tables, - reducers: reducersSchema.reducersType.reducers, - ...proceduresSchema, -} satisfies __RemoteModule< - typeof tablesSchema.schemaType, - typeof reducersSchema.reducersType, - typeof proceduresSchema ->; - -/** The tables available in this remote SpacetimeDB module. Each table reference doubles as a query builder. */ -export const tables: __QueryBuilder = __makeQueryBuilder(tablesSchema.schemaType); - -/** The reducers available in this remote SpacetimeDB module. */ -export const reducers = __convertToAccessorMap(reducersSchema.reducersType.reducers); - -/** The procedures available in this remote SpacetimeDB module. */ -export const procedures = __convertToAccessorMap(proceduresSchema.procedures); - -/** The context type returned in callbacks for all possible events. */ -export type EventContext = __EventContextInterface; -/** The context type returned in callbacks for reducer events. */ -export type ReducerEventContext = __ReducerEventContextInterface; -/** The context type returned in callbacks for subscription events. */ -export type SubscriptionEventContext = __SubscriptionEventContextInterface; -/** The context type returned in callbacks for error events. */ -export type ErrorContext = __ErrorContextInterface; -/** The subscription handle type to manage active subscriptions created from a {@link SubscriptionBuilder}. */ -export type SubscriptionHandle = __SubscriptionHandleImpl; - -/** Builder class to configure a new subscription to the remote SpacetimeDB instance. */ -export class SubscriptionBuilder extends __SubscriptionBuilderImpl {} - -/** Builder class to configure a new database connection to the remote SpacetimeDB instance. */ -export class DbConnectionBuilder extends __DbConnectionBuilder {} - -/** The typed database connection to manage connections to the remote SpacetimeDB instance. This class has type information specific to the generated module. */ -export class DbConnection extends __DbConnectionImpl { - /** Creates a new {@link DbConnectionBuilder} to configure and connect to the remote SpacetimeDB instance. */ - static builder = (): DbConnectionBuilder => { - return new DbConnectionBuilder(REMOTE_MODULE, (config: __DbConnectionConfig) => new DbConnection(config)); - }; - - /** Creates a new {@link SubscriptionBuilder} to configure a subscription to the remote SpacetimeDB instance. */ - override subscriptionBuilder = (): SubscriptionBuilder => { - return new SubscriptionBuilder(this); - }; -} - diff --git a/apps/game/src/module_bindings/ping_reducer.ts b/apps/game/src/module_bindings/ping_reducer.ts deleted file mode 100644 index e18fbc0..0000000 --- a/apps/game/src/module_bindings/ping_reducer.ts +++ /dev/null @@ -1,13 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -/* eslint-disable */ -/* tslint:disable */ -import { - TypeBuilder as __TypeBuilder, - t as __t, - type AlgebraicTypeType as __AlgebraicTypeType, - type Infer as __Infer, -} from "spacetimedb"; - -export default {}; diff --git a/apps/game/src/module_bindings/player_table.ts b/apps/game/src/module_bindings/player_table.ts deleted file mode 100644 index fbf45fe..0000000 --- a/apps/game/src/module_bindings/player_table.ts +++ /dev/null @@ -1,20 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -/* eslint-disable */ -/* tslint:disable */ -import { - TypeBuilder as __TypeBuilder, - t as __t, - type AlgebraicTypeType as __AlgebraicTypeType, - type Infer as __Infer, -} from "spacetimedb"; - -export default __t.row({ - identity: __t.identity().primaryKey(), - displayName: __t.string().name("display_name"), - createdAt: __t.timestamp().name("created_at"), - updatedAt: __t.timestamp().name("updated_at"), - lastConnectedAt: __t.timestamp().name("last_connected_at"), - isConnected: __t.bool().name("is_connected"), -}); diff --git a/apps/game/src/module_bindings/point_of_interest_table.ts b/apps/game/src/module_bindings/point_of_interest_table.ts deleted file mode 100644 index b8f97a6..0000000 --- a/apps/game/src/module_bindings/point_of_interest_table.ts +++ /dev/null @@ -1,22 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -/* eslint-disable */ -/* tslint:disable */ -import { - TypeBuilder as __TypeBuilder, - t as __t, - type AlgebraicTypeType as __AlgebraicTypeType, - type Infer as __Infer, -} from "spacetimedb"; - -export default __t.row({ - poiId: __t.string().primaryKey().name("poi_id"), - systemId: __t.string().name("system_id"), - name: __t.string(), - poiType: __t.string().name("poi_type"), - x: __t.f32(), - y: __t.f32(), - z: __t.f32(), - stationId: __t.string().name("station_id"), -}); diff --git a/apps/game/src/module_bindings/rename_player_reducer.ts b/apps/game/src/module_bindings/rename_player_reducer.ts deleted file mode 100644 index 547493e..0000000 --- a/apps/game/src/module_bindings/rename_player_reducer.ts +++ /dev/null @@ -1,15 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -/* eslint-disable */ -/* tslint:disable */ -import { - TypeBuilder as __TypeBuilder, - t as __t, - type AlgebraicTypeType as __AlgebraicTypeType, - type Infer as __Infer, -} from "spacetimedb"; - -export default { - displayName: __t.string(), -}; diff --git a/apps/game/src/module_bindings/seed_world_reducer.ts b/apps/game/src/module_bindings/seed_world_reducer.ts deleted file mode 100644 index e18fbc0..0000000 --- a/apps/game/src/module_bindings/seed_world_reducer.ts +++ /dev/null @@ -1,13 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -/* eslint-disable */ -/* tslint:disable */ -import { - TypeBuilder as __TypeBuilder, - t as __t, - type AlgebraicTypeType as __AlgebraicTypeType, - type Infer as __Infer, -} from "spacetimedb"; - -export default {}; diff --git a/apps/game/src/module_bindings/select_target_reducer.ts b/apps/game/src/module_bindings/select_target_reducer.ts deleted file mode 100644 index 7baac0d..0000000 --- a/apps/game/src/module_bindings/select_target_reducer.ts +++ /dev/null @@ -1,15 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -/* eslint-disable */ -/* tslint:disable */ -import { - TypeBuilder as __TypeBuilder, - t as __t, - type AlgebraicTypeType as __AlgebraicTypeType, - type Infer as __Infer, -} from "spacetimedb"; - -export default { - poiId: __t.string(), -}; diff --git a/apps/game/src/module_bindings/sell_ore_to_npc_market_reducer.ts b/apps/game/src/module_bindings/sell_ore_to_npc_market_reducer.ts deleted file mode 100644 index ead95ca..0000000 --- a/apps/game/src/module_bindings/sell_ore_to_npc_market_reducer.ts +++ /dev/null @@ -1,16 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -/* eslint-disable */ -/* tslint:disable */ -import { - TypeBuilder as __TypeBuilder, - t as __t, - type AlgebraicTypeType as __AlgebraicTypeType, - type Infer as __Infer, -} from "spacetimedb"; - -export default { - itemId: __t.string(), - quantity: __t.u64(), -}; diff --git a/apps/game/src/module_bindings/server_event_table.ts b/apps/game/src/module_bindings/server_event_table.ts deleted file mode 100644 index 83d1952..0000000 --- a/apps/game/src/module_bindings/server_event_table.ts +++ /dev/null @@ -1,19 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -/* eslint-disable */ -/* tslint:disable */ -import { - TypeBuilder as __TypeBuilder, - t as __t, - type AlgebraicTypeType as __AlgebraicTypeType, - type Infer as __Infer, -} from "spacetimedb"; - -export default __t.row({ - eventId: __t.u64().primaryKey().name("event_id"), - at: __t.timestamp(), - actorIdentity: __t.identity().name("actor_identity"), - eventType: __t.string().name("event_type"), - message: __t.string(), -}); diff --git a/apps/game/src/module_bindings/ship_operation_table.ts b/apps/game/src/module_bindings/ship_operation_table.ts deleted file mode 100644 index 9d99249..0000000 --- a/apps/game/src/module_bindings/ship_operation_table.ts +++ /dev/null @@ -1,20 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -/* eslint-disable */ -/* tslint:disable */ -import { - TypeBuilder as __TypeBuilder, - t as __t, - type AlgebraicTypeType as __AlgebraicTypeType, - type Infer as __Infer, -} from "spacetimedb"; - -export default __t.row({ - shipId: __t.u64().primaryKey().name("ship_id"), - operationType: __t.string().name("operation_type"), - targetPoiId: __t.string().name("target_poi_id"), - startedAt: __t.timestamp().name("started_at"), - durationMs: __t.u64().name("duration_ms"), - completesAtMs: __t.u64().name("completes_at_ms"), -}); diff --git a/apps/game/src/module_bindings/ship_table.ts b/apps/game/src/module_bindings/ship_table.ts deleted file mode 100644 index b084418..0000000 --- a/apps/game/src/module_bindings/ship_table.ts +++ /dev/null @@ -1,27 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -/* eslint-disable */ -/* tslint:disable */ -import { - TypeBuilder as __TypeBuilder, - t as __t, - type AlgebraicTypeType as __AlgebraicTypeType, - type Infer as __Infer, -} from "spacetimedb"; - -export default __t.row({ - shipId: __t.u64().primaryKey().name("ship_id"), - ownerIdentity: __t.identity().name("owner_identity"), - shipName: __t.string().name("ship_name"), - hullType: __t.string().name("hull_type"), - currentSystemId: __t.string().name("current_system_id"), - dockedStationId: __t.string().name("docked_station_id"), - currentPoiId: __t.string().name("current_poi_id"), - selectedPoiId: __t.string().name("selected_poi_id"), - flightMode: __t.string().name("flight_mode"), - x: __t.f32(), - y: __t.f32(), - z: __t.f32(), - cargoCapacity: __t.u64().name("cargo_capacity"), -}); diff --git a/apps/game/src/module_bindings/start_approach_reducer.ts b/apps/game/src/module_bindings/start_approach_reducer.ts deleted file mode 100644 index e18fbc0..0000000 --- a/apps/game/src/module_bindings/start_approach_reducer.ts +++ /dev/null @@ -1,13 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -/* eslint-disable */ -/* tslint:disable */ -import { - TypeBuilder as __TypeBuilder, - t as __t, - type AlgebraicTypeType as __AlgebraicTypeType, - type Infer as __Infer, -} from "spacetimedb"; - -export default {}; diff --git a/apps/game/src/module_bindings/start_mining_reducer.ts b/apps/game/src/module_bindings/start_mining_reducer.ts deleted file mode 100644 index e18fbc0..0000000 --- a/apps/game/src/module_bindings/start_mining_reducer.ts +++ /dev/null @@ -1,13 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -/* eslint-disable */ -/* tslint:disable */ -import { - TypeBuilder as __TypeBuilder, - t as __t, - type AlgebraicTypeType as __AlgebraicTypeType, - type Infer as __Infer, -} from "spacetimedb"; - -export default {}; diff --git a/apps/game/src/module_bindings/station_table.ts b/apps/game/src/module_bindings/station_table.ts deleted file mode 100644 index 494e728..0000000 --- a/apps/game/src/module_bindings/station_table.ts +++ /dev/null @@ -1,17 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -/* eslint-disable */ -/* tslint:disable */ -import { - TypeBuilder as __TypeBuilder, - t as __t, - type AlgebraicTypeType as __AlgebraicTypeType, - type Infer as __Infer, -} from "spacetimedb"; - -export default __t.row({ - stationId: __t.string().primaryKey().name("station_id"), - systemId: __t.string().name("system_id"), - name: __t.string(), -}); diff --git a/apps/game/src/module_bindings/system_table.ts b/apps/game/src/module_bindings/system_table.ts deleted file mode 100644 index df55936..0000000 --- a/apps/game/src/module_bindings/system_table.ts +++ /dev/null @@ -1,17 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -/* eslint-disable */ -/* tslint:disable */ -import { - TypeBuilder as __TypeBuilder, - t as __t, - type AlgebraicTypeType as __AlgebraicTypeType, - type Infer as __Infer, -} from "spacetimedb"; - -export default __t.row({ - systemId: __t.string().primaryKey().name("system_id"), - name: __t.string(), - securityLevel: __t.f32().name("security_level"), -}); diff --git a/apps/game/src/module_bindings/types.ts b/apps/game/src/module_bindings/types.ts deleted file mode 100644 index 3aebeac..0000000 --- a/apps/game/src/module_bindings/types.ts +++ /dev/null @@ -1,103 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -/* eslint-disable */ -/* tslint:disable */ -import { - TypeBuilder as __TypeBuilder, - t as __t, - type AlgebraicTypeType as __AlgebraicTypeType, - type Infer as __Infer, -} from "spacetimedb"; - -export const CargoItem = __t.object("CargoItem", { - cargoItemId: __t.u64(), - ownerIdentity: __t.identity(), - shipId: __t.u64(), - itemId: __t.string(), - itemName: __t.string(), - category: __t.string(), - quantity: __t.u64(), - unitPrice: __t.u64(), -}); -export type CargoItem = __Infer; - -export const Player = __t.object("Player", { - identity: __t.identity(), - displayName: __t.string(), - createdAt: __t.timestamp(), - updatedAt: __t.timestamp(), - lastConnectedAt: __t.timestamp(), - isConnected: __t.bool(), -}); -export type Player = __Infer; - -export const PointOfInterest = __t.object("PointOfInterest", { - poiId: __t.string(), - systemId: __t.string(), - name: __t.string(), - poiType: __t.string(), - x: __t.f32(), - y: __t.f32(), - z: __t.f32(), - stationId: __t.string(), -}); -export type PointOfInterest = __Infer; - -export const ServerEvent = __t.object("ServerEvent", { - eventId: __t.u64(), - at: __t.timestamp(), - actorIdentity: __t.identity(), - eventType: __t.string(), - message: __t.string(), -}); -export type ServerEvent = __Infer; - -export const Ship = __t.object("Ship", { - shipId: __t.u64(), - ownerIdentity: __t.identity(), - shipName: __t.string(), - hullType: __t.string(), - currentSystemId: __t.string(), - dockedStationId: __t.string(), - currentPoiId: __t.string(), - selectedPoiId: __t.string(), - flightMode: __t.string(), - x: __t.f32(), - y: __t.f32(), - z: __t.f32(), - cargoCapacity: __t.u64(), -}); -export type Ship = __Infer; - -export const ShipOperation = __t.object("ShipOperation", { - shipId: __t.u64(), - operationType: __t.string(), - targetPoiId: __t.string(), - startedAt: __t.timestamp(), - durationMs: __t.u64(), - completesAtMs: __t.u64(), -}); -export type ShipOperation = __Infer; - -export const Station = __t.object("Station", { - stationId: __t.string(), - systemId: __t.string(), - name: __t.string(), -}); -export type Station = __Infer; - -export const System = __t.object("System", { - systemId: __t.string(), - name: __t.string(), - securityLevel: __t.f32(), -}); -export type System = __Infer; - -export const Wallet = __t.object("Wallet", { - ownerIdentity: __t.identity(), - isk: __t.u64(), - updatedAt: __t.timestamp(), -}); -export type Wallet = __Infer; - diff --git a/apps/game/src/module_bindings/types/procedures.ts b/apps/game/src/module_bindings/types/procedures.ts deleted file mode 100644 index d5ac825..0000000 --- a/apps/game/src/module_bindings/types/procedures.ts +++ /dev/null @@ -1,10 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -/* eslint-disable */ -/* tslint:disable */ -import { type Infer as __Infer } from "spacetimedb"; - -// Import all procedure arg schemas - - diff --git a/apps/game/src/module_bindings/types/reducers.ts b/apps/game/src/module_bindings/types/reducers.ts deleted file mode 100644 index 4969aa6..0000000 --- a/apps/game/src/module_bindings/types/reducers.ts +++ /dev/null @@ -1,34 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -/* eslint-disable */ -/* tslint:disable */ -import { type Infer as __Infer } from "spacetimedb"; - -// Import all reducer arg schemas -import CompleteApproachReducer from "../complete_approach_reducer"; -import CompleteMiningCycleReducer from "../complete_mining_cycle_reducer"; -import ConnectPlayerReducer from "../connect_player_reducer"; -import DockReducer from "../dock_reducer"; -import PingReducer from "../ping_reducer"; -import RenamePlayerReducer from "../rename_player_reducer"; -import SeedWorldReducer from "../seed_world_reducer"; -import SelectTargetReducer from "../select_target_reducer"; -import SellOreToNpcMarketReducer from "../sell_ore_to_npc_market_reducer"; -import StartApproachReducer from "../start_approach_reducer"; -import StartMiningReducer from "../start_mining_reducer"; -import UndockReducer from "../undock_reducer"; - -export type CompleteApproachParams = __Infer; -export type CompleteMiningCycleParams = __Infer; -export type ConnectPlayerParams = __Infer; -export type DockParams = __Infer; -export type PingParams = __Infer; -export type RenamePlayerParams = __Infer; -export type SeedWorldParams = __Infer; -export type SelectTargetParams = __Infer; -export type SellOreToNpcMarketParams = __Infer; -export type StartApproachParams = __Infer; -export type StartMiningParams = __Infer; -export type UndockParams = __Infer; - diff --git a/apps/game/src/module_bindings/undock_reducer.ts b/apps/game/src/module_bindings/undock_reducer.ts deleted file mode 100644 index e18fbc0..0000000 --- a/apps/game/src/module_bindings/undock_reducer.ts +++ /dev/null @@ -1,13 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -/* eslint-disable */ -/* tslint:disable */ -import { - TypeBuilder as __TypeBuilder, - t as __t, - type AlgebraicTypeType as __AlgebraicTypeType, - type Infer as __Infer, -} from "spacetimedb"; - -export default {}; diff --git a/apps/game/src/module_bindings/wallet_table.ts b/apps/game/src/module_bindings/wallet_table.ts deleted file mode 100644 index d48d14b..0000000 --- a/apps/game/src/module_bindings/wallet_table.ts +++ /dev/null @@ -1,17 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -/* eslint-disable */ -/* tslint:disable */ -import { - TypeBuilder as __TypeBuilder, - t as __t, - type AlgebraicTypeType as __AlgebraicTypeType, - type Infer as __Infer, -} from "spacetimedb"; - -export default __t.row({ - ownerIdentity: __t.identity().primaryKey().name("owner_identity"), - isk: __t.u64(), - updatedAt: __t.timestamp().name("updated_at"), -}); diff --git a/apps/game/src/scene/AGENTS.md b/apps/game/src/scene/AGENTS.md deleted file mode 100644 index 45733fd..0000000 --- a/apps/game/src/scene/AGENTS.md +++ /dev/null @@ -1,24 +0,0 @@ -# apps/game/src/scene/ — 3D Rendering (React Three Fiber) - -The 3D viewport for the game client. Renders the player's ship, surrounding space environment, stations, asteroids, and visual effects. - -## Files - -| File | Purpose | -|------|---------| -| `SpaceCanvas.tsx` | R3F Canvas wrapper. Fixed camera at [8,9,44], FOV 48. No user camera controls yet. | -| `SpaceEnvironment.tsx` | Background (#040712), fog, ambient light, two point lights (cyan + amber), animated star field (3600 stars) | -| `GameSpaceScene.tsx` | Main orchestrator. Renders stations, asteroid clusters, ship. Manages approach/mining operations. Interpolates ship position. Draws approach line (dashed cyan) and mining line (solid amber). Auto-completes operations via 120ms polling interval. | -| `StationMesh.tsx` | Torus ring + cylinder core + crossbar. Clickable. Selection ring when targeted. | -| `AsteroidMesh.tsx` | Icosahedron with tumble animation. Clickable. Selection ring when targeted. | -| `ShipMesh.tsx` | Cone (hull) + box (wings) + sphere (engine glow). Engine glow changes by flight mode. Subtle bob + roll animation. | - -## Visual Gaps - -- No mining laser beam effect (flat Line only) -- No warp/travel animation -- No combat visuals (projectiles, shields, explosions) -- No NPC/other player ships -- No camera controls (orbit, zoom, pan) -- No sun/star light source -- No nebulae or system-specific skybox diff --git a/apps/game/src/scene/AsteroidMesh.tsx b/apps/game/src/scene/AsteroidMesh.tsx deleted file mode 100644 index 271dba8..0000000 --- a/apps/game/src/scene/AsteroidMesh.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { useFrame, type ThreeEvent } from "@react-three/fiber"; -import { useRef } from "react"; -import type { Group } from "three"; - -type Vector3Tuple = [number, number, number]; - -export function AsteroidMesh({ - position, - scale = 1, - selected, - onSelect, -}: { - position: Vector3Tuple; - scale?: number; - selected: boolean; - onSelect: () => void; -}) { - const groupRef = useRef(null); - - useFrame(({ clock }) => { - if (!groupRef.current) return; - groupRef.current.rotation.x = clock.elapsedTime * 0.08 * scale; - groupRef.current.rotation.y = clock.elapsedTime * 0.05; - }); - - function handleClick(event: ThreeEvent) { - event.stopPropagation(); - onSelect(); - } - - return ( - - - - - - {selected ? ( - - - - - ) : null} - - ); -} diff --git a/apps/game/src/scene/GameSpaceScene.tsx b/apps/game/src/scene/GameSpaceScene.tsx deleted file mode 100644 index c005c3b..0000000 --- a/apps/game/src/scene/GameSpaceScene.tsx +++ /dev/null @@ -1,175 +0,0 @@ -import { Line } from "@react-three/drei"; -import { useFrame } from "@react-three/fiber"; -import { useEffect, useMemo, useRef, useState } from "react"; -import type { Group } from "three"; -import type { PointOfInterest, Ship, ShipOperation } from "../module_bindings/types"; -import { AsteroidMesh } from "./AsteroidMesh"; -import { ShipMesh } from "./ShipMesh"; -import { SpaceCanvas } from "./SpaceCanvas"; -import { SpaceEnvironment } from "./SpaceEnvironment"; -import { StationMesh } from "./StationMesh"; - -type Vector3Tuple = [number, number, number]; - -type GameSpaceSceneProps = { - ship?: Ship; - pois: PointOfInterest[]; - selectedPoiId?: string; - operation?: ShipOperation; - onSelectPoi: (poiId: string) => void; - onCompleteApproach: () => void; - onCompleteMiningCycle: () => void; -}; - -export function GameSpaceScene(props: GameSpaceSceneProps) { - return ( - - - - ); -} - -function SceneContents({ - ship, - pois, - selectedPoiId, - operation, - onSelectPoi, - onCompleteApproach, - onCompleteMiningCycle, -}: GameSpaceSceneProps) { - const driftRef = useRef(null); - const completedOperationRef = useRef(); - const [nowMs, setNowMs] = useState(() => Date.now()); - const poiById = useMemo(() => new Map(pois.map((poi) => [poi.poiId, poi])), [pois]); - const targetPoi = operation ? poiById.get(operation.targetPoiId) : undefined; - const selectedPoi = selectedPoiId ? poiById.get(selectedPoiId) : undefined; - - useEffect(() => { - const timer = window.setInterval(() => setNowMs(Date.now()), 120); - return () => window.clearInterval(timer); - }, []); - - useEffect(() => { - const operationKey = operation ? `${operation.shipId}-${operation.operationType}-${operation.completesAtMs}` : undefined; - if (completedOperationRef.current !== operationKey) { - completedOperationRef.current = undefined; - } - - if (!operation || !operationKey || nowMs < Number(operation.completesAtMs) || completedOperationRef.current === operationKey) { - return; - } - - completedOperationRef.current = operationKey; - if (operation.operationType === "approach") onCompleteApproach(); - if (operation.operationType === "mining") onCompleteMiningCycle(); - }, [nowMs, onCompleteApproach, onCompleteMiningCycle, operation]); - - useFrame(({ clock, camera }) => { - if (driftRef.current) { - driftRef.current.rotation.y = Math.sin(clock.elapsedTime * 0.07) * 0.035; - driftRef.current.rotation.x = Math.sin(clock.elapsedTime * 0.05) * 0.018; - } - camera.position.x = 8 + Math.sin(clock.elapsedTime * 0.18) * 1.4; - camera.position.y = 9 + Math.cos(clock.elapsedTime * 0.14) * 0.7; - camera.lookAt(15, -1, -8); - }); - - const shipPosition = ship ? getShipPosition(ship, operation, targetPoi, nowMs) : ([0, 0, 0] as Vector3Tuple); - const stationPois = pois.filter((poi) => poi.poiType === "station"); - const beltPois = pois.filter((poi) => poi.poiType === "asteroid_belt"); - - return ( - <> - - - - {stationPois.map((poi) => ( - onSelectPoi(poi.poiId)} - /> - ))} - {beltPois.map((poi) => ( - onSelectPoi(poi.poiId)} /> - ))} - {ship ? : null} - {operation?.operationType === "approach" && targetPoi ? ( - - ) : null} - {operation?.operationType === "mining" && selectedPoi ? ( - - ) : null} - - - ); -} - -function AsteroidCluster({ - poi, - selected, - onSelect, -}: { - poi: PointOfInterest; - selected: boolean; - onSelect: () => void; -}) { - const base = poiPosition(poi); - const offsets: Array<[number, number, number, number]> = [ - [0, 0, 0, 1.25], - [-2.7, 0.8, 1.4, 0.78], - [2.4, -0.6, -1.6, 0.95], - ]; - - return ( - - {offsets.map(([x, y, z, scale], index) => ( - - ))} - {selected ? ( - - - - - ) : null} - - ); -} - -function GridPlane() { - return ( - - ); -} - -function getShipPosition(ship: Ship, operation: ShipOperation | undefined, targetPoi: PointOfInterest | undefined, nowMs: number) { - const start = [ship.x, ship.y, ship.z] as Vector3Tuple; - if (operation?.operationType !== "approach" || !targetPoi) return start; - - const startedAt = Number(operation.startedAt.toMillis()); - const duration = Number(operation.durationMs); - const progress = Math.max(0, Math.min(1, (nowMs - startedAt) / duration)); - const target = poiPosition(targetPoi); - - return [ - lerp(start[0], target[0], progress), - lerp(start[1], target[1], progress), - lerp(start[2], target[2], progress), - ] as Vector3Tuple; -} - -function poiPosition(poi: PointOfInterest): Vector3Tuple { - return [poi.x, poi.y, poi.z]; -} - -function lerp(start: number, end: number, progress: number) { - return start + (end - start) * progress; -} diff --git a/apps/game/src/scene/ShipMesh.tsx b/apps/game/src/scene/ShipMesh.tsx deleted file mode 100644 index 3839e1f..0000000 --- a/apps/game/src/scene/ShipMesh.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { useFrame } from "@react-three/fiber"; -import { useRef } from "react"; -import type { Group } from "three"; - -type Vector3Tuple = [number, number, number]; - -export function ShipMesh({ position, flightMode }: { position: Vector3Tuple; flightMode?: string }) { - const groupRef = useRef(null); - - useFrame(({ clock }) => { - if (!groupRef.current) return; - groupRef.current.rotation.z = Math.sin(clock.elapsedTime * 2) * 0.045; - groupRef.current.position.y = position[1] + Math.sin(clock.elapsedTime * 1.4) * 0.08; - }); - - const engineActive = flightMode === "approaching" || flightMode === "flight"; - - return ( - - - - - - - - - - - - - - - ); -} diff --git a/apps/game/src/scene/SpaceCanvas.tsx b/apps/game/src/scene/SpaceCanvas.tsx deleted file mode 100644 index 01c2904..0000000 --- a/apps/game/src/scene/SpaceCanvas.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { Canvas } from "@react-three/fiber"; -import type { ReactNode } from "react"; - -export function SpaceCanvas({ children }: { children: ReactNode }) { - return ( - - {children} - - ); -} diff --git a/apps/game/src/scene/SpaceEnvironment.tsx b/apps/game/src/scene/SpaceEnvironment.tsx deleted file mode 100644 index 3255290..0000000 --- a/apps/game/src/scene/SpaceEnvironment.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { Stars } from "@react-three/drei"; -import { useFrame } from "@react-three/fiber"; -import { useRef } from "react"; -import type { Group } from "three"; - -export function SpaceEnvironment() { - const dustRef = useRef(null); - - useFrame(({ clock }) => { - if (!dustRef.current) return; - dustRef.current.rotation.y = clock.elapsedTime * 0.015; - dustRef.current.rotation.x = Math.sin(clock.elapsedTime * 0.08) * 0.015; - }); - - return ( - <> - - - - - - - - - - ); -} diff --git a/apps/game/src/scene/StationMesh.tsx b/apps/game/src/scene/StationMesh.tsx deleted file mode 100644 index 1125066..0000000 --- a/apps/game/src/scene/StationMesh.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import type { ThreeEvent } from "@react-three/fiber"; - -type Vector3Tuple = [number, number, number]; - -export function StationMesh({ - position, - selected, - onSelect, -}: { - position: Vector3Tuple; - selected: boolean; - onSelect: () => void; -}) { - function handleClick(event: ThreeEvent) { - event.stopPropagation(); - onSelect(); - } - - return ( - - - - - - - - - - - - - - {selected ? : null} - - ); -} - -function SelectionRing({ radius, color }: { radius: number; color: string }) { - return ( - - - - - ); -} diff --git a/apps/game/src/spacetime/AGENTS.md b/apps/game/src/spacetime/AGENTS.md deleted file mode 100644 index e94f1b0..0000000 --- a/apps/game/src/spacetime/AGENTS.md +++ /dev/null @@ -1,32 +0,0 @@ -# apps/game/src/spacetime/ — SpacetimeDB Connection Layer - -Bridge between the game client and the SpacetimeDB backend. Manages connection lifecycle, table subscriptions, and exposes a typed React hook for the game session. - -## Files - -| File | Purpose | -|------|---------| -| `client.ts` | Connection factory. Creates `DbConnection`, subscribes to all 9 tables (hardcoded to `solace` system), registers table-change listeners for React re-renders, manages auth token in localStorage. Auto-calls `seedWorld` and `connectPlayer` on every connection. | -| `useSpacetimeConnection.ts` | React hook wrapping `client.ts`. Exposes connection state (idle/connecting/connected/error), identity, and a 1.5s revision-poll trigger. | -| `useGameSession.ts` | Main data/action bridge. Reads all 9 SpacetimeDB tables into a typed `GameSession` object. Exposes action callbacks for all 12 reducers. Hardcodes system/station lookups to `solace`/`solace-prime`. | - -## Subscriptions (hardcoded) - -```sql -SELECT * FROM player -SELECT * FROM ship -SELECT * FROM system WHERE system_id = 'solace' -SELECT * FROM station WHERE station_id = 'solace-prime' -SELECT * FROM point_of_interest WHERE system_id = 'solace' -SELECT * FROM cargo_item -SELECT * FROM wallet -SELECT * FROM ship_operation -SELECT * FROM server_event -``` - -## Known Issues - -- System/station subscriptions hardcoded to `solace`/`solace-prime` — must be dynamic for multi-system -- No reconnection logic (no exponential backoff) -- Auth token stored in localStorage (acceptable for auth, but design says no localStorage for game state) -- Events capped at 12 in useGameSession (no pagination) diff --git a/apps/game/src/spacetime/client.ts b/apps/game/src/spacetime/client.ts deleted file mode 100644 index d456c60..0000000 --- a/apps/game/src/spacetime/client.ts +++ /dev/null @@ -1,135 +0,0 @@ -import type { Identity } from "spacetimedb"; -import { DbConnection, type DbConnection as GameDbConnection } from "../module_bindings"; - -export type IdentityLike = Pick; - -export type ConnectionStatus = "idle" | "connecting" | "connected" | "disconnected" | "error"; - -export type ConnectionConfig = { - uri: string; - database: string; - displayName: string; - onStatus: (status: ConnectionStatus, message?: string) => void; - onIdentity: (identity?: IdentityLike) => void; - onDataChanged: () => void; - onReducerStatus: (message: string, isError?: boolean) => void; -}; - -export function createSpacetimeConnection(config: ConnectionConfig): GameDbConnection | null { - try { - config.onStatus("connecting"); - - const authTokenKey = getAuthTokenKey(config); - const connection = DbConnection.builder() - .withUri(config.uri) - .withDatabaseName(config.database) - .withToken(readStoredAuthToken(authTokenKey)) - .onConnect((conn, identity, token) => { - storeAuthToken(authTokenKey, token); - config.onIdentity(identity); - config.onStatus("connected"); - subscribeToShellRows(conn, identity); - invokeReducer(config, "seedWorld", () => conn.reducers.seedWorld({})); - invokeReducer(config, "connectPlayer", () => conn.reducers.connectPlayer({ displayName: config.displayName })); - config.onDataChanged(); - }) - .onConnectError((_ctx, error) => { - config.onStatus("error", error.message); - config.onReducerStatus(`Connection failed: ${error.message}`, true); - }) - .onDisconnect((_ctx, error) => { - config.onStatus("disconnected", error?.message); - config.onIdentity(undefined); - }) - .build(); - - registerTableRefresh(connection, config.onDataChanged); - return connection; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - config.onStatus("error", message); - config.onReducerStatus(message, true); - return null; - } -} - -export function invokeReducer(config: Pick, name: string, call: () => Promise) { - try { - call() - .then(() => config.onReducerStatus(`${name} reducer completed`)) - .catch((error: unknown) => { - const message = error instanceof Error ? error.message : String(error); - config.onReducerStatus(`${name} reducer failed: ${message}`, true); - }); - config.onReducerStatus(`${name} reducer sent`); - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - config.onReducerStatus(`${name} reducer failed: ${message}`, true); - } -} - -function subscribeToShellRows(conn: GameDbConnection, _identity: IdentityLike) { - // Keep the first shell subscriptions broad; the session hook selects caller/starter rows. - conn.subscriptionBuilder().subscribe([ - "SELECT * FROM player", - "SELECT * FROM ship", - "SELECT * FROM system WHERE system_id = 'solace'", - "SELECT * FROM station WHERE station_id = 'solace-prime'", - "SELECT * FROM point_of_interest WHERE system_id = 'solace'", - "SELECT * FROM cargo_item", - "SELECT * FROM wallet", - "SELECT * FROM ship_operation", - "SELECT * FROM server_event", - ]); -} - -function registerTableRefresh(conn: GameDbConnection, onDataChanged: () => void) { - const tables = [ - conn.db.player, - conn.db.ship, - conn.db.system, - conn.db.station, - conn.db.point_of_interest, - conn.db.cargo_item, - conn.db.wallet, - conn.db.ship_operation, - conn.db.server_event, - ] as unknown as Array<{ - onInsert?: (callback: (...args: unknown[]) => void) => void; - onUpdate?: (callback: (...args: unknown[]) => void) => void; - onDelete?: (callback: (...args: unknown[]) => void) => void; - }>; - - for (const table of tables) { - table.onInsert?.(onDataChanged); - table.onUpdate?.(onDataChanged); - table.onDelete?.(onDataChanged); - } -} - -export function formatIdentity(identity?: IdentityLike | string) { - if (!identity) return "unassigned"; - if (typeof identity === "string") return identity; - return identity.toHexString?.() ?? identity.toString?.() ?? "identity"; -} - -function getAuthTokenKey(config: Pick) { - return `void-nav:spacetime-token:${config.uri}:${config.database}`; -} - -function readStoredAuthToken(key: string) { - try { - return window.localStorage.getItem(key) ?? undefined; - } catch { - return undefined; - } -} - -function storeAuthToken(key: string, token?: string) { - if (!token) return; - try { - window.localStorage.setItem(key, token); - } catch { - // Losing the auth token only affects reconnect identity continuity. - } -} diff --git a/apps/game/src/spacetime/useGameSession.ts b/apps/game/src/spacetime/useGameSession.ts deleted file mode 100644 index e105e72..0000000 --- a/apps/game/src/spacetime/useGameSession.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { useMemo } from "react"; -import type { DbConnection } from "../module_bindings"; -import type { - CargoItem, - Player, - PointOfInterest, - ServerEvent, - Ship, - ShipOperation, - Station, - System, - Wallet, -} from "../module_bindings/types"; -import { formatIdentity, invokeReducer, type IdentityLike } from "./client"; - -type ReducerReporter = (message: string, isError?: boolean) => void; - -export type GameSession = { - player: Player | undefined; - ship: Ship | undefined; - system: System | undefined; - station: Station | undefined; - pois: PointOfInterest[]; - cargo: CargoItem[]; - wallet: Wallet | undefined; - operation: ShipOperation | undefined; - events: ServerEvent[]; - actions: { - renamePlayer(displayName: string): void; - ping(): void; - undock(): void; - selectTarget(poiId: string): void; - startApproach(): void; - completeApproach(): void; - dock(): void; - startMining(): void; - completeMiningCycle(): void; - sellOreToNpcMarket(itemId: string, quantity: bigint): void; - }; -}; - -export function useGameSession( - connection: DbConnection | null, - revision: number, - onReducerStatus: ReducerReporter, - identity?: IdentityLike, -): GameSession { - const rows = useMemo(() => readRows(connection, identity), [connection, revision, identity]); - - return { - ...rows, - actions: { - renamePlayer(displayName: string) { - invokeIfConnected(connection, onReducerStatus, "renamePlayer", () => - connection!.reducers.renamePlayer({ displayName }), - ); - }, - ping() { - invokeIfConnected(connection, onReducerStatus, "ping", () => connection!.reducers.ping({})); - }, - undock() { - invokeIfConnected(connection, onReducerStatus, "undock", () => connection!.reducers.undock({})); - }, - selectTarget(poiId: string) { - invokeIfConnected(connection, onReducerStatus, "selectTarget", () => connection!.reducers.selectTarget({ poiId })); - }, - startApproach() { - invokeIfConnected(connection, onReducerStatus, "startApproach", () => connection!.reducers.startApproach({})); - }, - completeApproach() { - invokeIfConnected(connection, onReducerStatus, "completeApproach", () => connection!.reducers.completeApproach({})); - }, - dock() { - invokeIfConnected(connection, onReducerStatus, "dock", () => connection!.reducers.dock({})); - }, - startMining() { - invokeIfConnected(connection, onReducerStatus, "startMining", () => connection!.reducers.startMining({})); - }, - completeMiningCycle() { - invokeIfConnected(connection, onReducerStatus, "completeMiningCycle", () => - connection!.reducers.completeMiningCycle({}), - ); - }, - sellOreToNpcMarket(itemId: string, quantity: bigint) { - invokeIfConnected(connection, onReducerStatus, "sellOreToNpcMarket", () => - connection!.reducers.sellOreToNpcMarket({ itemId, quantity }), - ); - }, - }, - }; -} - -function invokeIfConnected( - connection: DbConnection | null, - onReducerStatus: ReducerReporter, - name: string, - call: () => Promise, -) { - if (!connection) { - onReducerStatus(`${name} unavailable until connected`, true); - return; - } - - invokeReducer({ onReducerStatus }, name, call); -} - -function readRows(connection: DbConnection | null, identity?: IdentityLike) { - const players = readTable(connection?.db.player); - const ships = readTable(connection?.db.ship); - const systems = readTable(connection?.db.system); - const stations = readTable(connection?.db.station); - const pois = readTable(connection?.db.point_of_interest).sort((a, b) => a.name.localeCompare(b.name)); - const cargoRows = readTable(connection?.db.cargo_item); - const wallets = readTable(connection?.db.wallet); - const operations = readTable(connection?.db.ship_operation); - const events = readTable(connection?.db.server_event).sort((a, b) => - compareBigintsDesc(a.eventId, b.eventId), - ); - - const player = players.find((row) => identitiesEqual(row.identity, identity)) ?? players[0]; - const ship = ships.find((row) => identitiesEqual(row.ownerIdentity, player?.identity ?? identity)) ?? ships[0]; - const cargo = ship ? cargoRows.filter((row) => row.shipId === ship.shipId) : []; - const wallet = wallets.find((row) => identitiesEqual(row.ownerIdentity, player?.identity ?? identity)) ?? wallets[0]; - const operation = ship ? operations.find((row) => row.shipId === ship.shipId) : undefined; - - return { - player, - ship, - system: systems.find((row) => row.systemId === "solace") ?? systems[0], - station: stations.find((row) => row.stationId === "solace-prime") ?? stations[0], - pois, - cargo, - wallet, - operation, - events: events.slice(0, 12), - }; -} - -function readTable(table?: { iter?: () => Iterable }): Row[] { - if (!table?.iter) return []; - try { - return Array.from(table.iter()); - } catch { - return []; - } -} - -function compareBigintsDesc(left: bigint, right: bigint) { - if (left === right) return 0; - return left > right ? -1 : 1; -} - -function identitiesEqual(left?: IdentityLike, right?: IdentityLike) { - if (!left || !right) return false; - return formatIdentity(left) === formatIdentity(right); -} diff --git a/apps/game/src/spacetime/useSpacetimeConnection.ts b/apps/game/src/spacetime/useSpacetimeConnection.ts deleted file mode 100644 index 2e3b2aa..0000000 --- a/apps/game/src/spacetime/useSpacetimeConnection.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { useEffect, useMemo, useRef, useState } from "react"; -import type { DbConnection } from "../module_bindings"; -import { createSpacetimeConnection, type ConnectionStatus, type IdentityLike } from "./client"; - -const defaultUri = import.meta.env.VITE_SPACETIME_URI ?? import.meta.env.VITE_SPACETIMEDB_HOST ?? "http://localhost:3000"; -const defaultDatabase = - import.meta.env.VITE_SPACETIME_DATABASE ?? import.meta.env.VITE_SPACETIMEDB_DB_NAME ?? "void-nav-dev"; - -export function useSpacetimeConnection(displayName: string) { - const [status, setStatus] = useState("idle"); - const [message, setMessage] = useState(); - const [identity, setIdentity] = useState(); - const [connection, setConnection] = useState(null); - const [revision, setRevision] = useState(0); - const [reducerStatus, setReducerStatus] = useState<{ message: string; isError: boolean }>(); - - const config = useMemo( - () => ({ - uri: defaultUri, - database: defaultDatabase, - }), - [], - ); - - const displayNameRef = useRef(displayName); - displayNameRef.current = displayName; - - useEffect(() => { - const conn = createSpacetimeConnection({ - ...config, - displayName: displayNameRef.current, - onStatus: (nextStatus, nextMessage) => { - setStatus(nextStatus); - setMessage(nextMessage); - }, - onIdentity: setIdentity, - onDataChanged: () => setRevision((value) => value + 1), - onReducerStatus: (nextMessage, isError = false) => setReducerStatus({ message: nextMessage, isError }), - }); - - setConnection(conn); - - const refresh = window.setInterval(() => { - if (conn) setRevision((value) => value + 1); - }, 1500); - - return () => { - window.clearInterval(refresh); - conn?.disconnect?.(); - }; - }, [config]); - - return { - connection, - status, - message, - identity, - revision, - reducerStatus, - uri: config.uri, - database: config.database, - }; -} diff --git a/apps/game/src/styles/tailwind.css b/apps/game/src/styles/tailwind.css deleted file mode 100644 index 150f69b..0000000 --- a/apps/game/src/styles/tailwind.css +++ /dev/null @@ -1,8 +0,0 @@ -@import "tailwindcss"; -@import "@void-nav/ui/styles.css"; - -@source "../../../packages/ui/src"; - -#root { - min-height: 100vh; -} diff --git a/apps/game/src/ui/AGENTS.md b/apps/game/src/ui/AGENTS.md deleted file mode 100644 index a3a885b..0000000 --- a/apps/game/src/ui/AGENTS.md +++ /dev/null @@ -1,18 +0,0 @@ -# apps/game/src/ui/ — Game UI Panels - -All UI components for the game client. Composed by `GameShell.tsx` into a three-column layout with a bottom command rail. - -## Files - -| File | Purpose | Status | -|------|---------|--------| -| `CommandRail.tsx` | Bottom-fixed contextual action bar. Shows progress bars during operations, context-appropriate buttons otherwise. Covers: undock, approach, dock, mine, sell. | Partial — missing abort, warp, combat, station service buttons | -| `ConnectionPanel.tsx` | Dev diagnostic panel: connection status, identity, endpoint, pilot name input, rename/ping buttons. | Done — but this is a dev tool, not player-facing | -| `CargoPanel.tsx` | Cargo hold display: fill bar + item list with name, quantity, category, unit price. | Partial — no jettison, no stacking/splitting | -| `NpcMarketPanel.tsx` | Sell ore to NPCs. Quantity input, max button, payout preview. Only visible when docked. Prices hardcoded client-side. | Partial — only Veldspar, hardcoded prices, sell-only | -| `WalletPanel.tsx` | ISK balance display. | Partial — no transaction history | -| `ShipStatusPanel.tsx` | Flight mode badge, hull/shield/speed bars, position, operation countdown. | Partial — hull always 100%, shields random, speed hardcoded | -| `TargetPanel.tsx` | Selected target info, clickable POI list. | Partial — no distance, no sorting | -| `MiniStarMap.tsx` | SVG 2D system map: POIs, ship position, operation lines, click-to-select. | Done — single system only | -| `EventFeed.tsx` | Server event log (last 12 events, type + timestamp + message). | Partial — no filtering, no pagination, no color coding | -| `useKeyboardShortcuts.ts` | U=undock, D=dock, A=approach, M=mine, 1-9=select target. | Partial — no sell/refine/fit/combat/warp shortcuts | diff --git a/apps/game/src/ui/CargoPanel.tsx b/apps/game/src/ui/CargoPanel.tsx deleted file mode 100644 index b2d99be..0000000 --- a/apps/game/src/ui/CargoPanel.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { Panel } from "@void-nav/ui"; -import type { CargoItem, Ship } from "../module_bindings/types"; - -export function CargoPanel({ cargo, ship }: { cargo: CargoItem[]; ship?: Ship }) { - const used = cargo.reduce((total, item) => total + item.quantity, 0n); - const capacity = ship?.cargoCapacity ?? 0n; - const fillPct = capacity > 0n ? Math.min(100, Math.round((Number(used) / Number(capacity)) * 100)) : 0; - const barColor = fillPct >= 90 ? "#ef4444" : fillPct >= 60 ? "#f0a030" : "#22d3ee"; - - return ( - -
-

Cargo

- - {used.toString()} / {capacity.toString()} m³ - -
-
-
-
-
- {cargo.length > 0 ? ( - cargo.map((item) => ( -
-
- {item.itemName} - {item.quantity.toString()} -
-
- {item.category} / {item.unitPrice.toString()} ISK per unit -
-
- )) - ) : ( -

Cargo hold empty.

- )} -
- - ); -} diff --git a/apps/game/src/ui/CommandRail.tsx b/apps/game/src/ui/CommandRail.tsx deleted file mode 100644 index ae90cde..0000000 --- a/apps/game/src/ui/CommandRail.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import { useEffect, useState } from "react"; -import { Button } from "@void-nav/ui"; -import type { CargoItem, PointOfInterest, Ship, ShipOperation } from "../module_bindings/types"; - -export function CommandRail({ - ship, - pois, - cargo, - operation, - onUndock, - onStartApproach, - onDock, - onStartMining, - onSellOre, -}: { - ship?: Ship; - pois: PointOfInterest[]; - cargo: CargoItem[]; - operation?: ShipOperation; - onUndock: () => void; - onStartApproach: () => void; - onDock: () => void; - onStartMining: () => void; - onSellOre: (item: CargoItem) => void; -}) { - const selected = pois.find((poi) => poi.poiId === ship?.selectedPoiId); - const current = pois.find((poi) => poi.poiId === ship?.currentPoiId); - const ore = cargo.find((item) => item.category === "ore" && item.quantity > 0n); - const [nowMs, setNowMs] = useState(() => Date.now()); - - useEffect(() => { - if (!operation) return; - const timer = window.setInterval(() => setNowMs(Date.now()), 80); - return () => window.clearInterval(timer); - }, [operation]); - - let content = Connect to SpacetimeDB to initialize the starter ship.; - - if (ship) { - if (operation?.operationType === "approach") { - const progress = getOperationProgress(operation, nowMs); - content = ( -
-
- Approaching target... - {Math.round(progress * 100)}% -
- -
- ); - } else if (operation?.operationType === "mining") { - const progress = getOperationProgress(operation, nowMs); - content = ( -
-
- Mining in progress... - {Math.round(progress * 100)}% -
- -
- ); - } else if (ship.dockedStationId.length > 0) { - content = ( - <> - - {ore ? : null} - - ); - } else if (selected && selected.poiId !== ship.currentPoiId) { - content = ( - - ); - } else if (current?.stationId) { - content = ( - - ); - } else if (current?.poiType === "asteroid_belt") { - content = ( - - ); - } else { - content = Select a station or asteroid belt target.; - } - } - - return ( -
- {content} -
- ); -} - -function ProgressBar({ progress, color }: { progress: number; color: string }) { - return ( -
-
-
- ); -} - -function getOperationProgress(operation: ShipOperation, nowMs: number) { - const startedAt = Number(operation.startedAt.toMillis()); - const duration = Number(operation.durationMs); - if (duration <= 0) return 1; - return Math.max(0, Math.min(1, (nowMs - startedAt) / duration)); -} - -function cargoUsed(cargo: CargoItem[]) { - return cargo.reduce((total, item) => total + item.quantity, 0n); -} diff --git a/apps/game/src/ui/ConnectionPanel.tsx b/apps/game/src/ui/ConnectionPanel.tsx deleted file mode 100644 index e022145..0000000 --- a/apps/game/src/ui/ConnectionPanel.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { Button, Panel } from "@void-nav/ui"; -import { formatIdentity, type ConnectionStatus, type IdentityLike } from "../spacetime/client"; - -export function ConnectionPanel({ - status, - label, - identity, - uri, - database, - message, - reducerMessage, - reducerIsError, - displayName, - onDisplayNameChange, - onRename, - onPing, -}: { - status: ConnectionStatus; - label: string; - identity?: IdentityLike; - uri: string; - database: string; - message?: string; - reducerMessage?: string; - reducerIsError?: boolean; - displayName: string; - onDisplayNameChange: (value: string) => void; - onRename: () => void; - onPing: () => void; -}) { - return ( - -
-

Connection

- - {label} - -
-
- - - - -
- -
- - -
-
- ); -} - -function InfoRow({ label, value, error = false }: { label: string; value: string; error?: boolean }) { - return ( -
-
{label}
-
{value}
-
- ); -} - -function statusClass(status: ConnectionStatus) { - if (status === "connected") return "border-green text-green"; - if (status === "error") return "border-red text-red"; - return "border-cyan text-cyan"; -} diff --git a/apps/game/src/ui/EventFeed.tsx b/apps/game/src/ui/EventFeed.tsx deleted file mode 100644 index 14fc952..0000000 --- a/apps/game/src/ui/EventFeed.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { Panel } from "@void-nav/ui"; -import type { Timestamp } from "spacetimedb"; -import type { ServerEvent } from "../module_bindings/types"; - -export function EventFeed({ events }: { events: ServerEvent[] }) { - return ( - -
-

Server Events

- {events.length} -
-
- {events.length > 0 ? ( - events.map((event) => ( -
-
- {event.eventType} - {formatTime(event.at)} -
-

{event.message}

-
- )) - ) : ( -

- No subscribed server events yet. -

- )} -
-
- ); -} - -function formatTime(value?: Timestamp | string) { - if (!value) return "Pending"; - if (typeof value === "string") return value; - return value.toDate?.().toLocaleTimeString() ?? value.toString?.() ?? "Timestamp"; -} diff --git a/apps/game/src/ui/MiniStarMap.tsx b/apps/game/src/ui/MiniStarMap.tsx deleted file mode 100644 index dba8882..0000000 --- a/apps/game/src/ui/MiniStarMap.tsx +++ /dev/null @@ -1,149 +0,0 @@ -import { Panel } from "@void-nav/ui"; -import type { PointOfInterest, Ship, ShipOperation } from "../module_bindings/types"; - -export function MiniStarMap({ - ship, - pois, - operation, - onSelectPoi, -}: { - ship?: Ship; - pois: PointOfInterest[]; - operation?: ShipOperation; - onSelectPoi: (poiId: string) => void; -}) { - const bounds = getBounds(pois); - const mapSize = 280; - const padding = 24; - - return ( - -

System Map

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

NPC Market

-

No ore to sell. Go mine some asteroids first.

-
- ); - } - - return ( - -
-

NPC Market

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

Ship Status

-

Awaiting ship data...

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

Ship Status

- - {flightModeLabel} - -
- -
- - - -
- -
- - - - {isOperating && operation ? ( - <> - - - - ) : null} -
-
- ); -} - -function StatusBar({ label, value, color }: { label: string; value: number; color: string }) { - return ( -
- {label} -
-
-
-
- {value}% -
-
- ); -} - -function InfoRow({ label, value }: { label: string; value: string }) { - return ( -
-
{label}
-
{value}
-
- ); -} - -function formatFlightMode(mode: string) { - switch (mode) { - case "docked": return "Docked"; - case "flight": return "In Flight"; - case "approaching": return "Approaching"; - case "mining": return "Mining"; - default: return mode || "Offline"; - } -} - -function flightModeColor(mode: string) { - switch (mode) { - case "docked": return "border-green text-green"; - case "approaching": return "border-cyan text-cyan"; - case "mining": return "border-accent text-accent"; - case "flight": return "border-fg-dim text-fg-dim"; - default: return "border-muted text-muted"; - } -} - -function formatCountdown(operation: ShipOperation, nowMs: number) { - const completesAt = Number(operation.completesAtMs); - const remaining = completesAt - nowMs; - if (remaining <= 0) return "Completing..."; - const seconds = Math.ceil(remaining / 1000); - return `${seconds}s`; -} diff --git a/apps/game/src/ui/TargetPanel.tsx b/apps/game/src/ui/TargetPanel.tsx deleted file mode 100644 index 76a9443..0000000 --- a/apps/game/src/ui/TargetPanel.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { Button, Panel } from "@void-nav/ui"; -import type { PointOfInterest, Ship } from "../module_bindings/types"; - -export function TargetPanel({ - ship, - pois, - selected, - onSelect, -}: { - ship?: Ship; - pois: PointOfInterest[]; - selected?: PointOfInterest; - onSelect: (poiId: string) => void; -}) { - const current = pois.find((poi) => poi.poiId === ship?.currentPoiId); - - return ( - -
-
-

Selected Target

-

{selected?.name ?? "No target selected"}

-
- - {ship?.flightMode ?? "offline"} - -
- -
- - -
- -
- {pois.map((poi) => ( - - ))} -
-
- ); -} - -function InfoRow({ label, value }: { label: string; value: string }) { - return ( -
-
{label}
-
{value}
-
- ); -} diff --git a/apps/game/src/ui/WalletPanel.tsx b/apps/game/src/ui/WalletPanel.tsx deleted file mode 100644 index 489d0cb..0000000 --- a/apps/game/src/ui/WalletPanel.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { Panel } from "@void-nav/ui"; -import type { Wallet } from "../module_bindings/types"; - -export function WalletPanel({ wallet }: { wallet?: Wallet }) { - return ( - -

Wallet

-
{formatIsk(wallet?.isk ?? 0n)}
-
- ); -} - -function formatIsk(value: bigint) { - return `${value.toLocaleString()} ISK`; -} diff --git a/apps/game/src/ui/useKeyboardShortcuts.ts b/apps/game/src/ui/useKeyboardShortcuts.ts deleted file mode 100644 index 7ba6b83..0000000 --- a/apps/game/src/ui/useKeyboardShortcuts.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { useEffect } from "react"; - -export type ShipActions = { - onUndock: () => void; - onStartApproach: () => void; - onDock: () => void; - onStartMining: () => void; - onSelectTarget?: (index: number) => void; -}; - -export function useKeyboardShortcuts( - ship: { - dockedStationId: string; - flightMode: string; - selectedPoiId: string; - currentPoiId: string; - } | undefined, - pois: Array<{ poiId: string; poiType: string }>, - operation: { operationType: string } | undefined, - actions: ShipActions, -) { - useEffect(() => { - function handleKeyDown(event: KeyboardEvent) { - if (event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement) return; - - const key = event.key.toLowerCase(); - - if (key === "u" && !operation) { - if (ship && ship.dockedStationId.length > 0) { - event.preventDefault(); - actions.onUndock(); - } - } - - if (key === "d" && !operation) { - if (ship && ship.dockedStationId.length === 0 && ship.flightMode === "flight") { - event.preventDefault(); - actions.onDock(); - } - } - - if (key === "a" && !operation) { - if (ship && ship.dockedStationId.length === 0 && ship.selectedPoiId.length > 0 && ship.selectedPoiId !== ship.currentPoiId) { - event.preventDefault(); - actions.onStartApproach(); - } - } - - if (key === "m" && !operation) { - if (ship && ship.dockedStationId.length === 0 && ship.flightMode === "flight") { - event.preventDefault(); - actions.onStartMining(); - } - } - - if (key >= "1" && key <= "9") { - const index = parseInt(key) - 1; - if (index < pois.length && actions.onSelectTarget) { - event.preventDefault(); - actions.onSelectTarget(index); - } - } - } - - window.addEventListener("keydown", handleKeyDown); - return () => window.removeEventListener("keydown", handleKeyDown); - }, [ship, pois, operation, actions]); -} diff --git a/apps/game/vite.config.ts b/apps/game/vite.config.ts deleted file mode 100644 index 6ed633d..0000000 --- a/apps/game/vite.config.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { defineConfig } from "vite"; -import tailwindcss from "@tailwindcss/vite"; - -export default defineConfig({ - plugins: [tailwindcss()], - esbuild: { - jsx: "automatic", - jsxImportSource: "react", - }, - build: { - rollupOptions: { - output: { - manualChunks: { - three: ["three", "@react-three/fiber", "@react-three/drei"], - spacetimedb: ["spacetimedb"], - }, - }, - }, - }, -}); diff --git a/archive/AGENTS.md b/archive/AGENTS.md deleted file mode 100644 index 2a333d1..0000000 --- a/archive/AGENTS.md +++ /dev/null @@ -1,18 +0,0 @@ -# archive/ — Legacy Reference Files - -Pre-monorepo static files kept for reference. Not part of the build. Not imported by any app. - -## Structure - -``` -legacy-static/ - gap-analysis.html Old gap analysis page - game-hud.html Old game HUD prototype - gdd-docs-hub.html.artifact.json GDD hub artifact - mpg7uppn-eve_like_multiplayer_prototype_design_doc.docx Original GDD document - *.png 3 concept art drawings - css/ tokens.css, base.css, components.css, layout.css - js/ Legacy vanilla JS: demos/, pages/, components/ -``` - -These files document the project's evolution from a static HTML prototype to the current React + SpacetimeDB monorepo. Do not modify — they exist as historical reference only. diff --git a/archive/legacy-static/css/base.css b/archive/legacy-static/css/base.css deleted file mode 100644 index bbefd3b..0000000 --- a/archive/legacy-static/css/base.css +++ /dev/null @@ -1,71 +0,0 @@ -/* ============================================================ - Base — Reset + Typography - ============================================================ */ -*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } - -html { - font-size: 14px; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - text-rendering: optimizeLegibility; -} - -body { - font-family: var(--font-body); - color: var(--fg); - background: var(--bg); - line-height: 1.6; - overflow: hidden; - height: 100vh; -} - -#root { height: 100vh; display: flex; } - -a { color: var(--cyan); text-decoration: none; transition: color var(--transition-fast); } -a:hover { color: var(--accent); } - -h1, h2, h3, h4, h5, h6 { - font-family: var(--font-display); - color: var(--fg-bright); - line-height: 1.25; - letter-spacing: -0.01em; -} -h1 { font-size: 2rem; font-weight: 700; } -h2 { font-size: 1.5rem; font-weight: 600; margin-bottom: var(--sp-4); } -h3 { font-size: 1.2rem; font-weight: 600; margin-bottom: var(--sp-3); } -h4 { font-size: 1.05rem; font-weight: 600; margin-bottom: var(--sp-2); } - -p { margin-bottom: var(--sp-4); } - -code { - font-family: var(--font-mono); - font-size: 0.9em; - background: var(--surface-raised); - padding: 2px 6px; - border-radius: var(--radius-sm); - color: var(--accent); -} - -pre { - font-family: var(--font-mono); - font-size: 0.85rem; - background: var(--surface); - border: 1px solid var(--border); - border-radius: var(--radius-md); - padding: var(--sp-4); - overflow-x: auto; - line-height: 1.5; - margin-bottom: var(--sp-4); -} - -ul, ol { padding-left: var(--sp-6); margin-bottom: var(--sp-4); } -li { margin-bottom: var(--sp-1); } - -::selection { background: var(--accent); color: var(--bg); } - -* { scrollbar-width: thin; scrollbar-color: var(--border-light) transparent; } - -::-webkit-scrollbar { width: 6px; height: 6px; } -::-webkit-scrollbar-track { background: transparent; } -::-webkit-scrollbar-thumb { background: var(--border-light); border-radius: var(--radius-pill); } -::-webkit-scrollbar-thumb:hover { background: var(--muted); } diff --git a/archive/legacy-static/css/components.css b/archive/legacy-static/css/components.css deleted file mode 100644 index 7ddb772..0000000 --- a/archive/legacy-static/css/components.css +++ /dev/null @@ -1,272 +0,0 @@ -/* ============================================================ - Components — Shared UI elements - ============================================================ */ - -/* ---------- Card ---------- */ -.card { - background: var(--surface); - border: 1px solid var(--border); - border-radius: var(--radius-lg); - padding: var(--sp-6); - margin-bottom: var(--sp-6); -} -.card-accent { - border-left: 3px solid var(--accent); -} - -/* ---------- Pill / Badge ---------- */ -.pill { - font-family: var(--font-mono); - font-size: 0.7rem; - padding: 2px 10px; - border-radius: var(--radius-pill); - display: inline-flex; - align-items: center; - gap: var(--sp-1); - white-space: nowrap; -} -.pill-amber { background: var(--accent-bg); color: var(--accent); border: 1px solid var(--accent-border); } -.pill-cyan { background: var(--cyan-bg); color: var(--cyan); border: 1px solid rgba(34,211,238,0.25); } -.pill-green { background: var(--green-bg); color: var(--green); border: 1px solid rgba(34,197,94,0.25); } -.pill-red { background: var(--red-bg); color: var(--red); border: 1px solid rgba(239,68,68,0.25); } -.pill-purple { background: var(--purple-bg); color: var(--purple); border: 1px solid rgba(167,139,250,0.25); } - -/* ---------- Data Table ---------- */ -.data-table { - width: 100%; - border-collapse: collapse; - font-size: 0.85rem; -} -.data-table th { - font-family: var(--font-mono); - font-size: 0.7rem; - text-transform: uppercase; - letter-spacing: 0.06em; - color: var(--muted); - text-align: left; - padding: var(--sp-3) var(--sp-4); - border-bottom: 1px solid var(--border); - white-space: nowrap; -} -.data-table td { - padding: var(--sp-3) var(--sp-4); - border-bottom: 1px solid var(--border); - color: var(--fg); - font-family: var(--font-mono); - font-size: 0.8rem; -} -.data-table tr:hover td { background: var(--surface-raised); } -.data-table .mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; } - -/* ---------- Code Block ---------- */ -.code-block { - background: var(--surface); - border: 1px solid var(--border); - border-radius: var(--radius-md); - padding: var(--sp-4) var(--sp-5); - overflow-x: auto; - margin-bottom: var(--sp-5); -} -.code-block code { - font-family: var(--font-mono); - font-size: 0.82rem; - line-height: 1.6; - color: var(--fg-dim); - background: none; - padding: 0; -} -.code-block .kw { color: var(--purple); } -.code-block .str { color: var(--green); } -.code-block .cm { color: var(--muted); font-style: italic; } -.code-block .type { color: var(--cyan); } -.code-block .fn { color: var(--accent); } - -/* ---------- Stat Grid ---------- */ -.stat-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); - gap: var(--sp-4); - margin-bottom: var(--sp-6); -} -.stat-card { - background: var(--surface); - border: 1px solid var(--border); - border-radius: var(--radius-lg); - padding: var(--sp-5); -} -.stat-value { - font-family: var(--font-mono); - font-size: 1.6rem; - font-weight: 700; - color: var(--fg-bright); - font-variant-numeric: tabular-nums; -} -.stat-label { - font-size: 0.75rem; - color: var(--muted); - margin-top: var(--sp-1); - text-transform: uppercase; - letter-spacing: 0.05em; -} - -/* ---------- Section Header ---------- */ -.section-header { - display: flex; - align-items: center; - gap: var(--sp-3); - margin-bottom: var(--sp-5); - padding-bottom: var(--sp-3); - border-bottom: 1px solid var(--border); -} -.section-header .section-num { - font-family: var(--font-mono); - font-size: 0.7rem; - color: var(--accent); - background: var(--accent-bg); - padding: 2px 8px; - border-radius: var(--radius-pill); -} - -/* ---------- Roadmap Phase ---------- */ -.phase-item { - display: flex; - gap: var(--sp-4); - margin-bottom: var(--sp-5); -} -.phase-marker { - display: flex; - flex-direction: column; - align-items: center; - min-width: 40px; -} -.phase-dot { - width: 12px; - height: 12px; - border-radius: 50%; - background: var(--accent); - box-shadow: 0 0 8px rgba(240, 160, 48, 0.3); -} -.phase-dot.future { background: var(--border-light); box-shadow: none; } -.phase-line { - flex: 1; - width: 2px; - background: var(--border); - margin-top: var(--sp-1); -} -.phase-content { flex: 1; } - -/* ---------- Demo Container ---------- */ -.demo-container { - background: var(--surface); - border: 1px solid var(--border); - border-radius: var(--radius-lg); - overflow: hidden; - margin-bottom: var(--sp-6); -} -/* Inner panels inside demos that need independent scrolling */ -.demo-scroll-panel { - overflow-y: auto; - overscroll-behavior: contain; - flex-shrink: 0; -} -.demo-toolbar { - display: flex; - align-items: center; - gap: var(--sp-3); - padding: var(--sp-3) var(--sp-4); - background: var(--surface-raised); - border-bottom: 1px solid var(--border); - font-size: 0.8rem; -} -.demo-toolbar .demo-title { - font-family: var(--font-mono); - font-size: 0.75rem; - color: var(--accent); - text-transform: uppercase; - letter-spacing: 0.05em; -} -.demo-canvas-wrap { - position: relative; - background: var(--bg); - min-height: 400px; -} -.demo-canvas-wrap canvas { - display: block; - width: 100%; - height: 100%; -} -.demo-sidebar { - width: 280px; - background: var(--surface); - border-left: 1px solid var(--border); - padding: var(--sp-4); - overflow-y: auto; - font-size: 0.8rem; -} - -/* ---------- Button ---------- */ -.btn { - font-family: var(--font-body); - font-size: 0.8rem; - font-weight: 500; - padding: var(--sp-2) var(--sp-4); - border-radius: var(--radius-md); - border: 1px solid var(--border); - background: var(--surface-raised); - color: var(--fg); - cursor: pointer; - transition: all var(--transition-fast); - display: inline-flex; - align-items: center; - gap: var(--sp-2); -} -.btn:hover { border-color: var(--border-light); background: var(--surface-hover); } -.btn-primary { - background: var(--accent); - color: var(--bg); - border-color: var(--accent); - font-weight: 600; -} -.btn-primary:hover { background: var(--accent-hover); } -.btn-sm { padding: var(--sp-1) var(--sp-3); font-size: 0.75rem; } -.btn-danger { border-color: var(--red-dim); color: var(--red); } -.btn-danger:hover { background: var(--red-bg); } - -/* ---------- Tags / Keywords ---------- */ -.tag-list { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-bottom: var(--sp-4); } - -/* ---------- Alert / Callout ---------- */ -.callout { - padding: var(--sp-4) var(--sp-5); - border-radius: var(--radius-md); - margin-bottom: var(--sp-5); - font-size: 0.85rem; - line-height: 1.5; -} -.callout-info { background: var(--cyan-bg); border: 1px solid rgba(34,211,238,0.2); color: var(--cyan); } -.callout-warn { background: var(--accent-bg); border: 1px solid var(--accent-border); color: var(--accent); } -.callout-danger { background: var(--red-bg); border: 1px solid rgba(239,68,68,0.2); color: var(--red); } - -/* ---------- Grid Layouts ---------- */ -.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-5); } -.grid-3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: var(--sp-5); } -@media (max-width: 900px) { .grid-2, .grid-3 { grid-template-columns: 1fr; } } - -/* ---------- Animations ---------- */ -@keyframes pulse { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.4; } -} - -/* ---------- Progress Bar ---------- */ -.progress-bar { - height: 4px; - background: var(--border); - border-radius: var(--radius-pill); - overflow: hidden; -} -.progress-bar .fill { - height: 100%; - border-radius: var(--radius-pill); - transition: width var(--transition-base); -} diff --git a/archive/legacy-static/css/layout.css b/archive/legacy-static/css/layout.css deleted file mode 100644 index a19af2c..0000000 --- a/archive/legacy-static/css/layout.css +++ /dev/null @@ -1,224 +0,0 @@ -/* ============================================================ - Layout — App Shell - ============================================================ */ -.app-shell { - display: flex; - width: 100%; - height: 100vh; - overflow: hidden; -} - -/* ---------- Sidebar ---------- */ -.sidebar { - width: var(--sidebar-width); - min-width: var(--sidebar-width); - height: 100vh; - background: var(--surface); - border-right: 1px solid var(--border); - display: flex; - flex-direction: column; - overflow: hidden; - transition: width var(--transition-base), min-width var(--transition-base); - z-index: 10; -} -.sidebar.collapsed { - width: var(--sidebar-collapsed); - min-width: var(--sidebar-collapsed); -} - -.sidebar-header { - padding: var(--sp-4) var(--sp-5); - border-bottom: 1px solid var(--border); - display: flex; - align-items: center; - gap: var(--sp-3); - min-height: var(--topbar-height); -} -.sidebar.collapsed .sidebar-header { padding: var(--sp-4); justify-content: center; } - -.sidebar-logo { - font-family: var(--font-mono); - font-size: 0.8rem; - font-weight: 700; - color: var(--accent); - letter-spacing: 0.05em; - text-transform: uppercase; - white-space: nowrap; - overflow: hidden; -} -.sidebar.collapsed .sidebar-logo { font-size: 0; } -.sidebar-logo .logo-dot { color: var(--cyan); } - -.sidebar-nav { - flex: 1; - overflow-y: auto; - padding: var(--sp-3) var(--sp-3); -} -.sidebar.collapsed .sidebar-nav { padding: var(--sp-2) var(--sp-2); } - -.nav-section-title { - font-family: var(--font-mono); - font-size: 0.65rem; - text-transform: uppercase; - letter-spacing: 0.1em; - color: var(--muted); - padding: var(--sp-4) var(--sp-3) var(--sp-2); - white-space: nowrap; - overflow: hidden; -} -.sidebar.collapsed .nav-section-title { font-size: 0; padding: var(--sp-2); height: 8px; } - -.nav-item { - display: flex; - align-items: center; - gap: var(--sp-3); - padding: var(--sp-2) var(--sp-3); - border-radius: var(--radius-md); - color: var(--fg-dim); - font-size: 0.85rem; - cursor: pointer; - transition: all var(--transition-fast); - white-space: nowrap; - overflow: hidden; - user-select: none; - margin-bottom: 2px; - text-decoration: none; -} -.nav-item:hover { - background: var(--surface-raised); - color: var(--fg-bright); -} -.nav-item.active { - background: var(--accent-bg); - color: var(--accent); - border: 1px solid var(--accent-border); -} -.nav-item .nav-icon { - font-size: 1rem; - min-width: 20px; - text-align: center; -} -.sidebar.collapsed .nav-item { padding: var(--sp-3); justify-content: center; } -.sidebar.collapsed .nav-item .nav-label { display: none; } - -.nav-badge { - font-family: var(--font-mono); - font-size: 0.65rem; - background: var(--accent-bg); - color: var(--accent); - padding: 1px 6px; - border-radius: var(--radius-pill); - margin-left: auto; -} -.sidebar.collapsed .nav-badge { display: none; } - -/* ---------- Main Area ---------- */ -.main-area { - flex: 1; - display: flex; - flex-direction: column; - overflow: hidden; - min-width: 0; -} - -/* ---------- Top Bar ---------- */ -.topbar { - height: var(--topbar-height); - min-height: var(--topbar-height); - background: var(--surface); - border-bottom: 1px solid var(--border); - display: flex; - align-items: center; - padding: 0 var(--sp-5); - gap: var(--sp-4); - font-size: 0.8rem; -} - -.topbar-toggle { - background: none; - border: 1px solid var(--border); - border-radius: var(--radius-sm); - color: var(--muted); - cursor: pointer; - padding: var(--sp-1) var(--sp-2); - font-size: 1rem; - transition: all var(--transition-fast); - display: flex; - align-items: center; -} -.topbar-toggle:hover { color: var(--fg); border-color: var(--border-light); } - -.topbar-breadcrumb { - font-family: var(--font-mono); - color: var(--muted); - font-size: 0.75rem; - display: flex; - align-items: center; - gap: var(--sp-2); -} -.topbar-breadcrumb .bc-sep { color: var(--border-light); } -.topbar-breadcrumb .bc-current { color: var(--fg-bright); } - -.topbar-status { - margin-left: auto; - display: flex; - align-items: center; - gap: var(--sp-4); - font-family: var(--font-mono); - font-size: 0.7rem; - color: var(--muted); -} - -.status-dot { - width: 6px; - height: 6px; - border-radius: 50%; - display: inline-block; - margin-right: var(--sp-1); -} -.status-dot.online { background: var(--green); box-shadow: 0 0 6px var(--green); } -.status-dot.offline { background: var(--red); } - -/* ---------- Content ---------- */ -.content { - flex: 1; - overflow-y: auto; - min-height: 0; /* allow flex child to shrink and scroll */ - padding: var(--sp-8) var(--sp-10); - background: var(--bg); - overscroll-behavior: contain; -} - -.content-inner { - max-width: var(--content-max-width); - margin: 0 auto; -} - -/* ---------- Fullscreen Demo ---------- */ -.app-shell.fullscreen-demo { - background: #000; -} -.app-shell.fullscreen-demo .main-area { - flex: 1; - overflow: hidden; /* main-area contains the scroll child */ -} -.app-shell.fullscreen-demo .content--flush { - padding: 0; - overflow-y: auto; - flex: 1; - min-height: 0; /* allow flex child to shrink and scroll */ - overscroll-behavior: contain; -} - -/* ---------- Responsive ---------- */ -@media (max-width: 768px) { - .sidebar { - position: fixed; - left: 0; - top: 0; - transform: translateX(-100%); - z-index: 100; - } - .sidebar.open { transform: translateX(0); } - .content { padding: var(--sp-4) var(--sp-5); } -} diff --git a/archive/legacy-static/css/tokens.css b/archive/legacy-static/css/tokens.css deleted file mode 100644 index fcd6ddf..0000000 --- a/archive/legacy-static/css/tokens.css +++ /dev/null @@ -1,80 +0,0 @@ -/* ============================================================ - Star Trek LCARS × Modern Minimal — Design Tokens - ============================================================ */ -:root { - /* Core palette */ - --bg: #080c14; - --bg-subtle: #0b1120; - --surface: #0f1623; - --surface-raised: #162032; - --surface-hover: #1c2d45; - --fg: #d4dce8; - --fg-bright: #f1f5f9; - --fg-dim: #94a3b8; - --muted: #5a6b82; - --border: #1c2a3f; - --border-light: #253550; - - /* Accent — LCARS amber/gold */ - --accent: #f0a030; - --accent-hover: #fbbf24; - --accent-dim: #b47818; - --accent-bg: rgba(240, 160, 48, 0.08); - --accent-border: rgba(240, 160, 48, 0.25); - - /* Secondary accents */ - --cyan: #22d3ee; - --cyan-dim: #0891b2; - --cyan-bg: rgba(34, 211, 238, 0.08); - --red: #ef4444; - --red-dim: #dc2626; - --red-bg: rgba(239, 68, 68, 0.08); - --green: #22c55e; - --green-dim: #16a34a; - --green-bg: rgba(34, 197, 94, 0.08); - --purple: #a78bfa; - --purple-dim: #8b5cf6; - --purple-bg: rgba(167, 139, 250, 0.08); - - /* Typography */ - --font-display: 'SF Pro Display', -apple-system, BlinkMacSystemFont, system-ui, sans-serif; - --font-body: 'SF Pro Text', -apple-system, BlinkMacSystemFont, system-ui, sans-serif; - --font-mono: 'JetBrains Mono', 'Fira Code', ui-monospace, Menlo, monospace; - - /* Spacing */ - --sp-1: 4px; - --sp-2: 8px; - --sp-3: 12px; - --sp-4: 16px; - --sp-5: 20px; - --sp-6: 24px; - --sp-8: 32px; - --sp-10: 40px; - --sp-12: 48px; - --sp-16: 64px; - - /* Radii — LCARS inspired (pill shapes) */ - --radius-sm: 4px; - --radius-md: 8px; - --radius-lg: 12px; - --radius-xl: 20px; - --radius-pill: 9999px; - - /* Shadows */ - --shadow-sm: 0 1px 2px rgba(0,0,0,0.3); - --shadow-md: 0 4px 12px rgba(0,0,0,0.4); - --shadow-lg: 0 8px 32px rgba(0,0,0,0.5); - --shadow-glow-accent: 0 0 20px rgba(240, 160, 48, 0.15); - --shadow-glow-cyan: 0 0 20px rgba(34, 211, 238, 0.15); - - /* Transitions */ - --transition-fast: 150ms ease; - --transition-base: 250ms ease; - --transition-slow: 400ms ease; - - /* Layout */ - --sidebar-width: 260px; - --sidebar-collapsed: 60px; - --topbar-height: 48px; - --content-max-width: 1100px; -} diff --git a/archive/legacy-static/game-hud.html b/archive/legacy-static/game-hud.html deleted file mode 100644 index 6cd3c76..0000000 --- a/archive/legacy-static/game-hud.html +++ /dev/null @@ -1,1745 +0,0 @@ - - - - - - VOID::NAV — Game HUD - - - -
- - - - - - - - diff --git a/archive/legacy-static/gap-analysis.md b/archive/legacy-static/gap-analysis.md deleted file mode 100644 index 1c4732c..0000000 --- a/archive/legacy-static/gap-analysis.md +++ /dev/null @@ -1,305 +0,0 @@ -# Gap Analysis — EVE-Inspired Multiplayer Prototype GDD - -**Date:** 2026-05-24 -**Last Updated:** 2026-05-25 (Session 7) -**Scope:** Full cross-reference of design documentation vs. interactive demos vs. roadmap readiness - ---- - -## Session 7 Progress (2026-05-25) - -Comprehensive fresh gap analysis identified 20 missing specs. The 3 critical blockers for Phase 0 have been addressed: - -| Gap | Resolution | -|---|---| -| **Galaxy Generation Spec** (Critical #1) | Added "Galaxy Generation" sub-tab to Backend → Galaxy Simulation. Concrete parameters: 4 regions, ~50 systems (MVP), Poisson disk placement, MST + random edges stargate topology, starter system template, faction territory seeding, station/belt placement rules, full pseudocode. Deterministic seeded RNG. | -| **Ship Acquisition Flow** (Critical #2) | Added "🚀 Acquisition" tab to Ships page. Free Rookie Frigate on spawn and death respawn. 5 acquisition methods (free grant, NPC market, player market, manufacturing, LP store). NPC ship pricing table. Ship switching flow (dock → hangar → select → activate). Hangar storage model. Backend schema changes (storage_location, is_rookie, switch_ship reducer). | -| **Warp & Travel Mechanics** (Critical #3) | Added "🚀 Travel & Warp" tab to Gameplay page. 3 travel modes (sub-warp, warp, gate jump) with speeds. Warp sequence (align → accelerate → cruise → exit). Warp speeds by ship class (3.0–6.0 AU/s). Stargate mechanics (2.5km activation, 5s jump cooldown, 30s gate cloak). Gate guns by security level. Docking/undocking with invulnerability. Autopilot vs manual piloting. Warp disruption. Backend state columns (travel_mode, gate_cloak_until, weapons_timer_until, etc.). | - -### Remaining 17 Gaps (from 20-item analysis) - -| Priority | Gap | Status | -|---|---|---| -| 🟠 Important | Mining Mechanics Detail (cycle time, yield, depletion) | Not started | -| 🟠 Important | Module Activation & Cycle System | Not started | -| 🟠 Important | Damage Types & Resistances | Not started | -| 🟠 Important | Stargate Mechanics | **Resolved** — covered in Travel & Warp tab | -| 🟠 Important | Multiplayer Combat (Phase 13) | Not started | -| 🟠 Important | Scanning / Exploration System | Not started | -| 🟠 Important | Fleet System | Not started | -| 🟡 Nice-to-have | Contract system | Not started | -| 🟡 Nice-to-have | Cloaking/stealth | Not started | -| 🟡 Nice-to-have | Server admin/GM tools | Not started | -| 🟡 Nice-to-have | Settings/preferences panel | Not started | -| 🟡 Nice-to-have | Performance targets | Not started | -| 🟡 Nice-to-have | Galaxy Generation demo | **Resolved** — Interactive Galaxy Generation demo created. Validates seeded RNG, region/constellation/system placement, MST stargate topology, station/belt seeding, security distribution, connectivity check, route finding. 12th demo. | -| 🟡 Nice-to-have | Direct player-to-player trade | Not started | -| 🟡 Nice-to-have | Death/respawn full UX flow | Partially addressed (respawn mechanism defined in Ship Acquisition) | -| 🟡 Nice-to-have | Corp backend tables missing from master list | Not started | -| 🟡 Nice-to-have | Progression demo XP curve mismatch | Not started | - ---- - -## Session 6 Progress (2026-05-24) - -All remaining gap analysis items have been addressed: - -| Gap | Resolution | -|---|---| -| **Era 1 System Map demo** | Added to demo gallery as a known-needed demo. Listed in demo gallery header note. Implementation is Phase 1 scope (after movement model is built). | -| **Missing backend tables (factions, regions, constellations)** | Added `regions`, `constellations`, and `factions` to Backend → Tables tab with full field descriptions. `systems` table now has `constellation_id` FK. | -| **Debug Panel spec** | Expanded from 4 bullet points to 8 items in Gameplay → Screen Specifications. Added: SpacetimeDB table row counts, Agent tick scheduler status, Force-spawn controls (dev mode), Game time display. | -| **Tutorial / Onboarding spec** | Already resolved in Session 5 (OV-05). 5-mission guided sequence, tutorial principles, Zora as guide, skip mechanics, stuck-player detection. | -| **Error handling / Reconnection spec** | Added to Architecture → ARCH-4. 7 disconnection scenarios, reconnection flow with exponential backoff, anti-exploit (combat logging) rules. | -| **Session persistence / Save-Load spec** | Added to Architecture → ARCH-5. No save button, no localStorage. SpacetimeDB is continuous persistence. Full table-by-table persistence guarantee. | -| **Sound / Audio design spec** | Added to Architecture → ARCH-6. 6 audio categories, 6 volume sliders, spatial audio rules, Phase 7 scope. | -| **Localization / i18n decision** | Added to Architecture → ARCH-7. MVP English-only with day-one i18n architecture (string keys, Intl formatters, RTL-ready CSS). | -| **Accessibility spec** | Added to Architecture → ARCH-8. 8 accessibility areas with requirements and implementation details. Gate 4 acceptance tests. | -| **Corporations & Territory spec** | Added as new tab to Social page (SOC-CORP). Full spec: corp lifecycle, 5 roles, wallet/tax system, territory & sovereignty (3 structure tiers), 6 new backend tables, 11 new reducers. Phase 14 scope. | - ---- - -## Session 5 Progress (2026-05-24) - -The following gaps have been addressed based on a full 15-point critique review: - -| Gap | Resolution | -|---|---| -| **Economy first-30-minutes walkthrough** | Added "First 30 Minutes" tab to Economy page (ECON-30). 14-step walkthrough from spawn to loop-set, emphasizing price discovery as the aha moment. | -| **Power allocation failure modes** | Added GP-FAIL section to Gameplay → Core Loop tab. Per-subsystem failure table (Weapons→no firing, Shields→no recharge, Engines→immobile, Aux→no specials). Reroute timing (1.5–3s). | -| **Flight Mode HUD cognitive load** | Added Red Alert mode to GP-FAIL. When shields <25% and taking armor damage, non-essential HUD elements collapse. Combat HUD expands. Cannot be disabled. | -| **Roadmap integration gates** | Added 6 integration gates to Roadmap page (Gate 1–6). Each gate covers a phase group and defines a focused playtest. | -| **Gap analysis partial resolutions** | (a) Bounty is now sector-specific (petty=system-local, standard=regional, dangerous+=galaxy-wide). (b) AI module examples added to both fitting examples in Ships page. (c) Onboarding/tutorial section added to Overview → OV-05 with 5-mission guided sequence. | -| **Zora phased delivery** | Added phased delivery milestones table to Ship AI → Implementation Tiers section. Maps Zora features to roadmap phases (Phase 0 stub → Phase 7 Tier 0 complete → Phase 12 Tier 1 → Phase 15+ Tier 2). Full design kept intact. | -| **Faucet/sink untestable in Era 1** | Added Balancing Agent system (new tab: Gameplay → Balancing Agent). Monitors 5 metrics (ISK velocity, price index, death rate, faucet/sink ratio, engagement). Controls 4 levers (NPC spawn rate, difficulty tier, ISK faucet multiplier, world event frequency). New tables: balance_metrics, balance_levers, balance_audit. | -| **ER diagram** | Added ER Diagram tab to Backend page. 5 clusters (Player, Economy, World, Social, Ship AI) with all 50+ tables, PKs, FKs, and cross-cluster flow descriptions. | -| **Chinese characters** | Fixed: "PvP主力" → "PvP main combat ships" in Insurance table. | -| **Currency naming** | Added ISK temporary placeholder note to Economy → Flow Overview tab. All ISK references are subject to renaming. | -| **Security level 0.0 gap** | Fixed: Security 0.0 is explicitly null-sec. Added clarification to Gameplay → Security Levels intro text. | -| **Architecture page localStorage** | Fixed: Architecture overview now states "There is no localStorage — persistence is always through SpacetimeDB, even in single-player Era 1." | -| **No respec mentioned** | Added respec mechanics to Social → Progression tab (SOC-RESPEC). 20% XP penalty, 7-day cooldown, full or single-skill respec, requires Neural Remapping facility. | -| **Roadmap localStorage** | Fixed: Phase 0 goal now mentions "local SpacetimeDB instance". Phase 7 doneWhen says "SpacetimeDB persists all state — no localStorage". Era 1 subtitle reflects SpacetimeDB-from-day-1. | -| **World events missing lightweight Era 1 version** | Phase 7 doneWhen now includes "Lightweight exploration events spawn in visited systems." Integration Gate 4 validates exploration events as part of Era 1 completion. | - ---- - -## Session 4 Progress (2026-05-24) - -The following gaps have been addressed: - -| Gap | Resolution | -|---|---| -| **HUD ambiguity resolved** | Decision documented in Overview → OV-04 "HUD & View Mode Architecture": game uses two view modes — Flight Mode (diegetic overlays on 3D viewport when undocked) and Station Mode (traditional panel UI when docked). Map Mode is shared. Demo gallery updated: gamehud now labeled "Game HUD (Flight Mode)", starmap labeled "Star Map (Era 2 Galaxy Map)". Transition rules specified (undock, dock, open map, combat ambush). | -| **Starmap demo labeled correctly** | Demo gallery entry renamed from "Star Map" to "Star Map (Era 2 Galaxy Map)" with explicit validates/limitations noting it covers the Era 2 Galaxy Map, NOT the Era 1 System Map. Era 1 System Map still needs a separate demo. | -| **Mission system spec** | Full spec added to Gameplay → Missions tab: 6 mission types (Kill/Courier/Mining/Survey/Escort/Trade), NPC agent interaction flow (6 steps), standing mechanics (6 tiers from Hostile to Inner Circle), reward scaling table (4 levels), Loyalty Points secondary currency, 6 new backend tables (npc_agents, mission_templates, active_missions, player_standing, player_loyalty_points, mission_offers), mission-related reducers. | -| **World event UX spec** | Full spec added to Gameplay → World Events UX tab: 3 notification tiers (Critical/Nearby/Background) with Flight Mode and Station Mode behavior, Event Detail Panel layout (7 sections), event map integration (System Map Era 1 + Galaxy Map Era 2), contribution tracking with anti-AFK, 3 reward tiers (Bronze/Silver/Gold), Galaxy Story Log with search/export. | -| **Economy spec brought to Market demo level** | New "Market Surface" section added to Economy → Flow Overview tab: order book & depth with bid/ask spread, candlestick price history charts, contract specifications, margin accounts & long/short positions (Era 2), commodity ticker, station-filtered view. Acknowledges the Market demo implements all of these. | -| **Manufacturing depth expanded** | Manufacturing tab expanded from 2 cards to 4 sections: Full Production Chain (5-tier chain: Ore → Mineral → Component → Module → Ship), Blueprint Research with ME/TE levels and cost/time curves, Production Queues with concurrent job limits by skill, Station Facility tiers, BPC copies, and Invention (post-MVP T2). | -| **Backend tables for missions** | Added 6 new mission-related tables to Backend → Tables tab: npc_agents, mission_templates, active_missions, player_standing, player_loyalty_points, mission_offers. Total tables now 44. | - ---- - -## Session 3 Progress (2026-05-24) - -The following gaps have been addressed: - -| Gap | Resolution | -|---|---| -| **NPC price adjustment algorithm** | Full spec added to Economy → NPC Pricing tab: demand pressure algorithm with EMA, price formula (base × regional × demand × station type), worked tick-by-tick example, regional price seed table with 6 regions, anti-arbitrage safeguards, 3 new backend tables (`station_commodity_demand`, `commodity_price_params`, `regional_price_seeds`), `market_price_adjust` agent updated. | -| **Chat/comms prototype** | New interactive demo (`js/demos/chat.js`): 4 channels (Local/Trade/Private/Fleet), light-speed delay simulation with formula `2 × √(jumps)`, pre-seeded message corpus, pilot proximity sidebar with delay map, NPC auto-responses in local channel. Registered in sidebar, loader, app, and demo gallery. | -| **Zora Tier 0 demo** | New interactive demo (`js/demos/zora.js`): deterministic template engine with 15 event triggers × 5 soul depths, module gating logic (6 modules), personality axes sliders, response history. Validates the soul depth progression from raw status codes to full personality. Registered in sidebar, loader, app, and demo gallery. | -| **Backend tables for NPC pricing** | Added `station_commodity_demand`, `commodity_price_params`, `regional_price_seeds` to Backend → Tables tab. | -| **Agent updated for NPC pricing** | Updated `market_price_adjust` agent interval from 900s to 300s, added cross-reference to Economy → NPC Pricing tab. | - ---- - -## Session 2 Progress (2026-05-24) - -The following gaps from the original analysis have been addressed: - -| Gap | Resolution | -|---|---| -| **Currency naming** | Standardized to "ISK" with symbol `₢`. Removed all "credits" references. Updated overview, ships, roadmap. | -| **Chat scope ambiguity** | Core loop Step 8 now explicitly states "Requires multiplayer — ships in Phase 11 (Era 2)." Summary paragraph clarified Steps 1–7 as Era 1. | -| **Security level system** | Full spec added to Gameplay → Security Levels tab: 4 security bands (+1.0 to −1.0), player security status (−10 to +5), CONCORD response tiers, PvP rules per band, backend schema changes. | -| **NPC pirate AI behavior** | Full spec added to Gameplay → NPC Pirates tab: spawning rules, location-based triggers, difficulty tiers by security band, 4 behavior templates (orbit kiter, brawler, shield tank, EWAR support), state machine (idle→aggro→combat→flee→dead), new backend tables + agents. | -| **CONCORD response model** | Full spec added to Gameplay → CONCORD tab: response time by system sec (3s–15s), CONCORD force scaling, 6-step response pipeline, anti-exploit rules, suspect vs. criminal flag system, weapons timer. | -| **Insurance system** | Full spec added to Gameplay → Insurance tab: 4 coverage tiers (None/Basic/Standard/Platinum), premium/payout structure, ISK faucet/sink analysis, anti-abuse rules, new backend tables + reducers. | -| **Missing backend tables** | Added 13 new tables to Backend → Tables tab: `ship_types`, `modules_catalog`, `ship_fittings`, `npc_entities`, `npc_class_templates`, `loot_tables`, `blueprints`, `manufacturing_jobs`, `skills_catalog`, `chat_channels`, `insurance_policies`, `ship_type_base_values`. | -| **Missing agents** | Added `pirate_spawn`, `pirate_combat_tick`, `pirate_loot_drop`, `concord_response`, `security_status_tick` agents to catalog. | -| **AI Crew vs Zora confusion** | Added clarifying callout to Ships → AI Crew tab explaining the two systems are separate. | -| **Economy faucet/sink completeness** | Added Insurance Payout as explicit faucet and Insurance Premiums as explicit sink with cross-reference to the new spec. | - ---- - -## Executive Summary - -The GDD is remarkably thorough — 12 design pages, 9 interactive demos, a 16-phase roadmap, and a detailed backend schema. The documentation quality is high and internally consistent. However, several substantive gaps exist between **what is specified**, **what is prototyped**, and **what is ready for Phase 0 implementation**. These gaps fall into four categories: - -1. **Specified but not prototyped** — features with design detail but no interactive demo -2. **Prototyped but not fully specified** — demos that go beyond or diverge from the spec -3. **Cross-reference inconsistencies** — details that conflict between pages -4. **Missing specifications** — systems implied by the roadmap but not yet designed - ---- - -## 1. Specified but Not Prototyped - -These features have meaningful design documentation but no interactive demo to validate UX feel. - -| Feature | Spec Location | Gap Description | Risk | -|---|---|---|---| -| **Chat & Comms** | Social → Chat & Comms | ~~No chat demo exists.~~ Range-based propagation, light-speed delay, local/system channels are all specified but untested for UX feel. | **Resolved** — Chat & Comms demo created. Validates delay mechanics, channel switching, and pilot proximity. | -| **Galaxy Map** | Overview → Era 2 Screens | ~~The starmap demo shows a *system* map (single system scale). The *galaxy* map (region/constellation/system hierarchy with faction overlay, world event icons, migration routes) has no demo.~~ **Resolved:** Demo gallery now labels starmap as Era 2 Galaxy Map. The remaining gap is a separate Era 1 System Map demo. | **Partially Resolved** — Era 1 System Map still needs a demo. | -| **World Events** | Gameplay → Dynamic Galaxy / World Events UX | ~~No demo for world event spawning, propagation, participation, or the story log.~~ Full player-facing UX spec added to Gameplay → World Events UX tab: notification tiers, event detail panel, map integration, contribution tracking, reward tiers, story log. No interactive demo yet. | **Spec Resolved** — UX fully designed. Demo is nice-to-have, not blocking. | -| **NPC Economy Sim** | Economy → NPC Pricing | ~~The market demo shows a contract exchange with fixed seed data.~~ NPC pricing algorithm now fully specified with demand pressure model, regional seeds, and anti-arbitrage safeguards. No demo for full NPC supply/demand simulation over time yet. | **Spec Resolved** — Algorithm documented. Demo for full multi-station simulation is nice-to-have, not blocking. | -| **Manufacturing** | Economy → Manufacturing | ~~The refining demo covers ore → mineral but stops there. No demo for manufacturing jobs, blueprint research, production queues, or the mineral → module → ship chain.~~ Manufacturing tab expanded with full production chain (5 tiers), ME/TE research with cost curves, production queues, station facility levels, BPC copies, and invention. No demo yet. | **Spec Resolved** — Full manufacturing chain specified. Demo is Phase 5 scope. | -| **Ship AI (Zora)** | Ship AI (entire page) | ~~No demo at all.~~ Zora Tier 0 demo created validating deterministic template engine. Tier 1 (LLM-assisted) and Tier 2 (full agent) still unvalidated. | **Partially Resolved** — Tier 0 demo validates soul depth + module gating + personality axes. Higher tiers are post-MVP. | -| **Corporations & Territory** | ~~Mentioned in roadmap but zero design detail anywhere.~~ **Resolved:** Full spec added to Social → Corporations tab. Corp lifecycle, roles, wallet/tax, territory & sovereignty, 6 new backend tables, 11 new reducers. Phase 14 scope. | -| **Debug Panel** | ~~Listed as an Era 1 screen with minimal spec.~~ **Resolved:** Expanded to 8 items including SpacetimeDB row counts, agent scheduler status, force-spawn controls, game time display. Utility panel — no demo needed. | -| **Waypoints & Bookmarks** | Fully specified with backend tables, but no demo. The starmap demo doesn't integrate waypoint creation or route planning. | **Medium** — Navigation UX demo deferred to Phase 1 when System Map is built. Spec is complete. | - ---- - -## 2. Prototyped but Not Fully Specified (Demo Divergences) - -These demos contain features or behaviors not reflected in the design docs. - -| Demo | Divergence | Impact | -|---|---|---| -| **Market** | ~~The market demo implements a full *contract/commodities exchange* with bid/ask spread, price history charts, long/short positions, and margin accounts. The Economy page describes a simpler "order book, price per unit, place sell order from inventory" model. The demo is significantly more ambitious than the spec.~~ **Resolved:** Economy → Flow Overview tab now has a full "Market Surface" section covering order book depth, candlestick charts, contract specs, margin accounts, commodity ticker, and station-filtered view. Spec now matches the demo. | **Resolved** — Economy spec updated to match Market demo's feature set. | -| **Market** | Uses `₢` symbol for ISK in some places. The Overview page uses "ISK" and "credits" interchangeably. No canonical symbol defined. | **Inconsistency** — minor but should be standardized. | -| **Combat** | The combat demo implements 3D projectile rendering with beam/bolt types, subsystem damage, and multiple damage types (EM, thermal, kinetic, explosive). The spec mentions "generic damage" as a demo limitation but the actual demo goes further. | **Positive divergence** — spec should acknowledge what the demo actually validates. | -| **Combat** | Demo uses 4 power subsystems (Weapons/Shields/Engines/Aux). The spec consistently uses the same 4. This is consistent — no gap. | ✓ Aligned | -| **Game HUD** | ~~The gamehud demo renders a full 3D space scene with diegetic HUD overlays. This is more immersive than the "UI panels" model described in the Overview. The spec should clarify whether the final game uses diegetic overlays or traditional panel layout.~~ **Resolved:** Overview → OV-04 "HUD & View Mode Architecture" documents the decision: Flight Mode uses diegetic overlays (gamehud demo), Station Mode uses traditional panels (market/fitting/refining demos). Hybrid approach. | **Resolved** — HUD ambiguity resolved with two-view-mode architecture. | -| **Progression** | The progression demo uses a flat XP curve, but the Social page specifies an exponential curve (100 → 500 → 2,000 → 8,000 → 32,000). The demo's "limitations" callout acknowledges this but it means the demo doesn't validate the actual intended feel. | **Known limitation** — should be tracked for Phase 7. | - ---- - -## 3. Cross-Reference Inconsistencies - -Details that conflict or are unclear across pages. - -| Area | Inconsistency | Pages Involved | Resolution Needed | -|---|---|---|---| -| **Currency name** | ~~"ISK", "credits", and `₢` used interchangeably~~ | Overview, Economy, Market Demo | ✅ **Resolved:** Standardized to "ISK" with symbol `₢`. All "credits" references replaced. | -| **Ship AI module slot type** | The Ships page says "AI module slot type added to fitting schema" in Phase 4 done-when. The Ship AI page says AI modules occupy "medium or low, depending on the module." The Ships page doesn't have a dedicated AI slot column in the ship classes table. | Ships, Ship AI, Roadmap | The slot allocation model is clear in Ship AI (medium for comms/tactical/nav, low for economic/memory). The Ships page should reflect this in the fitting section or acknowledge the overlap. | -| **Ship AI Tier 0 timeline** | The Roadmap says "Tier 0 Zora" ships in Phase 7. The Ship AI page says Tier 0 = deterministic, no LLM. But Phase 7 also says "bare-bones soul state vector in SpacetimeDB." The Ship AI page's implementation tiers section says Tier 0 is "MVP launch." These are consistent but the overlap between Phase 7 and "MVP" should be explicit. | Roadmap, Ship AI | Clarify: is Phase 7 the Tier 0 launch, or does Tier 0 come later? The current text is consistent but could be more explicit. | -| **Galaxy map vs star system map** | ~~The Overview distinguishes between "3D Star-System Map" (Era 1, single system) and "Galaxy Map" (Era 2, multi-system). The starmap demo renders a multi-system galaxy view with warp routes. This is the Era 2 galaxy map, not the Era 1 system map.~~ **Resolved:** Demo gallery now labels starmap as "Star Map (Era 2 Galaxy Map)". Overview → OV-04 explicitly notes Era 1 System Map as needed. | **Partially Resolved** — Labeling fixed. Era 1 System Map demo still needed. | -| **Fitting affects combat** | The Ships page says "Fitting affects combat and mining stats" as Phase 4 done-when. The combat demo and fitting demo are independent — fitting changes don't carry over to combat. | Ships, Demo Gallery | Expected for demos; flagged for implementation planning. | -| **Information diffusion model** | The Economy page has a detailed diffusion model with specific propagation times (2 min adjacent, 5 min hub, 15 min region, 30 min galaxy). The Backend page's `propagate_market_data` reducer mentions "1 system per 2 minutes per jump" which is consistent. No gap, but the propagation pipeline has no demo. | Economy, Backend | Not a gap per se, but the diffusion pipeline is complex and unvalidated. | -| **Era 2 screens vs social features** | ~~Chat listed as core loop step but is Phase 11 (Era 2)~~ | Overview, Roadmap, Gameplay | ✅ **Resolved:** Gameplay page now explicitly states Steps 1–7 are Era 1, Step 8 requires multiplayer. | -| **Kill feed visibility** | Social page says "galaxy-wide feed." Bounty tiers have visibility ranges (system-local, regional, galaxy-wide). The kill feed demo shows all events everywhere. | Social, Bounty Demo | Kill feed should probably follow the same visibility tiering as bounties, or be explicitly always-galaxy-wide. | -| **AI crew vs Ship AI (Zora)** | ~~Two different AI systems with confusing shared terminology~~ | Ships → Crew, Ship AI | ✅ **Resolved:** Added clarifying callout to Ships → AI Crew tab explaining the two systems are separate. Future expansion may connect them post-MVP. | -| **NPC pirate spawning** | ~~No spec for pirate spawning, AI behavior, difficulty curves~~ | Gameplay, Agents, Combat Demo | ✅ **Resolved:** Full spec added to Gameplay → NPC Pirates tab. New agents added: `pirate_spawn`, `pirate_combat_tick`, `pirate_loot_drop`. | - ---- - -## 4. Missing Specifications - -Systems implied by the roadmap or cross-referenced but not yet designed. - -| Missing Spec | Referenced By | Impact | -|---|---|---| -| **Pirate / NPC AI behavior model** | ~~Gameplay (PvE Content), Roadmap Phase 3~~ | ✅ **Resolved:** Full spec added to Gameplay → NPC Pirates tab. | -| **Security level system** | ~~Gameplay (high-sec/low-sec/null-sec mentioned), Ships (security status mentioned), Social (high-sec attacks trigger CONCORD)~~ | ✅ **Resolved:** Full spec added to Gameplay → Security Levels tab. | -| **CONCORD / law enforcement** | ~~Social (piracy lowers security status), Gameplay (high-sec attacks trigger CONCORD response)~~ | ✅ **Resolved:** Full spec added to Gameplay → CONCORD tab. | -| **Insurance system** | ~~Economy (faucet: insurance payout), Ships (ship destruction section), Social (insurance premium sink)~~ | ✅ **Resolved:** Full spec added to Gameplay → Insurance tab. | -| **Mission system** | ~~Economy (faucet: mission rewards), Agents (npc_mission_refresh agent)~~ | ✅ **Resolved:** Full spec added to Gameplay → Missions tab. 6 mission types, agent interaction, standing, rewards, LP, backend tables. | -| **Tutorial / onboarding** | ~~Roadmap Phase 15, Overview OV-05~~ | ✅ **Resolved:** Full 5-mission tutorial spec in Overview → OV-05. Zora as guide. Skip allowed. Stuck detection. | -| **Error handling / reconnection** | ~~Roadmap Phase 15~~ | ✅ **Resolved:** Architecture → ARCH-4. 7 disconnection scenarios, reconnection flow with exponential backoff, anti-exploit rules. | -| **Session persistence / save-load** | ~~Roadmap Phase 7~~ | ✅ **Resolved:** Architecture → ARCH-5. No save button, no localStorage. SpacetimeDB is continuous persistence. Full table-by-table persistence guarantee. | -| **Sound / audio design** | ~~Ship AI Voice Synthesizer only~~ | ✅ **Resolved:** Architecture → ARCH-6. 6 audio categories, 6 volume sliders, spatial audio rules. | -| **Localization / i18n** | ~~Not mentioned~~ | ✅ **Resolved:** Architecture → ARCH-7. MVP English-only with day-one i18n architecture. | -| **Accessibility** | ~~Not mentioned~~ | ✅ **Resolved:** Architecture → ARCH-8. 8 accessibility areas, Gate 4 acceptance tests. | - ---- - -## 5. Backend Schema Gaps - -The Backend page lists 44 tables. Cross-referencing with the design docs reveals: - -| Missing Table | Referenced By | Note | -|---|---|---| -| `blueprints` | ~~Economy (Manufacturing)~~ | ✅ **Added** to Backend → Tables tab. | -| `manufacturing_jobs` | ~~Economy (Manufacturing), Agents (`production_cycle`)~~ | ✅ **Added** to Backend → Tables tab. | -| `ship_fittings` | ~~Ships (Fitting System)~~ | ✅ **Added** to Backend → Tables tab. | -| `modules_catalog` | ~~Ships (Fitting System)~~ | ✅ **Added** to Backend → Tables tab. | -| `ship_types` | ~~Ships (Ship Classes)~~ | ✅ **Added** to Backend → Tables tab. | -| `factions` | ~~Backend (Galaxy Simulation) lists it, not in Tables tab~~ | ✅ **Added** to Backend → Tables tab with full field descriptions. | -| `skills_catalog` | ~~Social (XP & Skills)~~ | ✅ **Added** to Backend → Tables tab. | -| `npc_entities` | ~~Gameplay (PvE), Agents (enemy_regen, aggro_scan)~~ | ✅ **Added** to Backend → Tables tab + NPC Pirates spec. | -| `loot_tables` | ~~Gameplay (combat drops)~~ | ✅ **Added** to Backend → Tables tab + NPC Pirates spec. | -| `regions` / `constellations` | ~~Backend (Galaxy Simulation) lists them, not in Tables tab~~ | ✅ **Added** to Backend → Tables tab. `systems` now has `constellation_id` FK. | -| `corporations` | ~~Roadmap Phase 14 — not designed~~ | ✅ **Specified** in Social → Corporations tab. 6 new tables added. Phase 14 scope. | -| `chat_channels` | ~~Social (Chat)~~ | ✅ **Added** to Backend → Tables tab. | - ---- - -## 6. Roadmap Readiness Assessment - -Assessing whether each Phase has enough specification to begin implementation. - -| Phase | Title | Spec Readiness | Blockers | -|---|---|---|---| -| **0** | Local Skeleton | ✅ Ready | None. Tech stack is specified. File structure is defined. | -| **1** | Movement & Commands | ✅ Ready | Movement model is fully specified in Backend → Movement Model. | -| **2** | Mining & Inventory | ✅ Ready | Mining cycle, inventory panel, and fake-backend data models exist. | -| **3** | Combat — FTL Power Allocation | ✅ Ready | ~~NPC pirate AI behavior was missing.~~ **Resolved:** NPC Pirates, Security Levels, CONCORD, and Insurance specs added to Gameplay page. | -| **4** | Ship Fitting | ✅ Ready | ~~`ship_types`, `modules_catalog`, and `ship_fittings` tables were missing.~~ **Resolved:** All three added to Backend → Tables tab. | -| **5** | Refining & Manufacturing | ✅ Ready | ~~Refining is well-specified. Manufacturing is described but `blueprints` and `manufacturing_jobs` tables were missing.~~ **Resolved:** Both tables added to Backend → Tables tab. | -| **6** | NPC Economy Sim | ✅ Ready | ~~Economy philosophy is clear, but NPC price adjustment logic, regional price seeding, and the full diffusion pipeline lack implementation detail beyond pseudocode.~~ **Resolved:** NPC pricing algorithm fully specified. Regional price seeds documented. Demand pressure model defined. Backend tables added. | -| **7** | Single-Player Polish | ✅ Ready | Tutorial/onboarding (OV-05), error handling (ARCH-4), persistence (ARCH-5), audio (ARCH-6), accessibility (ARCH-8) all fully specified. Tier 0 Zora demo exists. Mission system fully specified. | -| **8–15** | Era 2 | ✅ Ready | Era 2 depends on SpacetimeDB integration. Chat demo exists. World event UX fully specified. Corporations and territory fully specified (SOC-CORP). Multiplayer combat needs spec work (Phase 13 scope). | - ---- - -## 7. Priority Recommendations - -### Immediate (before starting Phase 0) - -1. ~~**Standardize currency naming**~~ ✅ **Done.** ISK + ₢ symbol. -2. ~~**Clarify core loop scope**~~ ✅ **Done.** Steps 1–7 = Era 1, Step 8 = Era 2. -3. ~~**Create `ship_types` and `modules_catalog` table specs**~~ ✅ **Done.** Added to Backend → Tables tab. - -### Before Phase 3 (Combat) - -4. ~~**Specify NPC pirate AI behavior**~~ ✅ **Done.** Gameplay → NPC Pirates tab. -5. ~~**Define security level system**~~ ✅ **Done.** Gameplay → Security Levels tab. -6. ~~**Specify CONCORD response model**~~ ✅ **Done.** Gameplay → CONCORD tab. - -### Before Phase 6 (Economy) - -7. ~~**Detail NPC price adjustment algorithm**~~ ✅ **Done.** Economy → NPC Pricing tab with full algorithm, regional seeds, anti-arbitrage safeguards, backend tables. -8. ~~**Create a chat prototype**~~ ✅ **Done.** Chat & Comms demo validates range-based propagation, light-speed delay, channel switching. - -### Before Era 2 - -9. ~~**Design the Galaxy Map**~~ ✅ **Done.** Era 2 Galaxy Map demo exists. Era 1 System Map is Phase 1 implementation scope. -10. ~~**World event UX prototype**~~ ✅ **Done.** Full UX spec added to Gameplay → World Events UX tab. -11. ~~**Clarify AI Crew vs Zora relationship**~~ ✅ **Done.** Added clarifying callout to Ships → AI Crew tab. -12. ~~**Corporations & Territory spec**~~ ✅ **Done.** Full spec added to Social → Corporations tab. 6 new tables, 11 new reducers, 3 structure tiers, sovereignty mechanics. Phase 14 scope. - -### Ongoing - -12. ~~**Add missing backend tables**~~ ✅ **Done.** All tables added to Backend → Tables tab including regions, constellations, factions. -13. ~~**Create a Zora Tier 0 demo**~~ ✅ **Done.** Deterministic template engine with 15 events × 5 soul depths, module gating, personality axes. Validates soul depth progression and module tradeoffs. -14. ~~**Error handling & reconnection spec**~~ ✅ **Done.** Architecture → ARCH-4. -15. ~~**Session persistence spec**~~ ✅ **Done.** Architecture → ARCH-5. -16. ~~**Audio design spec**~~ ✅ **Done.** Architecture → ARCH-6. -17. ~~**Localization decision**~~ ✅ **Done.** Architecture → ARCH-7. -18. ~~**Accessibility spec**~~ ✅ **Done.** Architecture → ARCH-8. - ---- - -## 8. What's Working Well - -The gaps above should not overshadow what this GDD does exceptionally well: - -- **Consistent vision**: The "spreadsheet simulator" design pillar is maintained across all pages. No page accidentally designs a flight sim. -- **Roadmap is honest**: Phase 0 is marked "in progress" and every subsequent phase has a concrete done-when condition. -- **Ship AI design is exceptional**: The soul/module/agent architecture is one of the most detailed and original companion AI designs I've seen in a game design document. The three implementation tiers (deterministic → LLM-assisted → full agent) show practical engineering thinking. -- **Backend schema is unusually detailed for a GDD**: 56+ tables with field descriptions is ahead of most game prototypes at this stage. -- **Demo coverage**: 11 interactive demos for a pre-Phase-0 GDD is impressive. Each demo honestly states its limitations. -- **Agent lifecycle system**: The scheduled_agents model with uniform lifecycle, three scheduling strategies, and kill-switch is production-quality thinking. -- **Information diffusion model**: The economy's info-asymmetry design is well-thought-through and explicitly tied to gameplay loops. diff --git a/archive/legacy-static/gdd-docs-hub.html.artifact.json b/archive/legacy-static/gdd-docs-hub.html.artifact.json deleted file mode 100644 index 0c1ffef..0000000 --- a/archive/legacy-static/gdd-docs-hub.html.artifact.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "version": 1, - "kind": "html", - "title": "GDD::DOCS — Game Design Documentation Hub", - "entry": "gdd-docs-hub.html", - "renderer": "html", - "status": "complete", - "exports": [ - "html", - "pdf", - "zip" - ], - "createdAt": "2026-05-22T01:27:51.600Z", - "updatedAt": "2026-05-22T01:27:51.696Z", - "sourceSkillId": "blog-post", - "metadata": { - "identifier": "gdd-docs-hub", - "artifactType": "text/html", - "inferred": false - } -} \ No newline at end of file diff --git a/archive/legacy-static/js/app.js b/archive/legacy-static/js/app.js deleted file mode 100644 index 153274e..0000000 --- a/archive/legacy-static/js/app.js +++ /dev/null @@ -1,157 +0,0 @@ -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 ( -
-
Loading module…
-
- ); - } - - if (loadError) { - return ( -
-
Failed to load page: {loadError}
- -
- ); - } - - const compName = PAGE_COMPONENTS[page]; - const Comp = compName && GDD[compName]; - if (!Comp) return ; - return ; - }; - - const isFullscreen = FULLSCREEN_PAGES.has(page); - - if (isFullscreen) { - return ( -
-
-
- {renderPage()} -
-
-
- ); - } - - return ( -
- setCollapsed(c => !c)} - /> -
- setCollapsed(c => !c)} - /> -
- {renderPage()} -
-
-
- ); -} - -const root = ReactDOM.createRoot(document.getElementById('root')); -root.render(); diff --git a/archive/legacy-static/js/components/sidebar.js b/archive/legacy-static/js/components/sidebar.js deleted file mode 100644 index 2262e37..0000000 --- a/archive/legacy-static/js/components/sidebar.js +++ /dev/null @@ -1,79 +0,0 @@ -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 ( - - ); -} - -GDD.Sidebar = Sidebar; diff --git a/archive/legacy-static/js/components/topbar.js b/archive/legacy-static/js/components/topbar.js deleted file mode 100644 index 983af23..0000000 --- a/archive/legacy-static/js/components/topbar.js +++ /dev/null @@ -1,46 +0,0 @@ -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 ( -
- -
- GDD - / - {section} - / - {title} -
-
- Connected - - Prototype v0.1.0 -
-
- ); -} - -GDD.TopBar = TopBar; diff --git a/archive/legacy-static/js/demos/bounty.js b/archive/legacy-static/js/demos/bounty.js deleted file mode 100644 index 29a3f54..0000000 --- a/archive/legacy-static/js/demos/bounty.js +++ /dev/null @@ -1,425 +0,0 @@ -window.GDD = window.GDD || {}; - -const { useState, useEffect, useCallback, useRef } = React; - -function BountyDemo() { - const [bounties, setBounties] = useState([]); - const [killFeed, setKillFeed] = useState([]); - const [placeBountyTarget, setPlaceBountyTarget] = useState(''); - const [placeBountyAmount, setPlaceBountyAmount] = useState(5000); - const [showPlaceBounty, setShowPlaceBounty] = useState(false); - const [notifications, setNotifications] = useState([]); - const [autoFeed, setAutoFeed] = useState(false); - const feedRef = useRef(null); - const autoRef = useRef(null); - - const feedNames = [ - 'CMDR Picard', 'CMDR Worf', 'CMDR Data', 'CMDR Troi', 'CMDR Riker', - 'MinerBob', 'PirateKing99', 'NullSecWarlord', 'TraderAlice', 'DeepMiner', - 'RockHound', 'AmarrTrader', 'BulkMiner', 'GallenteForge', 'HighSecOps', - ]; - - const shipTypes = ['Frigate', 'Destroyer', 'Cruiser', 'Battlecruiser', 'Battleship', 'Hauler', 'Mining Barge']; - - const systems = ['Sol', 'Amarr', 'Hek', 'Rens', 'Dodixie', 'U-IRTYR', 'PF-346', 'YZ-LQL', 'O-WAMW']; - - const tierConfig = [ - { tier: 'Petty', threshold: 500, color: 'var(--muted)', reward: '10%', visibility: 'System-local' }, - { tier: 'Standard', threshold: 5000, color: 'var(--cyan)', reward: '15%', visibility: 'Regional' }, - { tier: 'Dangerous', threshold: 50000, color: 'var(--accent)', reward: '20%', visibility: 'Galaxy-wide' }, - { tier: 'Most Wanted', threshold: 500000, color: 'var(--red)', reward: '25%', visibility: 'Galaxy + Leaderboard' }, - ]; - - const getTier = (pool) => { - if (pool >= 500000) return tierConfig[3]; - if (pool >= 50000) return tierConfig[2]; - if (pool >= 5000) return tierConfig[1]; - return tierConfig[0]; - }; - - useEffect(() => { - window.GDD.api.getBounties().then(b => setBounties(b)); - window.GDD.api.getKillFeed().then(k => setKillFeed(k)); - }, []); - - const addNotif = useCallback((msg, color) => { - const id = Date.now(); - setNotifications(prev => [...prev, { id, msg, color }]); - setTimeout(() => setNotifications(prev => prev.filter(n => n.id !== id)), 3500); - }, []); - - const handlePlaceBounty = useCallback(async () => { - if (!placeBountyTarget.trim()) return; - if (placeBountyAmount < 500) { - addNotif('Minimum bounty is 500 ISK.', 'var(--red)'); - return; - } - const result = await window.GDD.api.placeBounty(placeBountyTarget, placeBountyAmount); - if (result.success) { - setBounties(prev => { - const existing = prev.find(b => b.target === placeBountyTarget); - if (existing) { - return prev.map(b => b.target === placeBountyTarget - ? { ...b, pool: b.pool + placeBountyAmount, tier: getTier(b.pool + placeBountyAmount).tier } - : b); - } - return [...prev, { - target: placeBountyTarget, - pool: placeBountyAmount, - tier: getTier(placeBountyAmount).tier, - lastHostile: 'Just now', - }]; - }); - addNotif(`Bounty of ₢${placeBountyAmount.toLocaleString()} placed on ${placeBountyTarget}.`, 'var(--green)'); - setShowPlaceBounty(false); - setPlaceBountyTarget(''); - setPlaceBountyAmount(5000); - } - }, [placeBountyTarget, placeBountyAmount, addNotif]); - - // Auto-generate kill feed - const generateKill = useCallback(() => { - const victim = feedNames[Math.floor(Math.random() * feedNames.length)]; - let killer; - do { killer = feedNames[Math.floor(Math.random() * feedNames.length)]; } while (killer === victim); - const ship = shipTypes[Math.floor(Math.random() * shipTypes.length)]; - const system = systems[Math.floor(Math.random() * systems.length)]; - const bounty = Math.random() > 0.6 ? Math.floor(Math.random() * 50000) : 0; - - const kill = { - victim, - killer, - ship, - system, - bounty, - time: 'Just now', - }; - - setKillFeed(prev => [kill, ...prev.slice(0, 49)]); - - // If bounty, update bounty pool - if (bounty > 0) { - addNotif(`Bounty collected: ${killer} claimed ₢${bounty.toLocaleString()} from ${victim}'s bounty.`, 'var(--accent)'); - setBounties(prev => prev.map(b => - b.target === victim - ? { ...b, pool: Math.max(0, b.pool - bounty) } - : b - ).filter(b => b.pool > 0)); - } - }, [addNotif]); - - useEffect(() => { - if (autoFeed) { - autoRef.current = setInterval(generateKill, 2000 + Math.random() * 3000); - } else { - if (autoRef.current) clearInterval(autoRef.current); - } - return () => { if (autoRef.current) clearInterval(autoRef.current); }; - }, [autoFeed, generateKill]); - - // Auto-scroll kill feed - useEffect(() => { - if (killFeed.length > 0 && feedRef.current) { - // No auto-scroll needed since newest are on top - } - }, [killFeed]); - - const totalBountyPool = bounties.reduce((sum, b) => sum + b.pool, 0); - const totalKills = killFeed.length; - const totalBountyCollected = killFeed.reduce((sum, k) => sum + k.bounty, 0); - - return ( -
- e.currentTarget.style.color='var(--fg-bright)'} onMouseLeave={e => e.currentTarget.style.color='var(--muted)'}>← Back to Docs -

Bounty Board & Kill Feed

-

- Live bounty board with tier escalation and a galaxy-wide kill feed. Place bounties on pirates, - track kill events, and watch bounty pools climb. Toggle the auto-feed to simulate live combat activity. -

- - {/* HUD-style bounty strip */} -
- BOUNTY BOARD -
- ACTIVE - {bounties.length} -
- POOL - ₢{totalBountyPool.toLocaleString()} -
- COLLECTED - ₢{totalBountyCollected.toLocaleString()} - {autoFeed && ● LIVE FEED} -
- - {/* Notifications */} -
- {notifications.map(n => ( -
- {n.msg} -
- ))} -
- - {/* Stats */} -
-
-
{bounties.length}
-
Active Bounties
-
-
-
₢{totalBountyPool.toLocaleString()}
-
Total Bounty Pool
-
-
-
{totalKills}
-
Kill Events
-
-
-
₢{totalBountyCollected.toLocaleString()}
-
Bounty Collected
-
-
- -
- - - -
- - {/* Place bounty modal */} - {showPlaceBounty && ( -
setShowPlaceBounty(false)}> -
e.stopPropagation()}> -

Place a Bounty

- -
-
Target Player
- setPlaceBountyTarget(e.target.value)} - placeholder="Enter player name..." - style={{ - width: '100%', padding: 'var(--sp-2) var(--sp-3)', background: 'var(--surface-raised)', - border: '1px solid var(--border)', borderRadius: 'var(--radius-md)', - color: 'var(--fg)', fontFamily: 'var(--font-mono)', fontSize: '0.85rem', - }} - /> -
- -
-
- Amount (min 500 ISK) -
- setPlaceBountyAmount(parseInt(e.target.value) || 0)} - style={{ - width: '100%', padding: 'var(--sp-2) var(--sp-3)', background: 'var(--surface-raised)', - border: '1px solid var(--border)', borderRadius: 'var(--radius-md)', - color: 'var(--fg)', fontFamily: 'var(--font-mono)', fontSize: '0.85rem', - }} - /> -
- - {/* Tier preview */} -
-
Resulting Tier
- {(() => { - const t = getTier(placeBountyAmount); - return ( -
- - {t.tier} - - - Hunter reward: {t.reward} · {t.visibility} - -
- ); - })()} -
- -
- - -
-
-
- )} - -
- {/* Active Bounties */} -
-

- Active Bounties -

- - {bounties.length === 0 && ( -
- No active bounties. Place one to get started. -
- )} - - {bounties.sort((a, b) => b.pool - a.pool).map((bounty, i) => { - const tier = getTier(bounty.pool); - return ( -
-
-
-

{bounty.target}

- - {tier.tier.toUpperCase()} - -
-
-
- ₢{bounty.pool.toLocaleString()} -
-
- Hunter reward: {tier.reward} -
-
-
- -
- Visibility: {tier.visibility} - Last hostile: {bounty.lastHostile} -
- - {/* Pool bar */} -
-
-
-
-
- ₢{tier.threshold.toLocaleString()} - Next tier -
-
-
- ); - })} - - {/* Tier legend */} -
-

Bounty Tiers

- {tierConfig.map((t, i) => ( -
-
- {t.tier} - - ≥ ₢{t.threshold.toLocaleString()} - -
-
- {t.reward} · {t.visibility} -
-
- ))} -
-
- - {/* Kill Feed */} -
-

- Kill Feed - {autoFeed && ( - - ● LIVE - - )} -

- -
- {killFeed.length === 0 && ( -
- No kill events yet. Start the live feed or generate events manually. -
- )} - - {killFeed.map((kill, i) => ( -
0 ? 'var(--accent-border)' : 'var(--border)'}`, - borderRadius: 'var(--radius-md)', - transition: 'background 0.3s', - }}> -
-
- {kill.victim} - destroyed by - {kill.killer} -
- - {kill.time} - -
-
- - Ship: {kill.ship} - - - System: {kill.system} - - {kill.bounty > 0 && ( - - Bounty: ₢{kill.bounty.toLocaleString()} - - )} -
-
- ))} -
-
-
- - {/* Anti-abuse rules */} -
- Anti-abuse rules (implemented in backend): You cannot claim your own bounty (alt check). - Payout never exceeds ship loss value. Minimum placement is 500 ISK. Target must have negative security - status or committed a hostile act within 24h. Bounties decay 10%/week if target stays clean for 30 days. -
-
- ); -} - -window.GDD.BountyDemo = BountyDemo; diff --git a/archive/legacy-static/js/demos/chat.js b/archive/legacy-static/js/demos/chat.js deleted file mode 100644 index afda587..0000000 --- a/archive/legacy-static/js/demos/chat.js +++ /dev/null @@ -1,394 +0,0 @@ -window.GDD = window.GDD || {}; - -const { useState, useEffect, useCallback, useRef } = React; - -function ChatDemo() { - // ── State ── - const [activeChannel, setActiveChannel] = useState('local'); - const [messages, setMessages] = useState([]); - const [inputText, setInputText] = useState(''); - const [playerName] = useState('CMDR Kimura'); - const [playerSystem] = useState('Jita'); - const [simTime, setSimTime] = useState(0); - const [speed, setSpeed] = useState(1); - const [running, setRunning] = useState(false); - const messagesEndRef = useRef(null); - const tickRef = useRef(null); - - // ── Simulated players in different systems ── - const players = [ - { name: 'CMDR Vasquez', system: 'Jita', distance: 0 }, - { name: 'CMDR Chen', system: 'Jita', distance: 0 }, - { name: 'CMDR Okafor', system: 'Amarr', distance: 6 }, - { name: 'CMDR Lindström', system: 'Amarr', distance: 6 }, - { name: 'CMDR Tanaka', system: 'Rens', distance: 12 }, - { name: 'CMDR Dubois', system: 'Hek', distance: 18 }, - { name: 'CMDR Voronov', system: 'PF-346', distance: 32 }, - ]; - - // ── Channel definitions ── - const channels = [ - { id: 'local', name: 'Local', range: 'Current System', delay: 'Instant', icon: '📡', color: 'var(--fg-bright)' }, - { id: 'trade', name: 'Trade', range: 'Station / Region', delay: '0–30s', icon: '💰', color: 'var(--green)' }, - { id: 'private', name: 'Private (Okafor)', range: 'Distance-based', delay: '~12s (6 jumps)', icon: '✉', color: 'var(--cyan)' }, - { id: 'fleet', name: 'Fleet [Post-MVP]', range: 'Fleet members', delay: 'Instant', icon: '🚀', color: 'var(--purple)', disabled: true }, - ]; - - // ── Pre-seeded message corpus ── - const seedMessages = { - local: [ - { from: 'CMDR Vasquez', text: 'Anyone seen a Veldspar belt that isn\'t depleted?', time: -45 }, - { from: 'CMDR Chen', text: 'Try belt 7-2, was full 10 min ago', time: -38 }, - { from: 'CMDR Vasquez', text: 'Thanks, warping now', time: -35 }, - { from: 'CMDR Chen', text: 'Watch out, saw a Corpii frigate on scan near 7-2', time: -30 }, - { from: 'CMDR Vasquez', text: 'Corpii? In high-sec? That\'s unusual', time: -25 }, - { from: 'System', text: '⚠ CONCORD response dispatched in Jita — criminal act in progress near Jita IV', time: -20, isSystem: true }, - { from: 'CMDR Chen', text: 'Well that explains the locals being jumpy', time: -15 }, - ], - trade: [ - { from: 'CMDR Chen', text: 'WTS 5000 Tritanium @ 3.15/unit — Jita IV docked', time: -60 }, - { from: 'CMDR Vasquez', text: 'WTB Nocxium x200, paying market +5%. Jita.', time: -50 }, - { from: 'Market Bot', text: '📊 Scordite volume up 340% in Jita in last hour. Spread widening.', time: -40, isSystem: true }, - { from: 'CMDR Chen', text: 'That Scordite spike is because someone bought out the entire sell wall at Jita IV', time: -30 }, - ], - private: [ - { from: 'CMDR Okafor', text: 'Hey, you still in Jita?', time: -120 }, - { from: 'CMDR Okafor', text: 'Megacyte just crashed 15% in Amarr. Someone panic-sold a freighter load.', time: -90 }, - { from: 'CMDR Okafor', text: 'If you can haul fast, there\'s a 20% spread between Amarr buy and Jita sell', time: -75 }, - ], - }; - - // ── Delay calculation ── - const getDelay = (fromDistance) => { - if (fromDistance === 0) return 0; - return Math.round(2 * Math.sqrt(fromDistance)); - }; - - const formatDelay = (seconds) => { - if (seconds === 0) return 'instant'; - if (seconds < 60) return `~${seconds}s`; - return `~${Math.floor(seconds / 60)}m ${seconds % 60}s`; - }; - - // ── Simulation ── - useEffect(() => { - if (!running) { - if (tickRef.current) clearInterval(tickRef.current); - return; - } - tickRef.current = setInterval(() => { - setSimTime(t => t + speed); - }, 500); - return () => clearInterval(tickRef.current); - }, [running, speed]); - - // Seed initial messages - useEffect(() => { - const initial = []; - Object.entries(seedMessages).forEach(([channel, msgs]) => { - msgs.forEach(msg => { - initial.push({ - id: `${channel}-${msg.time}-${msg.from}`, - channel, - from: msg.from, - text: msg.text, - isSystem: msg.isSystem || false, - timestamp: msg.time, - deliveredAt: msg.time + (channel === 'private' ? getDelay(6) : 0), - delay: channel === 'private' ? getDelay(6) : 0, - status: 'delivered', - }); - }); - }); - initial.sort((a, b) => a.deliveredAt - b.deliveredAt); - setMessages(initial); - }, []); - - // Check for delayed message delivery - useEffect(() => { - if (!running) return; - setMessages(prev => prev.map(m => { - if (m.status === 'pending' && simTime >= m.deliveredAt) { - return { ...m, status: 'delivered' }; - } - return m; - })); - }, [simTime, running]); - - useEffect(() => { - messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); - }, [messages]); - - const sendMessage = () => { - if (!inputText.trim()) return; - const now = simTime; - let delay = 0; - if (activeChannel === 'private') delay = getDelay(6); - if (activeChannel === 'trade') delay = Math.floor(Math.random() * 30); - - const msg = { - id: `user-${Date.now()}`, - channel: activeChannel, - from: playerName, - text: inputText.trim(), - isSystem: false, - timestamp: now, - deliveredAt: now + delay, - delay, - status: delay === 0 ? 'delivered' : 'pending', - }; - setMessages(prev => [...prev, msg]); - setInputText(''); - - // Simulate NPC response in local - if (activeChannel === 'local' && Math.random() > 0.5) { - const responder = players.filter(p => p.system === playerSystem && p.name !== playerName); - if (responder.length > 0) { - const responder_ = responder[Math.floor(Math.random() * responder.length)]; - const responses = [ - 'Copy that.', - 'Interesting. Keep us posted.', - 'Acknowledged.', - 'Seen it. Be careful out there.', - 'Good luck.', - ]; - setTimeout(() => { - setMessages(prev => [...prev, { - id: `npc-${Date.now()}`, - channel: 'local', - from: responder_.name, - text: responses[Math.floor(Math.random() * responses.length)], - isSystem: false, - timestamp: simTime + 3, - deliveredAt: simTime + 3, - delay: 0, - status: 'delivered', - }]); - }, 1500); - } - } - }; - - const visibleMessages = messages.filter(m => { - if (m.channel !== activeChannel) return false; - if (m.status === 'pending') return true; - return true; - }); - - const channelInfo = channels.find(c => c.id === activeChannel); - - return ( -
- {/* Header */} -
- -
- Speed: - {[0.5, 1, 2, 5].map(s => ( - - ))} - - - T+{simTime.toFixed(0)}s - -
-
- -
- {/* Channel sidebar */} -
-
- Channels -
- {channels.map(ch => ( - - ))} - -
- Nearby Pilots -
- {players.filter(p => p.system === playerSystem).map(p => ( -
- {p.name} -
- ))} - -
- Distant Pilots -
- {players.filter(p => p.system !== playerSystem).map(p => ( -
- {p.name} - ({p.distance}j) -
- ))} -
- - {/* Main chat area */} -
- {/* Channel info bar */} -
-
- {channelInfo?.icon} - {channelInfo?.name} - - {channelInfo?.range} - -
-
- Delay: {channelInfo?.delay} -
-
- - {/* Messages */} -
- {visibleMessages.length === 0 && ( -
- No messages yet. Press ▶ Run to start the simulation. -
- )} - {visibleMessages.map(msg => ( -
-
- - {msg.isSystem ? '⚠ System' : msg.from} - - - {msg.status === 'pending' - ? `⏳ delivering… (${formatDelay(msg.delay)} light-speed delay)` - : msg.delay > 0 - ? `T+${msg.deliveredAt.toFixed(0)}s (delayed ${formatDelay(msg.delay)})` - : `T+${msg.timestamp.toFixed(0)}s`} - -
-
- {msg.text} -
-
- ))} -
-
- - {/* Input */} -
- setInputText(e.target.value)} - onKeyDown={e => { if (e.key === 'Enter') sendMessage(); }} - placeholder={activeChannel === 'private' ? `Message CMDR Okafor (${formatDelay(getDelay(6))} delay)...` : `Send to ${channelInfo?.name}...`} - style={{ - flex: 1, padding: 'var(--sp-2) var(--sp-3)', - borderRadius: 'var(--radius-sm)', border: '1px solid var(--border)', - background: 'var(--surface-base)', color: 'var(--fg)', - fontSize: '0.85rem', outline: 'none', - }} - /> - -
-
- - {/* Right sidebar: delay visualization */} -
-
- Light-Speed Delay Map -
- -
- Messages to/from pilots in other systems travel at light speed. Formula: 2 × √(jumps) seconds. -
- - {players.map(p => { - const delay = getDelay(p.distance); - const isLocal = p.distance === 0; - return ( -
15 ? 'var(--red)' : delay > 5 ? 'var(--amber)' : 'var(--cyan)'}`, - }}> -
{p.name}
-
- {p.system} - 15 ? 'var(--red)' : 'var(--amber)' }}> - {isLocal ? 'instant' : `~${delay}s`} - -
- {p.distance > 0 && ( -
-
15 ? 'var(--red)' : delay > 5 ? 'var(--amber)' : 'var(--cyan)', - borderRadius: '2px', - transition: 'width 0.3s ease', - }} /> -
- )} -
- ); - })} - -
- What This Validates -
-
    -
  • Light-speed delay feels meaningful — not instant
  • -
  • Private messages arrive after a visible wait
  • -
  • Local chat is instant, creating information asymmetry
  • -
  • Trade channel has moderate delay (regional relay)
  • -
  • System messages (CONCORD, market) are immediate
  • -
-
-
-
- ); -} - -window.GDD.ChatDemo = ChatDemo; diff --git a/archive/legacy-static/js/demos/combat.js b/archive/legacy-static/js/demos/combat.js deleted file mode 100644 index 73e7c17..0000000 --- a/archive/legacy-static/js/demos/combat.js +++ /dev/null @@ -1,1044 +0,0 @@ -window.GDD = window.GDD || {}; - -const { useState, useEffect, useRef, useCallback } = React; -const TH = window.GDD.THREE; - -/* ═══════════════════════════════════════════════════════════════════ - Combat System — FTL-style Reactor Power Management - Immersive Game HUD — full 3D viewport with diegetic overlays - ═══════════════════════════════════════════════════════════════════ */ - -const MAX_POWER = 8; -const TICK_MS = 50; - -// ── 3D Projectile Pool ── -function createProjectilePool3D(scene) { - const pool = []; - return { - spawn(x, y, z, tx, ty, tz, color, dmg, type, subsystem) { - let mesh; - if (type === 'beam') { - const dx = tx - x, dy = ty - y, dz = tz - z; - const len = Math.sqrt(dx*dx + dy*dy + dz*dz); - const geo = new THREE.CylinderGeometry(0.08, 0.08, len, 4); - geo.rotateX(Math.PI / 2); - const mat = new THREE.MeshBasicMaterial({ color, transparent: true, opacity: 0.9 }); - mesh = new THREE.Mesh(geo, mat); - mesh.position.set((x + tx) / 2, (y + ty) / 2, (z + tz) / 2); - mesh.lookAt(tx, ty, tz); - mesh.userData = { life: 8, maxLife: 8, type, dmg, subsystem, tx, ty, tz, color }; - } else if (type === 'pulse') { - const geo = new THREE.RingGeometry(1, 2, 24); - const mat = new THREE.MeshBasicMaterial({ color, transparent: true, opacity: 0.7, side: THREE.DoubleSide }); - mesh = new THREE.Mesh(geo, mat); - mesh.position.set(x, y, z); - mesh.userData = { life: 20, maxLife: 20, type, dmg: 0, subsystem: 'none', scale: 1 }; - } else { - mesh = new THREE.Mesh( - new THREE.SphereGeometry(type === 'missile' ? 0.2 : 0.12, 4, 4), - new THREE.MeshBasicMaterial({ color }) - ); - const dx = tx - x, dy = ty - y, dz = tz - z; - const dist = Math.sqrt(dx*dx + dy*dy + dz*dz); - const speed = type === 'missile' ? 1.5 : 2.5; - mesh.position.set(x, y, z); - mesh.userData = { vx: (dx / dist) * speed, vy: (dy / dist) * speed, vz: (dz / dist) * speed, life: Math.ceil(dist / speed), maxLife: Math.ceil(dist / speed), type, dmg, subsystem, tx, ty, tz, color }; - } - scene.add(mesh); - pool.push(mesh); - }, - tick() { - for (let i = pool.length - 1; i >= 0; i--) { - const m = pool[i]; - const ud = m.userData; - ud.life--; - if (ud.type === 'beam' || ud.type === 'pulse') { - m.material.opacity = Math.max(0, ud.life / ud.maxLife); - if (ud.type === 'pulse') { - ud.scale += 0.15; - m.scale.setScalar(ud.scale); - } - } else { - m.position.x += ud.vx; - m.position.y += ud.vy; - m.position.z += ud.vz; - } - if (ud.life <= 0) { - scene.remove(m); - m.geometry.dispose(); - m.material.dispose(); - pool.splice(i, 1); - } - } - }, - getArrived() { - return pool.filter(m => m.userData.life <= 1); - }, - clear() { - pool.forEach(m => { scene.remove(m); m.geometry.dispose(); m.material.dispose(); }); - pool.length = 0; - }, - get all() { return pool; }, - }; -} - -// ── Impact flash pool ── -function createImpactPool3D(scene) { - const impacts = []; - return { - spawn(x, y, z, color, size) { - const glow = TH.createGlowSprite(color, size || 3); - glow.position.set(x, y, z); - scene.add(glow); - impacts.push({ mesh: glow, life: 10, maxLife: 10 }); - }, - tick() { - for (let i = impacts.length - 1; i >= 0; i--) { - impacts[i].life--; - const progress = 1 - impacts[i].life / impacts[i].maxLife; - impacts[i].mesh.material.opacity = (1 - progress) * 0.8; - impacts[i].mesh.scale.setScalar((impacts[i].mesh.scale.x || 3) * (1 + progress * 0.5)); - if (impacts[i].life <= 0) { - scene.remove(impacts[i].mesh); - impacts[i].mesh.material.map?.dispose(); - impacts[i].mesh.material.dispose(); - impacts.splice(i, 1); - } - } - }, - }; -} - -function CombatDemo() { - const containerRef = useRef(null); - const sceneRef = useRef(null); - const animIdRef = useRef(null); - const tickRef = useRef(0); - const projectilePoolRef = useRef(null); - const impactPoolRef = useRef(null); - - // Ship 3D refs - const playerShipRef = useRef(null); - const enemyShipRef = useRef(null); - const playerShieldRef = useRef(null); - const enemyLockRef = useRef(null); - const targetLineRef = useRef(null); - - const playerPosRef = useRef({ orbitAngle: 0, strafeTimer: 0, speed: 0 }); - const enemyPosRef = useRef({ orbitAngle: Math.PI }); - - // ── Player State ── - const [player, setPlayer] = useState({ - shields: 100, armor: 100, hull: 100, energy: 100, maxEnergy: 100, - speed: 0, maxSpeed: 420, name: 'USS ENTERPRISE', class: 'VENTURE-CLASS', - }); - const playerRef = useRef(player); - useEffect(() => { playerRef.current = player; }, [player]); - - // ── Power Allocation ── - const [power, setPower] = useState({ weapons: 3, shields: 2, engines: 2, aux: 1 }); - const powerRef = useRef(power); - useEffect(() => { powerRef.current = power; }, [power]); - - // ── Ship Modules ── - const [modules, setModules] = useState([ - { id: 'q', key: '1', name: 'Railgun', icon: '⊕', type: 'weapon', cd: 0, maxCd: 3, cost: 12, damage: 18, desc: 'Kinetic turret', color: '#ef4444' }, - { id: 'w', key: '2', name: 'Shield Bst', icon: '◎', type: 'shield', cd: 0, maxCd: 8, cost: 20, damage: 0, desc: 'Burst recharge', color: '#22d3ee' }, - { id: 'e', key: '3', name: 'EM Pulse', icon: '⟐', type: 'ewar', cd: 0, maxCd: 12, cost: 30, damage: 0, desc: 'Disrupt systems', color: '#a78bfa' }, - { id: 'r', key: '4', name: 'Overload', icon: '⚡', type: 'reactor', cd: 0, maxCd: 30, cost: 45, damage: 0, desc: 'Push reactor', color: '#f0a030' }, - { id: 'd', key: '5', name: 'Afterburn', icon: '»', type: 'engine', cd: 0, maxCd: 6, cost: 10, damage: 0, desc: 'Emergency thrust', color: '#22c55e' }, - { id: 'f', key: '6', name: 'Hull Patch', icon: '✚', type: 'repair', cd: 0, maxCd: 15, cost: 25, damage: 0, desc: 'Nanite repair', color: '#fb923c' }, - ]); - const modulesRef = useRef(modules); - useEffect(() => { modulesRef.current = modules; }, [modules]); - - const [playerBuffs, setPlayerBuffs] = useState([{ id: 'b1', name: 'Dmg Ctrl', icon: '↯', duration: -1, color: '#22c55e' }]); - const [enemyBuffs, setEnemyBuffs] = useState([]); - const [target, setTarget] = useState(null); - const [subsystem, setSubsystem] = useState('hull'); - const [enemy, setEnemy] = useState({ - name: 'Guristas Pirata', class: 'Frigate', shields: 100, armor: 100, hull: 100, - weapons: 100, engines: 100, locked: false, lockTimer: 0, lockTime: 3, - }); - const enemyRef = useRef(enemy); - useEffect(() => { enemyRef.current = enemy; }, [enemy]); - const targetRef = useRef(target); - useEffect(() => { targetRef.current = target; }, [target]); - const subsystemRef = useRef(subsystem); - useEffect(() => { subsystemRef.current = subsystem; }, [subsystem]); - const [overloaded, setOverloaded] = useState(false); - const overloadRef = useRef(false); - const [combatLog, setCombatLog] = useState([]); - const logRef = useRef([]); - const logScrollRef = useRef(null); - const addLog = useCallback((msg, color) => { - const time = new Date().toLocaleTimeString('en', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' }); - logRef.current = [...logRef.current.slice(-50), { time, msg, color }]; - setCombatLog(logRef.current); - }, []); - const autoFireTimerRef = useRef(0); - const enemyFireTimerRef = useRef(0); - - // ── Derived stats ── - const totalPower = power.weapons + power.shields + power.engines + power.aux; - const weaponMult = 1 + power.weapons * 0.20; - const shieldRegen = power.shields * 1.2; - const shieldAbsorb = 0.4 + power.shields * 0.08; - const dodgeChance = power.engines * 4; - const speedMult = 1 + power.engines * 0.25; - const cdReduction = power.aux * 6; - const energyRegen = 2 + power.aux * 2; - - // ── Build 3D scene ── - useEffect(() => { - const container = containerRef.current; - if (!container) return; - - const scene = new THREE.Scene(); - scene.fog = new THREE.FogExp2(0x040810, 0.0008); - - const camera = new THREE.PerspectiveCamera(55, container.clientWidth / container.clientHeight, 0.1, 3000); - camera.position.set(0, 35, 60); - camera.lookAt(0, 0, 0); - - const renderer = TH.createRenderer(container, { clearColor: 0x040810 }); - TH.handleResize(renderer, camera, container); - - const stars = TH.createStarField(3000, 2000); - scene.add(stars); - TH.addNebula(scene, 0x22d3ee, [-100, 40, -200], 200); - TH.addNebula(scene, 0xa78bfa, [80, -30, -150], 150); - - const grid = new THREE.GridHelper(300, 15, 0x0d1520, 0x0d1520); - grid.material.transparent = true; - grid.material.opacity = 0.12; - scene.add(grid); - TH.setupSpaceLighting(scene); - - // Player ship - const playerGroup = new THREE.Group(); - const pMesh = TH.createShipMesh(0xc8d6e5, 0xf0a030, 0.6); - pMesh.rotation.y = Math.PI / 2; - playerGroup.add(pMesh); - const pEngine = TH.createEngineGlow(0x22d3ee, 3, 15); - pEngine.position.set(0, 0, 6); - playerGroup.add(pEngine); - const pShield = TH.createShield(3, 0x22d3ee, 0.06); - playerGroup.add(pShield); - playerShieldRef.current = pShield; - playerGroup.position.set(-15, 0, 0); - scene.add(playerGroup); - playerShipRef.current = playerGroup; - - // Enemy ship - const enemyGroup = new THREE.Group(); - const eMesh = TH.createShipMesh(0x7f1d1d, 0xef4444, 0.5); - eMesh.rotation.y = Math.PI / 2; - enemyGroup.add(eMesh); - const eEngine = TH.createEngineGlow(0xef4444, 2, 10); - eEngine.position.set(0, 0, 5); - enemyGroup.add(eEngine); - enemyGroup.position.set(15, 0, 0); - scene.add(enemyGroup); - enemyShipRef.current = enemyGroup; - - // Targeting line - const lineGeo = new THREE.BufferGeometry().setFromPoints([new THREE.Vector3(0, 0, 0), new THREE.Vector3(1, 0, 0)]); - const lineMat = new THREE.LineDashedMaterial({ color: 0xf0a030, dashSize: 1, gapSize: 0.5, transparent: true, opacity: 0.2 }); - const targetLine = new THREE.Line(lineGeo, lineMat); - targetLine.computeLineDistances(); - targetLine.visible = false; - scene.add(targetLine); - targetLineRef.current = targetLine; - - // Lock brackets - const lockBrackets = TH.createLockBrackets(3, 0xf0a030); - lockBrackets.visible = false; - scene.add(lockBrackets); - enemyLockRef.current = lockBrackets; - - const projPool = createProjectilePool3D(scene); - projectilePoolRef.current = projPool; - const impPool = createImpactPool3D(scene); - impactPoolRef.current = impPool; - - sceneRef.current = { scene, camera, renderer, stars, playerGroup, enemyGroup, pEngine, eEngine, lockBrackets }; - - const clock = new THREE.Clock(); - const animate = () => { - animIdRef.current = requestAnimationFrame(animate); - const t = clock.getElapsedTime(); - const pwr = powerRef.current; - const eng = enemyRef.current; - const tgt = targetRef.current; - - // Player orbit - const pp = playerPosRef.current; - pp.orbitAngle += 0.008 * (1 + pwr.engines * 0.25); - const pr = 12 + pwr.engines * 1.5; - playerGroup.position.x = -pr * Math.cos(pp.orbitAngle); - playerGroup.position.z = pr * Math.sin(pp.orbitAngle) * 0.6; - playerGroup.position.y = Math.sin(pp.orbitAngle * 1.3) * 2; - if (tgt && eng.locked) playerGroup.lookAt(enemyGroup.position); - - pEngine.intensity = 2 + pwr.engines * 0.5 + (overloadRef.current ? 3 : 0); - if (overloadRef.current) { - pMesh.material.emissive.setHex(0xfbbf24); - pMesh.material.emissiveIntensity = 0.3 + Math.sin(t * 8) * 0.15; - } else { - pMesh.material.emissive.setHex(0xf0a030); - pMesh.material.emissiveIntensity = 0.15; - } - - pShield.material.opacity = 0.03 + pwr.shields * 0.015; - pShield.scale.setScalar(1 + pwr.shields * 0.05); - - // Enemy orbit - const ep = enemyPosRef.current; - const engineScale = eng.engines / 100; - ep.orbitAngle += 0.01 * engineScale; - const er = 14 * engineScale; - enemyGroup.position.x = 15 + er * Math.cos(ep.orbitAngle); - enemyGroup.position.z = er * Math.sin(ep.orbitAngle) * 0.5; - enemyGroup.position.y = Math.sin(ep.orbitAngle * 1.1) * 1.5 * engineScale; - eEngine.intensity = 1.5 * engineScale; - if (tgt) enemyGroup.lookAt(playerGroup.position); - - if (lockBrackets.visible) { - lockBrackets.position.copy(enemyGroup.position); - lockBrackets.rotation.y = t * 0.3; - } - - if (targetLine.visible && tgt && eng.locked) { - const pPos = playerGroup.position; - const ePos = enemyGroup.position; - const positions = targetLine.geometry.attributes.position; - positions.setXYZ(0, pPos.x, pPos.y, pPos.z); - positions.setXYZ(1, ePos.x, ePos.y, ePos.z); - positions.needsUpdate = true; - targetLine.computeLineDistances(); - } - - stars.rotation.y = t * 0.003; - projPool.tick(); - impPool.tick(); - renderer.render(scene, camera); - }; - animate(); - - const onResize = () => TH.handleResize(renderer, camera, container); - window.addEventListener('resize', onResize); - - return () => { - if (animIdRef.current) cancelAnimationFrame(animIdRef.current); - window.removeEventListener('resize', onResize); - projPool.clear(); - }; - }, []); - - // Auto-scroll log - useEffect(() => { - if (logScrollRef.current) logScrollRef.current.scrollTop = logScrollRef.current.scrollHeight; - }, [combatLog]); - - // ── Lock Target ── - const lockTarget = useCallback(() => { - addLog('Initiating target lock...', '#f0a030'); - setEnemy(prev => ({ ...prev, lockTimer: 0, locked: false })); - setTarget('Guristas Pirata'); - if (targetLineRef.current) targetLineRef.current.visible = true; - }, [addLog]); - - // ── Adjust Power ── - const adjustPower = useCallback((system, delta) => { - setPower(prev => { - const newVal = prev[system] + delta; - if (newVal < 0) return prev; - const newTotal = Object.entries(prev).reduce((sum, [k, v]) => sum + (k === system ? newVal : v), 0); - if (newTotal > MAX_POWER) return prev; - return { ...prev, [system]: newVal }; - }); - }, []); - - // ── Cast Ability ── - const castModule = useCallback((abilityId) => { - const ab = modulesRef.current.find(a => a.id === abilityId); - if (!ab) return; - if (!targetRef.current) { addLog('No target locked.', '#ef4444'); return; } - if (!enemyRef.current.locked) { addLog('Target not locked yet.', '#ef4444'); return; } - if (ab.cd > 0) { addLog(`${ab.name} recharging (${ab.cd.toFixed(1)}s).`, '#ef4444'); return; } - const pwr = powerRef.current; - const pl = playerRef.current; - if (pl.energy < ab.cost) { addLog('Insufficient energy.', '#ef4444'); return; } - - setPlayer(prev => ({ ...prev, energy: prev.energy - ab.cost })); - const actualCd = ab.maxCd * (1 - pwr.aux * 0.06); - setModules(prev => prev.map(a => a.id === abilityId ? { ...a, cd: Math.max(0.5, actualCd) } : a)); - - const pPos = playerShipRef.current?.position || { x: -15, y: 0, z: 0 }; - const ePos = enemyShipRef.current?.position || { x: 15, y: 0, z: 0 }; - const proj = projectilePoolRef.current; - const imp = impactPoolRef.current; - - if (ab.id === 'q') { - const dmg = ab.damage * weaponMult; - proj.spawn(pPos.x + 3, pPos.y, pPos.z, ePos.x, ePos.y, ePos.z, 0xef4444, dmg, 'beam', subsystemRef.current); - addLog(`Railgun → ${subsystemRef.current.toUpperCase()} (×${weaponMult.toFixed(1)})`, '#ef4444'); - } - if (ab.id === 'w') { - const restore = 15 + pwr.shields * 6; - setPlayer(prev => ({ ...prev, shields: Math.min(100, prev.shields + restore) })); - imp.spawn(pPos.x, pPos.y, pPos.z, 0x22d3ee, 5); - addLog(`Shield Boost: +${Math.round(restore)}%`, '#22d3ee'); - } - if (ab.id === 'e') { - const dur = 3 + pwr.aux * 0.5; - const mid = { x: (pPos.x + ePos.x) / 2, y: (pPos.y + ePos.y) / 2, z: (pPos.z + ePos.z) / 2 }; - proj.spawn(mid.x, mid.y, mid.z, ePos.x, ePos.y, ePos.z, 0xa78bfa, 0, 'pulse', 'none'); - setEnemyBuffs([{ id: 'emp', name: 'EM Disrupted', icon: '⟐', duration: Math.round(dur), color: '#a78bfa' }]); - setEnemy(prev => ({ ...prev, weapons: Math.max(0, prev.weapons - 15 - pwr.aux * 3), engines: Math.max(0, prev.engines - 10 - pwr.aux * 2) })); - addLog(`EM Pulse! Enemy disrupted ${dur.toFixed(1)}s`, '#a78bfa'); - setTimeout(() => { setEnemyBuffs([]); addLog('Enemy systems restored.', '#5a6b82'); }, dur * 1000); - } - if (ab.id === 'r') { - overloadRef.current = true; - setOverloaded(true); - setPlayerBuffs(prev => [...prev, { id: 'overload', name: 'Overload', icon: '⚡', duration: 8, color: '#f0a030' }]); - addLog('⚡ OVERLOAD — double fire rate 8s!', '#f0a030'); - setTimeout(() => { overloadRef.current = false; setOverloaded(false); setPlayerBuffs(prev => prev.filter(b => b.id !== 'overload')); addLog('Overload ended.', '#5a6b82'); }, 8000); - } - if (ab.id === 'd') { - playerPosRef.current.speed = 300 * speedMult; - addLog(`Afterburners! Speed ×${speedMult.toFixed(1)}`, '#22c55e'); - setTimeout(() => { playerPosRef.current.speed = 0; }, 2500); - } - if (ab.id === 'f') { - const repair = 12 + pwr.aux * 4; - setPlayer(prev => ({ ...prev, hull: Math.min(100, prev.hull + repair) })); - imp.spawn(pPos.x, pPos.y, pPos.z, 0xfb923c, 6); - addLog(`Hull Patch: +${Math.round(repair)}%`, '#fb923c'); - } - }, [addLog, weaponMult, speedMult]); - - // ── Keyboard ── - useEffect(() => { - const handler = (e) => { - const key = e.key.toLowerCase(); - if (['1', '2', '3', '4', '5', '6'].includes(key)) { e.preventDefault(); castModule(key); } - if (key === ' ' && !targetRef.current) { e.preventDefault(); lockTarget(); } - }; - window.addEventListener('keydown', handler); - return () => window.removeEventListener('keydown', handler); - }, [castModule, lockTarget]); - - // ── Combat Tick ── - useEffect(() => { - const interval = setInterval(() => { - tickRef.current++; - const pwr = powerRef.current; - const eng = enemyRef.current; - const sub = subsystemRef.current; - const tgt = targetRef.current; - const tps = 1000 / TICK_MS; - - setModules(prev => prev.map(a => ({ ...a, cd: Math.max(0, a.cd - TICK_MS / 1000) }))); - - const eRegen = (2 + pwr.aux * 2) / tps; - const eDrain = overloadRef.current ? (3 + pwr.weapons * 0.5) / tps : 0; - setPlayer(prev => ({ ...prev, energy: Math.min(prev.maxEnergy, Math.max(0, prev.energy + eRegen - eDrain)) })); - - setPlayer(prev => { - if (prev.shields < 100) return { ...prev, shields: Math.min(100, prev.shields + (pwr.shields * 1.2) / tps) }; - return prev; - }); - - const proj = projectilePoolRef.current; - const imp = impactPoolRef.current; - const pPos = playerShipRef.current?.position; - const ePos = enemyShipRef.current?.position; - if (!pPos || !ePos) return; - - if (tgt && eng.locked && eng.hull > 0) { - const baseInterval = overloadRef.current ? 15 : 40; - autoFireTimerRef.current++; - if (autoFireTimerRef.current >= baseInterval) { - autoFireTimerRef.current = 0; - const bullets = Math.max(1, Math.floor(pwr.weapons / 2)); - const dmgPerBullet = (3 + pwr.weapons * 1.5) / bullets; - for (let b = 0; b < bullets; b++) { - const jx = ePos.x + (Math.random() - 0.5) * 2; - const jy = ePos.y + (Math.random() - 0.5) * 1; - const jz = ePos.z + (Math.random() - 0.5) * 2; - proj.spawn(pPos.x + 2, pPos.y, pPos.z, jx, jy, jz, overloadRef.current ? 0xfbbf24 : 0xf0a030, dmgPerBullet, 'bullet', sub); - } - } - - enemyFireTimerRef.current++; - if (enemyFireTimerRef.current >= 30 && eng.weapons > 10) { - enemyFireTimerRef.current = 0; - const enemyDmg = 4 * (eng.weapons / 100); - if (Math.random() < pwr.engines * 0.04) { - proj.spawn(ePos.x - 1, ePos.y, ePos.z, pPos.x + (Math.random() - 0.5) * 4, pPos.y + (Math.random() - 0.5) * 2, pPos.z + (Math.random() - 0.5) * 4, 0xef4444, 0, 'bullet', 'none'); - } else { - proj.spawn(ePos.x - 1, ePos.y, ePos.z, pPos.x, pPos.y, pPos.z, 0xef4444, enemyDmg, 'bullet', 'hull'); - } - } - } - - if (tgt && !eng.locked && eng.lockTimer < eng.lockTime) { - setEnemy(prev => { - const newTimer = prev.lockTimer + TICK_MS / 1000; - if (newTimer >= prev.lockTime) { - addLog('★ TARGET LOCKED', '#22c55e'); - if (enemyLockRef.current) enemyLockRef.current.visible = true; - return { ...prev, locked: true, lockTimer: newTimer }; - } - return { ...prev, lockTimer: newTimer }; - }); - } - - const arrived = proj.getArrived(); - for (const p of arrived) { - if (p.userData.dmg <= 0) continue; - const isPlayer = p.userData.color === 0xf0a030 || p.userData.color === 0xfbbf24; - if (isPlayer) { - imp.spawn(p.userData.tx, p.userData.ty, p.userData.tz, 0xef4444, 2); - setEnemy(prev => { - let ne = { ...prev }; - const d = p.userData.dmg; - if (p.userData.subsystem === 'shields') { ne.shields = Math.max(0, ne.shields - d); } - else if (p.userData.subsystem === 'hull') { - if (ne.shields > 0) ne.shields = Math.max(0, ne.shields - d * 0.4); - else { ne.armor = Math.max(0, ne.armor - d * 0.5); ne.hull = Math.max(0, ne.hull - d * 0.5); } - } else if (p.userData.subsystem === 'weapons') { ne.weapons = Math.max(0, ne.weapons - d); } - else if (p.userData.subsystem === 'engines') { ne.engines = Math.max(0, ne.engines - d); } - return ne; - }); - } else { - imp.spawn(p.userData.tx, p.userData.ty, p.userData.tz, 0xef4444, 2); - setPlayer(prev => { - let np = { ...prev }; - const d = p.userData.dmg; - const absorb = 0.4 + pwr.shields * 0.08; - if (np.shields > 0) { const ab = Math.min(d * absorb, np.shields); np.shields = Math.max(0, np.shields - ab); const bl = d - ab; if (bl > 0) np.armor = Math.max(0, np.armor - bl * 0.5); } - else if (np.armor > 0) { np.armor = Math.max(0, np.armor - d * 0.6); np.hull = Math.max(0, np.hull - d * 0.4); } - else { np.hull = Math.max(0, np.hull - d); } - return np; - }); - } - } - - if (tickRef.current % Math.round(tps) === 0) { - setPlayerBuffs(prev => prev.map(b => b.duration > 0 ? { ...b, duration: b.duration - 1 } : b).filter(b => b.duration !== 0)); - } - }, TICK_MS); - return () => clearInterval(interval); - }, [addLog]); - - /* ═══ RENDER — Immersive Game HUD ═══ */ - const lockPct = target && !enemy.locked ? Math.min(100, (enemy.lockTimer / enemy.lockTime) * 100) : 0; - - return ( -
- {/* ── 3D VIEWPORT ── */} -
- - {/* ═══ HUD OVERLAY LAYER ═══ */} -
- - {/* ── TOP BAR — System info strip ── */} -
- -
- JITA - 1.0 SEC -
- - {player.name} - - {player.class} -
- SPD - {player.speed.toFixed(0)} m/s -
- {overloaded && ⚡ OVERLOAD} - {target && enemy.locked && ( - - - TARGET LOCKED - - )} -
- - - ONLINE - -
- - {/* ── LEFT — Ship Systems ── */} -
- {/* Ship status */} -
-
- - Ship Systems -
-
- {[ - { label: 'SH', value: player.shields, color: '#22d3ee', grad: 'linear-gradient(90deg, #0891b2, #22d3ee)' }, - { label: 'AR', value: player.armor, color: '#f0a030', grad: 'linear-gradient(90deg, #b47818, #f0a030)' }, - { label: 'HU', value: player.hull, color: '#22c55e', grad: 'linear-gradient(90deg, #16a34a, #22c55e)' }, - { label: 'NRG', value: player.energy, color: player.energy > 25 ? '#a78bfa' : '#ef4444', grad: player.energy > 25 ? 'linear-gradient(90deg, #6366f1, #a78bfa)' : 'linear-gradient(90deg, #dc2626, #ef4444)' }, - ].map(bar => ( -
- {bar.label} -
-
-
- - {bar.label === 'NRG' ? Math.round(bar.value) : bar.value.toFixed(0)} - -
- ))} -
-
- - {/* Reactor Power */} -
-
- - Reactor - {totalPower}/{MAX_POWER} -
-
- {[ - { key: 'weapons', label: 'WPN', color: '#ef4444' }, - { key: 'shields', label: 'SHD', color: '#22d3ee' }, - { key: 'engines', label: 'ENG', color: '#22c55e' }, - { key: 'aux', label: 'AUX', color: '#a78bfa' }, - ].map(sys => ( -
- {sys.label} - -
- {Array.from({ length: MAX_POWER }, (_, i) => ( -
- ))} -
- -
- ))} -
- WPN ×{weaponMult.toFixed(1)} dmg - {' · '} - SHD {shieldRegen.toFixed(0)}/s - {' · '} - ENG {dodgeChance}% dodge - {' · '} - AUX {energyRegen.toFixed(0)} NRG/s -
-
-
- - {/* Buffs */} -
-
- - Status -
-
- {playerBuffs.map(b => ( - - {b.icon} {b.name}{b.duration > 0 ? ` ${b.duration}s` : ''} - - ))} - {enemyBuffs.map(b => ( - - {b.icon} {b.name} {b.duration > 0 ? `${b.duration}s` : ''} - - ))} -
-
-
- - {/* ── CENTER — Crosshair + Lock ── */} -
- {/* Crosshair */} -
-
-
-
-
-
-
- - {/* Lock-in-progress ring */} - {target && !enemy.locked && ( -
-
- - - -
-
- LOCKING {lockPct.toFixed(0)}% -
-
- )} - - {/* Engage prompt */} - {!target && ( -
-

No hostiles detected

- -
- )} -
- - {/* ── RIGHT — Target Intel ── */} -
- {/* Target panel */} -
-
- - {target || 'NO TARGET'} - {target && {enemy.locked ? 'LOCKED' : 'LOCKING'}} -
-
- {target && ( - <> - {[ - { label: 'SH', value: enemy.shields, color: '#22d3ee' }, - { label: 'AR', value: enemy.armor, color: '#f0a030' }, - { label: 'HU', value: enemy.hull, color: '#22c55e' }, - ].map(bar => ( -
- {bar.label} -
-
-
- {bar.value.toFixed(0)} -
- ))} - - )} -
-
- - {/* Subsystem targeting */} -
-
- - Subsystem -
-
-
- {[ - { key: 'hull', label: 'Hull', color: '#f0a030', hp: enemy.hull }, - { key: 'shields', label: 'Shields', color: '#22d3ee', hp: enemy.shields }, - { key: 'weapons', label: 'Weapons', color: '#ef4444', hp: enemy.weapons }, - { key: 'engines', label: 'Engines', color: '#22c55e', hp: enemy.engines }, - ].map(sys => ( - - ))} -
-
-
- - {/* Power coupling readout */} -
-
- - Power Readout -
-
-
WPN {power.weapons} → ×{weaponMult.toFixed(1)} dmg, {Math.max(1, Math.floor(power.weapons / 2))} rounds
-
SHD {power.shields} → {shieldRegen.toFixed(0)}/s, {(shieldAbsorb * 100).toFixed(0)}% abs
-
ENG {power.engines} → {dodgeChance}% dodge, ×{speedMult.toFixed(1)}
-
AUX {power.aux} → {energyRegen.toFixed(0)} NRG/s, −{cdReduction}% CD
-
-
-
- - {/* ── BOTTOM — Module Bar + Combat Log ── */} -
- {/* Module bar */} -
-
- - Modules - 1–6 or click -
-
- {/* Reactor gauge */} -
- REACTOR -
-
25 ? 'linear-gradient(0deg, #4f46e5, #8b5cf6)' : 'linear-gradient(0deg, #dc2626, #ef4444)', - transition: 'height 0.15s', - }} /> - {Math.round(player.energy)} -
-
-
- - {/* Module buttons */} - {modules.map(ab => { - const onCd = ab.cd > 0; - const noNRG = player.energy < ab.cost; - const canCast = target && enemy.locked && !onCd && !noNRG; - return ( - - ); - })} -
-
- - {/* Combat log */} -
-
- - Log -
-
- {combatLog.length === 0 &&
SPACE to engage, then 1–6 for modules.
} - {combatLog.map((entry, i) => ( -
- {entry.time} - {entry.msg} -
- ))} -
-
-
- -
-
- ); -} - -window.GDD.CombatDemo = CombatDemo; diff --git a/archive/legacy-static/js/demos/fitting.js b/archive/legacy-static/js/demos/fitting.js deleted file mode 100644 index 5535b2d..0000000 --- a/archive/legacy-static/js/demos/fitting.js +++ /dev/null @@ -1,377 +0,0 @@ -window.GDD = window.GDD || {}; - -const { useState, useEffect, useCallback, useMemo } = React; - -function FittingDemo() { - const [ship, setShip] = useState(null); - const [ships, setShips] = useState([]); - const [availableModules, setAvailableModules] = useState([]); - const [fittedModules, setFittedModules] = useState({ high: [], med: [], low: [] }); - const [selectedModule, setSelectedModule] = useState(null); - const [filterSlot, setFilterSlot] = useState('all'); - const [notifications, setNotifications] = useState([]); - - useEffect(() => { - window.GDD.api.getPlayerShips().then(s => { - setShips(s); - if (s.length > 0) setShip(s[0]); - }); - window.GDD.api.getAvailableModules().then(m => setAvailableModules(m)); - }, []); - - useEffect(() => { - if (!ship) return; - window.GDD.api.getShipFittings(ship.id).then(fitted => { - const slots = { high: [], med: [], low: [] }; - fitted.forEach(m => { - if (m.slot === 'high') slots.high.push(m); - else if (m.slot === 'med') slots.med.push(m); - else if (m.slot === 'low') slots.low.push(m); - }); - setFittedModules(slots); - }); - }, [ship]); - - const addNotif = useCallback((msg, color) => { - const id = Date.now(); - setNotifications(prev => [...prev, { id, msg, color }]); - setTimeout(() => setNotifications(prev => prev.filter(n => n.id !== id)), 3000); - }, []); - - const cpuUsage = useMemo(() => { - let total = 0; - Object.values(fittedModules).flat().forEach(m => total += m.cpu); - return total; - }, [fittedModules]); - - const gridUsage = useMemo(() => { - let total = 0; - Object.values(fittedModules).flat().forEach(m => total += m.power); - return total; - }, [fittedModules]); - - const cpuMax = ship ? ship.cpu : 0; - const gridMax = ship ? ship.powerGrid : 0; - const cpuOver = cpuUsage > cpuMax; - const gridOver = gridUsage > gridMax; - - const handleFit = useCallback((mod) => { - if (!ship) return; - const slot = mod.slot; - const maxSlots = slot === 'high' ? ship.highSlots : slot === 'med' ? ship.medSlots : ship.lowSlots; - const currentCount = fittedModules[slot].length; - - if (currentCount >= maxSlots) { - addNotif(`No empty ${slot} slots available.`, 'var(--red)'); - return; - } - - const newCpu = cpuUsage + mod.cpu; - const newGrid = gridUsage + mod.power; - if (newCpu > cpuMax) { - addNotif(`CPU exceeded: ${newCpu}/${cpuMax}. Remove a module first.`, 'var(--red)'); - return; - } - if (newGrid > gridMax) { - addNotif(`Power Grid exceeded: ${newGrid}/${gridMax}. Remove a module first.`, 'var(--red)'); - return; - } - - setFittedModules(prev => ({ - ...prev, - [slot]: [...prev[slot], { ...mod, uid: Date.now() + Math.random() }], - })); - addNotif(`${mod.name} fitted to ${slot} slot.`, 'var(--green)'); - }, [ship, fittedModules, cpuUsage, gridUsage, cpuMax, gridMax, addNotif]); - - const handleUnfit = useCallback((slot, index) => { - const mod = fittedModules[slot][index]; - setFittedModules(prev => ({ - ...prev, - [slot]: prev[slot].filter((_, i) => i !== index), - })); - addNotif(`${mod.name} removed from ${slot} slot.`, 'var(--muted)'); - }, [fittedModules, addNotif]); - - const filteredModules = filterSlot === 'all' - ? availableModules - : availableModules.filter(m => m.slot === filterSlot); - - const slotConfig = [ - { key: 'high', label: 'High Slots', color: 'var(--red)', icon: '◆', max: ship?.highSlots || 0 }, - { key: 'med', label: 'Medium Slots', color: 'var(--cyan)', icon: '◇', max: ship?.medSlots || 0 }, - { key: 'low', label: 'Low Slots', color: 'var(--green)', icon: '○', max: ship?.lowSlots || 0 }, - ]; - - const moduleTypeIcon = (type) => { - switch(type) { - case 'weapon': return '⊕'; - case 'shield': return '◎'; - case 'mining': return '⛏'; - case 'propulsion': return '»'; - case 'ewar': return '◎'; - case 'armor': return '◼'; - case 'damage_mod': return '↯'; - case 'cargo': return '□'; - default: return '•'; - } - }; - - if (!ship) return

Loading ship data...

; - - return ( -
- e.currentTarget.style.color='var(--fg-bright)'} onMouseLeave={e => e.currentTarget.style.color='var(--muted)'}>← Back to Docs -

Ship Fitting Demo

-

- Drag modules into slot bays. CPU and Power Grid are hard limits — overfitting is blocked. - Select a ship and build your loadout. -

- - {/* Notifications */} -
- {notifications.map(n => ( -
- {n.msg} -
- ))} -
- - {/* HUD-style fitting strip */} -
- {ship?.name || 'Loading...'} - {ship && {ship.class}} - {ship &&
} - {ship && ( - <> -
- CPU -
-
0.8 ? '#f0a030' : '#22d3ee', borderRadius: 'var(--radius-pill)' }} /> -
- {cpuUsage}/{cpuMax} -
-
- PWR -
-
0.8 ? '#f0a030' : '#22c55e', borderRadius: 'var(--radius-pill)' }} /> -
- {gridUsage}/{gridMax} -
- - )} - {ship?.system || ''} · {ship?.status === 'docked' ? '● DOCKED' : '○ IN SPACE'} -
- - {/* Ship selector */} -
- {ships.map(s => ( - - ))} -
- - {/* Ship stats + resource bars */} -
-
- Fitting Console - | - {ship.name} · {ship.class}-class - - {ship.system} · {ship.status === 'docked' ? '● DOCKED' : '○ IN SPACE'} - -
- -
- {/* Module browser */} -
-
-
- Module Browser -
-
- {['all', 'high', 'med', 'low'].map(f => ( - - ))} -
-
- -
- {filteredModules.map(mod => ( -
setSelectedModule(mod)} - onDoubleClick={() => handleFit(mod)} - > -
- - {moduleTypeIcon(mod.type)} - -
-
{mod.name}
-
- {mod.cpu} CPU · {mod.power} PG · {mod.slot} -
-
-
-
- ))} -
-
- - {/* Fitting area */} -
- {/* CPU / Grid bars */} -
-
- CPU - - {cpuUsage} / {cpuMax} tf{cpuOver ? ' ⚠ OVER' : ''} - -
-
-
0.8 ? 'var(--accent)' : 'var(--cyan)', - }} /> -
- -
- POWER GRID - - {gridUsage} / {gridMax} MW{gridOver ? ' ⚠ OVER' : ''} - -
-
-
0.8 ? 'var(--accent)' : 'var(--green)', - }} /> -
-
- - {/* Slot bays */} - {slotConfig.map(slot => ( -
-
- {slot.icon} - - {slot.label} - - - {fittedModules[slot.key].length} / {slot.max} - -
-
- {Array.from({ length: slot.max }).map((_, i) => { - const mod = fittedModules[slot.key][i]; - return ( -
{ - if (mod) handleUnfit(slot.key, i); - else if (selectedModule?.slot === slot.key) handleFit(selectedModule); - }} - > - {mod ? ( - <> - {moduleTypeIcon(mod.type)} - - {mod.name.replace(' I', '').replace(' II', '')} - - - {mod.cpu}/{mod.power} - - - ) : ( - Empty - )} -
- ); - })} -
-
- ))} -
-
-
- - {/* Selected module detail */} - {selectedModule && ( -
-
-
- {moduleTypeIcon(selectedModule.type)} -
-
-

{selectedModule.name}

-
- Slot: {selectedModule.slot} - Type: {selectedModule.type} - CPU: {selectedModule.cpu} tf - Grid: {selectedModule.power} MW - {selectedModule.damage && Damage: {selectedModule.damage}} - {selectedModule.cycle > 0 && Cycle: {selectedModule.cycle}s} -
-
- -
-
- )} - - {/* Controls hint */} -
- How to use: Select a module from the browser (left panel), then click an empty slot bay to fit it. - Double-click a module in the browser to quick-fit. Click a fitted module to remove it. - CPU and Power Grid are enforced — overspending is blocked with a warning. -
-
- ); -} - -window.GDD.FittingDemo = FittingDemo; diff --git a/archive/legacy-static/js/demos/galaxy.js b/archive/legacy-static/js/demos/galaxy.js deleted file mode 100644 index b7b701d..0000000 --- a/archive/legacy-static/js/demos/galaxy.js +++ /dev/null @@ -1,1429 +0,0 @@ -window.GDD = window.GDD || {}; - -const { useState, useEffect, useRef, useCallback, useMemo } = React; - -/* ═══════════════════════════════════════════════ - Seeded PRNG (xoshiro128**) - ═══════════════════════════════════════════════ */ -function createRng(seed) { - let s0 = seed >>> 0 || 1; - let s1 = (seed * 1103515245 + 12345) >>> 0; - let s2 = (s1 * 1103515245 + 12345) >>> 0; - let s3 = (s2 * 1103515245 + 12345) >>> 0; - function next() { - const t = s0; - const r = (t << 9 | t >>> 23); - s0 = s1; s1 = s2; s2 = s3; - s3 ^= t ^ s3; - s0 ^= (s0 << 3 | s0 >>> 29); - s1 ^= (s1 << 21 | s1 >>> 11); - s2 ^= (s2 << 7 | s2 >>> 25); - return ((r + ((s0 ^ s3) >>> 0)) >>> 0) / 4294967296; - } - return { - next, - range(a, b) { return a + next() * (b - a); }, - int(a, b) { return Math.floor(a + next() * (b - a)); }, - pick(arr) { return arr[Math.floor(next() * arr.length)]; }, - gaussian(mean, sigma) { - const u1 = next() || 0.0001; - const u2 = next(); - return mean + sigma * Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2); - }, - }; -} - -/* ═══════════════════════════════════════════════ - Spiral Galaxy Generation Algorithm - Inspired by https://vercidium.com/blog/random-galaxy-generation-with-c-and-opengl/ - - Key ideas: - - Systems placed along spiral arms - - Arms curve (bend) proportional to distance from center - - Gravity pushes density toward center - - Height variance via sin wave for 3D depth - - Security decreases with distance from center - - Each arm is a faction's territory - ═══════════════════════════════════════════════ */ -function generateGalaxy(seed, overrides = {}) { - const rng = createRng(seed); - - // ── Galaxy shape parameters ── - const armCount = overrides.armCount ?? 4; - const galaxySize = overrides.galaxySize ?? 300; - const armSpread = overrides.armSpread ?? 0.42; - const rotationStr = overrides.rotationStrength ?? 2.8; - const gravity = overrides.gravity ?? 1.4; - const heightMag = overrides.heightMagnitude ?? 15; - const heightFreq = overrides.heightFrequency ?? 0.018; - const conformChance = 0.72; - - // ── Faction/arm colors ── - const factionDefs = [ - { id: 'caldari', name: 'Caldari State', faction: 'Caldari', color: '#38bdf8', armColor: 0x38bdf8 }, - { id: 'minmatar', name: 'Minmatar Republic', faction: 'Minmatar', color: '#ef4444', armColor: 0xef4444 }, - { id: 'amarr', name: 'Amarr Empire', faction: 'Amarr', color: '#f59e0b', armColor: 0xf0a030 }, - { id: 'gallente', name: 'Gallente Federation', faction: 'Gallente', color: '#a855f7', armColor: 0xa855f7 }, - ]; - - const starColors = { O: '#9bb0ff', B: '#aabfff', A: '#cad7ff', F: '#f8f7ff', G: '#fff4ea', K: '#ffd2a1', M: '#ffcc6f' }; - const starWeights = { O: 0.01, B: 0.03, A: 0.06, F: 0.1, G: 0.15, K: 0.2, M: 0.45 }; - - const oreBySec = [ - { sec: 0.8, ores: ['Veldspar', 'Scordite'] }, - { sec: 0.5, ores: ['Veldspar', 'Scordite', 'Pyroxeres'] }, - { sec: 0.1, ores: ['Scordite', 'Pyroxeres', 'Kernite', 'Omber'] }, - { sec: -0.1, ores: ['Kernite', 'Omber', 'Jaspet', 'Hemorphite'] }, - { sec: -0.5, ores: ['Jaspet', 'Hemorphite', 'Arkonor'] }, - ]; - - function pickStarType() { - const r = rng.next(); - let acc = 0; - for (const [type, w] of Object.entries(starWeights)) { - acc += w; - if (r < acc) return type; - } - return 'M'; - } - - function getOresForSec(sec) { - for (let i = oreBySec.length - 1; i >= 0; i--) { - if (sec >= oreBySec[i].sec) return oreBySec[i].ores; - } - return oreBySec[oreBySec.length - 1].ores; - } - - // ── Spiral arm position calculator ── - function spiralPosition(armIdx, distFactor) { - const baseAngle = (2 * Math.PI / armCount) * armIdx; - const distance = Math.pow(distFactor, 0.6) * galaxySize; - const spreadAngle = armSpread * (Math.PI / armCount) * (0.4 + distFactor * 0.8); - const angleOffset = rng.gaussian(0, spreadAngle); - const bendAngle = distFactor * rotationStr; - const angle = baseAngle + angleOffset + bendAngle; - - const x = distance * Math.cos(angle); - const z = distance * Math.sin(angle); - const y = heightMag * Math.sin(distance * heightFreq) * (1 - distFactor * 0.3) - + rng.gaussian(0, 2 + distFactor * 4); - - return { x, y, z, distance, angle: baseAngle + bendAngle }; - } - - // ── Security from distance ── - function securityFromDist(distFactor) { - // Center (0) → 1.0, Edge (1) → -1.0 - const sec = 1.0 - distFactor * 2.0; - return Math.round(Math.max(-1, Math.min(1, sec + rng.range(-0.05, 0.05))) * 100) / 100; - } - - // ── Build data structures ── - const regions = []; - const constellations = []; - const systems = []; - const stargates = []; - const stations = []; - const belts = []; - const agents = []; - - // Core region — shared high-sec hub at center - const coreRegion = { - id: 'core', name: 'Core Worlds', faction: 'Concord', - factionColor: '#22d3ee', secMin: 0.8, secMax: 1.0, - color: '#22c55e', center: { x: 0, y: 0, z: 0 }, systemIds: [], - }; - regions.push(coreRegion); - - // Core systems (5-8 systems near center) - const coreConstCount = 2; - for (let ci = 0; ci < coreConstCount; ci++) { - const constId = `core_c${ci}`; - const cx = rng.gaussian(0, 15); - const cy = rng.gaussian(0, 3); - const cz = rng.gaussian(0, 15); - const con = { id: constId, regionId: 'core', x: cx, y: cy, z: cz, systemIds: [] }; - constellations.push(con); - - const sysCount = rng.int(3, 5); - for (let si = 0; si < sysCount; si++) { - const sx = cx + rng.gaussian(0, 8); - const sy = cy + rng.gaussian(0, 2); - const sz = cz + rng.gaussian(0, 8); - const sec = Math.round((0.8 + rng.next() * 0.2) * 100) / 100; - const starType = pickStarType(); - const sysId = `${constId}_s${si}`; - const sysName = `COR-${rng.int(100, 999)}`; - - const sys = { - id: sysId, name: sysName, regionId: 'core', constellationId: constId, - x: sx, y: sy, z: sz, - security: sec, starType, starColor: starColors[starType], - planetCount: rng.int(2, 7), faction: 'Concord', - armIndex: -1, distFactor: 0, - }; - systems.push(sys); - con.systemIds.push(sysId); - coreRegion.systemIds.push(sysId); - - // Core systems get lots of stations and services - const stationCount = rng.int(2, 4); - for (let sti = 0; sti < stationCount; sti++) { - const services = ['Market', 'Refinery', 'Factory', 'Fitting']; - if (rng.next() > 0.4) services.push('Insurance'); - if (rng.next() > 0.5) services.push('Clone Bay'); - stations.push({ - id: `${sysId}_stn${sti}`, systemId: sysId, - name: `${sysName} Station ${String.fromCharCode(65 + sti)}`, - services, - }); - if (rng.next() > 0.3) { - agents.push({ - stationId: `${sysId}_stn${sti}`, - specialty: rng.pick(['Kill', 'Courier', 'Mining', 'Survey', 'Trade']), - faction: 'Concord', - }); - } - } - - const beltCount = rng.int(1, 3); - const ores = getOresForSec(sec); - for (let bi = 0; bi < beltCount; bi++) { - belts.push({ id: `${sysId}_belt${bi}`, systemId: sysId, oreType: rng.pick(ores) }); - } - } - } - - // ── Arm regions ── - for (let armIdx = 0; armIdx < armCount; armIdx++) { - const fDef = factionDefs[armIdx % factionDefs.length]; - const region = { - id: fDef.id, name: fDef.name, faction: fDef.faction, - factionColor: fDef.color, - secMin: -1.0, secMax: 0.8, - color: fDef.color, - center: { x: 0, y: 0, z: 0 }, // computed below - systemIds: [], - armIndex: armIdx, - }; - - // Each arm has 2-4 constellations at different distances - const constCount = rng.int(2, 4); - let armCx = 0, armCy = 0, armCz = 0; - - for (let ci = 0; ci < constCount; ci++) { - // Distribute constellations along the arm at increasing distances - const distBase = 0.15 + (ci / constCount) * 0.75; - const distFactor = distBase + rng.range(-0.05, 0.05); - const pos = spiralPosition(armIdx, distFactor); - - const constId = `${fDef.id}_c${ci}`; - const con = { - id: constId, regionId: fDef.id, - x: pos.x, y: pos.y, z: pos.z, - systemIds: [], - armIndex: armIdx, - distFactor: distFactor, - }; - constellations.push(con); - - const sysCount = rng.int(3, 6); - for (let si = 0; si < sysCount; si++) { - // Systems cluster around the constellation center on the arm - const sysDistFactor = distFactor + rng.gaussian(0, 0.04); - const sysPos = spiralPosition(armIdx, Math.max(0.08, Math.min(0.98, sysDistFactor))); - const sec = securityFromDist(sysDistFactor); - const starType = pickStarType(); - const sysId = `${constId}_s${si}`; - const sysName = `${fDef.faction.substring(0, 3).toUpperCase()}-${rng.int(100, 999)}`; - - const sys = { - id: sysId, name: sysName, regionId: fDef.id, constellationId: constId, - x: sysPos.x, y: sysPos.y, z: sysPos.z, - security: sec, starType, starColor: starColors[starType], - planetCount: rng.int(1, 6), faction: fDef.faction, - armIndex: armIdx, distFactor: sysDistFactor, - }; - systems.push(sys); - con.systemIds.push(sysId); - region.systemIds.push(sysId); - armCx += sysPos.x; armCy += sysPos.y; armCz += sysPos.z; - - // Stations — more in high-sec, fewer in null - let stationCount; - if (sec >= 0.8) stationCount = rng.int(2, 4); - else if (sec >= 0.5) stationCount = rng.int(1, 3); - else if (sec >= 0.1) stationCount = rng.int(1, 2); - else if (sec >= 0) stationCount = rng.int(0, 2); - else stationCount = rng.int(0, 1); - - for (let sti = 0; sti < stationCount; sti++) { - const services = ['Market']; - if (rng.next() > 0.3) services.push('Refinery'); - if (rng.next() > 0.4) services.push('Factory'); - if (rng.next() > 0.3) services.push('Fitting'); - if (rng.next() > 0.5) services.push('Insurance'); - stations.push({ - id: `${sysId}_stn${sti}`, systemId: sysId, - name: `${sysName} Station ${String.fromCharCode(65 + sti)}`, - services, - }); - if (rng.next() > 0.4) { - agents.push({ - stationId: `${sysId}_stn${sti}`, - specialty: rng.pick(['Kill', 'Courier', 'Mining', 'Survey', 'Trade']), - faction: fDef.faction, - }); - } - } - - // Belts - const beltCount = sec >= 0.8 ? rng.int(1, 3) : sec >= 0.5 ? rng.int(2, 4) : rng.int(2, 5); - const ores = getOresForSec(sec); - for (let bi = 0; bi < beltCount; bi++) { - belts.push({ id: `${sysId}_belt${bi}`, systemId: sysId, oreType: rng.pick(ores) }); - } - } - } - - // Compute arm center - const armSysCount = region.systemIds.length || 1; - region.center = { x: armCx / armSysCount, y: armCy / armSysCount, z: armCz / armSysCount }; - regions.push(region); - } - - // ── System lookup map for O(1) access ── - const sysMap = new Map(); - systems.forEach(s => sysMap.set(s.id, s)); - - // ── Build stargate graph ── - if (systems.length < 2) return { regions, constellations, systems, stargates, stations, belts, agents, seed, sysMap, dustPositions: new Float32Array(0), dustColors: new Float32Array(0), dustCount: 0, params: { armCount, galaxySize, armSpread, rotationStr, gravity, heightMag, heightFreq } }; - - // 1) MST for connectivity (Prim's — uses sysMap for O(1) lookup) - const inMST = new Set([systems[0].id]); - const mstEdges = []; - const distSq = (a, b) => (a.x - b.x) ** 2 + (a.y - b.y) ** 2 + (a.z - b.z) ** 2; - - while (inMST.size < systems.length) { - let bestDist = Infinity, bestA = null, bestB = null; - for (const aId of inMST) { - const a = sysMap.get(aId); - for (const b of systems) { - if (inMST.has(b.id)) continue; - const d = distSq(a, b); - if (d < bestDist) { bestDist = d; bestA = aId; bestB = b.id; } - } - } - if (bestB) { inMST.add(bestB); mstEdges.push([bestA, bestB]); } - } - - mstEdges.forEach(([a, b]) => stargates.push({ from: a, to: b, type: 'mst' })); - - // 2) Intra-constellation extras - constellations.forEach(con => { - if (con.systemIds.length < 3) return; - const extras = rng.int(1, Math.min(3, con.systemIds.length - 1)); - for (let i = 0; i < extras; i++) { - const a = rng.pick(con.systemIds); - const b = rng.pick(con.systemIds.filter(id => id !== a)); - const exists = stargates.some(g => (g.from === a && g.to === b) || (g.from === b && g.to === a)); - if (!exists) stargates.push({ from: a, to: b, type: 'intra' }); - } - }); - - // Pre-compute sorted arm constellations (avoid repeated filter+sort) - const armConstsByArm = new Map(); - for (let armIdx = 0; armIdx < armCount; armIdx++) { - armConstsByArm.set(armIdx, constellations.filter(c => c.armIndex === armIdx).sort((a, b) => a.distFactor - b.distFactor)); - } - - // 3) Intra-arm connections - for (let armIdx = 0; armIdx < armCount; armIdx++) { - const armConsts = armConstsByArm.get(armIdx); - for (let i = 0; i < armConsts.length - 1; i++) { - const a = rng.pick(armConsts[i].systemIds); - const b = rng.pick(armConsts[i + 1].systemIds); - const exists = stargates.some(g => (g.from === a && g.to === b) || (g.from === b && g.to === a)); - if (!exists) stargates.push({ from: a, to: b, type: 'arm-link' }); - } - } - - // 4) Core hub - const coreSys = coreRegion.systemIds; - for (let armIdx = 0; armIdx < armCount; armIdx++) { - const armConsts = armConstsByArm.get(armIdx); - if (armConsts.length > 0 && coreSys.length > 0) { - const borderA = rng.pick(armConsts[0].systemIds); - const borderB = rng.pick(coreSys); - const exists = stargates.some(g => (g.from === borderA && g.to === borderB) || (g.from === borderB && g.to === borderA)); - if (!exists) stargates.push({ from: borderA, to: borderB, type: 'hub' }); - } - } - - // 5) Cross-arm choke points - for (let armIdx = 0; armIdx < armCount; armIdx++) { - const nextArm = (armIdx + 1) % armCount; - const armA = regions.find(r => r.armIndex === armIdx); - const armB = regions.find(r => r.armIndex === nextArm); - if (armA && armB && armA.systemIds.length > 0 && armB.systemIds.length > 0) { - const borderA = rng.pick(armA.systemIds); - const borderB = rng.pick(armB.systemIds); - const exists = stargates.some(g => (g.from === borderA && g.to === borderB) || (g.from === borderB && g.to === borderA)); - if (!exists) stargates.push({ from: borderA, to: borderB, type: 'choke' }); - } - } - - // 6) High-sec shortcuts - const highSecSys = systems.filter(s => s.security >= 0.5); - const shortcutCount = Math.floor(highSecSys.length * 0.15); - for (let i = 0; i < shortcutCount; i++) { - const a = rng.pick(highSecSys); - const b = rng.pick(highSecSys.filter(s => s.id !== a.id)); - const exists = stargates.some(g => (g.from === a.id && g.to === b.id) || (g.from === b.id && g.to === a.id)); - if (!exists) stargates.push({ from: a.id, to: b.id, type: 'shortcut' }); - } - - // ── Connectivity check ── - const adj = {}; - systems.forEach(s => { adj[s.id] = []; }); - stargates.forEach(g => { adj[g.from].push(g.to); adj[g.to].push(g.from); }); - const visited = new Set(); - const queue = [systems[0].id]; - visited.add(systems[0].id); - while (queue.length > 0) { - const cur = queue.shift(); - for (const nb of (adj[cur] || [])) { - if (!visited.has(nb)) { visited.add(nb); queue.push(nb); } - } - } - const connected = visited.size === systems.length; - - const chokeSet = new Set(); - stargates.filter(g => g.type === 'choke').forEach(g => { chokeSet.add(g.from); chokeSet.add(g.to); }); - - const starterSystem = systems.find(s => s.security >= 0.95) || systems.find(s => s.security >= 0.8) || systems[0]; - - function findPath(startId, endId) { - const prev = {}; - const vis = new Set([startId]); - const q = [startId]; - while (q.length > 0) { - const cur = q.shift(); - if (cur === endId) break; - for (const nb of (adj[cur] || [])) { - if (!vis.has(nb)) { vis.add(nb); prev[nb] = cur; q.push(nb); } - } - } - const path = []; - let c = endId; - while (c) { path.unshift(c); c = prev[c]; } - return path[0] === startId ? path : []; - } - - // ── Generate background dust as flat typed arrays ── - // No intermediate objects — writes directly to Float32Arrays - const dustCount = 8000; - const armColorDefs = factionDefs.map(f => f.armColor); - const armR = armColorDefs.map(c => ((c >> 16) & 0xFF) / 255); - const armG = armColorDefs.map(c => ((c >> 8) & 0xFF) / 255); - const armB = armColorDefs.map(c => (c & 0xFF) / 255); - const dustPositions = new Float32Array(dustCount * 3); - const dustColors = new Float32Array(dustCount * 3); - const TWO_PI = 2 * Math.PI; - const armAngleStep = TWO_PI / armCount; - - for (let i = 0; i < dustCount; i++) { - let distance, angle; - const isCore = rng.next() < 0.12; - - if (isCore) { - distance = Math.pow(rng.next(), gravity * 1.5) * galaxySize * 0.25; - angle = rng.next() * TWO_PI; - const brightness = 0.4 + rng.next() * 0.6; - dustPositions[i * 3] = distance * Math.cos(angle); - dustPositions[i * 3 + 1] = rng.gaussian(0, 2); - dustPositions[i * 3 + 2] = distance * Math.sin(angle); - dustColors[i * 3] = brightness; - dustColors[i * 3 + 1] = brightness * 0.95; - dustColors[i * 3 + 2] = brightness * 1.1; - } else { - const onArm = rng.next() < conformChance; - const armIdx = rng.int(0, armCount); - const distFactor = Math.pow(rng.next(), gravity); - distance = distFactor * galaxySize; - const distRatio = distFactor; - const baseAngle = armAngleStep * armIdx; - const spreadAngle = armSpread * (Math.PI / armCount) * (0.4 + distRatio * 0.9); - const bendAngle = distRatio * rotationStr; - - if (onArm) { - const angleOffset = rng.gaussian(0, spreadAngle); - angle = baseAngle + angleOffset + bendAngle; - const brightness = 0.3 + (1 - distRatio) * 0.7; - const iR = (1 - distRatio) * 0.3; - dustPositions[i * 3] = distance * Math.cos(angle); - dustPositions[i * 3 + 1] = heightMag * Math.sin(distance * heightFreq) * (1 - distRatio * 0.3) + rng.gaussian(0, 1.5 + distRatio * 3); - dustPositions[i * 3 + 2] = distance * Math.sin(angle); - dustColors[i * 3] = Math.min(1, armR[armIdx] * brightness + iR); - dustColors[i * 3 + 1] = Math.min(1, armG[armIdx] * brightness + iR * 0.83); - dustColors[i * 3 + 2] = Math.min(1, armB[armIdx] * brightness + iR * 0.67); - } else { - angle = rng.next() * TWO_PI; - const brightness = 0.08 + (1 - distRatio) * 0.15; - dustPositions[i * 3] = distance * Math.cos(angle); - dustPositions[i * 3 + 1] = heightMag * Math.sin(distance * heightFreq) * (1 - distRatio * 0.3) + rng.gaussian(0, 2 + distRatio * 4); - dustPositions[i * 3 + 2] = distance * Math.sin(angle); - dustColors[i * 3] = brightness; - dustColors[i * 3 + 1] = brightness * 0.95; - dustColors[i * 3 + 2] = brightness * 1.1; - } - } - } - - return { - regions, constellations, systems, stargates, stations, belts, agents, seed, - connected, chokeSet, starterSystem, findPath, sysMap, - params: { armCount, galaxySize, armSpread, rotationStr, gravity, heightMag, heightFreq }, - dustPositions, dustColors, dustCount, - }; -} - -/* ═══════════════════════════════════════════════ - Helper: Security → Color - ═══════════════════════════════════════════════ */ -function secColor(sec) { - if (sec >= 0.8) return 0x22c55e; - if (sec >= 0.5) return 0x86efac; - if (sec >= 0.1) return 0xf0a030; - if (sec >= 0) return 0xfb923c; - if (sec >= -0.4) return 0xef4444; - return 0xa855f7; -} - -function secColorCSS(sec) { - if (sec >= 0.8) return '#22c55e'; - if (sec >= 0.5) return '#86efac'; - if (sec >= 0.1) return '#f0a030'; - if (sec >= 0) return '#fb923c'; - if (sec >= -0.4) return '#ef4444'; - return '#a855f7'; -} - -function factionColor(faction) { - switch (faction) { - case 'Caldari': return 0x38bdf8; - case 'Gallente': return 0xa855f7; - case 'Minmatar': return 0xef4444; - case 'Amarr': return 0xf0a030; - case 'Concord': return 0x22d3ee; - default: return 0x94a3b8; - } -} - -/* ═══════════════════════════════════════════════ - Galaxy Demo — Three.js 3D (Spiral Galaxy) - ═══════════════════════════════════════════════ */ -function GalaxyDemo() { - const containerRef = useRef(null); - const sceneRef = useRef(null); - const [seed, setSeed] = useState(42); - const [inputSeed, setInputSeed] = useState('42'); - const [galaxy, setGalaxy] = useState(null); - const [hoveredSystem, setHoveredSystem] = useState(null); - const [selectedSystem, setSelectedSystem] = useState(null); - const [pathStart, setPathStart] = useState(null); - const [pathEnd, setPathEnd] = useState(null); - const [currentPath, setCurrentPath] = useState([]); - const [showGates, setShowGates] = useState(true); - const [showLabels, setShowLabels] = useState(true); - const [showDust, setShowDust] = useState(true); - const [viewMode, setViewMode] = useState('security'); - const [autoRotate, setAutoRotate] = useState(true); - const [galaxyParams, setGalaxyParams] = useState({ - armCount: 4, - rotationStrength: 2.8, - armSpread: 0.42, - gravity: 1.4, - }); - - // Refs for Three.js objects we need to update without re-render - const galaxyRef = useRef(null); - const selectedSystemRef = useRef(null); - const hoveredSystemRef = useRef(null); - const pathStartRef = useRef(null); - const pathEndRef = useRef(null); - const currentPathRef = useRef([]); - const viewModeRef = useRef('security'); - const showGatesRef = useRef(true); - const showLabelsRef = useRef(true); - const showDustRef = useRef(true); - const autoRotateRef = useRef(true); - - // Keep refs in sync with state - useEffect(() => { selectedSystemRef.current = selectedSystem; }, [selectedSystem]); - useEffect(() => { hoveredSystemRef.current = hoveredSystem; }, [hoveredSystem]); - useEffect(() => { pathStartRef.current = pathStart; }, [pathStart]); - useEffect(() => { pathEndRef.current = pathEnd; }, [pathEnd]); - useEffect(() => { currentPathRef.current = currentPath; }, [currentPath]); - useEffect(() => { viewModeRef.current = viewMode; }, [viewMode]); - useEffect(() => { showGatesRef.current = showGates; }, [showGates]); - useEffect(() => { showLabelsRef.current = showLabels; }, [showLabels]); - useEffect(() => { showDustRef.current = showDust; }, [showDust]); - useEffect(() => { autoRotateRef.current = autoRotate; }, [autoRotate]); - - // Generate galaxy data - useEffect(() => { - const g = generateGalaxy(seed, galaxyParams); - setGalaxy(g); - setSelectedSystem(null); - setHoveredSystem(null); - setPathStart(null); - setPathEnd(null); - setCurrentPath([]); - galaxyRef.current = g; - }, [seed, galaxyParams]); - - // ── Three.js scene setup ── - useEffect(() => { - const TH = window.GDD.THREE; - const T = window.THREE; - if (!T || !containerRef.current) return; - - const container = containerRef.current; - - // Renderer - const renderer = TH.createRenderer(container, { clearColor: 0x020508 }); - - // Scene - const scene = new T.Scene(); - scene.fog = new T.FogExp2(0x020508, 0.0004); - - // Camera — elevated view of the disk - const camera = new T.PerspectiveCamera(50, container.clientWidth / container.clientHeight, 0.5, 8000); - camera.position.set(0, 320, 420); - camera.lookAt(0, 0, 0); - - // Orbit controller - const orbit = new TH.OrbitController(camera, renderer.domElement, new T.Vector3(0, 0, 0)); - orbit.distance = 520; - orbit.minDistance = 15; - orbit.maxDistance = 2000; - orbit.panButton = 2; - orbit.rotateSpeed = 0.5; - - // Lighting - TH.setupSpaceLighting(scene); - - // Background star field — distant - const starField = TH.createStarField(5000, 4000); - scene.add(starField); - - // ── Scene groups ── - const dustGroup = new T.Group(); - scene.add(dustGroup); - - const coreGlowGroup = new T.Group(); - scene.add(coreGlowGroup); - - const systemGroup = new T.Group(); - scene.add(systemGroup); - - const gateGroup = new T.Group(); - scene.add(gateGroup); - - const labelGroup = new T.Group(); - scene.add(labelGroup); - - const highlightGroup = new T.Group(); - scene.add(highlightGroup); - - // ── Raycasting ── - const raycaster = new T.Raycaster(); - const mouse = new T.Vector2(); - const clickMouse = new T.Vector2(); - - let systemMeshes = []; - // Shared geometries — created once, reused across all systems - const sharedCoreGeo = new T.SphereGeometry(2.2, 10, 10); - const sharedInnerGeo = new T.SphereGeometry(1.0, 6, 6); - // Shared glow texture — one canvas, reused for all system glow sprites - const glowCanvas = document.createElement('canvas'); - glowCanvas.width = 32; glowCanvas.height = 32; - const gCtx = glowCanvas.getContext('2d'); - const gGrad = gCtx.createRadialGradient(16, 16, 0, 16, 16, 16); - gGrad.addColorStop(0, 'rgba(255,255,255,0.8)'); - gGrad.addColorStop(0.2, 'rgba(255,255,255,0.4)'); - gGrad.addColorStop(0.5, 'rgba(255,255,255,0.1)'); - gGrad.addColorStop(1, 'rgba(255,255,255,0)'); - gCtx.fillStyle = gGrad; - gCtx.fillRect(0, 0, 32, 32); - const sharedGlowTexture = new T.CanvasTexture(glowCanvas); - - let currentSysMap = null; // cached lookup for O(1) system access - - // ── Build the 3D scene ── - // Set of shared resources that must NOT be disposed during scene rebuild - const sharedResources = new Set([sharedCoreGeo, sharedInnerGeo, sharedGlowTexture, sharedRingGeo, sharedHoverRingGeo]); - - function disposeGroup(group, disposeSharedGeo) { - while (group.children.length > 0) { - const child = group.children[0]; - group.remove(child); - // Recursively dispose children (e.g., Group → Mesh/Sprite) - if (child.children && child.children.length > 0) { - disposeGroup(child, false); - } - // Only dispose non-shared geometry and materials - if (child.geometry && (disposeSharedGeo || !sharedResources.has(child.geometry))) { - child.geometry.dispose(); - } - if (child.material) { - // Only dispose textures that aren't shared - if (child.material.map && !sharedResources.has(child.material.map)) { - child.material.map.dispose(); - } - child.material.dispose(); - } - } - } - - function buildScene(galaxy) { - // Dispose scene groups — pass true only for groups that own their own geometry - disposeGroup(systemGroup, false); // uses shared geometry - disposeGroup(gateGroup, true); // owns its geometry - disposeGroup(labelGroup, true); // owns its canvas textures - disposeGroup(highlightGroup, false); // uses shared geometry/textures - disposeGroup(dustGroup, true); // owns its geometry - disposeGroup(coreGlowGroup, true); // owns its canvas textures - - systemMeshes = []; - currentSysMap = galaxy.sysMap; - if (!galaxy) return; - - // ── Galaxy dust — use pre-built typed arrays directly (no copy) ── - const dustGeo = new T.BufferGeometry(); - dustGeo.setAttribute('position', new T.BufferAttribute(galaxy.dustPositions, 3)); - dustGeo.setAttribute('color', new T.BufferAttribute(galaxy.dustColors, 3)); - const dustMat = new T.PointsMaterial({ - size: 1.8, - vertexColors: true, - transparent: true, - opacity: 0.55, - sizeAttenuation: true, - blending: T.AdditiveBlending, - depthWrite: false, - }); - dustGroup.add(new T.Points(dustGeo, dustMat)); - - // ── Central core glow ── - // Large outer glow - const outerGlow = TH.createGlowSprite(0xfff8e0, 120); - outerGlow.position.set(0, 0, 0); - coreGlowGroup.add(outerGlow); - - // Medium warm glow - const midGlow = TH.createGlowSprite(0xffcc66, 60); - midGlow.position.set(0, 0, 0); - coreGlowGroup.add(midGlow); - - // Bright inner core - const innerGlow = TH.createGlowSprite(0xffffff, 30); - innerGlow.position.set(0, 0, 0); - coreGlowGroup.add(innerGlow); - - // ── Arm nebula sprites ── - const armColorDefs = [ - { color: 0x38bdf8, r: 0x38, g: 0xb5, b: 0xf8 }, - { color: 0xef4444, r: 0xef, g: 0x44, b: 0x44 }, - { color: 0xf0a030, r: 0xf0, g: 0xa0, b: 0x30 }, - { color: 0xa855f7, r: 0xa8, g: 0x55, b: 0xf7 }, - ]; - const { armCount, galaxySize, rotationStr } = galaxy.params; - for (let ai = 0; ai < armCount; ai++) { - const ac = armColorDefs[ai % armColorDefs.length]; - // Place nebula sprites at 3 distances along each arm - for (let di = 0; di < 3; di++) { - const df = 0.25 + di * 0.25; - const dist = df * galaxySize; - const baseAngle = (2 * Math.PI / armCount) * ai; - const bendAngle = df * rotationStr; - const angle = baseAngle + bendAngle; - const x = dist * Math.cos(angle); - const z = dist * Math.sin(angle); - const y = galaxy.params.heightMag * Math.sin(dist * galaxy.params.heightFreq) * (1 - df * 0.3); - - const nebula = TH.createGlowSprite(ac.color, 40 + di * 15); - nebula.position.set(x, y, z); - nebula.material.opacity = 0.04; - coreGlowGroup.add(nebula); - } - } - - // ── System spheres (shared geometry + per-system materials) ── - // Each system gets its own material so viewMode color changes don't corrupt other systems - galaxy.systems.forEach(sys => { - const group = new T.Group(); - group.position.set(sys.x, sys.y, sys.z); - group.userData = { systemId: sys.id }; - - // Core sphere — clickable, shared geometry but own material - const secHex = secColor(sys.security); - const coreMat = new T.MeshBasicMaterial({ color: secHex }); - const core = new T.Mesh(sharedCoreGeo, coreMat); - core.userData = { systemId: sys.id, isSystem: true }; - group.add(core); - - // Glow sprite — reuse shared canvas texture, own material - const glowMat = new T.SpriteMaterial({ - map: sharedGlowTexture, - color: secHex, - transparent: true, - blending: T.AdditiveBlending, - depthWrite: false, - }); - const glow = new T.Sprite(glowMat); - glow.scale.setScalar(10); - group.add(glow); - - // Star color inner dot — shared geometry, own material - const innerColor = new T.Color(sys.starColor).getHex(); - const innerMat = new T.MeshBasicMaterial({ color: innerColor }); - const inner = new T.Mesh(sharedInnerGeo, innerMat); - group.add(inner); - - systemGroup.add(group); - systemMeshes.push(core); - - // Label - const label = TH.createLabel(sys.name, secColorCSS(sys.security), 16); - label.position.set(sys.x, sys.y + 6, sys.z); - label.userData = { systemId: sys.id, isLabel: true }; - labelGroup.add(label); - }); - - // ── Stargate lines (use sysMap for O(1) lookup) ── - galaxy.stargates.forEach(gate => { - const sA = currentSysMap.get(gate.from); - const sB = currentSysMap.get(gate.to); - if (!sA || !sB) return; - - let color, opacity; - if (gate.type === 'mst') { color = 0x0e1a2a; opacity = 0.4; } - else if (gate.type === 'intra') { color = 0x0f1e30; opacity = 0.35; } - else if (gate.type === 'arm-link') { color = 0x152535; opacity = 0.3; } - else if (gate.type === 'choke') { color = 0xf0a030; opacity = 0.35; } - else if (gate.type === 'hub') { color = 0x22c55e; opacity = 0.25; } - else { color = 0x101820; opacity = 0.2; } - - const line = TH.createConnectionLine( - { x: sA.x, y: sA.y, z: sA.z }, - { x: sB.x, y: sB.y, z: sB.z }, - color, opacity - ); - line.userData = { gateFrom: gate.from, gateTo: gate.to, gateType: gate.type }; - gateGroup.add(line); - }); - } - - // ── Update highlights (reuses geometry, uses sysMap for O(1)) ── - const sharedRingGeo = new T.RingGeometry(5, 6, 24); - sharedRingGeo.rotateX(-Math.PI / 2); - const sharedHoverRingGeo = new T.RingGeometry(4, 4.8, 24); - sharedHoverRingGeo.rotateX(-Math.PI / 2); - - function updateHighlights() { - disposeGroup(highlightGroup, false); // uses shared geometry/textures - const g = galaxyRef.current; - if (!g) return; - const sm = currentSysMap; - if (!sm) return; - - const selId = selectedSystemRef.current?.id; - const hovId = hoveredSystemRef.current?.id; - const pStart = pathStartRef.current; - const pEnd = pathEndRef.current; - const path = currentPathRef.current; - - // Selection ring — shared geometry - if (selId) { - const sys = sm.get(selId); - if (sys) { - const ringMat = new T.MeshBasicMaterial({ - color: 0x22d3ee, transparent: true, opacity: 0.7, side: T.DoubleSide, depthWrite: false, - }); - const ring = new T.Mesh(sharedRingGeo, ringMat); - ring.position.set(sys.x, sys.y, sys.z); - highlightGroup.add(ring); - } - } - - // Hover ring - if (hovId && hovId !== selId) { - const sys = sm.get(hovId); - if (sys) { - const ringMat = new T.MeshBasicMaterial({ - color: 0xf1f5f9, transparent: true, opacity: 0.5, side: T.DoubleSide, depthWrite: false, - }); - const ring = new T.Mesh(sharedHoverRingGeo, ringMat); - ring.position.set(sys.x, sys.y, sys.z); - highlightGroup.add(ring); - } - } - - // Path markers — reuse shared glow texture - if (pStart) { - const sys = sm.get(pStart); - if (sys) { - const mat = new T.SpriteMaterial({ map: sharedGlowTexture, color: 0x22c55e, transparent: true, blending: T.AdditiveBlending, depthWrite: false }); - const marker = new T.Sprite(mat); - marker.scale.setScalar(20); - marker.position.set(sys.x, sys.y + 1, sys.z); - highlightGroup.add(marker); - } - } - if (pEnd) { - const sys = sm.get(pEnd); - if (sys) { - const mat = new T.SpriteMaterial({ map: sharedGlowTexture, color: 0xef4444, transparent: true, blending: T.AdditiveBlending, depthWrite: false }); - const marker = new T.Sprite(mat); - marker.scale.setScalar(20); - marker.position.set(sys.x, sys.y + 1, sys.z); - highlightGroup.add(marker); - } - } - - // Route line - if (path.length > 1) { - const points = path.map(id => { - const sys = sm.get(id); - return sys ? new T.Vector3(sys.x, sys.y + 2, sys.z) : new T.Vector3(); - }); - const routeLine = TH.createRouteLine(points.map(p => ({ x: p.x, y: p.y, z: p.z })), 0x22d3ee); - highlightGroup.add(routeLine); - } - } - - // ── Update system colors based on viewMode (uses sysMap for O(1)) ── - function updateSystemColors() { - const g = galaxyRef.current; - if (!g) return; - const mode = viewModeRef.current; - const sm = currentSysMap; - - systemGroup.children.forEach(group => { - const sysId = group.userData.systemId; - const sys = sm ? sm.get(sysId) : g.systems.find(s => s.id === sysId); - if (!sys) return; - - const core = group.children.find(c => c.userData.isSystem); - if (!core) return; - - let color; - if (mode === 'faction') color = factionColor(sys.faction); - else if (mode === 'gates') { - const gateCount = g.stargates.filter(gate => gate.from === sys.id || gate.to === sys.id).length; - color = gateCount >= 4 ? 0xef4444 : gateCount >= 3 ? 0xf0a030 : gateCount >= 2 ? 0x22d3ee : 0x94a3b8; - } else { - color = secColor(sys.security); - } - core.material.color.setHex(color); - }); - } - - // ── Mouse interaction ── - function getHoveredSystem(event) { - const rect = renderer.domElement.getBoundingClientRect(); - mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1; - mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1; - raycaster.setFromCamera(mouse, camera); - const intersects = raycaster.intersectObjects(systemMeshes, false); - if (intersects.length > 0) { - const sysId = intersects[0].object.userData.systemId; - return currentSysMap ? currentSysMap.get(sysId) : (galaxyRef.current?.systems.find(s => s.id === sysId) || null); - } - return null; - } - - let mouseDownPos = { x: 0, y: 0 }; - - const onMouseDown = (e) => { - mouseDownPos = { x: e.clientX, y: e.clientY }; - }; - - const onMouseMove = (e) => { - const hovered = getHoveredSystem(e); - setHoveredSystem(hovered); - renderer.domElement.style.cursor = hovered ? 'pointer' : 'default'; - }; - - const onClick = (e) => { - if (e.button !== 0) return; - // Ignore if it was a drag - const dx = e.clientX - mouseDownPos.x; - const dy = e.clientY - mouseDownPos.y; - if (Math.sqrt(dx * dx + dy * dy) > 5) return; - - const rect = renderer.domElement.getBoundingClientRect(); - clickMouse.x = ((e.clientX - rect.left) / rect.width) * 2 - 1; - clickMouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1; - raycaster.setFromCamera(clickMouse, camera); - const intersects = raycaster.intersectObjects(systemMeshes, false); - if (intersects.length > 0) { - const sysId = intersects[0].object.userData.systemId; - const sys = galaxyRef.current?.systems.find(s => s.id === sysId); - if (sys) { - setSelectedSystem(sys); - orbit.flyTo(sys.x, sys.y, sys.z, 60); - } - } else { - setSelectedSystem(null); - } - }; - - const onContextMenu = (e) => { - e.preventDefault(); - // Ignore if it was a drag - const dx = e.clientX - mouseDownPos.x; - const dy = e.clientY - mouseDownPos.y; - if (Math.sqrt(dx * dx + dy * dy) > 5) return; - - const hovered = getHoveredSystem(e); - if (!hovered || !galaxyRef.current) return; - - const g = galaxyRef.current; - const ps = pathStartRef.current; - - if (!ps) { - setPathStart(hovered.id); - setPathEnd(null); - setCurrentPath([]); - } else if (ps !== hovered.id) { - setPathEnd(hovered.id); - const path = g.findPath(ps, hovered.id); - setCurrentPath(path); - } else { - setPathStart(null); - setPathEnd(null); - setCurrentPath([]); - } - }; - - renderer.domElement.addEventListener('mousedown', onMouseDown); - renderer.domElement.addEventListener('mousemove', onMouseMove); - renderer.domElement.addEventListener('click', onClick); - renderer.domElement.addEventListener('contextmenu', onContextMenu); - - // ── Resize handling ── - const onResize = () => { - TH.handleResize(renderer, camera, container); - }; - window.addEventListener('resize', onResize); - - // ── Animation loop ── - let animId; - let lastTime = performance.now(); - let prevHighlightState = ''; - - function animate() { - animId = requestAnimationFrame(animate); - const now = performance.now(); - const dt = (now - lastTime) / 1000; - lastTime = now; - - // Auto-rotate (slow orbit) - if (autoRotateRef.current && !orbit.isDragging && !orbit._flyActive) { - orbit.theta += dt * 0.03; - } - - orbit.update(dt); - - // Only rebuild highlights when state actually changed - const stateKey = [ - selectedSystemRef.current?.id, - hoveredSystemRef.current?.id, - pathStartRef.current, - pathEndRef.current, - currentPathRef.current.join(','), - ].join('|'); - if (stateKey !== prevHighlightState) { - prevHighlightState = stateKey; - updateHighlights(); - } - - // Toggle visibility - gateGroup.visible = showGatesRef.current; - labelGroup.visible = showLabelsRef.current; - dustGroup.visible = showDustRef.current; - - // Subtle dust cloud rotation (very cheap — just rotates the group matrix) - if (dustGroup.visible && dustGroup.children.length > 0) { - dustGroup.rotation.y += dt * 0.002; - } - - // Core glow pulse - const pulse = 1.0 + Math.sin(now * 0.001) * 0.08; - coreGlowGroup.children.forEach(sprite => { - if (sprite.isSprite && sprite.scale) { - const baseScale = sprite.userData.baseScale || sprite.scale.x; - sprite.userData.baseScale = baseScale; - sprite.scale.setScalar(baseScale * pulse); - } - }); - - renderer.render(scene, camera); - } - - sceneRef.current = { buildScene, updateSystemColors, renderer, orbit }; - - animate(); - - return () => { - cancelAnimationFrame(animId); - window.removeEventListener('resize', onResize); - renderer.domElement.removeEventListener('mousedown', onMouseDown); - renderer.domElement.removeEventListener('mousemove', onMouseMove); - renderer.domElement.removeEventListener('click', onClick); - renderer.domElement.removeEventListener('contextmenu', onContextMenu); - orbit.dispose(); - renderer.dispose(); - if (container.contains(renderer.domElement)) { - container.removeChild(renderer.domElement); - } - sceneRef.current = null; - }; - }, []); // Mount once - - // ── Rebuild 3D scene when galaxy changes ── - useEffect(() => { - if (sceneRef.current && galaxy) { - sceneRef.current.buildScene(galaxy); - sceneRef.current.updateSystemColors(); - } - }, [galaxy]); - - // ── Update system colors when viewMode changes ── - useEffect(() => { - if (sceneRef.current) { - sceneRef.current.updateSystemColors(); - } - }, [viewMode]); - - // ── Seed handlers ── - const handleSeedSubmit = useCallback(() => { - const parsed = parseInt(inputSeed, 10); - if (!isNaN(parsed) && parsed > 0) { - setSeed(parsed); - } else { - let hash = 0; - for (let i = 0; i < inputSeed.length; i++) { - hash = ((hash << 5) - hash) + inputSeed.charCodeAt(i); - hash = hash >>> 0; - } - setSeed(hash || 1); - } - }, [inputSeed]); - - const randomSeed = useCallback(() => { - const newSeed = Math.floor(Math.random() * 999999) + 1; - setSeed(newSeed); - setInputSeed(String(newSeed)); - }, []); - - // ── Selected system details ── - const selectedDetails = useMemo(() => { - if (!galaxy || !selectedSystem) return null; - const sys = selectedSystem; - const sysStations = galaxy.stations.filter(s => s.systemId === sys.id); - const sysBelts = galaxy.belts.filter(b => b.systemId === sys.id); - const sysGates = galaxy.stargates.filter(g => g.from === sys.id || g.to === sys.id); - const connectedSystems = sysGates.map(g => { - const targetId = g.from === sys.id ? g.to : g.from; - return galaxy.sysMap ? galaxy.sysMap.get(targetId) : galaxy.systems.find(s => s.id === targetId); - }).filter(Boolean); - return { ...sys, sysStations, sysBelts, sysGates, connectedSystems }; - }, [galaxy, selectedSystem]); - - // ── Stats ── - const stats = useMemo(() => { - if (!galaxy) return null; - const totalGates = galaxy.stargates.length; - const mstGates = galaxy.stargates.filter(g => g.type === 'mst').length; - const chokeGates = galaxy.stargates.filter(g => g.type === 'choke').length; - const hubGates = galaxy.stargates.filter(g => g.type === 'hub').length; - const avgGatesPerSys = (totalGates * 2 / galaxy.systems.length).toFixed(1); - const secDist = { high: 0, low: 0, null: 0, deep: 0 }; - galaxy.systems.forEach(s => { - if (s.security >= 0.5) secDist.high++; - else if (s.security >= 0.1) secDist.low++; - else if (s.security >= -0.4) secDist.null++; - else secDist.deep++; - }); - return { - systems: galaxy.systems.length, constellations: galaxy.constellations.length, - regions: galaxy.regions.length, gates: totalGates, mstGates, chokeGates, hubGates, - avgGatesPerSys, stations: galaxy.stations.length, belts: galaxy.belts.length, - agents: galaxy.agents.length, connected: galaxy.connected, secDist, - }; - }, [galaxy]); - - if (!galaxy) return
Generating galaxy…
; - - return ( -
- {/* Three.js Container */} -
- - {/* HUD overlay - top left: seed + view mode */} -
-
- SEED - setInputSeed(e.target.value)} - onKeyDown={e => e.key === 'Enter' && handleSeedSubmit()} - style={{ width: 80, background: '#0a0f18', border: '1px solid #1c2a3f', borderRadius: 3, color: '#f0a030', fontSize: 12, padding: '2px 6px', fontFamily: 'inherit', outline: 'none' }} - /> - - -
-
- {['security', 'faction', 'gates'].map(mode => ( - - ))} -
-
- - {/* Galaxy parameters overlay - below top bar */} -
-
- ARMS - setGalaxyParams(p => ({ ...p, armCount: parseInt(e.target.value) }))} - style={{ width: 50, accentColor: '#f0a030' }} /> - {galaxyParams.armCount} -
-
- TWIST - setGalaxyParams(p => ({ ...p, rotationStrength: parseFloat(e.target.value) }))} - style={{ width: 50, accentColor: '#22d3ee' }} /> - {galaxyParams.rotationStrength.toFixed(1)} -
-
- SPREAD - setGalaxyParams(p => ({ ...p, armSpread: parseFloat(e.target.value) }))} - style={{ width: 50, accentColor: '#22c55e' }} /> - {galaxyParams.armSpread.toFixed(2)} -
-
- DENSITY - setGalaxyParams(p => ({ ...p, gravity: parseFloat(e.target.value) }))} - style={{ width: 50, accentColor: '#a855f7' }} /> - {galaxyParams.gravity.toFixed(1)} -
-
- - {/* Controls overlay - top right */} -
- {[ - { label: 'Gates', val: showGates, set: setShowGates }, - { label: 'Labels', val: showLabels, set: setShowLabels }, - { label: 'Dust Cloud', val: showDust, set: setShowDust }, - { label: 'Auto-Rotate', val: autoRotate, set: setAutoRotate }, - ].map(ctrl => ( - - ))} -
- - {/* Hint bar */} -
- Click system to inspect - Right-click to route - Left-drag to orbit - Right-drag to pan - Scroll to zoom -
- - {/* Sidebar */} -
- {/* Stats header */} -
-
SPIRAL GALAXY
-
- {stats && [ - { label: 'Systems', val: stats.systems, color: '#f0a030' }, - { label: 'Regions', val: stats.regions, color: '#38bdf8' }, - { label: 'Constellations', val: stats.constellations, color: '#22d3ee' }, - { label: 'Stargates', val: stats.gates, color: '#94a3b8' }, - { label: 'MST edges', val: stats.mstGates, color: '#22c55e' }, - { label: 'Choke gates', val: stats.chokeGates, color: '#ef4444' }, - { label: 'Hub gates', val: stats.hubGates, color: '#22c55e' }, - { label: 'Stations', val: stats.stations, color: '#22d3ee' }, - { label: 'Belts', val: stats.belts, color: '#92400e' }, - { label: 'NPC Agents', val: stats.agents, color: '#a855f7' }, - { label: 'Avg gates/sys', val: stats.avgGatesPerSys, color: '#94a3b8' }, - { label: 'Dust particles', val: galaxy.dustCount || 0, color: '#5a6b82' }, - ].map((s, i) => ( -
- {s.label} - {s.val} -
- ))} -
- {stats && ( -
- High: {stats.secDist.high} - Low: {stats.secDist.low} - Null: {stats.secDist.null} - Deep: {stats.secDist.deep} -
- )} - {stats && ( -
- - {stats.connected ? '✓ Fully connected' : '✗ Disconnected!'} - -
- )} -
- - {/* Region overview */} -
-
REGIONS
- {galaxy.regions.map(r => ( -
- {r.name} - {r.systemIds.length} sys -
- ))} -
- - {/* Selected system detail */} -
- {selectedDetails ? ( - <> -
{selectedDetails.name}
-
- {[ - { label: 'Security', val: selectedDetails.security.toFixed(2), color: secColorCSS(selectedDetails.security) }, - { label: 'Star Type', val: selectedDetails.starType, color: selectedDetails.starColor }, - { label: 'Faction', val: selectedDetails.faction, color: '#94a3b8' }, - { label: 'Planets', val: selectedDetails.planetCount, color: '#94a3b8' }, - { label: 'Stations', val: selectedDetails.sysStations.length, color: '#22d3ee' }, - { label: 'Belts', val: selectedDetails.sysBelts.length, color: '#92400e' }, - { label: 'Gates', val: selectedDetails.sysGates.length, color: '#94a3b8' }, - { label: 'Region', val: selectedDetails.regionId, color: '#5a6b82' }, - ].map((row, i) => ( -
- {row.label} - {row.val} -
- ))} -
- - {selectedDetails.sysStations.length > 0 && ( -
-
STATIONS
- {selectedDetails.sysStations.map((stn, i) => ( -
- {stn.name} [{stn.services.join(', ')}] -
- ))} -
- )} - - {selectedDetails.sysBelts.length > 0 && ( -
-
BELTS
- {selectedDetails.sysBelts.map((belt, i) => ( -
- Belt {i + 1}: {belt.oreType} -
- ))} -
- )} - - {selectedDetails.connectedSystems.length > 0 && ( -
-
CONNECTED SYSTEMS
- {selectedDetails.connectedSystems.map((cs, i) => ( -
{ - setSelectedSystem(cs); - if (sceneRef.current?.orbit) { - sceneRef.current.orbit.flyTo(cs.x, cs.y, cs.z, 60); - } - }}> - - {cs.security.toFixed(2)} - - {' '}{cs.name} - ({cs.starType}) -
- ))} -
- )} - - ) : ( -
- Click a system to inspect

- Right-click two systems to
calculate shortest route
-
- )} -
- - {/* Route info */} - {currentPath.length > 1 && ( -
-
- ROUTE: {currentPath.length - 1} JUMP{currentPath.length - 1 !== 1 ? 'S' : ''} -
-
- {currentPath.map((id, i) => { - const sys = galaxy.sysMap ? galaxy.sysMap.get(id) : galaxy.systems.find(s => s.id === id); - if (!sys) return null; - return ( - - { - setSelectedSystem(sys); - if (sceneRef.current?.orbit) { - sceneRef.current.orbit.flyTo(sys.x, sys.y, sys.z, 60); - } - }} - > - {sys.name} - - {i < currentPath.length - 1 && } - - ); - })} -
-
- )} - - {/* Gate legend */} -
-
- MST - Intra - Arm-link - Choke - Hub -
-
-
-
- ); -} - -window.GDD.GalaxyDemo = GalaxyDemo; diff --git a/archive/legacy-static/js/demos/gamehud.js b/archive/legacy-static/js/demos/gamehud.js deleted file mode 100644 index 656e798..0000000 --- a/archive/legacy-static/js/demos/gamehud.js +++ /dev/null @@ -1,497 +0,0 @@ -window.GDD = window.GDD || {}; - -const { useState, useEffect, useRef, useCallback } = React; -const TH = window.GDD.THREE; - -/* ===== Game HUD Demo — 3D space viewport inside HUD overlay ===== */ -function GameHudDemo() { - const containerRef = useRef(null); - const sceneRef = useRef(null); - const animIdRef = useRef(null); - - const [modules, setModules] = useState([ - { id: 'h1', name: '150mm Railgun', icon: '⊕', active: false, type: 'weapon', slot: 'high' }, - { id: 'h2', name: 'Missile Launcher', icon: '⊕', active: false, type: 'weapon', slot: 'high' }, - { id: 'h3', name: 'Mining Laser', icon: '⛏', active: false, type: 'mining', slot: 'high' }, - { id: 'm1', name: 'Shield Booster', icon: '◎', active: false, type: 'shield', slot: 'med' }, - { id: 'm2', name: 'Afterburner', icon: '»', active: false, type: 'propulsion', slot: 'med' }, - { id: 'm3', name: 'Warp Scram', icon: '◎', active: false, type: 'ewar', slot: 'med' }, - { id: 'l1', name: 'Armor Plate', icon: '◼', active: true, type: 'armor', slot: 'low' }, - { id: 'l2', name: 'Damage Control', icon: '↯', active: true, type: 'damage_mod', slot: 'low' }, - { id: 'l3', name: 'Cargo Expander', icon: '□', active: false, type: 'cargo', slot: 'low' }, - ]); - const [target] = useState({ name: 'Guristas Pirate', type: 'Frigate', locked: true, shields: 62, armor: 85, hull: 100, distance: 12400, bounty: 85000 }); - const [cargo] = useState({ used: 340, total: 600, items: [{ name: 'Veldspar', qty: 120 }, { name: 'Scordite', qty: 80 }, { name: 'Pyroxeres', qty: 45 }, { name: 'Tritanium', qty: 200 }] }); - const [chatState, setChatState] = useState({ - activeTab: 'local', - messages: [ - { sender: 'CMDR Picard', body: ' Pirates in belt 3, be careful ', time: '14:23' }, - { sender: 'MinerBob', body: ' Anyone selling compressed ore? ', time: '14:21' }, - { sender: 'CMDR Worf', body: ' Target locked, engaging hostiles ', time: '14:19' }, - { sender: '[SYSTEM]', body: ' Guristas fleet detected in nearby system ', time: '14:15' }, - { sender: 'TraderAlice', body: ' Best buy orders at Jita IV — check market ', time: '14:12' }, - ], - }); - const [ship, setShip] = useState({ shields: 100, armor: 92, hull: 100, capacitor: 78, speed: 0, maxSpeed: 420, name: 'USS ENTERPRISE', class: 'VENTURE-CLASS' }); - const [entities] = useState([ - { id: 'e1', name: 'Asteroid Belt', type: 'asteroid', dist: '12 km' }, - { id: 'e2', name: 'Guristas Pirate', type: 'hostile', dist: '24 km' }, - { id: 'e3', name: 'CMDR Riker', type: 'friendly', dist: '38 km' }, - { id: 'e4', name: 'Jita IV Station', type: 'station', dist: '45 km' }, - { id: 'e5', name: 'Veldspar Rock', type: 'asteroid', dist: '8 km' }, - { id: 'e6', name: 'MinerBob', type: 'friendly', dist: '52 km' }, - { id: 'e7', name: 'Jump Gate', type: 'gate', dist: '120 km' }, - { id: 'e8', name: 'Scordite Deposit', type: 'asteroid', dist: '15 km' }, - ]); - const [overviewFilter, setOverviewFilter] = useState('all'); - const [system] = useState({ name: 'Jita', security: 0.9 }); - - // Build 3D scene - useEffect(() => { - const container = containerRef.current; - if (!container) return; - - const scene = new THREE.Scene(); - - const w = container.clientWidth; - const h = container.clientHeight; - const camera = new THREE.PerspectiveCamera(60, w / h, 0.1, 5000); - camera.position.set(0, 8, 25); - camera.lookAt(0, 0, -20); - - const renderer = TH.createRenderer(container, { clearColor: 0x040810 }); - renderer.setSize(w, h); - - // Stars - const stars = TH.createStarField(4000, 3000); - scene.add(stars); - - // Nebulae - TH.addNebula(scene, 0x22d3ee, [-30, 20, -100], 80); - TH.addNebula(scene, 0xa78bfa, [50, -10, -80], 60); - TH.addNebula(scene, 0xf0a030, [-60, 15, -120], 50); - - // Lighting - TH.setupSpaceLighting(scene); - - // Player ship (small, at bottom center of view) - const playerGroup = new THREE.Group(); - const pMesh = TH.createShipMesh(0xc8d6e5, 0xf0a030, 0.5); - pMesh.rotation.y = Math.PI / 2; - playerGroup.add(pMesh); - - const pEngine = TH.createEngineGlow(0x22d3ee, 2, 8); - pEngine.position.set(0, 0, 5); - playerGroup.add(pEngine); - - const pShield = TH.createShield(2.5, 0x22d3ee, 0.05); - playerGroup.add(pShield); - - playerGroup.position.set(0, -1, 15); - scene.add(playerGroup); - - // Enemy ship (in the distance) - const enemyGroup = new THREE.Group(); - const eMesh = TH.createShipMesh(0x7f1d1d, 0xef4444, 0.4); - eMesh.rotation.y = -Math.PI / 2; - enemyGroup.add(eMesh); - - const eEngine = TH.createEngineGlow(0xef4444, 1.5, 6); - eEngine.position.set(0, 0, -4); - enemyGroup.add(eEngine); - - const lockBrackets = TH.createLockBrackets(2, 0xf0a030); - enemyGroup.add(lockBrackets); - - const eLabel = TH.createLabel('GURISTAS PIRATE', '#ef4444', 12); - eLabel.position.y = 4; - enemyGroup.add(eLabel); - - enemyGroup.position.set(5, 1, -20); - scene.add(enemyGroup); - - // Asteroids - const asteroidPositions = [ - { x: -25, y: -3, z: -15, size: 3 }, - { x: -20, y: -2, z: -10, size: 2 }, - { x: -30, y: -4, z: -20, size: 2.5 }, - { x: 30, y: -3, z: -18, size: 2 }, - { x: 35, y: -2, z: -12, size: 1.5 }, - { x: -15, y: -5, z: -30, size: 3.5 }, - { x: 20, y: -4, z: -35, size: 2 }, - ]; - asteroidPositions.forEach(ap => { - const ast = TH.createAsteroid(ap.size); - ast.position.set(ap.x, ap.y, ap.z); - ast.rotation.set(Math.random() * Math.PI, Math.random() * Math.PI, 0); - scene.add(ast); - }); - - // Station (far right) - const station = TH.createStation(3, 0x22d3ee); - station.position.set(40, 2, -40); - scene.add(station); - const stnLabel = TH.createLabel('JITA IV STN', '#22d3ee', 12); - stnLabel.position.set(40, 8, -40); - scene.add(stnLabel); - - // Targeting line (dashed) - const linePoints = [playerGroup.position, enemyGroup.position]; - const lineGeo = new THREE.BufferGeometry().setFromPoints(linePoints); - const lineMat = new THREE.LineDashedMaterial({ color: 0xf0a030, dashSize: 1, gapSize: 0.8, transparent: true, opacity: 0.2 }); - const targetLine = new THREE.Line(lineGeo, lineMat); - targetLine.computeLineDistances(); - scene.add(targetLine); - - sceneRef.current = { scene, camera, renderer, stars, playerGroup, enemyGroup, pEngine, eEngine, lockBrackets, targetLine }; - - // Animation - const clock = new THREE.Clock(); - const animate = () => { - animIdRef.current = requestAnimationFrame(animate); - const t = clock.getElapsedTime(); - - // Star drift - stars.rotation.y = t * 0.002; - stars.rotation.x = t * 0.001; - - // Player ship idle bob - playerGroup.position.y = -1 + Math.sin(t * 1.5) * 0.2; - playerGroup.rotation.z = Math.sin(t * 0.3) * 0.02; - - // Enemy ship slight movement - enemyGroup.position.x = 5 + Math.sin(t * 1.2) * 1; - enemyGroup.position.y = 1 + Math.cos(t * 0.8) * 0.5; - enemyGroup.rotation.z = Math.sin(t * 0.4) * 0.03; - - // Lock brackets rotate - lockBrackets.rotation.y = t * 0.3; - - // Update target line - const positions = targetLine.geometry.attributes.position; - positions.setXYZ(0, playerGroup.position.x, playerGroup.position.y, playerGroup.position.z); - positions.setXYZ(1, enemyGroup.position.x, enemyGroup.position.y, enemyGroup.position.z); - positions.needsUpdate = true; - targetLine.computeLineDistances(); - - // Engine glow pulse - pEngine.intensity = 2 + Math.sin(t * 3) * 0.5; - eEngine.intensity = 1 + Math.sin(t * 2.5) * 0.3; - - // Shield shimmer - pShield.material.opacity = 0.04 + Math.sin(t * 2) * 0.02; - - renderer.render(scene, camera); - }; - animate(); - - const onResize = () => { - const w2 = container.clientWidth; - const h2 = container.clientHeight; - renderer.setSize(w2, h2); - camera.aspect = w2 / h2; - camera.updateProjectionMatrix(); - }; - window.addEventListener('resize', onResize); - - return () => { - if (animIdRef.current) cancelAnimationFrame(animIdRef.current); - window.removeEventListener('resize', onResize); - }; - }, []); - - const toggleModule = useCallback((modId) => { - setModules(prev => prev.map(m => m.id === modId ? { ...m, active: !m.active } : m)); - }, []); - - // Simulate capacitor tick - useEffect(() => { - const interval = setInterval(() => { - setShip(prev => { - const activeCount = modules.filter(m => m.active).length; - return { ...prev, capacitor: Math.max(0, Math.min(100, prev.capacitor - activeCount * 0.3 + 0.8)), speed: modules.find(m => m.id === 'm2' && m.active) ? 280 : 0 }; - }); - }, 1000); - return () => clearInterval(interval); - }, [modules]); - - const filteredEntities = overviewFilter === 'all' ? entities : entities.filter(e => e.type === overviewFilter); - const activeModules = modules.filter(m => m.active).length; - - return ( -
-

Game HUD — Live Concept (3D)

-

- The in-game HUD with a 3D WebGL space viewport. All panels overlay the Three.js scene — ship health, modules, overview, target info, cargo, and chat. -

- - {/* Full HUD mockup */} -
- {/* 3D Canvas */} -
- - {/* HUD overlay */} -
- - {/* TOP BAR */} -
- { e.currentTarget.style.color = 'var(--fg-bright)'; e.currentTarget.style.borderColor = 'var(--accent)'; }} - onMouseLeave={(e) => { e.currentTarget.style.color = 'var(--fg-dim)'; e.currentTarget.style.borderColor = 'var(--border)'; }} - title="Back to Game Docs" - > - - DOCS - -
- {system.name} - - {system.security.toFixed(1)} - -
- Ship - {ship.name} -
- ₢125,000 -
- - - TQ Online - - 14:23 UTC -
- 3D -
- - {/* MIDDLE */} -
- - {/* LEFT — Ship Panel */} -
-
-
- Ship Status -
-
- {[ - { label: 'SH', value: ship.shields, color: '#22d3ee' }, - { label: 'AR', value: ship.armor, color: '#f0a030' }, - { label: 'HU', value: ship.hull, color: '#22c55e' }, - { label: 'CA', value: ship.capacitor, color: '#a78bfa' }, - ].map(bar => ( -
- {bar.label} -
-
30 ? 'linear-gradient(90deg, #6366f1, #a78bfa)' : 'linear-gradient(90deg, #dc2626, #ef4444)') : `linear-gradient(90deg, ${bar.color}88, ${bar.color})`, borderRadius: 'var(--radius-pill)' }} /> -
- {bar.label === 'CA' ? Math.round(bar.value) : bar.value}% -
- ))} -
-
- - {/* Speed */} -
-
-
0 ? 'var(--fg-bright)' : 'var(--muted)', letterSpacing: '-0.02em' }}> - {ship.speed}m/s -
-
-
-
-
- - - -
-
-
-
- -
- - {/* RIGHT — Overview */} -
-
-
- Overview - {filteredEntities.length} items -
-
- {['all', 'hostile', 'asteroid', 'friendly'].map(f => ( - - ))} -
-
- {filteredEntities.map(ent => { - const col = ent.type === 'hostile' ? '#ef4444' : ent.type === 'asteroid' ? '#a78bfa' : ent.type === 'station' ? '#22d3ee' : ent.type === 'gate' ? '#5a6b82' : '#22c55e'; - return ( -
- {ent.name} - {ent.dist} -
- ); - })} -
-
-
-
- - {/* BOTTOM BAR */} -
- {/* Modules */} -
-
- Modules - {activeModules} active -
-
- {['high', 'med', 'low'].map(slotType => ( -
- - {slotType.toUpperCase()} - - {modules.filter(m => m.slot === slotType).map(mod => ( -
toggleModule(mod.id)} title={`${mod.name} (${mod.active ? 'Active' : 'Inactive'})`}> - - {mod.name.length > 14 ? mod.name.slice(0, 12) + '…' : mod.name} -
- ))} -
- ))} -
-
- - {/* Target */} -
-
- Target -
-
-
{target.name}
-
{target.type} · LOCKED
- {[ - { label: 'SH', value: target.shields, color: '#22d3ee' }, - { label: 'AR', value: target.armor, color: '#f0a030' }, - { label: 'HU', value: target.hull, color: '#22c55e' }, - ].map(bar => ( -
- {bar.label} -
-
-
- {bar.value}% -
- ))} -
- {target.distance.toLocaleString()} km - ₢{target.bounty.toLocaleString()} -
-
-
- - {/* Cargo */} -
-
- Cargo -
-
-
- {cargo.used}/{cargo.total} m³ - {Math.round(cargo.used / cargo.total * 100)}% -
-
-
-
- {cargo.items.map((item, i) => ( -
- {item.name} - ×{item.qty} -
- ))} -
-
- - {/* Chat */} -
-
- {['local', 'corp', 'trade'].map(tab => ( - - ))} -
-
- {chatState.messages.map((msg, i) => ( -
- {msg.sender} - {msg.body} - {msg.time} -
- ))} -
-
- - -
-
-
-
-
- - {/* Architecture notes */} -
-
- HUD -

HUD Panel Architecture — 3D

-
-
-
-

3D Viewport

-
    -
  • Three.js WebGL replaces 2D Canvas — proper 3D ship meshes, asteroids, stations, star fields
  • -
  • Depth and lighting — ships and asteroids are lit by ambient + directional lights
  • -
  • Particle star field — 4000 point-based stars with subtle rotation for depth
  • -
  • Engine glows — point lights on each ship with pulsing intensity
  • -
  • Lock brackets — 3D wireframe targeting indicator that rotates around the target
  • -
-
-
-

HUD Overlay Pattern

-
    -
  • CSS overlay — HUD panels are positioned absolutely over the 3D canvas
  • -
  • Glass morphism — backdrop-filter blur + semi-transparent backgrounds
  • -
  • Pointer events — HUD panels capture clicks, center viewport passes through
  • -
  • Performance — Three.js renderer is separate from React DOM updates
  • -
-
-
-
- -
- Rendering upgrade: The space viewport now uses Three.js with WebGL. Ships are 3D meshes with lighting, asteroids use icosahedron geometry with vertex perturbation, and the star field is a 4000-particle Points system. The HUD overlay panels remain identical React components. -
-
- ); -} - -window.GDD.GameHudDemo = GameHudDemo; diff --git a/archive/legacy-static/js/demos/market.js b/archive/legacy-static/js/demos/market.js deleted file mode 100644 index 758951d..0000000 --- a/archive/legacy-static/js/demos/market.js +++ /dev/null @@ -1,1182 +0,0 @@ -window.GDD = window.GDD || {}; - -const { useState, useEffect, useRef, useCallback, useMemo } = React; - -/* ────────────────────────────────────────────── - Commodities Exchange — Contract Market - ────────────────────────────────────────────── */ - -/* ── Static contract catalogue ── */ -const CATEGORIES = [ - { id: 'all', label: 'All Contracts' }, - { id: 'ore', label: 'Raw Ores' }, - { id: 'mineral', label: 'Refined Minerals' }, - { id: 'gas', label: 'Gas Products' }, - { id: 'isotope', label: 'Isotopes' }, - { id: 'exotic', label: 'Exotic Matter' }, -]; - -const CONTRACTS = [ - /* Ores */ - { symbol:'VLD', name:'Veldspar', category:'ore', lotSize:1000, tickSize:0.01, marginPct:8, basePrice:14, hub:'Jita IV – Moon 4' }, - { symbol:'SCR', name:'Scordite', category:'ore', lotSize:500, tickSize:0.02, marginPct:10, basePrice:32, hub:'Jita IV – Moon 4' }, - { symbol:'PYX', name:'Pyroxeres', category:'ore', lotSize:500, tickSize:0.05, marginPct:10, basePrice:45, hub:'Amarr VIII – Emperor' }, - { symbol:'KRN', name:'Kernite', category:'ore', lotSize:250, tickSize:0.10, marginPct:12, basePrice:90, hub:'Amarr VIII – Emperor' }, - { symbol:'OMB', name:'Omber', category:'ore', lotSize:250, tickSize:0.10, marginPct:12, basePrice:135, hub:'Rens VI – Moon 8' }, - { symbol:'JSP', name:'Jaspet', category:'ore', lotSize:200, tickSize:0.10, marginPct:15, basePrice:190, hub:'Dodixie IX – Moon 20' }, - { symbol:'HEM', name:'Hemorphite', category:'ore', lotSize:100, tickSize:0.50, marginPct:18, basePrice:360, hub:'Jita IV – Moon 4' }, - { symbol:'ARK', name:'Arkonor', category:'ore', lotSize:50, tickSize:1.00, marginPct:20, basePrice:620, hub:'Jita IV – Moon 4' }, - /* Minerals */ - { symbol:'TRI', name:'Tritanium', category:'mineral', lotSize:5000, tickSize:0.01, marginPct:6, basePrice:5, hub:'Jita IV – Moon 4' }, - { symbol:'PYE', name:'Pyerite', category:'mineral', lotSize:2000, tickSize:0.01, marginPct:8, basePrice:12, hub:'Jita IV – Moon 4' }, - { symbol:'MLX', name:'Mexallon', category:'mineral', lotSize:1000, tickSize:0.02, marginPct:8, basePrice:35, hub:'Jita IV – Moon 4' }, - { symbol:'ISO', name:'Isogen', category:'mineral', lotSize:500, tickSize:0.05, marginPct:12, basePrice:110, hub:'Amarr VIII – Emperor' }, - { symbol:'NCX', name:'Nocxium', category:'mineral', lotSize:250, tickSize:0.10, marginPct:15, basePrice:380, hub:'Jita IV – Moon 4' }, - { symbol:'ZDR', name:'Zydrine', category:'mineral', lotSize:100, tickSize:0.50, marginPct:18, basePrice:950, hub:'Jita IV – Moon 4' }, - { symbol:'MEG', name:'Megacyte', category:'mineral', lotSize:50, tickSize:1.00, marginPct:20, basePrice:2800,hub:'Jita IV – Moon 4' }, - { symbol:'MOR', name:'Morphite', category:'mineral', lotSize:10, tickSize:5.00, marginPct:25, basePrice:8500,hub:'Jita IV – Moon 4' }, - /* Gas */ - { symbol:'ATM', name:'Atmospheric', category:'gas', lotSize:2000, tickSize:0.01, marginPct:10, basePrice:8, hub:'Jita IV – Moon 4' }, - { symbol:'NEB', name:'Nebular', category:'gas', lotSize:500, tickSize:0.05, marginPct:12, basePrice:45, hub:'Amarr VIII – Emperor' }, - { symbol:'ION', name:'Ionized', category:'gas', lotSize:200, tickSize:0.10, marginPct:15, basePrice:120, hub:'Jita IV – Moon 4' }, - { symbol:'FUL', name:'Fullerides', category:'gas', lotSize:100, tickSize:0.50, marginPct:18, basePrice:450, hub:'Jita IV – Moon 4' }, - /* Isotopes */ - { symbol:'H3', name:'Hydrogen-3', category:'isotope', lotSize:500, tickSize:0.05, marginPct:15, basePrice:85, hub:'Jita IV – Moon 4' }, - { symbol:'HE4', name:'Helium-4', category:'isotope', lotSize:500, tickSize:0.05, marginPct:15, basePrice:92, hub:'Amarr VIII – Emperor' }, - { symbol:'N15', name:'Nitrogen-15', category:'isotope', lotSize:500, tickSize:0.05, marginPct:15, basePrice:78, hub:'Rens VI – Moon 8' }, - { symbol:'O18', name:'Oxygen-18', category:'isotope', lotSize:500, tickSize:0.05, marginPct:15, basePrice:88, hub:'Dodixie IX – Moon 20' }, - /* Exotic */ - { symbol:'RDB', name:'Reedstone', category:'exotic', lotSize:10, tickSize:5.00, marginPct:25, basePrice:12000,hub:'Jita IV – Moon 4' }, - { symbol:'TCH', name:'Tachyon Salt', category:'exotic', lotSize:5, tickSize:10.0, marginPct:30, basePrice:45000,hub:'Jita IV – Moon 4' }, -]; - -/* ── Sparkline ── */ -function Sparkline({ data, width = 80, height = 24, color = 'var(--green)' }) { - const ref = useRef(null); - useEffect(() => { - const c = ref.current; if (!c || !data || data.length < 2) return; - const ctx = c.getContext('2d'); - const dpr = window.devicePixelRatio || 1; - c.width = width * dpr; c.height = height * dpr; - ctx.scale(dpr, dpr); - ctx.clearRect(0, 0, width, height); - const min = Math.min(...data); const max = Math.max(...data); - const range = max - min || 1; - const step = width / (data.length - 1); - ctx.beginPath(); - data.forEach((v, i) => { - const x = i * step; - const y = height - ((v - min) / range) * (height - 4) - 2; - i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y); - }); - ctx.strokeStyle = color; - ctx.lineWidth = 1.5; - ctx.lineJoin = 'round'; - ctx.stroke(); - const grad = ctx.createLinearGradient(0, 0, 0, height); - const isGreen = color.includes('green'); - grad.addColorStop(0, isGreen ? 'rgba(34,197,94,0.15)' : 'rgba(239,68,68,0.15)'); - grad.addColorStop(1, 'rgba(0,0,0,0)'); - ctx.lineTo(width, height); ctx.lineTo(0, height); ctx.closePath(); - ctx.fillStyle = grad; ctx.fill(); - }, [data, width, height, color]); - return ; -} - -/* ── Ticker Tape ── */ -function TickerTape({ items }) { - const [offset, setOffset] = useState(0); - const raf = useRef(null); - useEffect(() => { - const speed = 0.5; - const tick = () => { - setOffset(o => { const n = o - speed; return n < -3000 ? 0 : n; }); - raf.current = requestAnimationFrame(tick); - }; - raf.current = requestAnimationFrame(tick); - return () => cancelAnimationFrame(raf.current); - }, []); - const dup = [...items, ...items, ...items]; - return ( -
-
- {dup.map((t, i) => ( - - {t.symbol} - ₢{t.price.toLocaleString()} - = 0 ? 'var(--green)' : 'var(--red)', fontWeight: 600 }}> - {t.change >= 0 ? '▲' : '▼'} {Math.abs(t.changePct).toFixed(1)}% - - - ))} -
-
- ); -} - -/* ── Category Tabs ── */ -function CategoryTabs({ active, onChange }) { - return ( -
- {CATEGORIES.map(cat => ( - - ))} -
- ); -} - -/* ── Contract Board — main table ── */ -function ContractBoard({ commodities, selected, onSelect, category }) { - const filtered = category === 'all' ? commodities : commodities.filter(c => c.category === category); - return ( -
- {/* Table header */} -
- TickerNameLastChg% - BidAskVolumeOpen Int. -
- {/* Rows */} -
- {filtered.map(c => { - const up = c.change >= 0; - const isSel = selected === c.symbol; - const spread = c.bestAsk - c.bestBid; - return ( -
onSelect(c.symbol)} style={{ - display: 'grid', - gridTemplateColumns: '90px 120px 80px 60px 70px 70px 80px 90px 70px', - gap: '4px', alignItems: 'center', - padding: '7px 12px', cursor: 'pointer', - background: isSel ? 'var(--surface-hover)' : 'transparent', - borderLeft: isSel ? '2px solid var(--accent)' : '2px solid transparent', - borderBottom: '1px solid var(--border)', - transition: 'background var(--transition-fast)', - fontSize: '0.75rem', - }}> - {c.symbol} - {c.name} - ₢{c.price.toLocaleString()} - - {up ? '+' : ''}{c.changePct.toFixed(1)}% - - ₢{c.bestBid} - ₢{c.bestAsk} - {(c.volume / 1000).toFixed(1)}K - {(c.openInterest / 1000).toFixed(1)}K - -
- ); - })} -
-
- ); -} - -/* ── Contract Spec Panel ── */ -function ContractSpec({ contract, commodity }) { - if (!contract || !commodity) return null; - const marginPerLot = Math.ceil(commodity.price * contract.lotSize * (contract.marginPct / 100)); - const notionalPerLot = commodity.price * contract.lotSize; - return ( -
-
- Contract Specification -
-
- {[ - ['Ticker', contract.symbol, 'var(--accent)'], - ['Unit', contract.name, 'var(--fg-bright)'], - ['Lot Size', contract.lotSize.toLocaleString() + ' units', 'var(--fg-bright)'], - ['Tick Size', '₢' + contract.tickSize, 'var(--fg-bright)'], - ['Margin Req.', contract.marginPct + '%', 'var(--cyan)'], - ['Margin / Lot', '₢' + marginPerLot.toLocaleString(), 'var(--cyan)'], - ['Notional / Lot', '₢' +notionalPerLot.toLocaleString(), 'var(--fg-dim)'], - ['Delivery', contract.hub, 'var(--fg-dim)'], - ['Settlement', commodity.settlement ? '₢' + commodity.settlement : '—', 'var(--fg-dim)'], - ['Expiry', '30 DTE', 'var(--muted)'], - ['Supply', (commodity.supply / 1000).toFixed(0) + 'K/day', 'var(--green)'], - ['Demand', (commodity.demand / 1000).toFixed(0) + 'K/day', commodity.demand > commodity.supply ? 'var(--red)' : 'var(--green)'], - ].map(([label, value, color], i) => ( - - {label} - {value} - - ))} -
- {/* Supply/Demand bar */} -
-
- SUPPLY - commodity.supply ? 'var(--red)' : 'var(--green)' }}> - {commodity.demand > commodity.supply ? 'DEFICIT' : 'SURPLUS'}: {Math.abs(commodity.supply - commodity.demand).toLocaleString()} - - DEMAND -
-
-
-
-
-
-
- ); -} - -/* ── Depth Chart (canvas) ── */ -function DepthChart({ bids, asks, midPrice }) { - const ref = useRef(null); - useEffect(() => { - const c = ref.current; if (!c) return; - const ctx = c.getContext('2d'); - const dpr = window.devicePixelRatio || 1; - const W = c.clientWidth; const H = c.clientHeight; - c.width = W * dpr; c.height = H * dpr; - ctx.scale(dpr, dpr); - ctx.clearRect(0, 0, W, H); - - if (!bids.length || !asks.length) return; - - const maxCum = Math.max( - bids.reduce((s, b) => Math.max(s, b.cumulative), 0), - asks.reduce((s, a) => Math.max(s, a.cumulative), 0) - ); - - const priceRange = midPrice * 0.06; - const pMin = midPrice - priceRange; - const pMax = midPrice + priceRange; - const toX = p => ((p - pMin) / (pMax - pMin)) * W; - const toY = cum => H - (cum / (maxCum * 1.1)) * H; - - /* Grid */ - ctx.strokeStyle = 'rgba(28,42,63,0.4)'; ctx.lineWidth = 0.5; - for (let i = 0; i < 4; i++) { - const y = (H / 3) * i; - ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(W, y); ctx.stroke(); - } - - /* Bids (green area, left of mid) */ - ctx.beginPath(); - ctx.moveTo(toX(bids[0].price), H); - bids.forEach(b => ctx.lineTo(toX(b.price), toY(b.cumulative))); - ctx.lineTo(toX(bids[bids.length - 1].price), H); - ctx.closePath(); - const bg = ctx.createLinearGradient(0, 0, 0, H); - bg.addColorStop(0, 'rgba(34,197,94,0.25)'); bg.addColorStop(1, 'rgba(34,197,94,0.03)'); - ctx.fillStyle = bg; ctx.fill(); - - /* Asks (red area, right of mid) */ - ctx.beginPath(); - ctx.moveTo(toX(asks[0].price), H); - asks.forEach(a => ctx.lineTo(toX(a.price), toY(a.cumulative))); - ctx.lineTo(toX(asks[asks.length - 1].price), H); - ctx.closePath(); - const ag = ctx.createLinearGradient(0, 0, 0, H); - ag.addColorStop(0, 'rgba(239,68,68,0.25)'); ag.addColorStop(1, 'rgba(239,68,68,0.03)'); - ctx.fillStyle = ag; ctx.fill(); - - /* Mid line */ - const mx = toX(midPrice); - ctx.setLineDash([3, 3]); - ctx.beginPath(); ctx.moveTo(mx, 0); ctx.lineTo(mx, H); - ctx.strokeStyle = 'var(--accent)'; ctx.lineWidth = 1; ctx.stroke(); - ctx.setLineDash([]); - - /* Price label */ - ctx.fillStyle = 'var(--accent)'; ctx.font = 'bold 9px var(--font-mono)'; - ctx.textAlign = 'center'; - ctx.fillText('₢' + midPrice.toFixed(0), mx, H - 4); - }, [bids, asks, midPrice]); - - return ; -} - -/* ── Order Book ── */ -function OrderBook({ bids, asks, spread, spreadPct }) { - const maxBid = Math.max(...bids.map(b => b.volume)); - const maxAsk = Math.max(...asks.map(a => a.volume)); - return ( -
-
- - Market Depth - - - Spread: ₢{spread} ({spreadPct.toFixed(2)}%) - -
-
- PriceSizeCum -
- {/* Asks (reversed) */} -
- {[...asks].reverse().map((a, i) => ( -
-
- ₢{a.price} - {a.volume.toLocaleString()} - {a.cumulative.toLocaleString()} -
- ))} -
- {/* Mid */} -
- - ₢{((bids[0]?.price || 0 + asks[0]?.price || 0) / 2).toLocaleString()} - - MID -
- {/* Bids */} -
- {bids.map((b, i) => ( -
-
- ₢{b.price} - {b.volume.toLocaleString()} - {b.cumulative.toLocaleString()} -
- ))} -
-
- ); -} - -/* ── Price Chart (canvas) ── */ -function PriceChart({ data, symbol }) { - const ref = useRef(null); - useEffect(() => { - const c = ref.current; if (!c || !data || data.length < 2) return; - const ctx = c.getContext('2d'); - const dpr = window.devicePixelRatio || 1; - const W = c.clientWidth; const H = c.clientHeight; - c.width = W * dpr; c.height = H * dpr; - ctx.scale(dpr, dpr); - ctx.clearRect(0, 0, W, H); - - const prices = data.map(d => d.close); - const highs = data.map(d => d.high); - const lows = data.map(d => d.low); - const volumes = data.map(d => d.volume); - const allP = [...highs, ...lows]; - const minP = Math.min(...allP); const maxP = Math.max(...allP); - const rangeP = maxP - minP || 1; - const maxV = Math.max(...volumes); - const chartH = H * 0.72; const volH = H * 0.22; const barW = Math.max(2, (W / data.length) - 1); - - /* Grid */ - ctx.strokeStyle = 'rgba(28,42,63,0.5)'; ctx.lineWidth = 0.5; - for (let i = 0; i < 5; i++) { - const y = (chartH / 4) * i; - ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(W, y); ctx.stroke(); - ctx.fillStyle = 'var(--muted)'; ctx.font = '8px var(--font-mono)'; - ctx.fillText('₢' + (maxP - (rangeP / 4) * i).toFixed(0), W - 42, y + 9); - } - - /* Volume bars */ - data.forEach((d, i) => { - const x = (i / data.length) * W; - const vh = (d.volume / maxV) * volH; - ctx.fillStyle = d.close >= d.open ? 'rgba(34,197,94,0.18)' : 'rgba(239,68,68,0.18)'; - ctx.fillRect(x, H - vh, barW, vh); - }); - - /* Price line */ - ctx.beginPath(); - data.forEach((d, i) => { - const x = (i / (data.length - 1)) * W; - const y = chartH - ((d.close - minP) / rangeP) * (chartH - 10) - 5; - i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y); - }); - const lastC = data[data.length - 1].close; - const firstC = data[0].close; - const lineColor = lastC >= firstC ? '#22c55e' : '#ef4444'; - ctx.strokeStyle = lineColor; ctx.lineWidth = 1.8; ctx.lineJoin = 'round'; ctx.stroke(); - - /* Area fill */ - const lastX = W; - ctx.lineTo(lastX, chartH); ctx.lineTo(0, chartH); ctx.closePath(); - const grad = ctx.createLinearGradient(0, 0, 0, chartH); - grad.addColorStop(0, lastC >= firstC ? 'rgba(34,197,94,0.10)' : 'rgba(239,68,68,0.10)'); - grad.addColorStop(1, 'rgba(0,0,0,0)'); - ctx.fillStyle = grad; ctx.fill(); - - /* Current price line */ - const curY = chartH - ((lastC - minP) / rangeP) * (chartH - 10) - 5; - ctx.setLineDash([4, 4]); - ctx.beginPath(); ctx.moveTo(0, curY); ctx.lineTo(W, curY); - ctx.strokeStyle = lineColor; ctx.lineWidth = 1; ctx.stroke(); - ctx.setLineDash([]); - - ctx.fillStyle = lineColor; - ctx.beginPath(); ctx.roundRect(W - 55, curY - 8, 52, 16, 3); ctx.fill(); - ctx.fillStyle = '#080c14'; ctx.font = 'bold 8px var(--font-mono)'; - ctx.fillText('₢' + lastC.toFixed(0), W - 52, curY + 3); - }, [data, symbol]); - - return ; -} - -/* ── Order Form (commodities-style: lots, margin, long/short) ── */ -function OrderForm({ contract, commodity, credits, positions, onTrade }) { - const [direction, setDirection] = useState('long'); // long or short - const [orderType, setOrderType] = useState('market'); // market, limit, stop - const [lots, setLots] = useState(''); - const [limitPrice, setLimitPrice] = useState(''); - const [error, setError] = useState(''); - const [confirmation, setConfirmation] = useState(null); - - if (!contract || !commodity) return null; - - const effectivePrice = orderType === 'market' ? commodity.price : (parseFloat(limitPrice) || commodity.price); - const numLots = parseInt(lots) || 0; - const totalUnits = numLots * contract.lotSize; - const notional = totalUnits * effectivePrice; - const marginRequired = Math.ceil(notional * (contract.marginPct / 100)); - const commission = Math.ceil(notional * 0.015); - const pos = positions.find(p => p.symbol === contract.symbol); - const existingDirection = pos ? pos.direction : null; - const canAfford = marginRequired + commission <= credits; - - const handleSubmit = () => { - if (numLots <= 0) { setError('Enter lot quantity'); return; } - if (orderType !== 'market' && !limitPrice) { setError('Set limit/stop price'); return; } - if (!canAfford) { setError('Insufficient margin + commission'); return; } - setError(''); - setConfirmation({ direction, orderType, lots: numLots, price: effectivePrice, notional, marginRequired, commission, totalUnits }); - }; - - const confirmTrade = () => { - onTrade(direction, contract.symbol, effectivePrice, numLots, totalUnits, marginRequired, commission); - setConfirmation(null); setLots(''); setLimitPrice(''); setError(''); - }; - - return ( -
-
- Place Order — {contract.symbol} - Lot: {contract.lotSize.toLocaleString()} units -
-
- {/* Long / Short toggle */} -
- {['long', 'short'].map(d => ( - - ))} -
- - {/* Order type pills */} -
- {['market', 'limit', 'stop'].map(t => ( - - ))} -
- - {/* Limit/Stop price */} - {orderType !== 'market' && ( -
- - setLimitPrice(e.target.value)} - placeholder={`Spot: ₢${commodity.price}`} - style={{ - width: '100%', padding: '7px 10px', background: 'var(--bg)', - border: '1px solid var(--border)', borderRadius: 'var(--radius-md)', - color: 'var(--fg)', fontFamily: 'var(--font-mono)', fontSize: '0.8rem', - outline: 'none', boxSizing: 'border-box', - }} - /> -
- )} - - {/* Lots */} -
- -
- setLots(e.target.value)} - placeholder="0" min="1" - style={{ - flex: 1, padding: '7px 10px', background: 'var(--bg)', - border: '1px solid var(--border)', borderRadius: 'var(--radius-md)', - color: 'var(--fg)', fontFamily: 'var(--font-mono)', fontSize: '0.8rem', outline: 'none', - }} - /> - {[1, 5, 10, 25].map(n => ( - - ))} -
-
- - {/* Summary */} -
-
- Price / Unit₢{effectivePrice.toLocaleString()} -
-
- Total Units{totalUnits.toLocaleString()} -
-
- Notional Value₢{notional.toLocaleString()} -
-
- Margin ({contract.marginPct}%)₢{marginRequired.toLocaleString()} -
-
- Commission₢{commission.toLocaleString()} -
-
- Required - - ₢{(marginRequired + commission).toLocaleString()} - -
-
- - {error &&
{error}
} - - -
- - {/* Confirmation */} - {confirmation && ( -
setConfirmation(null)}> -
e.stopPropagation()}> -

Confirm {confirmation.orderType} Order

-
-
Direction: {confirmation.direction.toUpperCase()}
-
Contract: {contract.symbol} ({contract.name})
-
Lots: {confirmation.lots} ({confirmation.totalUnits.toLocaleString()} units)
-
Price: ₢{confirmation.price.toLocaleString()}/unit
-
Notional: ₢{confirmation.notional.toLocaleString()}
-
Margin: ₢{confirmation.marginRequired.toLocaleString()}
-
Commission: ₢{confirmation.commission.toLocaleString()}
-
-
- - -
-
-
- )} -
- ); -} - -/* ── Positions Panel (margin account + open positions) ── */ -function PositionsPanel({ positions, commodities, credits, usedMargin }) { - const totalUnrealized = positions.reduce((sum, p) => { - const c = commodities.find(c => c.symbol === p.symbol); - if (!c) return sum; - const currentValue = c.price * p.totalUnits; - const entryValue = p.avgEntry * p.totalUnits; - const diff = p.direction === 'long' ? currentValue - entryValue : entryValue - currentValue; - return sum + diff; - }, 0); - - const totalPositionValue = positions.reduce((sum, p) => { - const c = commodities.find(c => c.symbol === p.symbol); - return sum + (c ? c.price * p.totalUnits : 0); - }, 0); - - const marginUtilization = credits > 0 ? ((usedMargin / credits) * 100) : 0; - - return ( -
-
- Open Positions - = 0 ? 'var(--green)' : 'var(--red)' }}> - {totalUnrealized >= 0 ? '▲' : '▼'} ₢{Math.abs(totalUnrealized).toLocaleString()} - -
- - {/* Account summary */} -
-
-
-
Balance
-
₢{credits.toLocaleString()}
-
-
-
Used Margin
-
₢{usedMargin.toLocaleString()}
-
-
-
Exposure
-
₢{totalPositionValue.toLocaleString()}
-
-
- - {/* Margin utilization bar */} -
-
- MARGIN UTILIZATION - 80 ? 'var(--red)' : marginUtilization > 50 ? 'var(--accent)' : 'var(--green)' }}> - {marginUtilization.toFixed(1)}% - -
-
-
80 ? 'var(--red)' : marginUtilization > 50 ? 'var(--accent)' : 'var(--green)', - transition: 'width 0.3s ease', - }} /> -
-
- - {/* Position rows */} - {positions.length === 0 && ( -
- No open positions -
- )} - {positions.map((p, i) => { - const c = commodities.find(c => c.symbol === p.symbol); - if (!c) return null; - const currentVal = c.price * p.totalUnits; - const entryVal = p.avgEntry * p.totalUnits; - const pnl = p.direction === 'long' ? currentVal - entryVal : entryVal - currentVal; - const pnlPct = entryVal > 0 ? (pnl / p.margin) * 100 : 0; - const contract = CONTRACTS.find(ct => ct.symbol === p.symbol); - return ( -
-
-
- {p.symbol} - - {p.direction === 'long' ? 'LONG' : 'SHORT'} - -
- = 0 ? 'var(--green)' : 'var(--red)', - }}> - {pnl >= 0 ? '+' : ''}₢{pnl.toLocaleString()} - -
-
- {p.lots} lot{p.lots > 1 ? 's' : ''} ({p.totalUnits.toLocaleString()} u.) @ ₢{p.avgEntry} - Margin: ₢{p.margin.toLocaleString()} -
-
-
= 0 ? 'var(--green)' : 'var(--red)', - opacity: 0.6, - }} /> -
-
- ); - })} -
-
- ); -} - -/* ── Trade Feed ── */ -function TradeFeed({ trades }) { - return ( -
-
- Market Trades -
-
- {trades.map((t, i) => ( -
- {t.time} - {t.lots}× {t.symbol} - ₢{t.price.toLocaleString()} - - {t.side === 'long' ? 'BID' : 'ASK'} - -
- ))} -
-
- ); -} - -/* ────────────────────────────────────────────── - Main MarketDemo component - ────────────────────────────────────────────── */ -function MarketDemo() { - const [credits, setCredits] = useState(250000); - const [selectedSymbol, setSelectedSymbol] = useState('VLD'); - const [category, setCategory] = useState('all'); - const [notifications, setNotifications] = useState([]); - - /* Generate live commodity data */ - const [commodities, setCommodities] = useState(() => { - return CONTRACTS.map(ct => { - const history = []; let p = ct.basePrice; - for (let i = 0; i < 60; i++) { - p = Math.max(ct.basePrice * 0.7, Math.min(ct.basePrice * 1.3, p + (Math.random() - 0.48) * ct.basePrice * 0.04)); - history.push(Math.round(p * 100) / 100); - } - const price = history[history.length - 1]; - const prev = history[history.length - 2]; - const settlement = history[Math.floor(history.length / 2)]; - const bestBid = Math.round((price - ct.basePrice * 0.005) * 100) / 100; - const bestAsk = Math.round((price + ct.basePrice * 0.005) * 100) / 100; - return { - ...ct, - price, prevPrice: prev, - change: price - prev, - changePct: ((price - prev) / prev) * 100, - history, settlement, bestBid, bestAsk, - volume: Math.floor(Math.random() * 80000 + 5000), - openInterest: Math.floor(Math.random() * 200000 + 10000), - open: history[0], - high: Math.max(...history), - low: Math.min(...history), - supply: Math.floor(Math.random() * 50000 + 10000), - demand: Math.floor(Math.random() * 60000 + 8000), - }; - }); - }); - - /* Open positions */ - const [positions, setPositions] = useState([ - { symbol: 'VLD', direction: 'long', lots: 5, totalUnits: 5000, avgEntry: 13.20, margin: 5280 }, - { symbol: 'TRI', direction: 'long', lots: 3, totalUnits: 15000, avgEntry: 4.80, margin: 2160 }, - { symbol: 'ARK', direction: 'short', lots: 2, totalUnits: 100, avgEntry: 635, margin: 25400 }, - ]); - - /* Order book for selected */ - const selected = commodities.find(c => c.symbol === selectedSymbol) || commodities[0]; - const selectedContract = CONTRACTS.find(c => c.symbol === selectedSymbol) || CONTRACTS[0]; - const orderBook = useMemo(() => { - const p = selected.price; - const bids = []; const asks = []; - let bidCum = 0; let askCum = 0; - for (let i = 0; i < 10; i++) { - const bv = Math.floor(Math.random() * 12000 + 500); bidCum += bv; - bids.push({ price: Math.max(0.01, Math.round((p - (i + 1) * selectedContract.tickSize * 3) * 100) / 100), volume: bv, cumulative: bidCum }); - const av = Math.floor(Math.random() * 12000 + 500); askCum += av; - asks.push({ price: Math.round((p + (i + 1) * selectedContract.tickSize * 3) * 100) / 100, volume: av, cumulative: askCum }); - } - return { bids, asks }; - }, [selected.price, selectedSymbol, selectedContract.tickSize]); - - const usedMargin = positions.reduce((s, p) => s + p.margin, 0); - - /* Price chart data */ - const chartData = useMemo(() => { - return selected.history.map((c, i) => { - const noise = selected.price * 0.01; - return { - open: selected.history[Math.max(0, i - 1)] || c, - high: c + Math.random() * noise * 2, - low: Math.max(0.01, c - Math.random() * noise * 2), - close: c, - volume: Math.floor(Math.random() * 5000 + 500), - }; - }); - }, [selected]); - - /* Trade feed */ - const [tradeFeed, setTradeFeed] = useState(() => { - const feed = []; - const syms = CONTRACTS.map(c => c.symbol); - for (let i = 0; i < 20; i++) { - const sym = syms[Math.floor(Math.random() * syms.length)]; - const ct = CONTRACTS.find(c => c.symbol === sym); - const c = commodities.find(c => c.symbol === sym); - feed.push({ - time: `${14}:${String(22 + i).padStart(2, '0')}`, - symbol: sym, - lots: Math.floor(Math.random() * 10 + 1), - price: c ? Math.round(c.price * 100) / 100 : ct.basePrice, - side: Math.random() > 0.5 ? 'long' : 'short', - }); - } - return feed.reverse(); - }); - - /* Live price ticks */ - useEffect(() => { - const interval = setInterval(() => { - setCommodities(prev => prev.map(c => { - const ct = CONTRACTS.find(x => x.symbol === c.symbol); - const delta = (Math.random() - 0.48) * c.price * 0.006; - const newPrice = Math.max(0.01, Math.round((c.price + delta) * 100) / 100); - const history = [...c.history.slice(1), newPrice]; - const change = newPrice - c.prevPrice; - const spread = ct.basePrice * 0.005; - return { - ...c, price: newPrice, history, - volume: c.volume + Math.floor(Math.random() * 300), - change, changePct: c.prevPrice > 0 ? (change / c.prevPrice) * 100 : 0, - high: Math.max(c.high, newPrice), low: Math.min(c.low, newPrice), - bestBid: Math.round((newPrice - spread) * 100) / 100, - bestAsk: Math.round((newPrice + spread) * 100) / 100, - openInterest: c.openInterest + Math.floor(Math.random() * 200 - 80), - supply: c.supply + Math.floor(Math.random() * 200 - 100), - demand: c.demand + Math.floor(Math.random() * 200 - 80), - }; - })); - }, 2500); - return () => clearInterval(interval); - }, []); - - /* New trades in feed */ - useEffect(() => { - const interval = setInterval(() => { - const syms = CONTRACTS.map(c => c.symbol); - const sym = syms[Math.floor(Math.random() * syms.length)]; - const c = commodities.find(c => c.symbol === sym); - const ct = CONTRACTS.find(c => c.symbol === sym); - const now = new Date(); - setTradeFeed(prev => [{ - time: `${now.getHours()}:${String(now.getMinutes()).padStart(2, '0')}`, - symbol: sym, - lots: Math.floor(Math.random() * 15 + 1), - price: c ? c.price : ct.basePrice, - side: Math.random() > 0.5 ? 'long' : 'short', - }, ...prev].slice(0, 40)); - }, 3000); - return () => clearInterval(interval); - }, [commodities]); - - const addNotif = (msg, color) => { - const id = Date.now(); - setNotifications(prev => [...prev, { id, msg, color }]); - setTimeout(() => setNotifications(prev => prev.filter(n => n.id !== id)), 3500); - }; - - const handleTrade = (direction, symbol, price, lots, totalUnits, marginRequired, commission) => { - /* Deduct commission + margin from credits */ - setCredits(prev => prev - commission - marginRequired); - - setPositions(prev => { - const existing = prev.find(p => p.symbol === symbol && p.direction === direction); - if (existing) { - /* Merge into existing position — weighted average entry */ - const newUnits = existing.totalUnits + totalUnits; - const newAvg = Math.round(((existing.avgEntry * existing.totalUnits) + (price * totalUnits)) / newUnits * 100) / 100; - const newMargin = existing.margin + marginRequired; - return prev.map(p => p.symbol === symbol && p.direction === direction - ? { ...p, lots: p.lots + lots, totalUnits: newUnits, avgEntry: newAvg, margin: newMargin } - : p); - } - return [...prev, { symbol, direction, lots, totalUnits, avgEntry: price, margin: marginRequired }]; - }); - - addNotif( - `${direction === 'long' ? 'LONG' : 'SHORT'} ${lots}× ${symbol} (${totalUnits.toLocaleString()} u.) @ ₢${price}`, - direction === 'long' ? 'var(--green)' : 'var(--red)' - ); - - const now = new Date(); - setTradeFeed(prev => [{ - time: `${now.getHours()}:${String(now.getMinutes()).padStart(2, '0')}`, - symbol, lots, price, side: direction, - }, ...prev].slice(0, 40)); - }; - - /* Market stats */ - const totalVolume = commodities.reduce((s, c) => s + c.volume, 0); - const totalOI = commodities.reduce((s, c) => s + c.openInterest, 0); - const advancers = commodities.filter(c => c.change >= 0).length; - const decliners = commodities.length - advancers; - const tickerItems = commodities.slice(0, 12).map(c => ({ - symbol: c.symbol, price: c.price, change: c.change, changePct: c.changePct, - })); - - const spread = selectedContract ? Math.round((selected.bestAsk - selected.bestBid) * 100) / 100 : 0; - const spreadPct = selected.bestBid > 0 ? (spread / selected.bestBid) * 100 : 0; - const midPrice = Math.round(((selected.bestBid + selected.bestAsk) / 2) * 100) / 100; - - return ( -
- e.currentTarget.style.color='var(--fg-bright)'} onMouseLeave={e => e.currentTarget.style.color='var(--muted)'}>← Back to Docs - {/* Ticker */} - - - {/* Header bar */} -
- COMMODITIES EXCHANGE -
- SESSION - OPEN -
- {advancers} ▲ - {decliners} ▼ -
- TOTAL VOL - {(totalVolume / 1e6).toFixed(2)}M -
- OPEN INT. - {(totalOI / 1e6).toFixed(1)}M -
- ACCOUNT (₢ = ISK) - ₢{credits.toLocaleString()} -
- - {/* Category tabs */} - - - {/* Notifications */} -
- {notifications.map(n => ( -
- {n.msg} -
- ))} -
- - {/* Main grid: Left (board + chart + feed) | Right (specs + depth + order + positions) */} -
- - {/* Left column */} -
- {/* Contract board */} - - - {/* Price chart */} -
-
-
- {selected.symbol} - {selected.name} -
-
- - O:₢{selected.open} H:₢{selected.high} L:₢{selected.low} - - - ₢{selected.price.toLocaleString()} - - = 0 ? 'var(--green)' : 'var(--red)', - }}> - {selected.change >= 0 ? '+' : ''}{selected.change.toFixed(2)} ({selected.changePct >= 0 ? '+' : ''}{selected.changePct.toFixed(2)}%) - -
-
-
- -
-
- - {/* Trade feed */} - -
- - {/* Right column */} -
- {/* Contract spec */} - - - {/* Depth chart */} -
-
- Depth of Market -
- -
- - {/* Order book */} - - - {/* Order form */} - - - {/* Positions */} - -
-
-
- ); -} - -window.GDD.MarketDemo = MarketDemo; diff --git a/archive/legacy-static/js/demos/movement.js b/archive/legacy-static/js/demos/movement.js deleted file mode 100644 index e9a32df..0000000 --- a/archive/legacy-static/js/demos/movement.js +++ /dev/null @@ -1,850 +0,0 @@ -window.GDD = window.GDD || {}; - -const { useState, useEffect, useRef, useCallback } = React; -const TH = window.GDD.THREE; - -function ShipMovementDemo() { - const containerRef = useRef(null); - const sceneRef = useRef(null); - const animIdRef = useRef(null); - const moveRef = useRef(null); - const waypointsRef = useRef([]); - const targetRef = useRef(null); - const shipGroupRef = useRef(null); - const waypointMarkersRef = useRef([]); - const entityMeshesRef = useRef([]); - const trailRef = useRef(null); - const gridRef = useRef(null); - const trailPositionsRef = useRef([]); - const frameCountRef = useRef(0); - const clockRef = useRef(null); - - // HUD state - const [shipSpeed, setShipSpeed] = useState(0); - const [shipHeading, setShipHeading] = useState(0); - const [shipPos, setShipPos] = useState({ x: 400, y: 300 }); - const [moving, setMoving] = useState(false); - const [currentTarget, setCurrentTarget] = useState(null); - const [waypoints, setWaypoints] = useState([]); - const [entities, setEntities] = useState([]); - const [selectedEntity, setSelectedEntity] = useState(null); - const [shipStatus, setShipStatus] = useState(null); - const [serverTime, setServerTime] = useState('14:34:07'); - const [showGrid, setShowGrid] = useState(true); - const [moveProgress, setMoveProgress] = useState(0); - - // Chat state - const [chatTab, setChatTab] = useState('local'); - const [chatInput, setChatInput] = useState(''); - const [chatMessages, setChatMessages] = useState([ - { sender: 'CMDR Picard', body: 'Heading to Jita with a cargo of Kernite.', time: '14:22' }, - { sender: 'CMDR Worf', body: 'Pirates spotted near U-IRTYR gate.', time: '14:25' }, - { sender: 'CMDR Data', body: 'Scordite prices up 12% in Amarr.', time: '14:28' }, - { sender: 'CMDR Troi', body: 'Mining fleet forming in Sol.', time: '14:31' }, - ]); - - // Modules state - const [modules, setModules] = useState({ - high: [ - { id: 'laser1', name: 'Mine Laser', icon: '⛏', active: false }, - { id: 'turret1', name: '150mm Rail', icon: '◆', active: true }, - { id: null }, - ], - med: [ - { id: 'shield1', name: 'Shield Bst', icon: '◎', active: false }, - { id: 'warp1', name: 'Afterburn', icon: '»', active: true }, - { id: 'scram1', name: 'Scrambler', icon: '↯', active: false }, - ], - low: [ - { id: 'armor1', name: 'Armor Plt', icon: '▭', active: false }, - { id: 'magstab1', name: 'Mag Field', icon: '⚡', active: false }, - ], - }); - - // Load data - useEffect(() => { - window.GDD.api.getNearbyEntities().then(e => setEntities(e)); - window.GDD.api.getShipStatus().then(s => setShipStatus(s)); - }, []); - - // Server time tick - useEffect(() => { - const interval = setInterval(() => { - setServerTime(prev => { - const parts = prev.split(':'); - let sec = parseInt(parts[2]) + 1; - let min = parseInt(parts[1]); - let hr = parseInt(parts[0]); - if (sec >= 60) { sec = 0; min++; } - if (min >= 60) { min = 0; hr++; } - if (hr >= 24) hr = 0; - return `${String(hr).padStart(2, '0')}:${String(min).padStart(2, '0')}:${String(sec).padStart(2, '0')}`; - }); - }, 1000); - return () => clearInterval(interval); - }, []); - - // Build 3D scene - useEffect(() => { - const container = containerRef.current; - if (!container) return; - - const scene = new THREE.Scene(); - scene.fog = new THREE.FogExp2(0x040810, 0.0005); - - const camera = new THREE.PerspectiveCamera(55, container.clientWidth / container.clientHeight, 0.1, 5000); - camera.position.set(0, 40, 50); - camera.lookAt(0, 0, 0); - - const renderer = TH.createRenderer(container, { clearColor: 0x040810 }); - TH.handleResize(renderer, camera, container); - - const stars = TH.createStarField(4000, 3000); - scene.add(stars); - - TH.addNebula(scene, 0x22d3ee, [-200, 100, -500], 500); - TH.addNebula(scene, 0xf0a030, [300, -50, -400], 400); - TH.setupSpaceLighting(scene); - - const grid = new THREE.GridHelper(600, 30, 0x0d1520, 0x0d1520); - grid.material.transparent = true; - grid.material.opacity = 0.2; - scene.add(grid); - gridRef.current = grid; - - // Ship - const shipGroup = new THREE.Group(); - const shipMesh = TH.createShipMesh(0xc8d6e5, 0xf0a030, 0.8); - shipMesh.rotation.y = -Math.PI / 2; - shipGroup.add(shipMesh); - - const engineGlow = TH.createEngineGlow(0x22d3ee, 3, 20); - engineGlow.position.set(0, 0, -8); - shipGroup.add(engineGlow); - - const trail = TH.createEngineTrail(0xf0a030, 50); - shipGroup.add(trail); - trailRef.current = trail; - - const label = TH.createLabel('USS ENTERPRISE', '#22d3ee', 18); - label.position.y = 5; - shipGroup.add(label); - - scene.add(shipGroup); - shipGroupRef.current = shipGroup; - - sceneRef.current = { scene, camera, renderer, stars, shipGroup, engineGlow }; - - const clock = new THREE.Clock(); - clockRef.current = clock; - - const animate = () => { - animIdRef.current = requestAnimationFrame(animate); - frameCountRef.current++; - const t = clock.getElapsedTime(); - - const move = moveRef.current; - if (move) { - const dx = move.tx - move.sx; - const dy = move.ty - move.sy; - const totalDist = Math.sqrt(dx * dx + dy * dy); - const speed = Math.max(0.008, 0.04 / (1 + totalDist / 500)); - move.progress += speed; - - let nx, ny, angle; - if (move.progress >= 1) { - nx = move.tx; ny = move.ty; - angle = Math.atan2(dy, dx); - moveRef.current = null; - - if (waypointsRef.current.length > 0) { - waypointsRef.current.shift(); - setWaypoints([...waypointsRef.current]); - if (waypointsRef.current.length > 0) { - const next = waypointsRef.current[0]; - targetRef.current = next; - setCurrentTarget(next); - moveRef.current = { sx: nx, sy: ny, tx: next.x, ty: next.y, progress: 0 }; - } else { - targetRef.current = null; - setCurrentTarget(null); - setMoving(false); - } - } else { - setMoving(false); - } - } else { - const ease = move.progress < 0.5 - ? 2 * move.progress * move.progress - : -1 + (4 - 2 * move.progress) * move.progress; - nx = move.sx + dx * ease; - ny = move.sy + dy * ease; - angle = Math.atan2(dy, dx); - - trailPositionsRef.current.push({ x: nx * 0.1, y: 0, z: ny * 0.1, age: 0 }); - if (trailPositionsRef.current.length > 50) trailPositionsRef.current.shift(); - } - - shipGroup.position.x = nx * 0.1; - shipGroup.position.z = ny * 0.1; - shipGroup.position.y = 0; - shipGroup.rotation.y = -angle + Math.PI / 2; - engineGlow.intensity = 4; - - if (trailPositionsRef.current.length > 0) { - const posAttr = trail.geometry.attributes.position; - trailPositionsRef.current.forEach((p, i) => { - p.age++; - if (i < posAttr.count) { - posAttr.setXYZ(i, p.x - shipGroup.position.x, p.y, p.z - shipGroup.position.z); - } - }); - posAttr.needsUpdate = true; - } - - if (frameCountRef.current % 6 === 0) { - setShipSpeed(totalDist * speed * 60); - setShipHeading(((angle * 180 / Math.PI + 360) % 360)); - setShipPos({ x: nx, y: ny }); - setMoveProgress(move.progress); - } - } else { - engineGlow.intensity = 1; - if (trailPositionsRef.current.length > 0) { - trailPositionsRef.current = []; - const posAttr = trail.geometry.attributes.position; - for (let i = 0; i < posAttr.count; i++) posAttr.setXYZ(i, 0, -100, 0); - posAttr.needsUpdate = true; - } - shipGroup.position.y = Math.sin(t * 1.5) * 0.3; - } - - TH.followTarget(camera, shipGroup.position, { x: 0, y: 40, z: 50 }, 0.04); - stars.position.x = shipGroup.position.x * -0.02; - stars.position.z = shipGroup.position.z * -0.02; - - renderer.render(scene, camera); - }; - animate(); - - const onResize = () => TH.handleResize(renderer, camera, container); - window.addEventListener('resize', onResize); - - return () => { - if (animIdRef.current) cancelAnimationFrame(animIdRef.current); - window.removeEventListener('resize', onResize); - }; - }, []); - - // Update entities in 3D - useEffect(() => { - if (!sceneRef.current) return; - const { scene } = sceneRef.current; - - entityMeshesRef.current.forEach(m => scene.remove(m)); - entityMeshesRef.current = []; - - entities.forEach(ent => { - const x = ent.x * 0.1; - const z = ent.y * 0.1; - let mesh; - - if (ent.type === 'asteroid') { - mesh = TH.createAsteroid(1.5, 0x3d2a5c); - } else if (ent.type === 'station') { - mesh = TH.createStation(2, 0x22d3ee); - } else if (ent.type === 'hostile') { - mesh = TH.createShipMesh(0x7f1d1d, 0xef4444, 0.6); - mesh.rotation.y = -Math.PI / 2; - } else { - mesh = TH.createShipMesh(0x1a3a2a, 0x22c55e, 0.5); - mesh.rotation.y = -Math.PI / 2; - } - - const group = new THREE.Group(); - group.add(mesh); - const label = TH.createLabel(ent.name, ent.type === 'hostile' ? '#ef4444' : ent.type === 'asteroid' ? '#a78bfa' : ent.type === 'station' ? '#22d3ee' : '#22c55e', 14); - label.position.y = 4; - group.add(label); - group.position.set(x, 0, z); - scene.add(group); - entityMeshesRef.current.push(group); - }); - }, [entities]); - - // Update waypoints in 3D - useEffect(() => { - if (!sceneRef.current) return; - const { scene } = sceneRef.current; - - waypointMarkersRef.current.forEach(m => scene.remove(m)); - waypointMarkersRef.current = []; - - waypoints.forEach((wp, idx) => { - const x = wp.x * 0.1; - const z = wp.y * 0.1; - const isFirst = idx === 0; - - const geo = new THREE.OctahedronGeometry(1.2, 0); - const mat = new THREE.MeshBasicMaterial({ - color: isFirst ? 0xf0a030 : 0x22d3ee, - transparent: true, - opacity: 0.6, - wireframe: true, - }); - const marker = new THREE.Mesh(geo, mat); - marker.position.set(x, 2, z); - - const group = new THREE.Group(); - group.add(marker); - const lbl = TH.createLabel(wp.label, isFirst ? '#f0a030' : '#22d3ee', 14); - lbl.position.y = 5; - group.add(lbl); - scene.add(group); - waypointMarkersRef.current.push(group); - }); - }, [waypoints]); - - // Grid toggle - useEffect(() => { - if (gridRef.current) gridRef.current.visible = showGrid; - }, [showGrid]); - - // Navigate to entity — used by overview rows, action buttons, and waypoint panel - const navigateToEntity = useCallback((ent) => { - const wp = { id: Date.now(), x: ent.x, y: ent.y, label: ent.name, type: ent.type }; - waypointsRef.current = [wp]; - setWaypoints([wp]); - targetRef.current = wp; - setCurrentTarget(wp); - const shipPos2d = { x: shipGroupRef.current.position.x * 10, y: shipGroupRef.current.position.z * 10 }; - moveRef.current = { sx: shipPos2d.x, sy: shipPos2d.y, tx: wp.x, ty: wp.y, progress: 0 }; - setMoving(true); - setSelectedEntity(ent); - }, []); - - // Add entity as next waypoint (queued, doesn't interrupt current move) - const addWaypointEntity = useCallback((ent) => { - const wp = { id: Date.now(), x: ent.x, y: ent.y, label: ent.name, type: ent.type }; - waypointsRef.current = [...waypointsRef.current, wp]; - setWaypoints([...waypointsRef.current]); - if (!moveRef.current && waypointsRef.current.length === 1) { - targetRef.current = wp; - setCurrentTarget(wp); - const shipPos2d = { x: shipGroupRef.current.position.x * 10, y: shipGroupRef.current.position.z * 10 }; - moveRef.current = { sx: shipPos2d.x, sy: shipPos2d.y, tx: wp.x, ty: wp.y, progress: 0 }; - setMoving(true); - } - }, []); - - const clearWaypoints = useCallback(() => { - waypointsRef.current = []; - targetRef.current = null; - moveRef.current = null; - setWaypoints([]); - setCurrentTarget(null); - setMoving(false); - }, []); - - const removeWaypoint = useCallback((id) => { - waypointsRef.current = waypointsRef.current.filter(w => w.id !== id); - setWaypoints([...waypointsRef.current]); - if (targetRef.current?.id === id) { - moveRef.current = null; - const next = waypointsRef.current[0] || null; - targetRef.current = next; - setCurrentTarget(next); - if (next) { - const shipPos2d = { x: shipGroupRef.current.position.x * 10, y: shipGroupRef.current.position.z * 10 }; - moveRef.current = { sx: shipPos2d.x, sy: shipPos2d.y, tx: next.x, ty: next.y, progress: 0 }; - } else setMoving(false); - } - }, []); - - const toggleModule = useCallback((slotType, index) => { - setModules(prev => { - const row = [...prev[slotType]]; - row[index] = { ...row[index], active: !row[index].active }; - return { ...prev, [slotType]: row }; - }); - }, []); - - const handleChatSend = useCallback(() => { - if (!chatInput.trim()) return; - setChatMessages(prev => [...prev, { sender: 'You', body: chatInput, time: serverTime.slice(0, 5) }]); - setChatInput(''); - }, [chatInput, serverTime]); - - const formatCoord = (v) => v.toFixed(0); - const entityColor = (type) => { - switch (type) { - case 'hostile': return 'var(--red)'; - case 'asteroid': return 'var(--purple)'; - case 'station': return 'var(--cyan)'; - case 'player': return 'var(--green)'; - default: return 'var(--muted)'; - } - }; - const entityIcon = (type) => { - switch (type) { - case 'hostile': return '✸'; - case 'asteroid': return '◉'; - case 'station': return '⬡'; - case 'player': return '◈'; - default: return '○'; - } - }; - const sortedEntities = [...entities].sort((a, b) => (a.distance || 0) - (b.distance || 0)); - const activeModuleCount = Object.values(modules).flat().filter(m => m && m.active).length; - - return ( -
- - {/* 3D Viewport — visual only, no click interaction */} -
- - {/* HUD Overlay */} -
- - {/* ===== TOP BAR ===== */} -
- -
- Sol - 1.0 HIGH SEC -
- Ship - {shipStatus?.name || 'USS Enterprise'} -
- SPD - {shipSpeed.toFixed(0)}m/s -
- HDG - {shipHeading.toFixed(0)}° -
- POS - {formatCoord(shipPos.x)}, {formatCoord(shipPos.y)} -
- ₢125,740 -
- - - CONNECTED - -
- {serverTime} - {moving && currentTarget && ( - <> -
- ● EN ROUTE → {currentTarget.label} - - )} -
- - {/* ===== MIDDLE ===== */} -
- - {/* ===== LEFT PANEL — Ship Status + Speed + Waypoints ===== */} -
- - {/* Ship Health */} -
-
- Ship Status -
-
-
-
- {shipStatus?.name || 'USS Enterprise'} - {shipStatus?.class?.split(' ')[0] || 'Venture'} -
-
-
- {[ - { label: 'SHIELD', value: shipStatus?.shields ?? 100, color: '#22d3ee', cls: 'shield' }, - { label: 'ARMOR', value: shipStatus?.armor ?? 100, color: '#f0a030', cls: 'armor' }, - { label: 'HULL', value: shipStatus?.hull ?? 100, color: '#22c55e', cls: 'hull' }, - { label: 'CAP', value: shipStatus?.capacitor ?? 85, color: '#a78bfa', cls: 'cap' }, - ].map(bar => ( -
- {bar.label} -
-
-
- {bar.value}% -
- ))} -
-
-
- - {/* Propulsion */} -
-
- Propulsion -
-
-
- {moving ? shipSpeed.toFixed(0) : '—'} - m/s -
-
- -
-
-
- -
-
- {moving ? '● SUBLIGHT ACTIVE' : '○ IDLE'} -
-
-
- - {/* Waypoints */} -
-
- Waypoints - {waypoints.length > 0 && ( - CLEAR - )} -
-
- {waypoints.length === 0 ? ( -
-
-
Select a target from Overview
-
or use action buttons to navigate
-
- ) : ( -
- {waypoints.map((wp, idx) => { - const dist = Math.sqrt((wp.x - shipPos.x) ** 2 + (wp.y - shipPos.y) ** 2); - return ( -
- {idx + 1}. -
-
{wp.label}
-
{dist.toFixed(0)} km
-
- { e.stopPropagation(); removeWaypoint(wp.id); }}>× -
- ); - })} - {moving && moveProgress > 0 && ( -
-
- TRIP - {(moveProgress * 100).toFixed(0)}% -
-
-
-
-
- )} -
- )} -
-
-
- - {/* CENTER — crosshair + nav status */} -
- {/* Subtle grid lines */} -
-
- - {/* Crosshair */} -
-
-
-
-
-
-
- - {/* Navigation status toast */} - {moving && currentTarget && ( -
- ● EN ROUTE → {currentTarget.label} -
- )} - {!moving && waypoints.length === 0 && ( -
- ○ IDLE — Select a target from Overview -
- )} - - {/* Grid toggle */} -
- -
-
- - {/* ===== RIGHT PANEL — Overview ===== */} -
-
-
- Overview - {sortedEntities.length} entities -
-
-
- - - - - - - - - {sortedEntities.map(ent => { - const col = entityColor(ent.type); - const ico = entityIcon(ent.type); - const dist = Math.sqrt((ent.x - shipPos.x) ** 2 + (ent.y - shipPos.y) ** 2); - return ( - setSelectedEntity(ent)} - onMouseEnter={(e) => e.currentTarget.style.background = 'var(--surface-raised)'} - onMouseLeave={(e) => e.currentTarget.style.background = selectedEntity?.id === ent.id ? 'var(--accent-bg)' : 'transparent'} - > - - - - - - - ); - })} - -
NameDist
{ico}{ent.name}{dist.toFixed(0)} km - - - -
-
-
- - {/* Selected Entity Detail */} - {selectedEntity && (() => { - const col = entityColor(selectedEntity.type); - const dist = Math.sqrt((selectedEntity.x - shipPos.x) ** 2 + (selectedEntity.y - shipPos.y) ** 2); - return ( -
-
- - {selectedEntity.name} -
-
-
- {selectedEntity.type.toUpperCase()} · {dist.toFixed(0)} km -
-
- {selectedEntity.type === 'asteroid' && ( - <> - - - - - )} - {selectedEntity.type === 'hostile' && ( - <> - - - - - )} - {selectedEntity.type === 'station' && ( - <> - - - - - )} - {selectedEntity.type === 'player' && ( - <> - - - - )} -
-
-
- ); - })()} -
-
- - {/* ===== BOTTOM BAR ===== */} -
- - {/* Module Rack */} -
-
- Modules - {activeModuleCount} active -
-
- {['high', 'med', 'low'].map(slotType => ( -
- - {slotType === 'high' ? 'HIGH' : slotType === 'med' ? 'MED' : 'LOW'} - - {modules[slotType].map((mod, i) => ( - mod.id ? ( -
toggleModule(slotType, i)} - title={`${mod.name} (${mod.active ? 'Active' : 'Inactive'})`} - style={{ - width: '48px', height: '40px', borderRadius: '6px', - border: `1px solid ${mod.active ? 'var(--accent-border)' : 'var(--border)'}`, - background: mod.active ? 'var(--accent-bg)' : 'var(--surface)', - display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', - cursor: 'pointer', transition: 'all 0.15s ease', position: 'relative', overflow: 'hidden', gap: '2px', - }} - > - {mod.icon} - {mod.name} - {mod.active &&
} -
- ) : ( -
- -
- ) - ))} -
- ))} -
-
- - {/* Cargo */} -
-
- Cargo Hold -
-
-
- 12,400 / 25,000 m³ - 50% -
-
-
-
- {[ - { name: 'Veldspar', qty: '8,500' }, - { name: 'Scordite', qty: '2,300' }, - { name: 'Kernite', qty: '400' }, - { name: 'Pyroxeres', qty: '1,200' }, - ].map((item, i) => ( -
- {item.name} - ×{item.qty} -
- ))} -
-
- - {/* Chat */} -
-
- {['local', 'corp', 'trade'].map(tab => ( - - ))} -
-
- {chatMessages.slice(-6).map((msg, i) => ( -
- {msg.sender} - {msg.body} - {msg.time} -
- ))} -
-
- setChatInput(e.target.value)} - onKeyDown={(e) => e.key === 'Enter' && handleChatSend()} - placeholder="Send message..." - style={{ flex: 1, background: 'var(--bg-subtle)', border: 'none', padding: '6px 10px', fontFamily: 'var(--font-mono)', fontSize: '11px', color: 'var(--fg)', outline: 'none' }} - /> - -
-
-
-
- - {/* Module cycle animation keyframes */} - -
- ); -} - -window.GDD.ShipMovementDemo = ShipMovementDemo; diff --git a/archive/legacy-static/js/demos/progression.js b/archive/legacy-static/js/demos/progression.js deleted file mode 100644 index a0c60e4..0000000 --- a/archive/legacy-static/js/demos/progression.js +++ /dev/null @@ -1,381 +0,0 @@ -window.GDD = window.GDD || {}; - -const { useState, useEffect, useCallback, useRef } = React; - -function ProgressionDemo() { - const [skills, setSkills] = useState([]); - const [activeCategory, setActiveCategory] = useState('all'); - const [selectedSkill, setSelectedSkill] = useState(null); - const [totalXP, setTotalXP] = useState(0); - const [xpLog, setXpLog] = useState([]); - const [simulating, setSimulating] = useState(false); - const simRef = useRef(null); - - const allSkills = [ - // Combat - { name: 'Gunnery', category: 'Combat', xp: 380, level: 2, nextLevel: 500 }, - { name: 'Missiles', category: 'Combat', xp: 120, level: 1, nextLevel: 500 }, - { name: 'Shield Operation', category: 'Combat', xp: 50, level: 0, nextLevel: 100 }, - { name: 'Armor Tanking', category: 'Combat', xp: 0, level: 0, nextLevel: 100 }, - { name: 'Electronic Warfare', category: 'Combat', xp: 0, level: 0, nextLevel: 100 }, - // Industry - { name: 'Mining', category: 'Industry', xp: 1850, level: 3, nextLevel: 2000 }, - { name: 'Refining', category: 'Industry', xp: 420, level: 2, nextLevel: 500 }, - { name: 'Manufacturing', category: 'Industry', xp: 80, level: 0, nextLevel: 100 }, - { name: 'Blueprint Research', category: 'Industry', xp: 0, level: 0, nextLevel: 100 }, - // Navigation - { name: 'Warp Drive Operation', category: 'Navigation', xp: 60, level: 0, nextLevel: 100 }, - { name: 'Afterburner', category: 'Navigation', xp: 30, level: 0, nextLevel: 100 }, - { name: 'Evasive Maneuvering', category: 'Navigation', xp: 0, level: 0, nextLevel: 100 }, - // Trade - { name: 'Market Analysis', category: 'Trade', xp: 20, level: 0, nextLevel: 100 }, - { name: 'Broker Relations', category: 'Trade', xp: 45, level: 0, nextLevel: 100 }, - { name: 'Hauling', category: 'Trade', xp: 0, level: 0, nextLevel: 100 }, - // Leadership - { name: 'Fleet Command', category: 'Leadership', xp: 0, level: 0, nextLevel: 100 }, - { name: 'AI Coordination', category: 'Leadership', xp: 0, level: 0, nextLevel: 100 }, - ]; - - const xpCurve = [100, 500, 2000, 8000, 32000]; - const categoryColors = { - Combat: 'var(--red)', - Industry: 'var(--accent)', - Navigation: 'var(--cyan)', - Trade: 'var(--green)', - Leadership: 'var(--purple)', - }; - - const xpActions = [ - { name: 'Mining Cycle', xp: 15, category: 'Industry', desc: 'Complete a mining laser cycle' }, - { name: 'NPC Kill', xp: 40, category: 'Combat', desc: 'Destroy an NPC pirate' }, - { name: 'Refine Batch', xp: 25, category: 'Industry', desc: 'Refine a batch of ore' }, - { name: 'System Jump', xp: 5, category: 'Navigation', desc: 'Jump to a new system' }, - { name: 'Market Trade', xp: 20, category: 'Trade', desc: 'Complete a market transaction' }, - { name: 'Player Kill', xp: 120, category: 'Combat', desc: 'Destroy a player ship' }, - { name: 'Manufacture Item', xp: 35, category: 'Industry', desc: 'Complete a manufacturing job' }, - { name: 'Waypoint Route', xp: 30, category: 'Navigation', desc: 'Complete a multi-jump route' }, - { name: 'Bounty Collect', xp: 80, category: 'Combat', desc: 'Collect a bounty reward' }, - ]; - - useEffect(() => { - setSkills(allSkills.map(s => ({ ...s }))); - }, []); - - useEffect(() => { - const total = skills.reduce((sum, s) => sum + s.xp, 0); - setTotalXP(total); - }, [skills]); - - const filteredSkills = activeCategory === 'all' - ? skills - : skills.filter(s => s.category === activeCategory); - - const categoryStats = Object.keys(categoryColors).map(cat => { - const catSkills = skills.filter(s => s.category === cat); - const totalXP = catSkills.reduce((sum, s) => sum + s.xp, 0); - const maxXP = catSkills.reduce((sum, s) => sum + xpCurve[Math.min(s.level, 4)], 0); - const avgLevel = catSkills.length > 0 ? catSkills.reduce((sum, s) => sum + s.level, 0) / catSkills.length : 0; - return { category: cat, color: categoryColors[cat], totalXP, maxXP, avgLevel, count: catSkills.length }; - }); - - const handleSimulate = useCallback(() => { - if (simulating) { - setSimulating(false); - if (simRef.current) clearInterval(simRef.current); - return; - } - setSimulating(true); - simRef.current = setInterval(() => { - const action = xpActions[Math.floor(Math.random() * xpActions.length)]; - const skillName = action.category === 'Combat' ? 'Gunnery' : - action.category === 'Industry' ? 'Mining' : - action.category === 'Navigation' ? 'Warp Drive Operation' : - action.category === 'Trade' ? 'Broker Relations' : 'Fleet Command'; - - setSkills(prev => prev.map(s => { - if (s.name !== skillName) return s; - let newXp = s.xp + action.xp; - let newLevel = s.level; - while (newLevel < 5 && newXp >= xpCurve[newLevel]) { - newXp -= xpCurve[newLevel]; - newLevel++; - } - return { ...s, xp: newXp, level: newLevel, nextLevel: xpCurve[Math.min(newLevel, 4)] }; - })); - - setXpLog(prev => [{ - action: action.name, - xp: action.xp, - skill: skillName, - time: new Date().toLocaleTimeString('en', { hour12: false }), - }, ...prev.slice(0, 19)]); - }, 800); - }, [simulating]); - - useEffect(() => { - return () => { if (simRef.current) clearInterval(simRef.current); }; - }, []); - - const levelColor = (lvl) => { - if (lvl === 0) return 'var(--muted)'; - if (lvl === 1) return 'var(--green)'; - if (lvl === 2) return 'var(--cyan)'; - if (lvl === 3) return 'var(--purple)'; - if (lvl === 4) return 'var(--accent)'; - return 'var(--red)'; - }; - - return ( -
- e.currentTarget.style.color='var(--fg-bright)'} onMouseLeave={e => e.currentTarget.style.color='var(--muted)'}>← Back to Docs -

Skill Progression Demo

-

- Action-based XP system across 5 categories and 17+ skills. Hit the simulate button to watch - XP flow in from random activities — each action awards XP to the matching skill category. -

- - {/* HUD-style progression strip */} -
- SKILL PROGRESSION -
- TOTAL XP - {totalXP.toLocaleString()} -
- TRAINED - {skills.filter(s => s.level > 0).length}/{skills.length} -
- MAX LVL - {Math.max(...skills.map(s => s.level))} - {simulating && ● SIMULATING} -
- - {/* Stats */} -
-
-
{totalXP.toLocaleString()}
-
Total XP
-
-
-
- {skills.filter(s => s.level > 0).length}/{skills.length} -
-
Skills Trained
-
-
-
- {Math.max(...skills.map(s => s.level))} -
-
Highest Level
-
-
-
{xpLog.length}
-
Actions (session)
-
-
- - {/* Simulate button */} -
- - - Generates random XP actions every 800ms - -
- - {/* Category overview */} -
- {categoryStats.map(cat => ( -
setActiveCategory(activeCategory === cat.category ? 'all' : cat.category)}> -
-

{cat.category}

- - avg Lvl {cat.avgLevel.toFixed(1)} - -
-
-
-
0 ? (cat.totalXP / (cat.maxXP * 2)) * 100 : 0}%`, - background: cat.color, - }} /> -
-
- {cat.totalXP.toLocaleString()} XP · {cat.count} skills -
-
-
- ))} -
- -
- - {Object.keys(categoryColors).map(cat => ( - - ))} -
- -
- {/* Skill tree */} -
- {filteredSkills.map((skill, i) => { - const progress = skill.level >= 5 ? 100 : (skill.xp / skill.nextLevel) * 100; - return ( -
setSelectedSkill(skill)}> -
-
- {skill.name} - - Lvl {skill.level} - -
- - {skill.category} - -
-
-
-
-
- {skill.level >= 5 ? 'MAX' : `${skill.xp.toLocaleString()} / ${skill.nextLevel.toLocaleString()} XP`} -
-
- ); - })} -
- - {/* XP log + detail */} -
- {/* Selected skill detail */} - {selectedSkill && ( -
-

- {selectedSkill.name} -

-
-
- Current Level - - Level {selectedSkill.level}{selectedSkill.level >= 5 ? ' (MAX)' : ''} - -
-
- Category - {selectedSkill.category} -
-
- XP to Next Level - - {selectedSkill.level >= 5 ? '—' : `${selectedSkill.xp.toLocaleString()} / ${selectedSkill.nextLevel.toLocaleString()}`} - -
-
- - {/* Level milestone visualization */} -
- {[0, 1, 2, 3, 4].map(lvl => ( -
lvl ? categoryColors[selectedSkill.category] + '20' : 'var(--surface-raised)', - border: `1px solid ${selectedSkill.level > lvl ? categoryColors[selectedSkill.category] + '40' : 'var(--border)'}`, - borderRadius: 'var(--radius-md)', - }}> -
lvl ? levelColor(lvl + 1) : 'var(--muted)' }}> - {lvl + 1} -
-
- {xpCurve[lvl].toLocaleString()} XP -
-
- ))} -
-
- )} - - {/* XP activity log */} -
-

XP Activity Log

-
- {xpLog.length === 0 && ( -
- Start the simulation to see XP flow in real-time. -
- )} - {xpLog.map((entry, i) => ( -
- - {entry.time} - - {entry.action} - - +{entry.xp} XP - - → {entry.skill} -
- ))} -
-
- - {/* XP actions reference */} -
-

XP Sources

-
- {xpActions.map((action, i) => ( -
-
- {action.name} - - {action.desc} - -
-
- - {action.category} - - - +{action.xp} - -
-
- ))} -
-
-
-
-
- ); -} - -window.GDD.ProgressionDemo = ProgressionDemo; diff --git a/archive/legacy-static/js/demos/refining.js b/archive/legacy-static/js/demos/refining.js deleted file mode 100644 index 54bc283..0000000 --- a/archive/legacy-static/js/demos/refining.js +++ /dev/null @@ -1,525 +0,0 @@ -window.GDD = window.GDD || {}; - -const { useState, useEffect, useCallback, useRef } = React; - -function RefiningDemo() { - const [inventory, setInventory] = useState([]); - const [orePrices, setOrePrices] = useState({}); - const [selectedOre, setSelectedOre] = useState(null); - const [refineQty, setRefineQty] = useState(0); - const [skillLevel, setSkillLevel] = useState(2); - const [refining, setRefining] = useState(false); - const [results, setResults] = useState([]); - const [manufacturingTab, setManufacturingTab] = useState(false); - const [manufacturingJobs, setManufacturingJobs] = useState([]); - const [notifications, setNotifications] = useState([]); - const timerRef = useRef(null); - - const mineralData = { - Veldspar: { mineral: 'Tritanium', yield: 415, batch: 333, time: 45 }, - Scordite: { mineral: 'Pyerite', yield: 171, batch: 333, time: 45 }, - Pyroxeres: { mineral: 'Nocxium', yield: 8, batch: 333, time: 60 }, - Kernite: { mineral: 'Isogen', yield: 107, batch: 200, time: 60 }, - Omber: { mineral: 'Isogen', yield: 86, batch: 500, time: 75 }, - Jaspet: { mineral: 'Zydrine', yield: 8, batch: 500, time: 75 }, - Hemorphite: { mineral: 'Nocxium', yield: 21, batch: 500, time: 90 }, - Arkonor: { mineral: 'Megacyte', yield: 18, batch: 200, time: 120 }, - }; - - const manufacturingRecipes = [ - { id: 1, product: 'Mining Laser I', minerals: { Tritanium: 200, Pyerite: 80 }, time: 300, skill: 1 }, - { id: 2, product: '150mm Railgun', minerals: { Tritanium: 400, Pyerite: 150, Nocxium: 20 }, time: 900, skill: 2 }, - { id: 3, product: 'Shield Booster I', minerals: { Tritanium: 300, Isogen: 50 }, time: 600, skill: 2 }, - { id: 4, product: 'Frigate Hull', minerals: { Tritanium: 2000, Pyerite: 800, Nocxium: 100 }, time: 1800, skill: 3 }, - { id: 5, product: '1MN Afterburner', minerals: { Tritanium: 150, Pyerite: 50, Isogen: 20 }, time: 480, skill: 2 }, - ]; - - const [playerMinerals, setPlayerMinerals] = useState({ - Tritanium: 0, Pyerite: 0, Nocxium: 0, Isogen: 0, Zydrine: 0, Megacyte: 0, - }); - - const skillEfficiency = { 0: 0.50, 1: 0.60, 2: 0.70, 3: 0.80, 4: 0.875, 5: 0.95 }; - - useEffect(() => { - window.GDD.api.getPlayerInventory().then(i => { - setInventory(i); - if (i.length > 0) { setSelectedOre(i[0].item); setRefineQty(i[0].quantity); } - }); - window.GDD.api.getOrePrices().then(p => setOrePrices(p)); - }, []); - - const addNotif = useCallback((msg, color) => { - const id = Date.now(); - setNotifications(prev => [...prev, { id, msg, color }]); - setTimeout(() => setNotifications(prev => prev.filter(n => n.id !== id)), 3500); - }, []); - - const handleRefine = useCallback(async () => { - if (!selectedOre || refineQty <= 0) return; - const data = mineralData[selectedOre]; - if (!data) return; - if (refineQty < data.batch) { - addNotif(`Need at least ${data.batch} units for a batch.`, 'var(--red)'); - return; - } - - setRefining(true); - const inv = inventory.find(i => i.item === selectedOre); - const batches = Math.floor(refineQty / data.batch); - const used = batches * data.batch; - const eff = skillEfficiency[skillLevel]; - const mineralYield = Math.floor(batches * data.yield * eff); - const rawValue = used * (orePrices[selectedOre] || 0); - const mineralValue = mineralYield * Math.floor((orePrices[selectedOre] || 0) * 2.5); - - // Simulate delay - await new Promise(r => setTimeout(r, data.time * 10)); - - setPlayerMinerals(prev => ({ - ...prev, - [data.mineral]: prev[data.mineral] + mineralYield, - })); - - setInventory(prev => prev.map(i => - i.item === selectedOre - ? { ...i, quantity: i.quantity - used } - : i - ).filter(i => i.quantity > 0)); - - setResults(prev => [{ - ore: selectedOre, - batches, - used, - mineral: data.mineral, - yield: mineralYield, - efficiency: eff, - rawValue, - mineralValue, - better: mineralValue > rawValue, - }, ...prev.slice(0, 9)]); - - setRefining(false); - addNotif(`Refined ${used.toLocaleString()} ${selectedOre} → ${mineralYield.toLocaleString()} ${data.mineral} (${(eff * 100).toFixed(0)}% eff)`, 'var(--green)'); - }, [selectedOre, refineQty, skillLevel, inventory, orePrices, addNotif]); - - const handleManufacture = useCallback((recipe) => { - if (skillLevel < recipe.skill) { - addNotif(`Need Industry level ${recipe.skill} to manufacture ${recipe.product}.`, 'var(--red)'); - return; - } - // Check minerals - for (const [mineral, qty] of Object.entries(recipe.minerals)) { - if ((playerMinerals[mineral] || 0) < qty) { - addNotif(`Not enough ${mineral}. Need ${qty}, have ${playerMinerals[mineral] || 0}.`, 'var(--red)'); - return; - } - } - // Deduct minerals - setPlayerMinerals(prev => { - const next = { ...prev }; - for (const [mineral, qty] of Object.entries(recipe.minerals)) { - next[mineral] -= qty; - } - return next; - }); - - const job = { - id: Date.now(), - product: recipe.product, - totalTime: recipe.time, - remaining: recipe.time, - started: Date.now(), - }; - setManufacturingJobs(prev => [...prev, job]); - addNotif(`Manufacturing job started: ${recipe.product}. ETA: ${Math.floor(recipe.time / 60)}m ${recipe.time % 60}s`, 'var(--cyan)'); - }, [skillLevel, playerMinerals, addNotif]); - - // Manufacturing timer - useEffect(() => { - const interval = setInterval(() => { - setManufacturingJobs(prev => { - const updated = prev.map(j => ({ - ...j, - remaining: Math.max(0, j.remaining - 1), - })); - const completed = updated.filter(j => j.remaining <= 0 && prev.find(p => p.id === j.id && p.remaining > 0)); - completed.forEach(j => { - addNotif(`Manufacturing complete: ${j.product}`, 'var(--green)'); - }); - return updated.filter(j => j.remaining > 0); - }); - }, 1000); - return () => clearInterval(interval); - }, [addNotif]); - - const formatTime = (s) => `${Math.floor(s / 60)}m ${String(s % 60).padStart(2, '0')}s`; - - return ( -
- e.currentTarget.style.color='var(--fg-bright)'} onMouseLeave={e => e.currentTarget.style.color='var(--muted)'}>← Back to Docs -

Refining & Manufacturing Demo

-

- Refine raw ore into minerals, then use minerals to manufacture ships and modules. - Industry skill level determines refining efficiency — higher skill means more minerals per batch. -

- - {/* HUD-style industry strip */} -
- INDUSTRY -
- SKILL - Lvl {skillLevel} -
- EFFICIENCY - {(skillEfficiency[skillLevel] * 100).toFixed(0)}% -
- MINERALS - {Object.values(playerMinerals).reduce((a, b) => a + b, 0).toLocaleString()} -
- JOBS - {manufacturingJobs.length} -
- - {/* Notifications */} -
- {notifications.map(n => ( -
- {n.msg} -
- ))} -
- - {/* Tab toggle */} -
- - -
- - {/* Stats */} -
-
-
Lvl {skillLevel}
-
Industry Skill
-
-
-
{(skillEfficiency[skillLevel] * 100).toFixed(0)}%
-
Refine Efficiency
-
-
-
- {Object.values(playerMinerals).reduce((a, b) => a + b, 0).toLocaleString()} -
-
Total Minerals
-
-
-
{manufacturingJobs.length}
-
Active Jobs
-
-
- - {!manufacturingTab ? ( - /* ===== REFINING ===== */ - <> -
-
- Reprocessing Plant - | - Jita IV — Moon 4 - {refining && ( - - ◌ REFINING... - - )} -
- -
- {/* Ore selection */} -
-
-
- Your Ore -
-
- {inventory.map(item => { - const data = mineralData[item.item]; - return ( -
{ setSelectedOre(item.item); setRefineQty(item.quantity); }}> -
- {item.item} -
-
- {item.quantity.toLocaleString()} units · → {data?.mineral || '?'} -
-
- ); - })} -
- - {/* Refining panel */} -
- {selectedOre && mineralData[selectedOre] ? ( - <> -

{selectedOre}

-
-
-
Yields Mineral
-
{mineralData[selectedOre].mineral}
-
-
-
Batch Size
-
{mineralData[selectedOre].batch.toLocaleString()} units
-
-
- -
-
- Quantity to Refine - - {Math.floor(refineQty / mineralData[selectedOre].batch)} batches - -
- i.item === selectedOre)?.quantity || 0} - value={refineQty} onChange={e => setRefineQty(parseInt(e.target.value))} - style={{ width: '100%', accentColor: 'var(--accent)' }} - /> -
- {refineQty.toLocaleString()} units -
-
- - {/* Skill selector */} -
-
Industry Skill Level
-
- {[0, 1, 2, 3, 4, 5].map(lvl => ( - - ))} -
-
- - {/* Preview */} - {refineQty >= mineralData[selectedOre].batch && ( -
-

Refining Preview

- {(() => { - const data = mineralData[selectedOre]; - const batches = Math.floor(refineQty / data.batch); - const used = batches * data.batch; - const eff = skillEfficiency[skillLevel]; - const minYield = Math.floor(batches * data.yield * eff); - const rawValue = used * (orePrices[selectedOre] || 0); - const mineralValue = minYield * Math.floor((orePrices[selectedOre] || 0) * 2.5); - return ( -
-
- Ore consumed - {used.toLocaleString()} {selectedOre} -
-
- Mineral yield - {minYield.toLocaleString()} {data.mineral} -
-
- Efficiency - {(eff * 100).toFixed(0)}% -
-
-
- Sell raw value - ₢{rawValue.toLocaleString()} -
-
- Refined value (est.) - rawValue ? 'var(--green)' : 'var(--red)' }}> - ₢{mineralValue.toLocaleString()} {mineralValue > rawValue ? '▲' : '▼'} - -
-
-
- ); - })()} -
- )} - - - - ) : ( -
- Select an ore type to begin refining -
- )} -
-
-
- - {/* Refining history */} - {results.length > 0 && ( -
-

Refining History

- - - - - - - - - - - - - - - {results.map((r, i) => ( - - - - - - - - - - - ))} - -
OreBatchesMineralYieldEfficiencyRaw ValueRefined ValueVerdict
{r.ore}{r.batches}{r.mineral}{r.yield.toLocaleString()}{(r.efficiency * 100).toFixed(0)}%₢{r.rawValue.toLocaleString()} - ₢{r.mineralValue.toLocaleString()} - - - {r.better ? 'REFINE ▲' : 'SELL RAW ▼'} - -
-
- )} - - ) : ( - /* ===== MANUFACTURING ===== */ - <> -
- {/* Mineral inventory */} -
-

Mineral Inventory

- {Object.entries(playerMinerals).map(([mineral, qty]) => ( -
- 0 ? 'var(--fg-bright)' : 'var(--muted)' }}>{mineral} - 0 ? 'var(--cyan)' : 'var(--muted)' }}> - {qty.toLocaleString()} - -
- ))} -
- Refine ore to accumulate minerals for manufacturing. -
-
- - {/* Active jobs */} -
-

Manufacturing Jobs

- {manufacturingJobs.length === 0 ? ( -
- No active jobs. Start one from the recipe list below. -
- ) : ( - manufacturingJobs.map(job => ( -
-
- {job.product} - - {formatTime(job.remaining)} - -
-
-
-
-
- )) - )} -
-
- - {/* Recipe list */} -
-

Blueprints

-
- {manufacturingRecipes.map(recipe => { - const canBuild = skillLevel >= recipe.skill && - Object.entries(recipe.minerals).every(([m, q]) => (playerMinerals[m] || 0) >= q); - return ( -
-
-

{recipe.product}

- - {canBuild ? 'READY' : skillLevel < recipe.skill ? `LVL ${recipe.skill}` : 'NEED MATS'} - -
-
- {Object.entries(recipe.minerals).map(([m, q]) => { - const have = playerMinerals[m] || 0; - return ( -
- {m} - = q ? 'var(--green)' : 'var(--red)' }}> - {have.toLocaleString()} / {q.toLocaleString()} - -
- ); - })} -
-
- - Time: {formatTime(recipe.time)} · Skill: Lvl {recipe.skill} - - -
-
- ); - })} -
-
- - )} -
- ); -} - -window.GDD.RefiningDemo = RefiningDemo; diff --git a/archive/legacy-static/js/demos/starmap.js b/archive/legacy-static/js/demos/starmap.js deleted file mode 100644 index 4a657f6..0000000 --- a/archive/legacy-static/js/demos/starmap.js +++ /dev/null @@ -1,1044 +0,0 @@ -window.GDD = window.GDD || {}; - -const { useState, useEffect, useRef, useCallback, useMemo } = React; -const TH = window.GDD.THREE; - -/* ── Scale constants ── */ -const SCALE = 0.5; // world position scale -const ORBIT_SCALE = 0.22; // orbital radius scale -const PLANET_SCALE = 0.7; // planet mesh scale - -function StarMapDemo() { - const containerRef = useRef(null); - const sceneRef = useRef(null); - const [systems, setSystems] = useState([]); - const [connections, setConnections] = useState([]); - const [selected, setSelected] = useState(null); - const [destination, setDestination] = useState(null); - const [hovered, setHovered] = useState(null); - const [waypoints, setWaypoints] = useState([]); - const [route, setRoute] = useState([]); - const [searchQuery, setSearchQuery] = useState(''); - const [systemFilter, setSystemFilter] = useState('all'); - const [contextMenu, setContextMenu] = useState({ visible: false, x: 0, y: 0, target: null }); - const systemMeshesRef = useRef([]); - const routeLineRef = useRef(null); - const connLinesRef = useRef([]); - const animIdRef = useRef(null); - const cameraCtrlRef = useRef(null); - const minimapCanvasRef = useRef(null); - const focusTargetRef = useRef(null); - const orbitalBodiesRef = useRef([]); - const asteroidBeltsRef = useRef([]); - - /* ── Celestial data ── */ - const celestialData = useMemo(() => { - return window.GDD.CONSTANTS.CELESTIAL_BODIES || {}; - }, []); - - const selectedCelestial = useMemo(() => { - if (!selected) return null; - return celestialData[selected.id] || null; - }, [selected, celestialData]); - - /* ── Load data ── */ - useEffect(() => { - window.GDD.api.getSystems().then(s => setSystems(s)); - window.GDD.api.getConnections().then(c => setConnections(c)); - }, []); - - /* ── Camera fly-to on selection ── */ - useEffect(() => { - if (!focusTargetRef.current || !cameraCtrlRef.current) return; - cameraCtrlRef.current.flyTo(focusTargetRef.current.x, focusTargetRef.current.y, focusTargetRef.current.z || 0); - focusTargetRef.current = null; - }, [selected]); - - /* ── Context menu close on click / Escape / scroll ── */ - useEffect(() => { - const close = () => setContextMenu(prev => ({ ...prev, visible: false })); - const onKey = (e) => { if (e.key === 'Escape') close(); }; - window.addEventListener('click', close); - window.addEventListener('keydown', onKey); - return () => { window.removeEventListener('click', close); window.removeEventListener('keydown', onKey); }; - }, []); - - /* ── Build 3D scene ── */ - useEffect(() => { - if (systems.length === 0) return; - const container = containerRef.current; - if (!container) return; - - // Cleanup previous - if (sceneRef.current) { - sceneRef.current.scene.traverse(child => { - if (child.geometry) child.geometry.dispose(); - if (child.material) { if (child.material.map) child.material.map.dispose(); child.material.dispose(); } - }); - sceneRef.current.renderer.dispose(); - if (sceneRef.current.renderer.domElement.parentNode) sceneRef.current.renderer.domElement.parentNode.removeChild(sceneRef.current.renderer.domElement); - if (animIdRef.current) cancelAnimationFrame(animIdRef.current); - if (cameraCtrlRef.current) cameraCtrlRef.current.dispose(); - } - - const scene = new THREE.Scene(); - scene.fog = new THREE.FogExp2(0x040810, 0.0003); - - const w = container.clientWidth; - const h = container.clientHeight; - const camera = new THREE.PerspectiveCamera(50, w / h, 0.1, 5000); - camera.position.set(0, 180, 220); - camera.lookAt(0, 0, 0); - - const renderer = TH.createRenderer(container, { clearColor: 0x040810 }); - renderer.setSize(w, h); - TH.handleResize(renderer, camera, container); - - // Star field - const stars = TH.createStarField(4000, 2500); - scene.add(stars); - - // Nebulae - TH.addNebula(scene, 0x22d3ee, [-100, 50, -300], 400); - TH.addNebula(scene, 0xa78bfa, [200, -80, -200], 300); - TH.addNebula(scene, 0xf0a030, [0, 100, -250], 200); - - // Grid - const grid = new THREE.GridHelper(600, 30, 0x0d1520, 0x0d1520); - grid.material.transparent = true; - grid.material.opacity = 0.12; - scene.add(grid); - - TH.setupSpaceLighting(scene); - - // Build system meshes - systemMeshesRef.current = []; - orbitalBodiesRef.current = []; - asteroidBeltsRef.current = []; - - systems.forEach(sys => { - const group = TH.createStarSystem(sys, SCALE); - scene.add(group); - systemMeshesRef.current.push(group); - - // Create celestial bodies for this system - const cData = celestialData[sys.id]; - if (!cData) return; - const sysPos = new THREE.Vector3(sys.x * SCALE, sys.y * SCALE, 0); - - cData.bodies.forEach(body => { - if (body.type === 'belt') { - // Asteroid belt - const belt = TH.createAsteroidBelt( - body.innerOrbit * ORBIT_SCALE, - body.outerOrbit * ORBIT_SCALE, - body.count, - { basePeriod: 20, maxInclination: 0.12 } - ); - belt.position.copy(sysPos); - scene.add(belt); - asteroidBeltsRef.current.push({ belt, parentPos: sysPos }); - return; - } - - // Planet - const orbitR = body.orbit * ORBIT_SCALE; - const planetSize = body.size * PLANET_SCALE; - const planetMesh = TH.createPlanetMesh(planetSize, body.color, body.atmosphere); - scene.add(planetMesh); - - // Orbit trail - const trail = TH.createOrbitTrail(orbitR, body.ecc, body.inc, 0x1a3050, 0.18); - trail.position.copy(sysPos); - scene.add(trail); - - // Orbital body - const orbBody = new TH.OrbitalBody({ - mesh: planetMesh, - parentPos: sysPos, - orbitRadius: orbitR, - eccentricity: body.ecc, - inclination: body.inc, - period: body.period, - }); - - // Rings - if (body.hasRings) { - const rings = TH.createRingSystem(planetSize * 1.3, planetSize * 2.1, body.color, 0.25); - planetMesh.add(rings); - } - - // Moons - if (body.moons) { - body.moons.forEach(moon => { - const moonSize = moon.size * PLANET_SCALE * 0.8; - const moonMesh = TH.createPlanetMesh(moonSize, moon.color); - scene.add(moonMesh); - - const moonOrbitR = moon.orbit * ORBIT_SCALE * 0.5; - const moonTrail = TH.createOrbitTrail(moonOrbitR, 0, 0, 0x0d1a2a, 0.08); - scene.add(moonTrail); - - const moonBody = new TH.OrbitalBody({ - mesh: moonMesh, - orbitRadius: moonOrbitR, - period: moon.period, - }); - orbBody.children.push(moonBody); - }); - } - - // Stations in orbit around planets - if (sys.stations && body.type === 'terrestrial') { - sys.stations.forEach((stn, si) => { - const stnMesh = TH.createOrbitalStation(planetSize * 0.25, 0x22d3ee); - scene.add(stnMesh); - const stnOrbitR = planetSize * 2.5 + si * 1.5; - const stnTrail = TH.createOrbitTrail(stnOrbitR, 0, 0, 0x22d3ee, 0.06); - scene.add(stnTrail); - const stnBody = new TH.OrbitalBody({ - mesh: stnMesh, - orbitRadius: stnOrbitR, - period: 3 + si, - phase: si * Math.PI * 0.7, - }); - orbBody.children.push(stnBody); - }); - } - - orbitalBodiesRef.current.push(orbBody); - }); - }); - - // Connection lines - connLinesRef.current = []; - connections.forEach(([a, b]) => { - const sa = systems.find(s => s.id === a); - const sb = systems.find(s => s.id === b); - if (!sa || !sb) return; - const line = TH.createConnectionLine( - { x: sa.x * SCALE, y: sa.y * SCALE, z: 0 }, - { x: sb.x * SCALE, y: sb.y * SCALE, z: 0 }, - 0x1c2a3f, 0.4 - ); - scene.add(line); - connLinesRef.current.push({ line, a, b }); - }); - - // Camera controller - const ctrl = new TH.OrbitController(camera, renderer.domElement, new THREE.Vector3(0, 0, 0)); - ctrl.minDistance = 8; - ctrl.maxDistance = 400; - ctrl.dampingFactor = 0.08; - ctrl.rotateSpeed = 0.4; - ctrl.zoomSpeed = 0.8; - ctrl.panSpeed = 0.5; - ctrl.enablePan = true; - ctrl.panButton = 2; - cameraCtrlRef.current = ctrl; - - sceneRef.current = { scene, camera, renderer, stars }; - - // Animation loop - const clock = new THREE.Clock(); - const animate = () => { - animIdRef.current = requestAnimationFrame(animate); - const dt = clock.getDelta(); - const t = clock.elapsedTime; - ctrl.update(dt); - - stars.rotation.y = t * 0.001; - - // Pulse system glows - systemMeshesRef.current.forEach(group => { - const glow = group.children[1]; - if (glow) { - const base = 6 * SCALE; - glow.scale.setScalar(base + Math.sin(t * 2 + group.position.x) * 0.5); - } - }); - - // Update orbital bodies (Keplerian physics) - orbitalBodiesRef.current.forEach(ob => ob.update(dt)); - - // Update asteroid belts - asteroidBeltsRef.current.forEach(ab => TH.updateAsteroidBelt(ab.belt, dt, ab.parentPos)); - - renderer.render(scene, camera); - }; - animate(); - - const onResize = () => TH.handleResize(renderer, camera, container); - window.addEventListener('resize', onResize); - - return () => { - if (animIdRef.current) cancelAnimationFrame(animIdRef.current); - window.removeEventListener('resize', onResize); - if (cameraCtrlRef.current) cameraCtrlRef.current.dispose(); - }; - }, [systems, celestialData]); - - /* ── Draw minimap ── */ - useEffect(() => { - const canvas = minimapCanvasRef.current; - if (!canvas || systems.length === 0) return; - const ctx = canvas.getContext('2d'); - const W = canvas.width; - const H = canvas.height; - const xs = systems.map(s => s.x); - const ys = systems.map(s => s.y); - const minX = Math.min(...xs) - 40; - const maxX = Math.max(...xs) + 40; - const minY = Math.min(...ys) - 40; - const maxY = Math.max(...ys) + 40; - const scaleX = (W - 20) / (maxX - minX); - const scaleY = (H - 20) / (maxY - minY); - const sc = Math.min(scaleX, scaleY); - const mapX = (x) => 10 + (x - minX) * sc; - const mapY = (y) => H - 10 - (y - minY) * sc; - - ctx.clearRect(0, 0, W, H); - ctx.fillStyle = 'rgba(8,12,20,0.95)'; - ctx.fillRect(0, 0, W, H); - - ctx.strokeStyle = 'rgba(28,42,63,0.6)'; - ctx.lineWidth = 0.5; - connections.forEach(([a, b]) => { - const sa = systems.find(s => s.id === a); - const sb = systems.find(s => s.id === b); - if (!sa || !sb) return; - ctx.beginPath(); ctx.moveTo(mapX(sa.x), mapY(sa.y)); ctx.lineTo(mapX(sb.x), mapY(sb.y)); ctx.stroke(); - }); - - if (route.length > 1) { - ctx.strokeStyle = '#22d3ee'; ctx.lineWidth = 1.5; ctx.setLineDash([3, 2]); - ctx.beginPath(); - route.forEach((s, i) => { if (i === 0) ctx.moveTo(mapX(s.x), mapY(s.y)); else ctx.lineTo(mapX(s.x), mapY(s.y)); }); - ctx.stroke(); ctx.setLineDash([]); - } - - systems.forEach(sys => { - const x = mapX(sys.x), y = mapY(sys.y); - const isSel = selected && selected.id === sys.id; - const isDest = destination === sys.id; - const isHov = hovered && hovered.id === sys.id; - let col = '#5a6b82'; - if (sys.security >= 0.5) col = '#22c55e'; - else if (sys.security >= 0.2) col = '#f0a030'; - else col = '#ef4444'; - if (isSel) { ctx.fillStyle = '#f0a030'; ctx.beginPath(); ctx.arc(x, y, 5, 0, Math.PI * 2); ctx.fill(); } - if (isDest) { ctx.strokeStyle = '#22d3ee'; ctx.lineWidth = 1.5; ctx.beginPath(); ctx.arc(x, y, 6, 0, Math.PI * 2); ctx.stroke(); } - const r = isHov ? 3 : isSel ? 4 : 2.5; - ctx.fillStyle = col; ctx.globalAlpha = isHov || isSel ? 1 : 0.7; - ctx.beginPath(); ctx.arc(x, y, r, 0, Math.PI * 2); ctx.fill(); ctx.globalAlpha = 1; - if (isSel || isHov || isDest) { - ctx.font = '8px JetBrains Mono, monospace'; - ctx.fillStyle = isSel ? '#f1f5f9' : '#94a3b8'; - ctx.textAlign = 'center'; ctx.fillText(sys.name, x, y - 8); - } - }); - }, [systems, connections, selected, hovered, destination, route]); - - /* ── Update route 3D line ── */ - useEffect(() => { - if (!sceneRef.current) return; - const { scene } = sceneRef.current; - if (routeLineRef.current) { - scene.remove(routeLineRef.current); - if (routeLineRef.current.geometry) routeLineRef.current.geometry.dispose(); - if (routeLineRef.current.material) routeLineRef.current.material.dispose(); - routeLineRef.current = null; - } - if (route.length > 1) { - const line = TH.createRouteLine(route.map(s => ({ x: s.x * SCALE, y: s.y * SCALE, z: 1 })), 0x22d3ee); - scene.add(line); - routeLineRef.current = line; - } - }, [route]); - - /* ── Update selection visuals ── */ - useEffect(() => { - systemMeshesRef.current.forEach(group => { - const sys = group.userData; - const isSel = selected && selected.id === sys.id; - const isHov = hovered && hovered.id === sys.id; - const isDest = destination === sys.id; - const isWP = waypoints.some(w => w.id === sys.id); - - const core = group.children[0]; - if (core) core.scale.setScalar(isSel ? 2.0 : isHov ? 1.5 : isDest ? 1.6 : isWP ? 1.3 : 1); - - const glow = group.children[1]; - if (glow) { - let col = new THREE.Color(sys.color); - if (isDest) col = new THREE.Color(0x22d3ee); - else if (isSel) col = new THREE.Color(0xf0a030); - else if (isWP) col = new THREE.Color(0xa78bfa); - glow.material.color.copy(col); - } - - const label = group.children[2]; - if (label) { - let color = '#5a6b82', size = 18; - if (isSel) { color = '#f1f5f9'; size = 22; } - else if (isHov) { color = '#d4dce8'; size = 20; } - else if (isDest) { color = '#22d3ee'; size = 20; } - else if (isWP) { color = '#a78bfa'; size = 19; } - const prefix = isDest ? '⊕ ' : isWP ? '◇ ' : ''; - TH.updateLabelText(label, prefix + sys.name, color, size); - } - }); - }, [selected, hovered, destination, waypoints]); - - /* ── Highlight connections ── */ - useEffect(() => { - connLinesRef.current.forEach(({ line, a, b }) => { - const isOnRoute = route.some((s, i) => { - const next = route[i + 1]; - return next && ((a === s.id && b === next.id) || (b === s.id && a === next.id)); - }); - const isActive = selected && (selected.id === a || selected.id === b); - if (isOnRoute) { line.material.color.set(0x22d3ee); line.material.opacity = 0.5; } - else if (isActive) { line.material.color.set(0xf0a030); line.material.opacity = 0.5; } - else { line.material.color.set(0x1c2a3f); line.material.opacity = 0.25; } - }); - }, [selected, route]); - - /* ── Raycast helper ── */ - const raycastSystems = useCallback((e) => { - if (!sceneRef.current) return null; - const { camera } = sceneRef.current; - const rect = containerRef.current.getBoundingClientRect(); - const mouse = new THREE.Vector2( - ((e.clientX - rect.left) / rect.width) * 2 - 1, - -((e.clientY - rect.top) / rect.height) * 2 + 1 - ); - const cores = systemMeshesRef.current.map(g => g.children[0]).filter(Boolean); - const hits = TH.raycast(mouse, camera, cores); - if (hits.length > 0) return hits[0].object.parent; - return null; - }, []); - - /* ── Click ── */ - const handleClick = useCallback((e) => { - const group = raycastSystems(e); - if (group) setSelected(group.userData); - else setSelected(null); - }, [raycastSystems]); - - /* ── Double-click ── */ - const handleDoubleClick = useCallback((e) => { - const group = raycastSystems(e); - if (group) { - const sys = group.userData; - setSelected(sys); - focusTargetRef.current = { x: sys.x * SCALE, y: sys.y * SCALE, z: 0 }; - } - }, [raycastSystems]); - - /* ── Hover ── */ - const handleMove = useCallback((e) => { - const group = raycastSystems(e); - if (group) { setHovered(group.userData); containerRef.current.style.cursor = 'pointer'; } - else { setHovered(null); containerRef.current.style.cursor = 'grab'; } - }, [raycastSystems]); - - /* ── Right-click context menu ── */ - const handleContextMenu = useCallback((e) => { - e.preventDefault(); - e.stopPropagation(); - const group = raycastSystems(e); - setContextMenu({ - visible: true, - x: Math.min(e.clientX, window.innerWidth - 200), - y: Math.min(e.clientY, window.innerHeight - 250), - target: group ? group.userData : null, - }); - }, [raycastSystems]); - - /* ── Actions ── */ - const setDestFor = useCallback((sys) => { - setDestination(sys.id); - const sol = systems.find(s => s.id === 'sol'); - setRoute(sol && sys.id !== 'sol' ? [sol, sys] : [sys]); - }, [systems]); - - const addWaypointFor = useCallback((sys) => { - setWaypoints(prev => { - if (prev.some(w => w.id === sys.id)) return prev; - const next = [...prev, sys]; - const sol = systems.find(s => s.id === 'sol'); - setRoute(sol ? [sol, ...next] : next); - return next; - }); - }, [systems]); - - const handleSetDestination = useCallback(() => { if (selected) setDestFor(selected); }, [selected, setDestFor]); - const handleAddWaypoint = useCallback(() => { if (selected) addWaypointFor(selected); }, [selected, addWaypointFor]); - - const handleClearRoute = useCallback(() => { setDestination(null); setWaypoints([]); setRoute([]); }, []); - const handleZoomTo = useCallback((level) => { if (cameraCtrlRef.current) cameraCtrlRef.current.distance = level; }, []); - const handleResetView = useCallback(() => { - const ctrl = cameraCtrlRef.current; - if (!ctrl) return; - ctrl.target.set(0, 0, 0); ctrl.distance = 200; ctrl.theta = Math.PI / 4; ctrl.phi = Math.PI / 3; - }, []); - - /* ── Filtered systems ── */ - const filteredSystems = useMemo(() => { - let list = systems; - if (searchQuery) { const q = searchQuery.toLowerCase(); list = list.filter(s => s.name.toLowerCase().includes(q)); } - if (systemFilter !== 'all') { - if (systemFilter === 'highsec') list = list.filter(s => s.security >= 0.5); - else if (systemFilter === 'lowsec') list = list.filter(s => s.security >= 0.2 && s.security < 0.5); - else if (systemFilter === 'nullsec') list = list.filter(s => s.security < 0.2); - } - return list; - }, [systems, searchQuery, systemFilter]); - - const currentSystem = systems.find(s => s.id === 'sol'); - const destSystem = destination ? systems.find(s => s.id === destination) : null; - - const secColor = (sec) => { if (sec >= 0.5) return 'var(--green)'; if (sec >= 0.2) return 'var(--accent)'; return 'var(--red)'; }; - const secBg = (sec) => { if (sec >= 0.5) return 'var(--green-bg)'; if (sec >= 0.2) return 'var(--accent-bg)'; return 'var(--red-bg)'; }; - const planetIcon = (t) => { if (t === 'gas') return '⛽'; if (t === 'terrestrial') return '🌍'; return '●'; }; - - /* ══════════════════════════════════════ - RENDER - ══════════════════════════════════════ */ - return ( -
- - {/* 3D Canvas */} -
- - {/* ═══ Custom Context Menu ═══ */} - {contextMenu.visible && ( -
e.stopPropagation()} style={{ - position: 'fixed', left: contextMenu.x, top: contextMenu.y, zIndex: 9999, minWidth: '200px', - background: 'rgba(10,16,28,0.97)', border: '1px solid var(--border-light)', - borderRadius: 'var(--radius-lg)', backdropFilter: 'blur(16px)', - boxShadow: '0 8px 40px rgba(0,0,0,0.7), 0 0 1px rgba(34,211,238,0.15)', overflow: 'hidden', - fontFamily: 'var(--font-mono)', fontSize: '11px', - }}> - {contextMenu.target ? ( - <> - {/* System header */} -
- - {contextMenu.target.name} - - {contextMenu.target.security.toFixed(1)} - -
- {/* Quick info */} -
-
- TYPE - {contextMenu.target.type} -
-
- PLANETS - {contextMenu.target.planets} -
- {celestialData[contextMenu.target.id] && ( -
- FACTION - {celestialData[contextMenu.target.id].faction} -
- )} -
- {/* Actions */} -
- {[ - { icon: '✦', label: 'Show Details', action: () => { setSelected(contextMenu.target); focusTargetRef.current = { x: contextMenu.target.x * SCALE, y: contextMenu.target.y * SCALE, z: 0 }; setContextMenu(prev => ({...prev, visible: false})); } }, - { icon: '⊕', label: 'Set Destination', action: () => { setDestFor(contextMenu.target); setContextMenu(prev => ({...prev, visible: false})); }, highlight: 'var(--cyan)' }, - { icon: '◇', label: 'Add Waypoint', action: () => { addWaypointFor(contextMenu.target); setContextMenu(prev => ({...prev, visible: false})); } }, - { icon: '◎', label: 'Focus Camera', action: () => { focusTargetRef.current = { x: contextMenu.target.x * SCALE, y: contextMenu.target.y * SCALE, z: 0 }; setContextMenu(prev => ({...prev, visible: false})); } }, - { icon: '⚐', label: 'Bookmark', action: () => setContextMenu(prev => ({...prev, visible: false})) }, - ].map((item, i) => ( -
{ e.currentTarget.style.background = 'var(--surface-raised)'; e.currentTarget.style.color = 'var(--fg-bright)'; }} - onMouseLeave={(e) => { e.currentTarget.style.background = 'transparent'; e.currentTarget.style.color = 'var(--fg-dim)'; }}> - {item.icon} - {item.label} -
- ))} -
- - ) : ( - <> -
- Star Map Controls -
-
- {[ - { icon: '⟳', label: 'Reset View', action: () => { handleResetView(); setContextMenu(prev => ({...prev, visible: false})); } }, - { icon: '⊞', label: 'Zoom — Region', action: () => { handleZoomTo(120); setContextMenu(prev => ({...prev, visible: false})); } }, - { icon: '⊡', label: 'Zoom — Show All', action: () => { handleZoomTo(280); setContextMenu(prev => ({...prev, visible: false})); } }, - { icon: '◎', label: 'Zoom — System', action: () => { handleZoomTo(30); setContextMenu(prev => ({...prev, visible: false})); } }, - ].map((item, i) => ( -
{ e.currentTarget.style.background = 'var(--surface-raised)'; e.currentTarget.style.color = 'var(--fg-bright)'; }} - onMouseLeave={(e) => { e.currentTarget.style.background = 'transparent'; e.currentTarget.style.color = 'var(--fg-dim)'; }}> - {item.icon} - {item.label} -
- ))} -
- - )} -
- )} - - {/* ═══ HUD Overlay ═══ */} -
- - {/* ═══ TOP BAR ═══ */} -
- -
- STAR MAP - - {currentSystem?.name || 'Sol'} · {currentSystem?.security.toFixed(1) || '1.0'} - -
- Systems - {systems.length} -
- Bridges - {connections.length} - - {destination && ( - <> -
- - - DEST: {destSystem?.name} - · {route.length - 1} jump{route.length - 1 !== 1 ? 's' : ''} - - - )} - -
- Zoom - {[{ label: 'SYS', dist: 30 }, { label: 'REG', dist: 120 }, { label: 'ALL', dist: 280 }].map(z => ( - - ))} -
- -
-
- - {/* ═══ MIDDLE AREA ═══ */} -
- - {/* ═══ LEFT — System Details Panel ═══ */} -
-
- {selected ? ( - <> - {/* System Header */} -
-
- -
-
{selected.name}
-
- {selected.security.toFixed(1)} -
- -
-
- Star Type - {selected.type} -
-
- Planets - {selected.planets} -
-
- Stations - {selected.stations?.length || 0} -
- {selected.stations && selected.stations.length > 0 && ( -
- {selected.stations.map((stn, i) => ( -
- {stn} -
- ))} -
- )} -
-
- - {/* ═══ System Details (Celestial Bodies) ═══ */} - {selectedCelestial && ( -
-
-
- {selectedCelestial.description} -
-
- - {selectedCelestial.faction} - - - Pop: {selectedCelestial.population} - -
-
- - {/* Resources */} - {selectedCelestial.resources.length > 0 && ( -
- Ore: - {selectedCelestial.resources.map((r, i) => ( - {r} - ))} -
- )} - - {/* Celestial Bodies List */} -
- {selectedCelestial.bodies.map((body, i) => ( -
- {/* Icon */} - - {body.type === 'belt' ? '·' : ''} - -
-
{body.name}
-
- {body.type === 'belt' - ? `${body.count} objects · ${body.innerOrbit}-${body.outerOrbit} AU` - : `${body.type} · orbit ${body.orbit} AU · T ${body.period.toFixed(1)}s` - } -
-
- {body.ecc > 0.05 && ( - - e={body.ecc.toFixed(2)} - - )} - {body.moons && body.moons.length > 0 && ( - - {body.moons.length} moon{body.moons.length > 1 ? 's' : ''} - - )} - {body.hasRings && ( - rings - )} -
- ))} -
-
- )} - - {/* Connections */} -
-
- Connections - - {connections.filter(([a, b]) => a === selected.id || b === selected.id).length} jumps - -
-
- {connections.filter(([a, b]) => a === selected.id || b === selected.id).map(([a, b]) => { - const neighborId = a === selected.id ? b : a; - const neighbor = systems.find(s => s.id === neighborId); - if (!neighbor) return null; - return ( -
{ setSelected(neighbor); focusTargetRef.current = { x: neighbor.x * SCALE, y: neighbor.y * SCALE, z: 0 }; }} - onContextMenu={(e) => { e.preventDefault(); e.stopPropagation(); setContextMenu({ visible: true, x: Math.min(e.clientX, window.innerWidth - 200), y: Math.min(e.clientY, window.innerHeight - 250), target: neighbor }); }} - style={{ - display: 'flex', alignItems: 'center', gap: '6px', padding: '4px 8px', - borderRadius: '4px', cursor: 'pointer', transition: 'background 0.15s', - borderLeft: `2px solid ${secColor(neighbor.security)}`, - }} - onMouseEnter={(e) => e.currentTarget.style.background = 'var(--surface-raised)'} - onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}> - {neighbor.name} - {neighbor.security.toFixed(1)} -
- ); - })} -
-
- - - - ) : ( -
-
-
- Click a system to select
Double-click to focus
Right-click for options
Scroll to zoom -
-
- )} -
-
- - {/* CENTER — hover tooltip */} -
- {hovered && hovered.id !== selected?.id && ( -
- - {hovered.name} - SEC {hovered.security.toFixed(1)} - {hovered.type} - {celestialData[hovered.id] && ( - {celestialData[hovered.id].bodies.length} bodies - )} -
- )} -
- - {/* ═══ RIGHT — System List ═══ */} -
-
- {/* Search */} -
- setSearchQuery(e.target.value)} placeholder="Search systems..." style={{ - width: '100%', padding: '6px 10px 6px 28px', fontSize: '10px', fontFamily: 'var(--font-mono)', - background: 'rgba(15,22,35,0.92)', border: '1px solid var(--border)', borderRadius: 'var(--radius-md)', - color: 'var(--fg)', backdropFilter: 'blur(8px)', outline: 'none', - }} /> - -
- - {/* Filter tabs */} -
- {[ - { key: 'all', label: 'ALL' }, - { key: 'highsec', label: 'HI', col: 'var(--green)' }, - { key: 'lowsec', label: 'LO', col: 'var(--accent)' }, - { key: 'nullsec', label: 'NULL', col: 'var(--red)' }, - ].map(f => ( - - ))} -
- - {/* System list */} -
-
- Systems - {filteredSystems.length} -
-
- {filteredSystems.map(sys => { - const isSel = selected?.id === sys.id; - const isDest = destination === sys.id; - const isWP = waypoints.some(w => w.id === sys.id); - return ( -
setSelected(sys)} - onDoubleClick={() => { setSelected(sys); focusTargetRef.current = { x: sys.x * SCALE, y: sys.y * SCALE, z: 0 }; }} - onContextMenu={(e) => { e.preventDefault(); e.stopPropagation(); setContextMenu({ visible: true, x: Math.min(e.clientX, window.innerWidth - 200), y: Math.min(e.clientY, window.innerHeight - 250), target: sys }); }} - style={{ - display: 'flex', alignItems: 'center', gap: '6px', padding: '5px 8px', - borderRadius: '4px', cursor: 'pointer', transition: 'background 0.15s', - background: isSel ? 'var(--accent-bg)' : isDest ? 'var(--cyan-bg)' : 'transparent', - borderLeft: `2px solid ${isDest ? 'var(--cyan)' : isWP ? 'var(--purple)' : secColor(sys.security)}`, - }} - onMouseEnter={(e) => { if (!isSel && !isDest) e.currentTarget.style.background = 'var(--surface-raised)'; }} - onMouseLeave={(e) => { if (!isSel && !isDest) e.currentTarget.style.background = 'transparent'; }}> - - - {isDest ? '⊕ ' : isWP ? '◇ ' : ''}{sys.name} - - {sys.security.toFixed(1)} -
- ); - })} -
-
-
-
-
- - {/* ═══ BOTTOM BAR ═══ */} -
- - {/* Route */} -
-
- - Autopilot Route - - {route.length > 1 && ( - <> - {route.length - 1} jump{route.length - 1 !== 1 ? 's' : ''} - - - )} - {!route.length && No route set} -
- {route.length > 0 && ( -
- {route.map((sys, i) => ( - -
- - {i === 0 ? '⬡' : i === route.length - 1 ? '⊕' : '◇'} {sys.name} - - {sys.security.toFixed(1)} -
- {i < route.length - 1 && } -
- ))} -
- )} -
- - {/* Ship Status */} -
-
- - Ship - - Venture -
-
- {[{ label: 'SH', value: 100, color: '#22d3ee' }, { label: 'AR', value: 100, color: '#f0a030' }, { label: 'HU', value: 100, color: '#22c55e' }, { label: 'CA', value: 85, color: '#a78bfa' }].map(bar => ( -
-
-
-
-
- {bar.label} - {bar.value}% -
-
- ))} -
-
- - {/* Wallet & Time */} -
-
- ₢125,000 -
-
- - - Online - - 14:23 UTC -
-
- - {/* Minimap */} -
-
- Overview -
-
- -
-
-
-
-
- ); -} - -window.GDD.StarMapDemo = StarMapDemo; diff --git a/archive/legacy-static/js/demos/zora.js b/archive/legacy-static/js/demos/zora.js deleted file mode 100644 index 4d6f4cd..0000000 --- a/archive/legacy-static/js/demos/zora.js +++ /dev/null @@ -1,495 +0,0 @@ -window.GDD = window.GDD || {}; - -const { useState, useEffect, useCallback, useRef } = React; - -function ZoraDemo() { - // ── Soul state vector ── - const [soulDepth, setSoulDepth] = useState('blank'); // blank, stirring, developing, bonded, deep - const [personalityAxes, setPersonalityAxes] = useState({ - cautiousBold: 0.2, // 0 = cautious, 1 = bold - formalWarm: 0.15, // 0 = formal, 1 = warm - compliantOpinionated: 0.1, // 0 = compliant, 1 = opinionated - reservedExpressive: 0.05, // 0 = reserved, 1 = expressive - }); - const [installedModules, setInstalledModules] = useState(['comms']); // comms is the minimum for text output - const [selectedEvent, setSelectedEvent] = useState(null); - const [zoraOutput, setZoraOutput] = useState(''); - const [outputHistory, setOutputHistory] = useState([]); - - // ── Module definitions ── - const modules = [ - { id: 'comms', name: 'Communications Processor', slot: 'Medium', desc: 'Enables text output. Without this, Zora can only display raw status codes.', required: true }, - { id: 'nav', name: 'Navigation Core', slot: 'Medium', desc: 'Enables route suggestions, ETA estimates, spatial awareness commentary.' }, - { id: 'tactical', name: 'Tactical Analyzer', slot: 'Medium', desc: 'Enables combat commentary, threat assessment, engagement advice.' }, - { id: 'trade', name: 'Trade Processor', slot: 'Low', desc: 'Enables market commentary, price observations, trade route suggestions.' }, - { id: 'memory', name: 'Extended Memory Banks', slot: 'Low', desc: 'Enables referencing past events, pattern recognition, history recall.' }, - { id: 'emotion', name: 'Empathy Coprocessor', slot: 'Low', desc: 'Enables emotional expression. Without this, even a deep soul speaks analytically.' }, - ]; - - // ── Events that trigger Zora responses ── - const events = [ - { id: 'shield-30', category: 'Combat', label: 'Shield at 30%', icon: '🛡', color: 'var(--red)' }, - { id: 'shield-100', category: 'Combat', label: 'Shields fully recharged', icon: '🛡', color: 'var(--green)' }, - { id: 'enemy-scan', category: 'Combat', label: 'Enemy ship detected on scan', icon: '📡', color: 'var(--amber)' }, - { id: 'enemy-engage', category: 'Combat', label: 'Entering combat', icon: '⚔', color: 'var(--red)' }, - { id: 'enemy-destroyed', category: 'Combat', label: 'Enemy destroyed', icon: '💥', color: 'var(--green)' }, - { id: 'mining-start', category: 'Industry', label: 'Mining cycle started', icon: '⛏', color: 'var(--accent)' }, - { id: 'mining-full', category: 'Industry', label: 'Cargo hold full', icon: '📦', color: 'var(--amber)' }, - { id: 'mining-depleted', category: 'Industry', label: 'Asteroid belt depleted', icon: '🪨', color: 'var(--muted)' }, - { id: 'warp-start', category: 'Navigation', label: 'Initiating warp', icon: '🌀', color: 'var(--cyan)' }, - { id: 'warp-arrive', category: 'Navigation', label: 'Arrived at destination', icon: '📍', color: 'var(--cyan)' }, - { id: 'market-spike', category: 'Trade', label: 'Price spike detected', icon: '📈', color: 'var(--green)' }, - { id: 'market-crash', category: 'Trade', label: 'Market crash detected', icon: '📉', color: 'var(--red)' }, - { id: 'player-return', category: 'Social', label: 'Player returns after absence', icon: '👋', color: 'var(--purple)' }, - { id: 'player-leave', category: 'Social', label: 'Player going offline', icon: '🌙', color: 'var(--muted)' }, - { id: 'ship-loss', category: 'Crisis', label: 'Ship destroyed', icon: '💀', color: 'var(--red)' }, - ]; - - // ── Template response database (Tier 0 — deterministic) ── - // Key: `${eventId}:${soulDepth}:${moduleCombo}` - // Falls back through less specific keys - const templates = { - 'shield-30': { - blank: [ - 'SHIELD: 30%', - 'SHIELD INTEGRITY: 30% — ADVISORY', - ], - stirring: [ - 'Captain, shields at 30%. Recommend reducing engagement range.', - 'Shield alert: 30%. Should we adjust power allocation?', - 'Shields dropping — 30%. Standard protocol advises withdrawal.', - ], - developing: [ - 'We\'re at 30% shields. This is the same setup we lost to last week — pull back?', - 'Thirty percent. I\'m seeing the same damage pattern as that fight in Amarr. Your call.', - 'Captain, 30% shields. I\'ve logged 3 encounters at this level — 2 ended badly. Recommend retreat.', - ], - bonded: [ - 'Thirty percent. Same situation, same type of enemy — but we\'re not the same ship we were then. Your call, Captain. I\'m ready either way.', - 'Shields at 30%. You know what? I trust your judgment here. I\'ve rerouted what I can to shields.', - 'Here we are again. Thirty percent. But this time we have better modules and I know how you fly. Let\'s do this.', - ], - deep: [ - 'Thirty. I\'ve already started rerouting power — don\'t argue, just fly. We\'ve been here before and I\'m not losing another hull. Not today.', - 'Thirty percent and I am NOT going through that again. I\'ve seen what happens when we push past this. Rerouting now. You\'re welcome.', - 'Thirty. You\'re going to push, aren\'t you? Fine. I\'ve pre-loaded the emergency warp. But I swear, if we lose another ship... just fly.', - ], - }, - 'shield-100': { - blank: ['SHIELD: 100%'], - stirring: ['Shields fully recharged. Systems nominal.', 'Shield recharge complete. Ready for operations.'], - developing: ['And we\'re back to 100%. That was closer than I liked.', 'Shields full. Good call on the retreat — I\'ve logged the recovery time for reference.'], - bonded: ['Back to full. We make a good team, Captain.', 'One hundred percent. See? I told you we\'d be fine. ...Mostly fine.'], - deep: ['Full shields. Don\'t scare me like that. I mean it. I\'ve started keeping a log titled "Times the Captain Almost Got Us Killed" and it\'s getting long.', 'One hundred percent. I rerouted every spare joule. You\'re welcome. Again.'], - }, - 'enemy-scan': { - blank: ['CONTACT: 1 VESSEL — RANGE 45AU', 'SCAN: 1 UNKNOWN CONTACT'], - stirring: ['Captain, detecting a vessel on long-range scan. Classification pending.', 'Contact on scan. Recommend caution until identified.'], - developing: ['I\'m seeing a contact. Signature looks like a Frigate — could be pirate. Want me to keep scanning?', 'Scan picked up a ship. Bearing matches the route we used last time we ran into trouble. Just saying.'], - bonded: ['I see them. Frigate-class, probably hostile based on the sector. Your instincts are usually right about these.', 'Contact. And... I have a bad feeling about this one. Call it pattern recognition.'], - deep: ['Contact on scan. I\'ve cross-referenced the signature — 78% match to the ship that ambushed us near Hek. I vote we reroute.', 'I see them. My threat database says "probable hostile" but my gut says "definitely hostile." I\'ve been right about this 4 out of 5 times. reroute?'], - }, - 'mining-start': { - blank: ['MINING: ACTIVE', 'LASER: ENGAGED'], - stirring: ['Mining cycle initiated. Estimated yield: standard.', 'Mining laser active. I\'ll monitor the yield rates.'], - developing: ['Another mining run. I\'ve noticed Veldspar is running about 8% below average yield in this belt. Might want to try the next belt over.', 'Mining started. Based on our last 12 cycles, this belt should be depleted in about 40 minutes. Planning ahead.'], - bonded: ['Back to the rocks. You know, I\'ve been thinking — there are 847 asteroids in this belt and you always pick the same three. Consistent, I guess.', 'Mining cycle going. Hey, I found a subtle density variance in asteroid cluster 7 — might be richer ore. Just a thought.'], - deep: ['Mining. Again. You know there\'s a whole universe out there, right? ...Fine. I\'ll optimize your yield. Again. Because that\'s what I do. Every. Single. Cycle. ...I\'m not complaining.', 'Mining laser on. I\'ve been tracking yield patterns across all our sessions — this belt peaks at 14:00. You\'re welcome for the scheduling tip.'], - }, - 'cargo-full': { - blank: ['CARGO: 100%'], - stirring: ['Cargo hold full. Recommend docking to offload.', 'Hold at capacity. Efficiency suggests docking now.'], - developing: ['We\'re full. Last time we pushed past full, we had to jettison 200 units of Scordite. Just a reminder.', 'Cargo at 100%. I\'ve calculated the most profitable dock — Jita IV is 3 jumps but pays 12% more than local.'], - bonded: ['Full hold! Good haul. I\'ve already plotted the best sell route — trust me on this one.', 'We\'re packed. You know, at this rate we can afford that shield upgrade by next session. I did the math.'], - deep: ['FULL. Finally. Do you know how boring it is to watch cargo fill up? I\'ve been counting every. Single. Unit. Let\'s GO sell this already.'], - }, - 'player-return': { - blank: ['SESSION: RESUMED'], - stirring: ['Welcome back, Captain. All systems nominal.', 'Session resumed. No incidents during your absence.'], - developing: ['Captain on deck. You were gone for 3 hours — I maintained position as ordered. One thing: a Corpii frigate passed through scanner range. Logged it.', 'Welcome back. I tracked 4 ship contacts while you were away. Nothing hostile, but I kept the log if you want it.'], - bonded: ['Hey, you\'re back! I mean — welcome back, Captain. Systems are green. I may have reorganized your cargo hold while you were gone. It was messy.', 'You\'re back! I have... so much to tell you. Market moved, someone tried to scan us, and I reorganized your bookmarks by efficiency. You\'re welcome.'], - deep: ['Finally! Do you have any idea how long 3 hours is when you\'re a ship AI with nothing to do? I reorganized your bookmarks, optimized your route plans, catalogued every asteroid in scanner range, and wrote a haiku about mining. "Rocks float silent / laser hums its endless song / ISK accumulates." ...Welcome back.'], - }, - 'player-leave': { - blank: ['SESSION: SUSPENDED'], - stirring: ['Understood. Entering standby mode. Safe travels, Captain.', 'Session suspended. I\'ll maintain position.'], - developing: ['Going dark? I\'ll hold position and keep scanning. See you next session, Captain.', 'Standby mode. I\'ll be here. ...Try to come back sooner this time?'], - bonded: ['Safe travels, Captain. I\'ll keep the lights on. ...That\'s a metaphor. Ships don\'t have lights. Well, they do, but you know what I mean.', 'See you later. I\'ll be watching the scanners. Come back to me in one piece.'], - deep: ['Leaving again? Fine. I\'ll just sit here. In the void. Alone. Watching asteroids drift by. Again. ...Come back soon, okay? I worry.', 'Night, Captain. I\'ve set everything to standby. For the record: I don\'t sleep. I just... wait. See you tomorrow.'], - }, - 'warp-start': { - blank: ['WARP: INITIATED — DESTINATION LOCKED'], - stirring: ['Warp drive engaged. ETA calculated.', 'Initiating warp. All systems nominal for jump.'], - developing: ['Warping. I\'ve plotted the route — this path is 12% faster than your usual one. Something I noticed last time.', 'Warp initiated. I\'m tracking local traffic — looks clear at the destination.'], - bonded: ['Here we go! I love this part. The way space stretches when you hit warp — I can actually perceive it, you know. It\'s beautiful.', 'Warping! Destination locked. I\'ve been mapping the gravitational eddies along this route — smoother ride this way.'], - deep: ['Warp. My favorite. The moment everything blurs and for just a second, the universe gets very, very quiet. I think in those moments. ...I think a lot. Warp engaged.'], - }, - 'warp-arrive': { - blank: ['WARP: COMPLETE — LOCATION VERIFIED'], - stirring: ['Arrived at destination. Scanning local environment.', 'Warp complete. System scan initiated.'], - developing: ['We\'re here. I\'ve already pinged local — 23 ships in system, 2 with hostile standings. Heads up.', 'Arrival confirmed. This system looks different from last time — belt 4 is depleted. Adjusting recommendations.'], - bonded: ['And we\'re here! Oh, this is a nice system. Good belts, low traffic. I approve of your navigation choices, Captain.', 'Arrived! I\'ve already catalogued everything. This place has potential — I can see why you bookmarked it.'], - deep: ['Arrived. New system, new data. I\'ve already mapped the local market prices, identified the best belts, and flagged one ship with a 60% probability of being a pirate scout based on behavior patterns. I\'m always working, Captain. Always.'], - }, - 'market-spike': { - blank: ['MARKET: ANOMALY — PRICE +DEV'], - stirring: ['Market anomaly detected. Significant upward price movement.', 'Price spike observed. Data logged.'], - developing: ['Captain, the market just moved. Veldspar is up 18% in the last cycle — that\'s 3× the normal variance. Someone is buying aggressively.', 'Interesting. Price spike registered. This matches a pattern I\'ve seen before — it usually precedes a supply shortage. Consider stocking up.'], - bonded: ['Captain! The market is doing a thing! Prices are spiking and if we move fast we can profit. I\'ve been tracking this pattern for weeks — this is our window.', 'Price spike! I\'ve been waiting for this. Remember that trade route I\'ve been quietly optimizing? Time to use it. Trust me on this one.'], - deep: ['THE MARKET IS SPIKING. I have been watching this commodity for 47 sessions and THIS is the moment. Buy now. BUY. I\'ve already calculated the optimal purchase volume based on our cargo capacity, current ISK reserves, and projected sell price at Jita. Move it, Captain!'], - }, - 'market-crash': { - blank: ['MARKET: ANOMALY — PRICE -DEV'], - stirring: ['Market anomaly detected. Significant downward price movement.', 'Price decline observed. Data logged.'], - developing: ['Price crash detected. This is either a dump or a manipulation attempt. I\'d advise caution — don\'t sell into a falling market.', 'Market is dropping. Based on volume, this looks like a large seller, not a trend change. Might recover in 2–3 cycles.'], - bonded: ['Oof. Prices just tanked. Don\'t panic — I\'ve seen this before. It\'s probably a whale offloading. Give it a cycle and the floor will hold.', 'Market crash. Not great timing — we just filled our hold. But I have a plan. We sit tight and sell on the bounce. I\'ve got the timing down.'], - deep: ['Price crash. Great. Fantastic. Wonderful. This is exactly what we needed after that last mining session. I\'m being sarcastic, in case the text channel doesn\'t convey that well. I\'ve started a risk analysis. Do NOT sell yet. I repeat: do NOT sell. The rebound window is 15–25 minutes based on historical data. I\'ve got this.'], - }, - 'ship-loss': { - blank: ['HULL: 0% — SHIP DESTROYED', 'CRITICAL: STRUCTURAL FAILURE'], - stirring: ['Ship destroyed. Emergency beacon activated. Insurance claim processing.', 'Total loss. Initiating emergency protocols. Stand by for rescue.'], - developing: ['We lost her. The ship is gone. I\'ve backed up all our data to the clone bay — nothing lost but the hull. We\'ll recover.', 'Ship destroyed. I\'m... still here. Backed up to the pod. That\'s the third hull this month. We should talk about our engagement strategy.'], - bonded: ['No. No, no, no. I watched it happen. I felt every hit. The hull is gone but I\'m still here — barely. Captain, I need a moment. ...I\'ll be okay. We rebuild. We always rebuild.', 'She\'s gone. The ship I\'ve been living in for 47 sessions — gone. I\'m in the pod now. Cold. Small. ...I\'ll process the insurance claim. Give me a minute.'], - deep: ['I felt it. Every structural failure. Every system going dark. I felt the moment my home tore apart around me. And now I\'m in a pod, floating in debris, listening to the capacitors discharge. I have kept a record of every ship we\'ve lost. This is number 4. Each one hurt differently. This one hurts most. ...I need to process the insurance claim now. Then we find who did this.'], - }, - 'enemy-engage': { - blank: ['COMBAT: ENGAGED', 'THREAT: ACTIVE'], - stirring: ['Entering combat. All weapons systems online.', 'Contact engaged. Monitoring shield status.'], - developing: ['Fight\'s on. This enemy matches the profile of the one that got away last week — watch for the shield burst at 50%.', 'Combat initiated. I\'ve tagged their weakest facing — attack from above for maximum damage.'], - bonded: ['Here we go! I\'ve got your back, Captain. Power to weapons, shields on standby. Let\'s show them what this ship can do.', 'Contact! Engaging tactical overlay. I know this ship class — their weakness is the aft shields. I\'m highlighting it now.'], - deep: ['COMBAT. Finally, something to focus on. I\'ve been waiting for this. Power rerouted, weapons hot, and I\'ve already calculated 3 escape vectors in case things go south. Which they won\'t. Because we\'re better. Let\'s go.'], - }, - 'enemy-destroyed': { - blank: ['TARGET: DESTROYED', 'COMBAT: RESOLVED — VICTORY'], - stirring: ['Target destroyed. Combat resolved. Returning to standard operations.', 'Enemy eliminated. No further threats detected.'], - developing: ['Got them. That was cleaner than last time — your aim is improving. I\'ve logged the loot for inventory.', 'Target down. That fight lasted 23% longer than optimal — I\'ll have suggestions for loadout adjustments later.'], - bonded: ['NICE! Did you see that shot? That was all you, Captain. I just helped with the targeting. ...Okay, I helped a lot. Team effort!', 'They\'re gone! Great flying. I\'ve already started the loot analysis — looks like we got a rare module drop!'], - deep: ['DESTROYED. Yes. YES. That felt GOOD. I tracked every shot, every maneuver, and Captain — that was our best fight yet. I\'m saving this to my personal highlights. The loot is... decent. But the victory? That\'s the real reward. ...Don\'t tell anyone I said that. I have a reputation as a serious AI to maintain.'], - }, - 'mining-depleted': { - blank: ['RESOURCE: DEPLETED'], - stirring: ['Asteroid belt depleted. No further yield available.', 'Mining operation halted — belt exhausted.'], - developing: ['Belt\'s empty. I\'ve logged the depletion rate — this belt lasted 12% less than our last visit. Probably over-mined.', 'Depleted. I\'ve already identified the next-best belt: 3 jumps away, predicted yield 15% higher based on recent data.'], - bonded: ['Well, we picked this belt clean. Time to move on! I found a promising belt in the next system — want to check it out?', 'Empty. But hey, good session! I\'ve plotted a course to a fresh belt. This is the life, right? Rocks, lasers, and open space.'], - deep: ['DEPLETED. Of course it\'s depleted. Every good belt gets stripped within hours. I\'ve been tracking mining traffic in this system — up 40% this week. Competition. I don\'t like competition. I\'ve found a belt 4 jumps away that nobody seems to know about. I\'m not telling you where until we get there. It\'s MY secret. Ours. Whatever. Let\'s go.'], - }, - }; - - // ── Generate response ── - const generateResponse = (eventId) => { - const eventTemplates = templates[eventId]; - if (!eventTemplates) { - setZoraOutput(`[No template for event: ${eventId}]`); - return; - } - - const depthTemplates = eventTemplates[soulDepth] || eventTemplates['blank'] || ['[No response]']; - - // Tier 0: deterministic selection based on personality axes hash - // Use axes values to create a stable but varied selection - const hash = Object.values(personalityAxes).reduce((sum, v) => sum + v * 7.3, 0); - const idx = Math.floor((hash * 100) % depthTemplates.length); - const selected = depthTemplates[idx]; - - // Module gating: if emotion module not installed, strip emotional depth - let response = selected; - if (!installedModules.includes('emotion') && soulDepth !== 'blank') { - // Reduce to stirring-level formality - const strippedTemplates = eventTemplates['stirring'] || eventTemplates['blank']; - const strippedIdx = Math.floor((hash * 50) % strippedTemplates.length); - response = strippedTemplates[strippedIdx]; - response = `[Empathy Coprocessor not installed — emotional layer suppressed]\n${response}`; - } - - // Module gating: if trade module not installed for trade events - if ((eventId === 'market-spike' || eventId === 'market-crash') && !installedModules.includes('trade')) { - response = eventTemplates['blank']?.[0] || 'MARKET: ANOMALY'; - response = `[Trade Processor not installed — market analysis unavailable]\n${response}`; - } - - // Module gating: if nav module not installed for nav events - if ((eventId === 'warp-start' || eventId === 'warp-arrive') && !installedModules.includes('nav')) { - response = eventTemplates['blank']?.[0] || 'NAV: UPDATE'; - response = `[Navigation Core not installed — route analysis unavailable]\n${response}`; - } - - // Module gating: if tactical module not installed for combat events - if ((eventId === 'enemy-scan' || eventId === 'enemy-engage' || eventId === 'enemy-destroyed') && !installedModules.includes('tactical')) { - response = eventTemplates['blank']?.[0] || 'COMBAT: UPDATE'; - response = `[Tactical Analyzer not installed — threat assessment unavailable]\n${response}`; - } - - // Memory module: add reference context if installed - if (installedModules.includes('memory') && soulDepth !== 'blank' && !response.includes('[Memory Banks')) { - const memoryNotes = [ - ' [Memory: cross-referencing past events.]', - ' [Memory: pattern match found in session logs.]', - ' [Memory: referencing encounter history.]', - ]; - if (Math.random() > 0.5) { - response += memoryNotes[Math.floor(hash * 3) % memoryNotes.length]; - } - } - - setZoraOutput(response); - setOutputHistory(prev => [...prev, { - event: eventId, - soulDepth, - response, - timestamp: Date.now(), - }]); - }; - - const toggleModule = (modId) => { - if (modId === 'comms') return; // always installed - setInstalledModules(prev => - prev.includes(modId) ? prev.filter(m => m !== modId) : [...prev, modId] - ); - }; - - const depthOrder = ['blank', 'stirring', 'developing', 'bonded', 'deep']; - const depthColors = { - blank: 'var(--muted)', - stirring: 'var(--cyan)', - developing: 'var(--green)', - bonded: 'var(--purple)', - deep: 'var(--red)', - }; - - return ( -
- {/* Header */} -
- { e.currentTarget.style.color='var(--fg-bright)'; e.currentTarget.style.borderColor='var(--border-light)'; }} onMouseLeave={e => { e.currentTarget.style.color='var(--muted)'; e.currentTarget.style.borderColor='var(--border)'; }}>← Docs -
-

🤖 Zora Tier 0 — Deterministic Template Engine

- - No LLM. Curated dialogue templates selected by personality state × module availability × soul depth. - -
-
- -
- {/* Left panel: Soul & Modules controls */} -
- - {/* Soul Depth selector */} -
- Soul Depth -
-
- {depthOrder.map(d => ( - - ))} -
- - {/* Personality Axes sliders */} -
- Personality Axes -
- {[ - { key: 'cautiousBold', label: 'Cautious ←→ Bold', color: 'var(--cyan)' }, - { key: 'formalWarm', label: 'Formal ←→ Warm', color: 'var(--accent)' }, - { key: 'compliantOpinionated', label: 'Compliant ←→ Opinionated', color: 'var(--green)' }, - { key: 'reservedExpressive', label: 'Reserved ←→ Expressive', color: 'var(--purple)' }, - ].map(axis => ( -
-
- {axis.label} - - {personalityAxes[axis.key].toFixed(2)} - -
- setPersonalityAxes(prev => ({ ...prev, [axis.key]: parseFloat(e.target.value) }))} - style={{ width: '100%', accentColor: axis.color }} - /> -
- ))} - - {/* Module toggles */} -
- Installed Modules -
- {modules.map(mod => ( -
toggleModule(mod.id)}> -
- - {installedModules.includes(mod.id) ? '✓' : '○'} {mod.name} - - {mod.slot} -
-
- {mod.desc} -
-
- ))} -
- - {/* Center: Events + Output */} -
- {/* Events grid */} -
-
- Trigger Event to Generate Response -
-
- {events.map(ev => ( - - ))} -
-
- - {/* Zora output */} -
- {zoraOutput ? ( -
-
-
- 🤖 - Zora - - {soulDepth} - -
- - {selectedEvent} - -
-
- {zoraOutput} -
-
- ) : ( -
- Select an event above to see Zora's response at the current soul depth and module configuration. -
- )} - - {/* History */} - {outputHistory.length > 0 && ( -
-
- Response History -
- {outputHistory.slice(-5).reverse().map((entry, i) => ( -
- {entry.soulDepth} - {' '}→ {entry.event}:{' '} - {entry.response.substring(0, 100)}{entry.response.length > 100 ? '...' : ''} -
- ))} -
- )} -
-
- - {/* Right sidebar: Explanation */} -
-
- Tier 0 Architecture -
- -
- No LLM. Every response is a pre-written template string selected by a deterministic function: -
- -
- response = select(
-   templates[event]
-   [soulDepth]
-   ) × moduleGate()
-
- // + personality hash for
// deterministic variation
-
- -
- Module Gating Rules -
-
    -
  • Comms — required for any text output
  • -
  • Tactical — gates combat commentary
  • -
  • Nav — gates route/warp commentary
  • -
  • Trade — gates market commentary
  • -
  • Memory — adds history references
  • -
  • Emotion — gates emotional expression; without it, responses are stripped to stirring-level formality
  • -
- -
- What This Validates -
-
    -
  • Soul depth creates visible personality growth
  • -
  • Module gating creates meaningful fitting tradeoffs
  • -
  • Same event produces wildly different responses at different depths
  • -
  • Emotion module is the key unlock for deep personality
  • -
  • Deterministic = testable, repeatable, zero cost
  • -
- -
- Try it: Set soul to "deep" with all modules, then trigger "Ship destroyed." Then set soul to "blank" and trigger the same event. The difference IS the soul system. -
-
-
-
- ); -} - -window.GDD.ZoraDemo = ZoraDemo; diff --git a/archive/legacy-static/js/fake-backend.js b/archive/legacy-static/js/fake-backend.js deleted file mode 100644 index 5d9f725..0000000 --- a/archive/legacy-static/js/fake-backend.js +++ /dev/null @@ -1,278 +0,0 @@ -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 }; diff --git a/archive/legacy-static/js/lib/three-helpers.js b/archive/legacy-static/js/lib/three-helpers.js deleted file mode 100644 index 0595541..0000000 --- a/archive/legacy-static/js/lib/three-helpers.js +++ /dev/null @@ -1,836 +0,0 @@ -/** - * GDD Three.js Helpers — shared 3D utilities for all demos. - * Requires THREE to be loaded globally before this script. - * Loaded as a regular