A high-performance, web-based 3D design and planning utility for engineering modular spacecraft. Built on a volumetric grid system, it enables players to plan layout physics, structural integrity, power grids, and thermal profiles prior to in-game ship deployment.
- Project Overview
- Core Simulation & Game Mechanics
- Technology Stack
- Extending Block Geometries
- Performance Architecture & Potato Mode
- Testing Strategy
- Getting Started & Development
The Spacecraft Shipbuilder is a React and Three.js-based Single Page Application (SPA) designed to let players architect 3D spacecraft. The planner focuses on spatial geometry, Axis-Aligned Bounding Box (AABB) collision checks, and real-time physical simulation models.
Every design is subjected to realistic in-game constraint validations—ensuring the resulting vessel remains structurally sound, powered, and thermally stable.
- Integer Grid: All coordinate bounds, offsets, and dimensions utilize integer units.
-
Anchor point: Block positions are anchored at their absolute minimum bounds:
$[X_{min}, Y_{min}, Z_{min}]$ . -
Floor Limit: Placements cannot occur below the ground plane (
$Y = 0$ ).
- Contiguous Structure: Ships must represent a single, contiguous physical body.
- Face Connection: Adjacent blocks must share a full physical face. Vertex-to-vertex or edge-to-edge connections are structurally invalid.
- Cockpit Core dependency: A Breadth-First Search (BFS) computes connectivity back to the cockpit/core block. Disconnected segments are flagged as invalid.
- Binary Flips: Standard structural blocks do not use arbitrary 3D rotation vectors. Instead, they utilize a predefined base orientation adjusted via three independent binary flags:
- Flip X: Mirrors the block along the East-West axis.
- Flip Y: Mirrors the block along the Up-Down axis (upside down).
- Flip Z: Mirrors the block along the North-South axis.
- Thruster Rotations: Thrusters are a key exception and can be rotated in 90-degree increments on their local X-axis to orient forward, lateral, and vertical thrust vectors.
- SP Providers: Cockpits, structural blocks, and wings provide System Support (SP).
- SP Consumers: Components like thrusters, mining lasers, storage, and weaponry consume SP.
-
Efficiency Formula: If requirements exceed supply, the ship operates at reduced efficiency:
$$\text{Efficiency} = \min\left(1.0, \frac{\text{Total SP Provided}}{\text{Total SP Consumed}}\right)$$
- Energy Balance: Solars, batteries, and fuel generators feed a global power grid.
- Depletion Penalty: If consumption exceeds generation and battery reserves are depleted, active components go offline and steering speed is throttled to ~15% of normal.
- Global Heat Pool: Simulated as a single global pool representing the ship's current thermal level.
- Heat Capacity: The cumulative thermal energy the ship can absorb before overheating.
- Interface & Conductivity: High-conductivity blocks increase the global heat interface, speeding up temperature equalization with environmental conditions.
- Radiators: Radiators and cockpits actively dissipate heat. If generation exceeds dissipation, thermal capacity fills, eventually causing system damage.
- Integrity: Computed via the Total Frame to Total Weight ratio. Heavy blocks (e.g., machinery) require structural frames (Steel, Titanium) to prevent buckling.
- Hull Strength: Sum of all block health pools. When Hull HP drops to 0, the ship is disabled.
- Force vs. Thrust: Force denotes engine power limits, while Thrust denotes linear force in kilonewtons (kN).
-
Boost Mechanics: Boosting adds metrics additively to standard operation:
$\text{Total Thrust} = \text{Normal Thrust} + \text{Boost Thrust}$ $\text{Total Power} = \text{Normal Power} + \text{Boost Power}$ $\text{Total Heat} = \text{Normal Heat} + \text{Boost Heat}$
- Selection of cosmetic hull shapes (Slope, Slope Flat, Full Block) is purely visual. They share the same physical collision bounding box and resource stats as standard Full Blocks to prevent structural exploits.
- Core Framework: React 19 + TypeScript (via Vite)
- 3D Graphics Engine: Three.js mapped via React Three Fiber (R3F) &
@react-three/drei - State Store: Zustand (immutable actions, pure functional selectors)
- Styling: Tailwind CSS v4 (Overlay HUD, Stats panels, BOM indicators)
- Tooling: Node.js & Vite dev tooling
The builder supports extending block shapes (Slopes, Corners, Wedges). All custom geometries are registered in src/utils/geometry/.
All custom meshes must align to the coordinate system:
-
Depth (X-Axis):
$X=0$ represents the Front Face (labelled "Front" when selected).$X=w$ represents the back edge. Custom slopes or tapers must rise/taper starting from$X=0$ . -
Height (Y-Axis):
$Y=0$ is the bottom,$Y=h$ is the top. -
Width (Z-Axis):
$Z=0$ to$Z=d$ represent side-to-side boundaries.
- Register the shape identifier in the
HULL_SHAPESarray in blocks.ts. - Create a configuration file
src/utils/geometry/shapes/[shape_id].ts. - Register the config in
SHAPE_CONFIGSin index.ts.
import * as THREE from 'three';
import { ShapeConfig } from '../types';
export const custom_slope: ShapeConfig = {
id: 'custom_slope',
name: 'Custom Slope',
svgPath: 'M 6,26 L 26,26 L 26,6 Z', // 2D SVG Icon
generateGeometry(w: number, h: number, d: number) {
const vertices = [
[0, 0, 0], // v0 (bottom-left-front)
[w, 0, 0], // v1 (bottom-right-front)
[0, 0, d], // v2 (bottom-left-back)
[w, 0, d], // v3 (bottom-right-back)
[w, h, 0], // v4 (top-right-front)
[w, h, d], // v5 (top-right-back)
];
const indices = [
0, 3, 2, 0, 1, 3, // Bottom
1, 5, 3, 1, 4, 5, // Right
0, 4, 1, // Front
2, 3, 5, // Back
0, 2, 5, 0, 5, 4, // Slope
];
const positions: number[] = [];
for (const idx of indices) {
positions.push(...vertices[idx]);
}
const geom = new THREE.BufferGeometry();
geom.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
geom.computeVertexNormals();
return geom;
},
getTopSurfaceAt(x: number, w: number, h: number) {
return {
y: h * (x / w),
tilt: Math.atan2(h, w),
};
}
};To support low-specification machines and prevent browser limits on WebGL context bounds (usually capped at 8–16 per page), the builder includes a Potato Mode.
Rendering multiple small 3D canvases in sidebar lists forces the browser to instantiate multiple WebGL contexts. Potato Mode dynamically swaps all R3F canvases in UI panels with lightweight vector-based SVG graphics (Shape2DPreview), keeping the total context count to exactly 1 (the main viewport).
To save GPU and CPU resources:
- The canvas uses
frameloop="demand". - Frameloop invalidations are driven by the
<Invalidator>subscribing only to state mutations that physically impact visual representations. - Heavy post-processing, env lights (
Environment preset="city"), and rotating canvas icons are completely disabled.
- Gate non-essentials: wrap cosmetic details in
{!potatoMode && <Component />}. - Frame updates: Never trigger React or Zustand state writes inside
useFrame. - Derived Selectors: Wrap calculations like BFS connectivity, SP efficiency, and BOM aggregates in
useMemohooks keyed on theblocksarray.
To ensure stability across modular shipbuilding math, collision systems, and connectivity algorithms, the codebase employs a unit testing strategy powered by Vitest.
- Runner: Vitest acts as the native testing framework, integrating directly with our Vite compiler config.
- Environment: A lightweight, pure Node.js test environment is used for maximum speed. Browser APIs (like
window.locationorlocalStorage) are mocked or safely ignored using environment guards. - Location: Test files reside in
__tests__/subdirectories adjacent to the target modules.
We maintain test coverage across critical application layers:
- Calculation Engine (shipStats.test.ts): Validates the correctness of formulas (SP efficiency limits, power balances, global heat pool balances, and structural frame ratios) and verifies thruster boost additive math according to IN_GAME_SPEC.md.
- Adjacency & Connectivity (BFS) (shipStats.test.ts): Tests face-to-face adjacency checks (excluding invalid edge/corner contact) and checks that structural BFS connectivity traversal correctly marks floating block segments.
- Serialization (serialization.test.ts): Confirms URL-safe base64 string round-trips correctly preserve ship designs (including block types, coords, rotations, colors, custom shapes, and binary flips) and verify robust crash-prevention for invalid strings.
- Geometry & Collision (shipStore.test.ts): Validates vertex rotation calculations in
getBlockBoundsand overlap detection insideisBoundsColliding. - Store Actions (shipStore.test.ts): Simulates Zustand store mutations like adding/deleting blocks, preventing placement overlap, and enforcing the single-cockpit limit.
- Run Tests (CI/CD):
npm run test(runs tests once). - Interactive Watch Mode:
npm run test:watch(automatically re-runs tests on file edits).
Clone the repository and install npm packages:
npm installLaunch Vite development server with Hot Module Replacement (HMR):
npm run devTo build the application:
npm run buildPre-render UI preview assets from geometry files:
npm run export-shapesRun the Vitest unit tests:
npm run testEnforce code quality with ESLint rules:
npm run lint