Forge combines interactive 3D assembly visualization with a graph model of component relationships, so engineers can explore designs, trace dependencies, and understand the downstream impact of engineering changes.
Disclaimer — Unofficial fan/engineering project. Not affiliated with, endorsed by, or connected to SpaceX. The vehicle is a Starship-class methalox launcher assembled from entirely synthetic parts: a procedurally generated 52 m × 9 m exterior (smooth lathe ogive nose, stainless hull with construction-ring textures, asymmetric tile heat shield, four flaps on animated pivots, six engine bells) plus fictional internal propellant and avionics components. No proprietary data or CAD was used or imitated.
The model follows the asset PRD's fidelity ladder: correct silhouette first, then PBR
stainless + tile-pattern heat shield via procedurally painted color/metallicRoughness/normal
maps embedded in the GLB, then engineering granularity — 32 selectable components
(SS_* meshes ↔ AERO-* / TPS-* / ENG-* ids) instead of thousands of fasteners.
Flap-cant sliders, an assembly-explode slider and camera presets are built into the viewer.
Change-impact analysis — selecting the mid heat shield dims the vehicle to its dependency chain, with affected components and subsystems reported live in the inspector.
Studio inspection — the aft left flap selected against the hexagonal tile heat shield, its flight-computer and hull links traced in the dependency graph below.
Engine bay close-up — the ENGINES camera preset frames the Raptor Vacuum bells under the skirt while the selected engine's supply and control wiring is highlighted.
- Open Forge → Starship loads in Three.js (assembly tree, search, viewer).
- Search
V-104→ camera flies to the Methane Isolation Valve. - Click it → inspector shows material/mass/revision/status/owner/manufacturing.
- Graph panel shows its dependency neighborhood (React Flow).
- Press Analyze Change Impact → Neo4j traverses
ATTACHED_TO | CONNECTED_TO | DEPENDS_ON | SUPPLIESedges outward. - The valve stays amber, direct dependencies glow orange, downstream components cyan, everything else fades to near-invisible.
- Click any impacted part → camera flies there; graph re-centers.
$ curl "localhost:5080/api/components/V-104/impact?depth=4"
{
"componentId": "V-104",
"directImpact": 1,
"transitiveImpact": 10,
"maxDistance": 4,
"affectedSubsystems": ["Propellant System", "Raptor Propulsion"],
...
}
Downstream at depth 4: both manifolds feed all six engines (RAP-*), which hang off the
thrust puck.
React + TypeScript (apps/web)
├── raw Three.js viewer physical structure
└── React Flow panel logical structure
│ REST
▼
ASP.NET Core 8 (apps/api)
├── ImportService validation + batched graph writes
├── ImportValidator pure, unit-tested rules (§19-style)
├── GraphRepository every Cypher query lives here
│ bolt
▼
Neo4j 5 design graph
Why Neo4j here: change impact is a variable-depth graph traversal
((start)-[:ATTACHED_TO|CONNECTED_TO|DEPENDS_ON|SUPPLIES*1..4]->(affected)). In SQL this is
recursive CTE territory with poor locality; Cypher expresses the pattern directly, and
Neo4j's index-free adjacency makes each hop cheap. Conventional application data
(users/projects/revisions) would live in PostgreSQL — deferred until the revision system lands.
Edges are oriented away from the changed part toward affected parts:
tank SUPPLIES line SUPPLIES valve SUPPLIES manifold SUPPLIES raptor,
bolt ATTACHED_TO bracket, raptor ATTACHED_TO mount. Impact follows outgoing edges only;
cycles over traversal types are rejected at import time, and depth bounds guarantee
termination regardless. CONTROLLED_BY / POWERED_BY point consumer→source and are shown in
dependency views but deliberately excluded from impact propagation (MVP simplification).
apps/web React + TS + Vite + raw Three.js + React Flow + TanStack Query + zustand
apps/api ASP.NET Core 8 (Controllers / Services / Domain / Infrastructure)
apps/api/tests xUnit — validator rules (duplicates, dangling refs, meshes, cycles)
packages/shared-types DTO contracts shared by API consumers and the UI
data/astra-x/starship.mjs high-fidelity procedural Starship generator (zero-dep GLB + PNG writer)
data/astra-x/*.json|.glb generated package (committed for instant demos)
infrastructure/docker Dockerfiles, nginx.conf, docker-compose.yml
(data/astra-x is a legacy folder name from an earlier iteration; the assembly id is
STARSHIP.)
Prereqs: Node 22+, .NET 8 SDK, Docker.
npm install # workspaces: web + shared-types + generator
docker compose -f infrastructure/docker-compose.yml up -d neo4j
ASPNETCORE_URLS=http://localhost:5080 dotnet run --project apps/api
# first boot waits for Neo4j, validates the package, imports the vehicle graph (~2 s)
npm run dev --workspace apps/web # http://localhost:5173Port 5080 (not 5000) because macOS AirPlay Receiver occupies 5000.
docker compose -f infrastructure/docker-compose.yml --profile full up --build
# web :5173 api :5080 neo4j :7474 (browser) / :7687 (bolt)npm run generate:data # writes vehicle.glb + *.json
node data/astra-x/generator/verify.mjs # glTF-transform round-trip validationThen wipe & reimport: curl -X POST localhost:5080/api/assemblies/STARSHIP/import.
| Method | Route | Purpose |
|---|---|---|
| GET | /api/assemblies/{id} |
summary: counts, revision, subsystems |
| GET | /api/assemblies/{id}/tree |
component tree grouped by subsystem |
| POST | /api/assemblies/{id}/import |
validate + (re)build the design graph |
| GET | /api/components/{id} |
full metadata |
| GET | /api/components/search?q=&take= |
id / name / subsystem / material / revision |
| GET | /api/components/{id}/dependencies |
depth-1 neighborhood as {nodes, edges} |
| GET | /api/components/{id}/impact?depth=4 |
directed traversal, clamped depth 1–8 |
Static: /models/vehicle.glb served by the API (model/gltf-binary).
Rejects packages containing duplicate ids, dangling relationship endpoints, mesh references
absent from the GLB, components without geometry nodes, revisions < 1, self loops, unknown
relationship types, and cycles over impact-traversal edge types. Errors are reported per
component (400 + report JSON); the API stays up and imports can be retried.
Nodes: Component, Subsystem, Material, ManufacturingProcess, Revision
Engineering rels: ATTACHED_TO · CONNECTED_TO · DEPENDS_ON · SUPPLIES · CONTROLLED_BY · POWERED_BY
Derived rels at import: PART_OF · USES_MATERIAL · MANUFACTURED_BY · HAS_REVISION
Uniqueness constraints on Component.id, Subsystem.id, Material.id, Revision.key;
index on Component.name. Mesh ↔ component linkage: each GLB node's name is the component
id; raycasting resolves mesh → userData.componentId → REST → Neo4j.
| Metric | Value | Target |
|---|---|---|
| Import vehicle graph (32 comps / 44 rels) + GLB serve | < 1 s | — |
| Impact V-104 depth 4 (warm) | ~9 ms | < 500 ms |
| Impact cold (first hit) | ~280 ms | — |
| vehicle.glb size | 3.9 MB incl. 6 embedded PBR textures, 33k tris | — |
| Frontend tests / backend tests | 7 / 11 passing | — |
Rendering FPS is reported in the viewer HUD. Scaling to 10k components, instancing, LOD and caching are the next performance milestone.
dotnet test apps/api/tests/Forge.Api.Tests # validator: 11 cases incl. cycle detection
npm run test:web # layout/tree pure functions: 7 cases
npm run test:e2e # 9 Playwright specs driving the real UIE2E requires the live stack (compose neo4j + api) and launches its own vite dev server on :5199. Specs cover app shell, search→select→inspector wiring, impact analysis flow, graph panel switching, flap/explode sliders (asserted through the viewer's node graph), and camera presets. Integration tests against a disposable Neo4j container are the next testing milestone.
Revision intelligence → 10k-part performance pass with before/after numbers → STEP ingestion service → rule-based design validation ("every raptor must connect to exactly one controller") → path finding between any two components → live events via RabbitMQ/SignalR.


