Phase 2: bases, quest board, and known-vs-new targets
Four allied bases, placed by greedy farthest-point so no choke point can strand the player, with the first at the spawn junction so the loop is available at once. Missions hand in at any base, not the issuing one. The board offers known and new targets side by side and describes each route in words. Crucially it never reads heat directly — it reads sim/intel.ts, a record of what the player has actually observed and when. Notes age while roads keep escalating unwatched, so a known route is knowable, stale, and never a promise. Recon missions survey everything within 130m, which is how a new target becomes a known one without driving every road there. New targets pay 1.8x. That multiplier is the phase's tuning dial: too low and nobody takes the unknown, too high and nobody takes the known. Payment is parts, which repair the car. To keep "decline, not reset" intact, each subsystem gains a ceiling that falls permanently with damage — repairs restore up to what the car is still capable of, never to what it was. Without an economy the board's rewards had no stakes; with one, the risky job is what keeps the car running a while longer. Adds Dijkstra over the road graph, taking a cost function so a "safest route" preview is a small change later. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
import * as THREE from 'three';
|
||||
import type { Base } from '../sim/bases';
|
||||
|
||||
/**
|
||||
* Bases and the active objective, as things you can see from a distance.
|
||||
* The player navigates by looking, so both need to be visible over scenery.
|
||||
*/
|
||||
const BEACON_HEIGHT = 26;
|
||||
|
||||
function beacon(colour: number, radius: number): THREE.Mesh {
|
||||
return new THREE.Mesh(
|
||||
new THREE.CylinderGeometry(radius, radius, BEACON_HEIGHT, 12, 1, true),
|
||||
new THREE.MeshBasicMaterial({
|
||||
color: colour,
|
||||
transparent: true,
|
||||
opacity: 0.18,
|
||||
side: THREE.DoubleSide,
|
||||
depthWrite: false,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function createMarkers(scene: THREE.Scene, bases: Base[]) {
|
||||
for (const base of bases) {
|
||||
const hut = new THREE.Mesh(
|
||||
new THREE.BoxGeometry(7, 3.4, 7),
|
||||
new THREE.MeshStandardMaterial({ color: 0x3f5a46, roughness: 0.85 }),
|
||||
);
|
||||
hut.position.set(base.x, 1.7, base.z);
|
||||
hut.castShadow = true;
|
||||
hut.receiveShadow = true;
|
||||
scene.add(hut);
|
||||
|
||||
const light = beacon(0x5fd08a, 5);
|
||||
light.position.set(base.x, BEACON_HEIGHT / 2, base.z);
|
||||
scene.add(light);
|
||||
}
|
||||
|
||||
const objective = beacon(0xffc247, 6);
|
||||
objective.position.y = BEACON_HEIGHT / 2;
|
||||
objective.visible = false;
|
||||
scene.add(objective);
|
||||
|
||||
return {
|
||||
/** Point the objective beacon at a target, or hide it when idle. */
|
||||
setObjective(target: { x: number; z: number } | null) {
|
||||
objective.visible = target !== null;
|
||||
if (target) objective.position.set(target.x, BEACON_HEIGHT / 2, target.z);
|
||||
},
|
||||
/** Slow spin, so a beacon reads as a marker rather than scenery. */
|
||||
update(elapsed: number) {
|
||||
objective.rotation.y = elapsed * 0.6;
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user