initial commit

This commit is contained in:
2026-03-11 16:56:28 -04:00
commit caa9d40cba
11 changed files with 4415 additions and 0 deletions

17
.gitignore vendored Normal file
View File

@@ -0,0 +1,17 @@
node_modules/
dist/
*.tsbuildinfo
.DS_Store
Thumbs.db
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
.env
.env.*
!.env.example

173
SESSION.md Normal file
View File

@@ -0,0 +1,173 @@
# Session Summary
## Project State
This repository now contains a playable Three.js/Vite prototype for a space RTS / economy sim testbed inspired by EVE Online and X4.
The current prototype includes:
- Two solar systems: `Helios Reach` and `Perseus Gate`
- A large space environment with stars, planets, orbit lines, nebulae, asteroid/resource fields, and starfield
- RTS-style ship selection, command issuance, camera movement, zoom levels, and follow-camera support
- Three view levels based on zoom: `local`, `solar`, and `universe`
- A bottom command bar with selection info, order buttons, and a minimap
- A strategic HUD overlay that switches to NATO / military-style symbols at higher zoom levels
## Major Gameplay Systems Added
### World / Navigation
- Ships can travel between the two systems using staged FTL travel
- Travel flow includes:
- leaving gravity well
- FTL spool
- warp
- arrival
- FTL speed was increased and a basic warp streak / tunnel effect was added
- Local ship movement is no longer purely straight-line:
- ships bias toward curved orbital-style transfers around the system center
- idle ships hold a passive orbit instead of freezing in place
### Orbital Model
- Stations are no longer static arbitrary points
- Stations in `Helios` are placed on Lagrange-style offsets relative to planets
- Stations update position over time with the planetary orbital motion
- Ships and stations are beginning to behave like orbitals rather than free-floating markers
### Units / AI / Orders
- Ship roles currently in the prototype:
- military
- transport
- mining
- Unit state machine now includes states for:
- idle / moving
- FTL travel
- mining and delivery
- docking approach / docking / docked / undocking
- patrol / escort
- Orders currently supported:
- move
- transfer
- mine
- patrol
- escort
### Docking / Logistics
- Docking was added as a required step for transfer to stations
- Stations have limited docking capacity and explicit docking ports
- Mining ships now:
- mine ore in `Perseus`
- return to `Helios`
- dock at a refinery
- transfer ore
- undock and repeat
### Economy / Inventory Foundations
- Added item storage classes:
- `bulk-solid`
- `bulk-liquid`
- `bulk-gas`
- `container`
- `manufactured`
- Added module categories and starter module definitions for ships/stations
- Ships and stations now expose compatible cargo/storage/module metadata
- Refineries track:
- ore stored
- active refining batch
- refining timer
- refined output stock
### Energy / Fuel
- Ships now track:
- fuel
- energy
- Stations now track:
- fuel
- energy
- Ships consume energy/fuel depending on activity
- Docked ships recharge energy
- Stations recharge energy passively
## Testbed Layout
- `Helios Reach` is now the industrial / infrastructure system
- stations are concentrated there
- refinery loop terminates there
- `Perseus Gate` is now the extraction / resource system
- resource asteroid nodes are concentrated there
- miners operate there before hauling back
## UI / UX State
- Ship and station selection is supported
- Ship multi-selection is supported via click modifiers and marquee drag selection
- `Solar` and `universe` views now overlay high-level tactical symbology instead of relying only on 3D meshes
- Ships use role-specific long-range symbols:
- military: hostile/combat-style diamond iconography
- transport: boxed logistics symbol
- mining: angular resource / industrial symbol
- Stations and constructibles use square strategic markers with category-specific internal glyphs
- `Universe` view groups ships into fleet counts per system and role for cleaner strategic readability
- Focusing works for:
- single ships: follow camera
- stations: focus camera on the station
- Selection panels show:
- ship state, order, cargo, hold type, fuel, energy, modules
- station role, docking occupancy, stored resources, refinery timing, fuel, energy, modules
## Controls
- `Left Click`: select ships or stations
- `Shift + Left Click`: add ships to ship selection
- `Ctrl/Cmd + Left Click`: toggle ships in selection
- `Left Drag`: marquee-select multiple ships
- `Right Click`: issue move/transfer orders
- `Mouse Wheel` or `-` / `=`: zoom
- `W A S D`: pan camera
- `Q / E`: rotate camera
- `F`: focus selection, and follow a single selected ship
- `Tab`: jump camera between systems
- `B`: toggle build mode
- `1-5`: choose constructible
- `M`: assign mining
- `P`: assign patrol
- `E`: assign escort
## Technical Notes
- The prototype is built with:
- Vite
- TypeScript
- Three.js
- High-level symbology is rendered through a dedicated 2D HUD overlay canvas layered above the 3D scene
- Production build is currently passing with `npm run build`
## Known Limitations / Caveats
- Orbital behavior is still an approximation for gameplay, not a full orbital mechanics simulation
- Stations are on Lagrange-style offsets, but not using a physically rigorous orbital solver
- Ship transfer paths are curved and orbit-biased, but still use authored steering rather than patched conics or n-body integration
- Fuel / energy exist but station refueling, resupply, and depletion failure states are still minimal
- Module definitions exist, but there is no actual ship/station designer yet
- Inventory classes exist, but only a subset of economic flows are implemented
- Docking works for logistics, but there is not yet a richer docking queue / reservation UI
- NATO-style symbology is gameplay-oriented inspiration, not a strict APP-6 / MIL-STD implementation
## Suggested Next Steps
- Introduce explicit orbital anchors for:
- stars
- planets
- stations
- asteroid belts / resource fields
- Replace the current movement approximation with a more formal orbital transfer model
- Add refueling and power management gameplay
- Add ship/station fitting data structures that can later drive a designer UI
- Expand the economy beyond ore/refining into manufactured goods and trade lanes
- Improve FTL visuals with a fullscreen post-process distortion or tunnel effect
- Expand the strategic overlay with threat rings, route arrows, and fleet stance/status markers

12
index.html Normal file
View File

@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Space Command</title>
<script type="module" src="/src/main.ts"></script>
</head>
<body>
<div id="app"></div>
</body>
</html>

1121
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

19
package.json Normal file
View File

@@ -0,0 +1,19 @@
{
"name": "space-game",
"private": true,
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
"three": "^0.179.1"
},
"devDependencies": {
"@types/three": "^0.183.1",
"typescript": "^5.9.2",
"vite": "^7.1.3"
}
}

2513
src/game/GameApp.ts Normal file

File diff suppressed because it is too large Load Diff

273
src/game/definitions.ts Normal file
View File

@@ -0,0 +1,273 @@
export type ShipRole = "military" | "transport" | "mining";
export type ConstructibleCategory =
| "station"
| "refining"
| "farm"
| "shipyard"
| "defense";
export type UnitState =
| "idle"
| "moving"
| "leaving-gravity-well"
| "spooling-ftl"
| "warping"
| "arriving"
| "mining-approach"
| "mining"
| "delivering"
| "docking-approach"
| "docking"
| "docked"
| "undocking"
| "patrolling"
| "escorting";
export type UnitOrderKind = "idle" | "move" | "transfer" | "mine" | "patrol" | "escort";
export type ItemStorageKind = "bulk-solid" | "bulk-liquid" | "bulk-gas" | "container" | "manufactured";
export type ModuleCategory =
| "bridge"
| "engine"
| "ftl"
| "mining"
| "cargo-bulk"
| "cargo-container"
| "dock"
| "refinery"
| "defense"
| "habitat"
| "production";
export interface ModuleDefinition {
id: string;
label: string;
category: ModuleCategory;
summary: string;
}
export interface ItemDefinition {
id: string;
label: string;
storage: ItemStorageKind;
}
export interface ShipDefinition {
id: string;
label: string;
role: ShipRole;
speed: number;
ftlSpeed: number;
spoolTime: number;
cargoCapacity: number;
cargoKind?: ItemStorageKind;
cargoItemId?: string;
color: number;
hullColor: number;
size: number;
maxHealth: number;
modules: string[];
}
export interface ConstructibleDefinition {
id: string;
label: string;
category: ConstructibleCategory;
color: number;
radius: number;
dockingCapacity: number;
storage: Partial<Record<ItemStorageKind, number>>;
modules: string[];
}
export interface PlanetDefinition {
label: string;
orbitRadius: number;
orbitSpeed: number;
size: number;
color: number;
tilt: number;
hasRing?: boolean;
}
export interface SolarSystemDefinition {
id: string;
label: string;
position: [number, number, number];
starColor: number;
starGlow: number;
starSize: number;
gravityWellRadius: number;
planets: PlanetDefinition[];
}
export const itemDefinitions: ItemDefinition[] = [
{ id: "ore", label: "Raw Ore", storage: "bulk-solid" },
{ id: "refined-metals", label: "Refined Metals", storage: "manufactured" },
{ id: "gas", label: "Volatile Gas", storage: "bulk-gas" },
{ id: "water", label: "Water", storage: "bulk-liquid" },
{ id: "drone-parts", label: "Drone Parts", storage: "container" },
];
export const moduleDefinitions: ModuleDefinition[] = [
{ id: "command-bridge", label: "Command Bridge", category: "bridge", summary: "Core ship control and crew systems." },
{ id: "ion-drive", label: "Ion Drive", category: "engine", summary: "Sub-light propulsion package." },
{ id: "ftl-core", label: "FTL Core", category: "ftl", summary: "Spool and warp inter-system engine." },
{ id: "strip-miner", label: "Strip Miner", category: "mining", summary: "Excavation laser and ore intake." },
{ id: "bulk-bay", label: "Bulk Cargo Bay", category: "cargo-bulk", summary: "Reinforced storage for raw solids." },
{ id: "container-bay", label: "Container Hold", category: "cargo-container", summary: "Standardized freight racks." },
{ id: "docking-clamps", label: "Docking Clamps", category: "dock", summary: "Docking collar and transfer arms." },
{ id: "refinery-stack", label: "Refinery Stack", category: "refinery", summary: "Ore cracking and metal separation." },
{ id: "turret-grid", label: "Turret Grid", category: "defense", summary: "Close defense batteries." },
{ id: "habitat-ring", label: "Habitat Ring", category: "habitat", summary: "Crew quarters and service modules." },
{ id: "fabricator-array", label: "Fabricator Array", category: "production", summary: "Assembly lines for manufactured goods." },
];
export const shipDefinitions: ShipDefinition[] = [
{
id: "frigate",
label: "Vanguard Frigate",
role: "military",
speed: 50,
ftlSpeed: 3200,
spoolTime: 2.2,
cargoCapacity: 0,
color: 0x7ed4ff,
hullColor: 0x1f4f78,
size: 4,
maxHealth: 100,
modules: ["command-bridge", "ion-drive", "ftl-core", "turret-grid"],
},
{
id: "destroyer",
label: "Bulwark Destroyer",
role: "military",
speed: 34,
ftlSpeed: 2900,
spoolTime: 2.8,
cargoCapacity: 0,
color: 0xff8f70,
hullColor: 0x6a2e26,
size: 7,
maxHealth: 240,
modules: ["command-bridge", "ion-drive", "ftl-core", "turret-grid", "turret-grid"],
},
{
id: "hauler",
label: "Atlas Hauler",
role: "transport",
speed: 22,
ftlSpeed: 2600,
spoolTime: 3.3,
cargoCapacity: 180,
cargoKind: "container",
cargoItemId: "drone-parts",
color: 0xb0ff8d,
hullColor: 0x365f2a,
size: 8,
maxHealth: 180,
modules: ["command-bridge", "ion-drive", "ftl-core", "container-bay", "docking-clamps"],
},
{
id: "miner",
label: "Prospector Miner",
role: "mining",
speed: 26,
ftlSpeed: 2400,
spoolTime: 3.1,
cargoCapacity: 120,
cargoKind: "bulk-solid",
cargoItemId: "ore",
color: 0xffdd75,
hullColor: 0x68552b,
size: 6,
maxHealth: 150,
modules: ["command-bridge", "ion-drive", "ftl-core", "strip-miner", "bulk-bay", "docking-clamps"],
},
];
export const constructibleDefinitions: ConstructibleDefinition[] = [
{
id: "trade-hub",
label: "Trade Hub",
category: "station",
color: 0x8bd3ff,
radius: 20,
dockingCapacity: 4,
storage: { container: 1200, manufactured: 800 },
modules: ["habitat-ring", "docking-clamps", "container-bay"],
},
{
id: "refinery",
label: "Refining Station",
category: "refining",
color: 0xffb86c,
radius: 24,
dockingCapacity: 3,
storage: { "bulk-solid": 2000, manufactured: 1000 },
modules: ["docking-clamps", "refinery-stack", "bulk-bay", "fabricator-array"],
},
{
id: "farm-ring",
label: "Farm Station",
category: "farm",
color: 0x92ef8a,
radius: 22,
dockingCapacity: 2,
storage: { "bulk-liquid": 600, container: 400 },
modules: ["habitat-ring", "production", "container-bay"],
},
{
id: "shipyard",
label: "Orbital Shipyard",
category: "shipyard",
color: 0xd0a2ff,
radius: 28,
dockingCapacity: 5,
storage: { manufactured: 1800, container: 1200 },
modules: ["docking-clamps", "fabricator-array", "habitat-ring"],
},
{
id: "defense-grid",
label: "Defense Platform",
category: "defense",
color: 0xff7a95,
radius: 18,
dockingCapacity: 1,
storage: { manufactured: 300 },
modules: ["turret-grid", "command-bridge"],
},
];
export const solarSystemDefinitions: SolarSystemDefinition[] = [
{
id: "helios",
label: "Helios Reach",
position: [0, 0, 0],
starColor: 0xffd27a,
starGlow: 0xffb14a,
starSize: 56,
gravityWellRadius: 210,
planets: [
{ label: "Icarus", orbitRadius: 180, orbitSpeed: 0.18, size: 20, color: 0xd4a373, tilt: 0.2 },
{ label: "Viridia", orbitRadius: 300, orbitSpeed: 0.11, size: 30, color: 0x58a36c, tilt: -0.4 },
{ label: "Aster", orbitRadius: 460, orbitSpeed: 0.08, size: 38, color: 0x6ea7d4, tilt: 0.3, hasRing: true },
{ label: "Noctis", orbitRadius: 670, orbitSpeed: 0.05, size: 50, color: 0x6958a8, tilt: -0.15 },
],
},
{
id: "perseus",
label: "Perseus Gate",
position: [4200, 0, 600],
starColor: 0x9fd4ff,
starGlow: 0x66b6ff,
starSize: 48,
gravityWellRadius: 190,
planets: [
{ label: "Kepler", orbitRadius: 150, orbitSpeed: 0.22, size: 16, color: 0xd9b188, tilt: 0.12 },
{ label: "Tethys", orbitRadius: 280, orbitSpeed: 0.12, size: 28, color: 0x73b0a1, tilt: -0.22 },
{ label: "Orpheon", orbitRadius: 430, orbitSpeed: 0.07, size: 42, color: 0x4a67a8, tilt: 0.25, hasRing: true },
{ label: "Cinder", orbitRadius: 610, orbitSpeed: 0.045, size: 36, color: 0xb15e49, tilt: -0.08 },
],
},
];

11
src/main.ts Normal file
View File

@@ -0,0 +1,11 @@
import "./style.css";
import { GameApp } from "./game/GameApp";
const appRoot = document.querySelector<HTMLDivElement>("#app");
if (!appRoot) {
throw new Error("Missing #app root element");
}
const game = new GameApp(appRoot);
game.start();

245
src/style.css Normal file
View File

@@ -0,0 +1,245 @@
:root {
color-scheme: dark;
font-family: "Space Grotesk", "Segoe UI", sans-serif;
--bg: #050914;
--panel: rgba(5, 12, 26, 0.78);
--panel-border: rgba(126, 212, 255, 0.18);
--text: #ebf7ff;
--muted: #9fb6c8;
--accent: #7ed4ff;
--warning: #ffbf69;
}
* {
box-sizing: border-box;
}
html,
body,
#app {
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
background:
radial-gradient(circle at top, rgba(75, 123, 236, 0.18), transparent 36%),
radial-gradient(circle at 20% 40%, rgba(255, 134, 91, 0.14), transparent 26%),
linear-gradient(180deg, #03070f 0%, #060c18 100%);
color: var(--text);
}
canvas {
display: block;
}
.hud {
position: fixed;
inset: 0;
pointer-events: none;
}
.strategic-overlay {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
opacity: 0.96;
}
.marquee {
position: fixed;
display: none;
border: 1px solid rgba(126, 212, 255, 0.85);
background: rgba(126, 212, 255, 0.14);
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08);
z-index: 3;
}
.panel {
position: absolute;
backdrop-filter: blur(14px);
background: var(--panel);
border: 1px solid var(--panel-border);
border-radius: 18px;
box-shadow: 0 18px 60px rgba(0, 0, 0, 0.35);
z-index: 2;
}
.panel h1,
.panel h2,
.panel p {
margin: 0;
}
.summary {
top: 24px;
left: 24px;
width: min(380px, calc(100vw - 48px));
padding: 18px 20px;
}
.summary h1 {
font-size: 1rem;
letter-spacing: 0.22em;
text-transform: uppercase;
}
.summary p {
margin-top: 10px;
color: var(--muted);
line-height: 1.5;
}
.details {
right: 24px;
top: 24px;
width: min(320px, calc(100vw - 48px));
padding: 18px 20px;
}
.details h2 {
font-size: 0.82rem;
text-transform: uppercase;
letter-spacing: 0.16em;
color: var(--accent);
}
.details .content {
margin-top: 12px;
color: var(--muted);
line-height: 1.55;
white-space: pre-line;
}
.commandbar {
left: 24px;
right: 24px;
bottom: 24px;
min-height: 180px;
display: grid;
grid-template-columns: minmax(240px, 300px) 1fr minmax(220px, 260px);
gap: 16px;
padding: 16px;
align-items: stretch;
pointer-events: auto;
}
.selection-panel,
.orders-panel,
.minimap-panel {
border: 1px solid rgba(126, 212, 255, 0.14);
border-radius: 14px;
background:
linear-gradient(180deg, rgba(7, 15, 29, 0.82), rgba(4, 10, 20, 0.72)),
repeating-linear-gradient(
90deg,
rgba(126, 212, 255, 0.025) 0,
rgba(126, 212, 255, 0.025) 1px,
transparent 1px,
transparent 16px
);
padding: 14px 16px;
}
.selection-title,
.orders-panel .mode {
margin: 0;
font-size: 0.86rem;
text-transform: uppercase;
letter-spacing: 0.14em;
}
.selection-panel .compact {
margin-top: 10px;
color: var(--muted);
line-height: 1.45;
white-space: pre-line;
}
.orders-panel {
display: flex;
flex-direction: column;
gap: 14px;
}
.orders-panel .mode {
color: var(--warning);
text-shadow: 0 0 18px rgba(255, 191, 105, 0.24);
}
.orders {
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
gap: 10px;
}
.orders button {
border: 1px solid rgba(126, 212, 255, 0.16);
border-radius: 12px;
background: linear-gradient(180deg, rgba(13, 30, 56, 0.95), rgba(8, 17, 33, 0.95));
color: var(--text);
font: inherit;
padding: 12px 10px;
text-transform: uppercase;
letter-spacing: 0.08em;
cursor: pointer;
transition: border-color 120ms ease, transform 120ms ease, background 120ms ease;
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.02);
}
.orders button:hover {
border-color: rgba(126, 212, 255, 0.4);
transform: translateY(-1px);
}
.orders[data-mode="none"] button:not([data-action="focus"]) {
opacity: 0.45;
}
.orders-panel .hint {
color: var(--muted);
line-height: 1.45;
}
.minimap-panel {
display: flex;
align-items: center;
justify-content: center;
}
.minimap {
width: 100%;
height: auto;
border-radius: 10px;
border: 1px solid rgba(126, 212, 255, 0.16);
background: rgba(2, 6, 13, 0.92);
}
@media (max-width: 900px) {
.summary,
.details,
.commandbar {
left: 16px;
right: 16px;
}
.summary,
.details {
width: auto;
}
.details {
top: auto;
bottom: 92px;
}
.commandbar {
grid-template-columns: 1fr;
bottom: 16px;
}
.orders {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}

14
tsconfig.json Normal file
View File

@@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "ES2022",
"useDefineForClassFields": true,
"module": "ESNext",
"moduleResolution": "Bundler",
"lib": ["ES2022", "DOM"],
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src"]
}

17
vite.config.ts Normal file
View File

@@ -0,0 +1,17 @@
import { defineConfig } from "vite";
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
three: ["three"],
},
},
},
},
server: {
host: "0.0.0.0",
port: 4173,
},
});