Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
110534d
Add ship-vs-planet collision and throttle-reactive flight feel
claude Jul 5, 2026
a0862a8
Make asteroid belt placement deterministic and structured
claude Jul 5, 2026
5f18d80
Add per-asteroid identity, spatial grid, and a runtime handle
claude Jul 5, 2026
dc07f21
Add ship-vs-asteroid collision via the spatial grid
claude Jul 5, 2026
c029870
Add asteroid fracture and pooled debris physics
claude Jul 5, 2026
1c919d2
Add space mining/weapon system with ore collection
claude Jul 5, 2026
d266569
Add the space narrative/POI layer
claude Jul 5, 2026
070537c
Document the Act 1 space layer pass in ROADMAP.md
claude Jul 5, 2026
a387a60
Add touch roll control and space mining visibility/feedback
claude Jul 5, 2026
730e70f
Convert mining beam to discrete automatic shots with impact chips and…
claude Jul 5, 2026
c237f88
Add real local asteroid damage: promotion + per-vertex dents
claude Jul 5, 2026
2ef44ac
Add pattern-based fracture chunks and real fragment momentum
claude Jul 5, 2026
16d712a
Replace debris stick-and-expire collision with real bounce + cascade …
claude Jul 5, 2026
e25b922
Render debris fragments in per-shape buckets instead of one generic rock
claude Jul 5, 2026
b57339c
Fix lint findings from the cascade/bucketing pass
claude Jul 5, 2026
100725a
Fix mining/debris invisible on low quality: zero-budget tier disabled…
claude Jul 5, 2026
803ea1c
Lower asteroid health so kills land reliably instead of only ever den…
claude Jul 5, 2026
149915a
Replace hairline Line beam flash with a real-width cylinder mesh
claude Jul 5, 2026
41a203a
Replace instant hitscan mining with a real traveling projectile weapon
claude Jul 5, 2026
fb4d54b
Fix projectiles missing their target and debris vanishing on spawn
claude Jul 5, 2026
d7bd822
Chip debris on every connecting hit, and cap runaway ejection speed
claude Jul 5, 2026
aca4b3c
Add an item wheel for on-foot gameplay: pickaxe, gun, flashlight
claude Jul 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ was folded directly into `storyline` rather than merged through the PR itself.
technical audit + six follow-up rounds (see "Technical audit & hardening"
below) — it is not one of the numbered phases.

**`space`** branches off `storyline` for the Act 1 space-layer pass described
in Phase 11.8 below (ship collision, asteroid fracture/debris, space mining,
space-POI narrative content) — not yet merged back or manually playtested.

---

## Completed phases
Expand Down Expand Up @@ -119,6 +123,59 @@ to the resource/crafting system below despite sharing the "Phase 11" label.
| 11.6 — Backpack fidelity + offline progression | **Implemented, pending playtest** | PR #8 Round 6 (Minecraft-style grid inventory; extractor/condenser passive production with capped offline catch-up) delivers this sub-phase's actual goals, but was committed as part of the audit PR, not tagged `11.6`. The PR body itself flags that manual in-game playtesting of placing/producing/offline-catch-up has not been done — this is currently being manually playtested. |
| 11.7 — Balance, persistence hardening, perf pass | **Partially covered, unlabeled — not a dedicated pass** | PR #8 Round 1 covers real ground here (persistence bug fixes: cargo capacity, resource duplication, ground-drop loss; perf: telemetry decoupling, capped raycasts, fewer setState calls) but there has been no dedicated economy-balance or full regression pass across all 16 bodies. |

### Phase 11.8 — Act 1 space layer (flight collision, asteroid fracture, space mining, space narrative) — branch `space`, unmerged
Built to make the Solar System (Act 1) feel like an archaeological, structured
place to fly through rather than empty traversal space, in strict priority
order: flight feel first, then exploration density, then narrative, then
performance — extending `shipPhysics.ts`/`ShipController.tsx`/`AsteroidBelt.tsx`/
`quality.ts` rather than adding a parallel physics engine or ECS.

- **Ship collision**: the ship previously flew straight through planets and
asteroids with zero physical response. It now slides off both on contact —
an analytic sphere push-out + tangential-velocity-retain correction, never
an added force, so the "Phase 11: pure thrust, no gravity" flight model is
untouched. Asteroid collision is broadphased through a new cylindrical
spatial grid (radial × angular bins tuned to the belt's thin-torus shape)
instead of scanning up to 9000 instances.
- **Flight-feel juice**: fixed the thruster glow (previously a random flicker
unrelated to throttle) to track actual throttle; added a pooled
engine-exhaust particle trail and a throttle-reactive engine hum.
- **Deterministic, structured belt**: the belt's instance placement was
unseeded `Math.random()` (reshuffled on every quality change). Replaced with
a pure function of `(tier, variant, index, seed)` using the existing
`cellHash`/`seedFromName` primitives, so a given index always resolves to
the same rock — required for asteroid collision/fracture state to stay in
sync with the render matrices, and for narrative content to anchor to a
specific asteroid. Placement is also sector-weighted (24 angular sectors,
deterministic gap/sparse/normal/dense density) so the belt reads as
corridors and clusters rather than uniform noise.
- **Asteroid fracture + debris**: asteroids now hold real health instead of
being binary — a hit that doesn't finish one off just wears it down (no
debris); a lethal hit spawns 1–5 debris fragments, or 4–8 on a big-overkill
hit, with deterministically hashed (not `Math.random()`) spawn directions/
sizes. Debris never recursively fractures. A pooled, quality-budgeted debris
population drifts freely via the ship's existing `integrate()` helper and
sticks-and-expires on any contact rather than continuing to bounce.
- **Space mining**: a raycast beam (mirroring the voxel mining crosshair
convention) feeds sustained fire into the same fracture-damage pipeline
collision damage uses. A fraction of any fracture's debris comes back as
collectible ore with a resource type from the existing inventory system;
ore chunks home toward the ship within range and collect into the same
backpack voxel mining uses.
- **Space narrative/POI layer**: a small, fixed set of space POIs (wreckage,
a signal anomaly reusing the existing cross-body "signal" mystery, a
resource cluster, a distant landmark), discovered by proximity while
piloting and recorded through the *existing* `recordDiscovery` journal with
`planet: 'space'` — no new store field, no new narrative system. Wreckage
can anchor to a specific, stable belt asteroid and flags it indestructible.
- Not done / explicitly deferred: crater cosmetics on sub-lethal hits, mining
muzzle-flash VFX, and space-POI density scaling were all cut as
cosmetic-only per the "cut simulation complexity first" performance rule —
no quality-tier fields were added for capabilities that don't exist yet.
Manual in-game/visual playtesting has not been done (this environment
cannot render WebGPU live); flight feel, collision weight, frame time in a
dense belt, and fracture-burst cost all need a live-device check.

### Phase 12 — Procedural galaxy
Travel beyond the solar system into procedurally generated star systems, each
with its own unique planets to discover and explore. Not started.
Expand Down
10 changes: 9 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useEffect } from 'react';
import { useStore } from './store';
import { loadInventory, loadItems, loadSeen, loadMode, loadUpgrades } from './voxel/persistence';
import { loadInventory, loadItems, loadSeen, loadMode, loadUpgrades, loadActiveTool, loadFlashlightOn } from './voxel/persistence';
import { DEFAULT_UPGRADES, type ShipUpgrades } from './ship/upgrades';
import { SolarSystem } from './scene/SolarSystem';
import { HUD } from './ui/HUD';
Expand All @@ -13,6 +13,7 @@ import { VoxelAudio } from './audio/VoxelAudio';
import { SettingsPanel } from './ui/SettingsPanel';
import { Labels } from './ui/Labels';
import { ShipHUD } from './ui/ShipHUD';
import { SpaceCrosshair } from './ui/SpaceCrosshair';
import { DescentOverlay } from './ui/DescentOverlay';
import { SurfaceHUD } from './ui/SurfaceHUD';
import { VoxelHUD } from './ui/VoxelHUD';
Expand Down Expand Up @@ -43,6 +44,12 @@ export default function App() {
loadUpgrades().then((saved) => {
if (saved) useStore.getState().setShipUpgrades({ ...DEFAULT_UPGRADES, ...saved } as ShipUpgrades);
});
loadActiveTool().then((tool) => {
if (tool) useStore.getState().setActiveTool(tool);
});
loadFlashlightOn().then((on) => {
if (on) useStore.getState().setFlashlightOn(on);
});
}, []);

return (
Expand All @@ -53,6 +60,7 @@ export default function App() {
<InfoPanel />
<SettingsPanel />
<ShipHUD />
<SpaceCrosshair />
<DescentOverlay />
<SurfaceHUD />
<VoxelHUD />
Expand Down
56 changes: 56 additions & 0 deletions src/audio/AudioManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ class AudioManager {
private master?: GainNode;
private droneGain?: GainNode;
private droneFilter?: BiquadFilterNode;
private engineGain?: GainNode;
private engineFilter?: BiquadFilterNode;
private started = false;

// Surface ambience graph (built lazily on first landing, reused after).
Expand Down Expand Up @@ -118,6 +120,23 @@ class AudioManager {
this.droneGain = droneGain;
this.droneFilter = droneFilter;

// --- Ship engine hum: gain/pitch track throttle while piloting. ---
const engineFilter = ctx.createBiquadFilter();
engineFilter.type = 'lowpass';
engineFilter.frequency.value = 300;
const engineGain = ctx.createGain();
engineGain.gain.value = 0;
engineFilter.connect(engineGain).connect(master);
const engineOsc = ctx.createOscillator();
engineOsc.type = 'sawtooth';
engineOsc.frequency.value = 60;
const engineOscGain = ctx.createGain();
engineOscGain.gain.value = 0.6;
engineOsc.connect(engineOscGain).connect(engineFilter);
engineOsc.start();
this.engineGain = engineGain;
this.engineFilter = engineFilter;

this.started = true;
}

Expand All @@ -144,6 +163,43 @@ class AudioManager {
}
}

/** Ship engine hum, driven by throttle (0..1) while piloting. Same
* last-quarter-emphasis shape as the camera shake / HUD heat glow so all
* the throttle-reactive feedback kicks in together. Call with 0 when
* leaving piloting to fade it out. */
setEngineHum(throttle: number) {
if (!this.engineGain || !this.engineFilter || !this.ctx) return;
const t = this.ctx.currentTime;
this.engineGain.gain.setTargetAtTime(throttle * 0.05, t, 0.15);
this.engineFilter.frequency.setTargetAtTime(150 + throttle * 500, t, 0.15);
}

/** One discrete mining-beam shot: a short laser-ish blip (sawtooth chirp +
* a touch of noise crack), pitched a bit higher and louder when the shot
* actually connects with an asteroid vs. firing into empty space — so
* automatic fire reads as a rhythm of individual shots, not a sustained
* tone, and hits vs. misses are audibly distinguishable. */
playMiningShot(onTarget: boolean) {
if (!this.master || !this.ctx) return;
const ctx = this.ctx;
const t = ctx.currentTime;
const dur = 0.05;
const osc = ctx.createOscillator();
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(onTarget ? 780 : 520, t);
osc.frequency.exponentialRampToValueAtTime(onTarget ? 420 : 300, t + dur);
const g = ctx.createGain();
g.gain.setValueAtTime(0.0001, t);
g.gain.exponentialRampToValueAtTime(onTarget ? 0.07 : 0.045, t + 0.005);
g.gain.exponentialRampToValueAtTime(0.0001, t + dur);
const filter = ctx.createBiquadFilter();
filter.type = 'bandpass';
filter.Q.value = 3;
osc.connect(filter).connect(g).connect(this.master);
osc.start(t);
osc.stop(t + dur + 0.02);
}

// --- Surface ambience --------------------------------------------------

/** Build the persistent surface graph once: wind (filtered noise) + rumble. */
Expand Down
6 changes: 6 additions & 0 deletions src/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,9 @@ const STRINGS = {
days: 'days',
hours: 'hours',
millionKm: 'million km',
pickaxe: 'Pickaxe',
gun: 'Gun',
flashlight: 'Flashlight',
},
sv: {
timeScale: 'Tidsskala',
Expand Down Expand Up @@ -228,6 +231,9 @@ const STRINGS = {
days: 'dygn',
hours: 'timmar',
millionKm: 'miljoner km',
pickaxe: 'Hacka',
gun: 'Pistol',
flashlight: 'Ficklampa',
},
} as const;

Expand Down
Loading