Make the running game inspectable, and stop the beacons swallowing the view
The pane this build runs in does not composite, so requestAnimationFrame never fires, the loop stalls, and nothing can be screenshotted from outside. Adds a `?debug=1` handle that steps the simulation by hand and renders on demand, plus a dev-only endpoint that takes a POSTed frame and writes it to shots/. Moving the image as bytes rather than a pasted data URL matters: a truncated base64 string decodes to nothing. Looking at the result immediately found a real problem. Base beacons were wide translucent cylinders — legible from a distance, a solid green wall from inside, and the car parks in the middle of one. Now a slim pillar plus a ring on the ground that marks where to stop. Also raised the sky and fog brightness, which was dark enough to read as night. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+40
-16
@@ -5,19 +5,44 @@ 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;
|
||||
const BEACON_HEIGHT = 34;
|
||||
|
||||
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,
|
||||
}),
|
||||
/**
|
||||
* A slim pillar plus a ring on the ground.
|
||||
*
|
||||
* The first version was a wide translucent cylinder, which was legible from a
|
||||
* distance and a solid green wall from inside — and since the car parks in the
|
||||
* middle of one, that is exactly where you spend your time. Narrow enough to see
|
||||
* past, tall enough to see over buildings.
|
||||
*/
|
||||
function beacon(colour: number, radius: number): THREE.Group {
|
||||
const group = new THREE.Group();
|
||||
const material = new THREE.MeshBasicMaterial({
|
||||
color: colour,
|
||||
transparent: true,
|
||||
opacity: 0.3,
|
||||
side: THREE.DoubleSide,
|
||||
depthWrite: false,
|
||||
// Additive keeps it reading as light rather than as a pane of glass.
|
||||
blending: THREE.AdditiveBlending,
|
||||
});
|
||||
|
||||
const pillar = new THREE.Mesh(
|
||||
new THREE.CylinderGeometry(0.9, 0.9, BEACON_HEIGHT, 8, 1, true),
|
||||
material,
|
||||
);
|
||||
pillar.position.y = BEACON_HEIGHT / 2;
|
||||
group.add(pillar);
|
||||
|
||||
// The ring is what tells you where to actually stop.
|
||||
const ring = new THREE.Mesh(
|
||||
new THREE.RingGeometry(radius - 0.7, radius, 32).rotateX(-Math.PI / 2),
|
||||
material,
|
||||
);
|
||||
ring.position.y = 0.08;
|
||||
group.add(ring);
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
export function createMarkers(scene: THREE.Scene, bases: Base[]) {
|
||||
@@ -34,13 +59,12 @@ export function createMarkers(scene: THREE.Scene, bases: Base[]) {
|
||||
scene.add(hut);
|
||||
|
||||
// The beacon stays over the junction, since that is where you park.
|
||||
const light = beacon(0x5fd08a, 5);
|
||||
light.position.set(base.x, BEACON_HEIGHT / 2, base.z);
|
||||
const light = beacon(0x5fd08a, 9);
|
||||
light.position.set(base.x, 0, base.z);
|
||||
scene.add(light);
|
||||
}
|
||||
|
||||
const objective = beacon(0xffc247, 6);
|
||||
objective.position.y = BEACON_HEIGHT / 2;
|
||||
const objective = beacon(0xffc247, 11);
|
||||
objective.visible = false;
|
||||
scene.add(objective);
|
||||
|
||||
@@ -48,7 +72,7 @@ export function createMarkers(scene: THREE.Scene, bases: Base[]) {
|
||||
/** 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);
|
||||
if (target) objective.position.set(target.x, 0, target.z);
|
||||
},
|
||||
/** Slow spin, so a beacon reads as a marker rather than scenery. */
|
||||
update(elapsed: number) {
|
||||
|
||||
+6
-2
@@ -37,7 +37,11 @@ const TONES: Record<Control, { sky: number; ground: number; sun: number; fog: [n
|
||||
const SKY = 0x11161c;
|
||||
|
||||
export function createScene(model: WorldModel): SceneView {
|
||||
const renderer = new THREE.WebGLRenderer({ antialias: true });
|
||||
const renderer = new THREE.WebGLRenderer({
|
||||
antialias: true,
|
||||
// Needed so a rendered frame can still be read back as an image afterwards.
|
||||
preserveDrawingBuffer: new URLSearchParams(location.search).has('debug'),
|
||||
});
|
||||
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
|
||||
renderer.setSize(innerWidth, innerHeight);
|
||||
renderer.shadowMap.enabled = true;
|
||||
@@ -242,7 +246,7 @@ export function createScene(model: WorldModel): SceneView {
|
||||
hemi.color.lerp(toneColour.set(tone.sky), t);
|
||||
hemi.groundColor.lerp(toneColour.set(tone.ground), t);
|
||||
sun.color.lerp(toneColour.set(tone.sun), t);
|
||||
(scene.background as THREE.Color).lerp(toneColour.set(tone.sky).multiplyScalar(0.22), t);
|
||||
(scene.background as THREE.Color).lerp(toneColour.set(tone.sky).multiplyScalar(0.5), t);
|
||||
const fog = scene.fog as THREE.Fog;
|
||||
fog.color.copy(scene.background as THREE.Color);
|
||||
fog.near += (tone.fog[0] - fog.near) * t;
|
||||
|
||||
Reference in New Issue
Block a user