Blender pipeline overview
New to level-making?
Start at the Making a level hub for the newcomer reading order, the which-doc-for-whom map, and the two authoring workflows.
The Hoverbike asset pipeline runs through Blender 5.1+. Most of what you build in Blender exports to a single .glb per track or per bike, which the runtime loads at boot. The pipeline has three distinct surfaces:
- The in-Blender addon (
tools/blender/hoverbike_addon/) — a sidebar in Blender's 3D viewport with one-click Export to Game buttons, parametric authoring tools (road, tunnel, ramp, downtown, horizon, wave zones, particle emitters), and live previews of gates / racers / water / boost pads / wave zones that follow your edits. - Headless builders (
tools/blender/build_*.py) — Python scripts run viablender --background. Pair with JSON specs to produce GLBs in CI / batch. The sprite atlas behind every particle emitter is built by the same family (build_sprite_atlas.py). - Seed scripts (
tools/blender/seed_*.py) — one-shot generators that materialise canonical.blendfiles (template islands / alpine / dunes / mesa / downtown / tunnels, prop and landmark libraries, calibration scene) from code.
Authoring usually means opening an existing .blend, editing, clicking Export. The seed scripts are how those .blends get created in the first place; the headless builders are how CI keeps the canonical scenes in sync with their JSON specs.
What the addon gives you
Once installed, press N in any 3D viewport → Hoverbike tab to reveal the panel. It re-renders based on whether the open .blend lives in tracks-src/ or bikes-src/. There's also a top-bar Hoverbike menu (Add / Build / Spline / Terrain / Thumbnail / Utility submenus) and a Shift+H pie menu for keyboard-driven workflow — both register the same operators as the sidebar.
In track mode the panel exposes ~24 selection-driven sub-panels. Most are scoped: they only render when an object of the matching kind is active in the viewport (e.g. the Wave zones sub-panel only appears when a wave_zone_NN empty is selected). This keeps the sidebar short during normal editing; use the Hoverbike → Add menu to spawn an object of a given kind and the matching sub-panel appears.
| Sub-panel | Drops you into |
|---|---|
| Spline tools | Editing the racing line, snapping it to terrain, auto-placing ramps at curvature peaks. |
| Road tool | Bezier curve → drivable road slab with banking, F1 curbs, terrain conform. |
| Tunnels | Bezier curve → boolean cut through the hill + concrete-liner interior shell. |
| Placement helper | A persistent curve-constrained empty for parking ramps / boosts / props at any (t, offset). |
| Downtown | Procedural city block at the 3D cursor (multi-block grid, terrain-conformed plinth). |
| Ramps | Drop a parametric stunt wedge at the 3D cursor. |
| Terrain | Heightmap import, sculpt-mode entry, raise/lower at cursor, smooth, AO + path-wear bakes. |
| Terrain templates | One-click Add Island Terrain / Add Multi-Biome Terrain starter landforms + island mod-zones. |
| Terrain material | Adds the runtime-matching terrain material so the viewport previews the in-game shading. |
| Water | Sea-level slider + wave preview plane. |
| Gate buoys | Auto-places marker buoys where the racing line crosses open water (needs a water reference present). |
| Horizon | Per-track distant silhouette mesh (or procedural ring fallback). |
| Sky preset | Per-track tint, cloudiness, sun, fog, time-of-day, colour grade, sea-state Beaufort. |
| Wave zones | Per-zone wave amplitude / frequency / surge multipliers. See Wave zones cookbook. |
| Gameplay | Gates, boost pads, racer preview, turn indicators — placement + previews. |
| Props | Place library props (prop_NN) along the track. |
| Prop lines | Parametric "asset along a curve" — scatter a prop down a spline at a given spacing. |
| Decals | Thin projected decal meshes (paint, signage, scorch) laid onto terrain / geometry. |
| Scatter / biome palette / scatter stroke | Foliage subsystem — scatter zones, per-biome palette masks, and hand-painted scatter strokes. |
| Emitters | Particle systems for VFX (steam, foam, embers, gulls, neon, ash). |
| Ghost lap + chase cam | Auto-flying preview bike along the racing line. |
| Terrain shader (runtime) | Tunes the runtime ramp / slope / wet-band / coloration without touching the .ts. |
| Track hero render | Camera-driven 1280×720 hero + 320×180 tile for the loading screen. |
| Track stats | Spline length, lap-time estimate, terrain min/max y, water coverage. |
In bike mode the panel is much smaller — header with bike id, Export Bike to Game, Copy Play / Viewer URL.
Both modes share a Lint button (pre-export sanity check) and a Reload from JSON button (pulls scalar fields back from the runtime JSON into the scene custom properties).
For the full operator + panel reference, see Addon reference. For worked, end-to-end examples of the wave-mastery tools see the Wave zones cookbook.
What lives where
tools/blender/
├── hoverbike_addon/ ← in-Blender addon (package)
│ ├── __init__.py ← bl_info + per-module register
│ ├── panel.py ← sidebar UI (parent + sub-panels; more register from modules)
│ ├── menu.py ← top-bar menu + Shift+H pie menu
│ ├── prefs.py ← addon preferences (project-root path, etc.)
│ │
│ ├── road.py · road_conform_gn.py · tunnel.py · ramp.py · downtown.py
│ ├── terrain.py · terrain_material.py · island_terrain.py · multibiome_terrain.py
│ ├── spline.py · placement_helper.py · turn_indicators.py
│ ├── water.py · wave_zone.py · gate_buoys.py · horizon.py · sky_preset.py
│ ├── antigrav.py · antigrav_ribbon.py ← PARKED (anti-grav cut from races; kept for a possible DLC)
│ ├── emitter.py · boost_pad.py
│ ├── prop_placements.py · propline_placements.py · prop_bake.py · decal.py
│ ├── scatter.py · scatter_stroke.py · biome_palette.py ← foliage subsystem
│ ├── previews.py · ghost_lap.py · thumbnail.py · bake.py
│ ├── export.py · track_meta.py · terrain_shader.py · auto_tag.py
│ ├── new_map.py ← "duplicate a template" wizard
│ ├── handlers.py ← live-preview auto-rebuild
│ └── _legacy.py ← validation + JSON sync (shared infra)
│
├── build_bike.py ← headless bike → GLB
├── build_track.py ← spec-driven track → .blend + GLB
├── build_prop.py ← spec-driven prop → GLB
├── build_sprite_atlas.py ← packs particle-atlas.png (Pillow)
│
├── seed_template_island.py ← procedural island scene
├── seed_template_alpine.py ← procedural alpine scene
├── seed_template_dunes.py ← procedural dunes scene
├── seed_template_mesa.py ← procedural mesa scene
├── seed_template_downtown.py ← procedural downtown scene
├── seed_template_tunnels.py ← procedural tunnels-through-hills
├── seed_template_antigrav_showcase.py ← PARKED — anti-grav tube/ribbon/strip reference (cut from races)
├── seed_props_library.py ← rebuilds tracks-src/props-library.blend
├── seed_landmarks_library.py ← rebuilds tracks-src/landmarks-library.blend
├── seed_prop_kit.py ← rebuilds tools/blender/lib/prop_kit.blend
│
├── seed_track_*.py ← per-track seeders (one per ship track)
│
├── lint_track.py ← CI lint entry (same checks as the addon)
├── run-lint.mjs ← Node wrapper for batch lint
├── run.mjs ← Node CLI for headless batch builds
├── install-addon.mjs ← symlinks the addon for live editing
├── test-addon.mjs ← smoke-tests addon registration
├── inspect_glb.mjs ← quick GLB inspection (extensions, nodes)
│
├── common.py · sockets.py · colliders.py · mounts.py
├── hoverbike_kinds.py ← ExportedKind / AuthoringKind enums
└── lib/ ← committed source-art kits
└── prop_kit.blendOutside tools/blender/ the relevant directories are:
| Path | Contents |
|---|---|
bikes-src/<id>.blend | One per bike — the geometry source of truth. |
tracks-src/<id>.blend | One per track — editing this is the track workflow. The tracks-src/ folder can live in your clone or a Drive-synced folder outside it; either way the .blend is gitignored. |
tracks-src/props-library.blend | Linked-library source for prop_gate_mesh (the real gate prop) and other shared props. |
tracks-src/landmarks-library.blend | Linked-library source for per-city skyline / landmark meshes. |
tracks-src/template-*.blend | Seed-generated template scenes (island, alpine, dunes, mesa, downtown, tunnels; antigrav-showcase is parked). Start a new track by duplicating one with Hoverbike → Utility → New Map from Template. |
specs/bikes/<id>.json | Slim metadata + recolour overrides for each bike. |
specs/tracks/<id>.json | Declarative spec for spec-driven tracks (the headless builder reads these). |
specs/props/<id>.json | Parametric specs for kit-assembled props. |
public/assets/tracks/<id>.glb | Output track environment GLBs. |
public/assets/tracks/<id>-hero.jpg / -thumb.jpg | Loading-screen + tile art rendered from camera_hero. |
public/tracks/<id>.json | Output track gameplay data — round-trips with the .blend. |
public/assets/bikes/<id>.glb | Output bike GLBs. |
public/assets/fx/particle-atlas.png | Shared 1024×1024 4×4 sprite sheet for every emitter. |
public/assets/manifest.json | Index of every built asset (driven by gen:tracks / gen:bikes / gen:props). |
One-time setup
Install Blender 5.1+. Typical paths:
- Windows:
C:\Program Files\Blender Foundation\Blender 5.1\blender.exe - macOS:
/Applications/Blender.app/Contents/MacOS/Blender - Linux:
/opt/blender/blender
- Windows:
Set
BLENDER_EXEif Blender isn't on yourPATH— the headless builders andpnpm gen:*scripts use it to find the executable. Add to your shell rc or set it per-session.Install the addon as a symlink so every edit in the repo is live in Blender after
F3 → Reload Scripts:bashpnpm install:blender-addonThe script symlinks
tools/blender/hoverbike_addon/into your Blender user scripts directory (%APPDATA%\Blender\5.1\scripts\addons\on Windows,~/Library/Application Support/Blender/...on macOS,~/.config/blender/...on Linux). On Windows without Developer Mode it falls back to a recursive copy; the script prints how to enable it. The script also backs up any leftover legacy single-filehoverbike_addon.pyfrom before the package split to.bakso it can't collide with the package.Enable the addon in Blender. Edit → Preferences → Add-ons → search "Hoverbike" → tick the checkbox. You only need to do this once. After that, the Hoverbike menu appears in the top bar and the N-panel → Hoverbike tab appears in the 3D viewport.
Smoke-test the install:
bashpnpm test:blenderRuns Blender headless, registers the addon, asserts every operator + panel registers cleanly.
If a panel or operator disappears from the N-panel after pulling, the installed addon has drifted — re-run pnpm install:blender-addon (or check that the symlink wasn't broken by a deleted worktree).
The contract: Blender objects ↔ runtime
Every metadata-bearing object in a .blend has a kind custom property declaring what the runtime should do with it. The exporter copies custom properties verbatim into glTF extras; the runtime loader (src/game/tracks/glb-loader.ts) walks the GLB nodes and reads extras.kind.
For the full naming + extras matrix see Scene conventions.
Where to go next
- Building your first track from scratch? → Your first track — full walk-through from blank scene to playable map.
- Want a comprehensive reference of every panel + operator? → Addon reference.
- In-depth wave-mastery authoring (tsunami timers, harbour calm, set-piece swells)? → Wave zones cookbook.
- Need the at-a-glance kind / extras matrix? → Scene conventions.
- Authoring a bike, not a track? → Modding → Authoring bikes.
- CI / batch builds? The headless
pnpm gen:tracks/pnpm gen:bikes/pnpm gen:propsscripts are documented inline in Addon reference → Headless builders.