Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion src/core/course.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ export function createCourse({ seed = 1, difficultyRun = DIFFICULTY_RUN } = {})
const roll = rng();
if (roll < 0.18) runway();
else if (roll < 0.42) slalom(d);
else if (roll < 0.62) chasm(d);
else if (roll < 0.62) chasm();
else if (roll < 0.78) catwalk(d);
else if (roll < 0.9) stack(d);
else gauntlet(d);
Expand Down
4 changes: 3 additions & 1 deletion src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ mute.addEventListener('click', (event) => {
setMuted(!sound.muted);
});
addEventListener('keydown', (event) => {
if (event.code === 'KeyM') setMuted(!sound.muted);
/** A held key repeats, and a mute that flips thirty times a second is a
* mute nobody can aim. */
if (event.code === 'KeyM' && !event.repeat) setMuted(!sound.muted);
});

/**
Expand Down
5 changes: 4 additions & 1 deletion src/render/props.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,12 @@ export function createHazard() {
wire(THEME.agent, 0.55),
);
core.position.y = HAZARD.height / 2;
core.name = 'core';
group.add(core);

/** Handed over rather than looked up: the renderer turns this every frame
* for every agent in shot, and a search of the group is a search. */
group.userData.core = core;

return group;
}

Expand Down
2 changes: 1 addition & 1 deletion src/render/view.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export function createView({ scene, camera, studio }) {
if (hazard.z > player.z + DRAW.ahead) break;
const mesh = agents.take();
mesh.position.set(hazard.x, hazard.y, hazard.z);
mesh.getObjectByName('core').rotation.set(time * 0.9, time * 1.4, 0);
mesh.userData.core.rotation.set(time * 0.9, time * 1.4, 0);
}
agents.end();

Expand Down
8 changes: 7 additions & 1 deletion src/ui/hud.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@ const LEAD = {
over: 'Connection terminated. The shell was never the problem.',
};

/** HTML-safe: every figure on this panel is one of our own numbers, but a
* panel that writes unescaped strings is a habit worth not having. */
function safe(text) {
return String(text).replace(/[&<>"]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]));
}

function stat(label, value) {
return `<div class="stat"><dt>${label}</dt><dd>${value}</dd></div>`;
return `<div class="stat"><dt>${safe(label)}</dt><dd>${safe(value)}</dd></div>`;
}

function metres(value) {
Expand Down
11 changes: 10 additions & 1 deletion src/ui/sound.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ export function createSound() {
if (!voice || muted) return;
const audio = context();
if (!audio) return;
if (audio.state === 'suspended') audio.resume();
/** A resume that is refused is a run with no sound in it, not an
* unhandled rejection in everybody's console. */
if (audio.state === 'suspended') audio.resume()?.catch(() => {});

const now = audio.currentTime;
const osc = audio.createOscillator();
Expand All @@ -44,6 +46,13 @@ export function createSound() {
osc.connect(gain).connect(audio.destination);
osc.start(now);
osc.stop(now + voice.time + 0.02);
/** Off the graph once it has been heard. A run plays hundreds of
* these, and every one of them stays wired to the destination
* until it is let go of. */
osc.onended = () => {
osc.disconnect();
gain.disconnect();
};
},
};
}
Loading