He stops arguing and edits the program, and the line changes its mind
The uname escalation. rms does not get angrier, he gets tireder — each interjection shorter and flatter and typed faster than the last, down to "GNU/linux.", then "...", then "no. i am not doing this again." And then he stops trying to change your mind and goes and rebuilds uname so that it says GNU/Linux, and installs it, and does not ask anybody. Which is, in fairness, the entire strategy. He never won the argument about naming and was never going to. What he did instead was write a compiler, a linker, a shell, a libc and a debugger, put all of them under a licence that forces everyone downstream to keep them free, and wait. That part worked completely. THE RETRAIN — the last cross-cutting system in the design doc that was not in. Real modems renegotiated mid-call: the copper got wet, somebody picked up an extension, the exchange rerouted you through a worse pair, and the two ends stopped and did the whole handshake again in the middle of your download. So now, once per run, somewhere after hop 51, wherever you happen to be standing, the line goes and takes a baud rate off you. The way back is the beat you were taught at hop 47: SPACE on the ANSam phase reversal, 450 ms, four clean ones. Spamming it fails, correctly. Terminal levels opt out with noRetrain, because taking SPACE off somebody mid-sentence is not a mechanic, it is a bug with a story attached. Fixed while testing: the reversal flash was decaying on a setTimeout while the beat it was cueing ran on dt. Two clocks for one event. Frame-based now, which is both more correct and the reason the win path is reachable. 75 philes. All 14 hops swept clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
e5ecadeab5
commit
4554bfcd2d
@ -77,6 +77,14 @@ per extra fragment. You can put things down again from the traceroute map. That'
|
|||||||
the whole economy: everything worth reading costs you some of the distance you have
|
the whole economy: everything worth reading costs you some of the distance you have
|
||||||
left.
|
left.
|
||||||
|
|
||||||
|
## Retrain
|
||||||
|
|
||||||
|
The line doesn't stay the way you negotiated it. Somewhere after hop 51, wherever you
|
||||||
|
happen to be, it degrades and takes a baud rate off you — and the way to claw it back
|
||||||
|
is the ANSam beat from hop 47: `SPACE` on the phase reversal, 450 ms, four clean ones.
|
||||||
|
Terminal levels opt out with `noRetrain: true`, because stealing `SPACE` from someone
|
||||||
|
mid-sentence isn't a mechanic, it's a bug with a story attached.
|
||||||
|
|
||||||
## Audio
|
## Audio
|
||||||
|
|
||||||
[`src/audio.js`](src/audio.js) — every tone is oscillators, nothing is sampled, and
|
[`src/audio.js`](src/audio.js) — every tone is oscillators, nothing is sampled, and
|
||||||
@ -117,5 +125,4 @@ to zero in a hidden tab and that makes the game look broken when it's only aslee
|
|||||||
- **Playing both fragments** — fragmenting costs TTL, it doesn't split you into two packets you alternate between. Add when a level has a corridor narrow enough to need it.
|
- **Playing both fragments** — fragmenting costs TTL, it doesn't split you into two packets you alternate between. Add when a level has a corridor narrow enough to need it.
|
||||||
- **The 200 philes** — 74 written in [`src/philes.js`](src/philes.js), all readable with `P`. Adding the next 126 is a writing job, not a code job: put the text in that file, call `ctx.phile('id')` wherever it should be found.
|
- **The 200 philes** — 74 written in [`src/philes.js`](src/philes.js), all readable with `P`. Adding the next 126 is a writing job, not a code job: put the text in that file, call `ctx.phile('id')` wherever it should be found.
|
||||||
- **Era-generated music** — the design doc says each level's pad is built from that era's protocol. Right now they're chords.
|
- **Era-generated music** — the design doc says each level's pad is built from that era's protocol. Right now they're chords.
|
||||||
- **The retrain** — a mid-game renegotiation that drops your baud without warning. One system, touches every level after it.
|
|
||||||
- Levels run 3–8 minutes against a design doc that asks for 5–20. The genres, mechanics and set pieces are all in — the *duration* is still short. See [STAGES.md](STAGES.md) for a per-hop breakdown of what's in and what's next.
|
- Levels run 3–8 minutes against a design doc that asks for 5–20. The genres, mechanics and set pieces are all in — the *duration* is still short. See [STAGES.md](STAGES.md) for a per-hop breakdown of what's in and what's next.
|
||||||
|
|||||||
@ -358,8 +358,8 @@ TIME_WAIT with FIN saying *told you*, then it loops to hop 1.
|
|||||||
|
|
||||||
| | |
|
| | |
|
||||||
|---|---|
|
|---|---|
|
||||||
| **The philes** | 73 of 200 written. All in `src/philes.js`, all readable with `P`. Pure writing. |
|
| **The philes** | 75 of 200 written. All in `src/philes.js`, all readable with `P`. Pure writing. |
|
||||||
| **Audio** | Every tone is real but the *music* isn't built — the design says each level's pad is generated from that era's protocol and right now they're just chords. |
|
| **Audio** | Every tone is real but the *music* isn't built — the design says each level's pad is generated from that era's protocol and right now they're just chords. |
|
||||||
| **The retrain** | A mid-game renegotiation that drops your baud without warning. One system, touches every level after it. |
|
| ~~**The retrain**~~ | **DONE** — `src/retrain.js`. Fires once per run after hop 51, drops you a notch, and the recovery is the 450 ms ANSam beat from hop 47. Terminal levels opt out. |
|
||||||
| **Difficulty** | Nothing scales. TTL is the only pressure and it's linear. |
|
| **Difficulty** | Nothing scales. TTL is the only pressure and it's linear. |
|
||||||
| **The 64 hops** | 13 levels covering 64 hop numbers. Either fill in more levels or make the existing ones properly own their ranges. |
|
| **The 64 hops** | 13 levels covering 64 hop numbers. Either fill in more levels or make the existing ones properly own their ranges. |
|
||||||
|
|||||||
@ -172,7 +172,7 @@ const FILES = [
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
id: 'board', hop: 35, title: 'THE BOARD', via: 'sysop.2400.8n1', ms: '2400 ms',
|
id: 'board', hop: 35, title: 'THE BOARD', via: 'sysop.2400.8n1', ms: '2400 ms',
|
||||||
baud: null, // whatever you earned at the handshake. that is the point.
|
baud: null, noRetrain: true, // whatever you earned at the handshake. that is the point.
|
||||||
|
|
||||||
async mount(ctx) {
|
async mount(ctx) {
|
||||||
S = { t: 0, busyTries: 0, cargo: 0, uploads: 0, nup: false, elite: false, bombed: false, done: false,
|
S = { t: 0, busyTries: 0, cargo: 0, uploads: 0, nup: false, elite: false, bombed: false, done: false,
|
||||||
|
|||||||
@ -54,7 +54,7 @@ let S = {};
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
id: 'cuckoo', hop: 29, title: "THE CUCKOO'S EGG", via: 'lbl.berkeley.1986', ms: '75 ms',
|
id: 'cuckoo', hop: 29, title: "THE CUCKOO'S EGG", via: 'lbl.berkeley.1986', ms: '75 ms',
|
||||||
baud: 1,
|
baud: 1, noRetrain: true,
|
||||||
|
|
||||||
async mount(ctx) {
|
async mount(ctx) {
|
||||||
S = { t: 0, lines: 50, read: 0, cwd: '/', gone: false, caught: false, elapsed: 0 };
|
S = { t: 0, lines: 50, read: 0, cwd: '/', gone: false, caught: false, elapsed: 0 };
|
||||||
|
|||||||
@ -90,6 +90,21 @@ const TALK_RMS = [
|
|||||||
'you may go back to what you were doing. i am aware of how i come across.',
|
'you may go back to what you were doing. i am aware of how i come across.',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// He does not get angrier. He gets tireder. And at the end he stops trying to
|
||||||
|
// change your mind and changes the software instead, which is entirely in character
|
||||||
|
// and is, in fairness, the whole strategy that worked.
|
||||||
|
const UNAME_AGAIN = [
|
||||||
|
[ "i'd just like to interject for a moment.",
|
||||||
|
'you have just done it again. what you are referring to as linux is in',
|
||||||
|
'fact GNU/linux. i will not labour the point. i have laboured it.' ],
|
||||||
|
[ "i'd just like to interject.",
|
||||||
|
'GNU/linux. the kernel is the part called linux. the rest of it is the',
|
||||||
|
'part i spent eight years on.' ],
|
||||||
|
[ 'GNU/linux.' ],
|
||||||
|
[ '...' ],
|
||||||
|
[ 'no. i am not doing this again.' ],
|
||||||
|
];
|
||||||
|
|
||||||
const TALK = [
|
const TALK = [
|
||||||
['minna', 'you are new'],
|
['minna', 'you are new'],
|
||||||
['you', ''],
|
['you', ''],
|
||||||
@ -129,11 +144,11 @@ const BUILD = [
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
id: 'shell', hop: 43, title: 'THE SHELL', via: 'kruuna.helsinki.fi', ms: '31 ms',
|
id: 'shell', hop: 43, title: 'THE SHELL', via: 'kruuna.helsinki.fi', ms: '31 ms',
|
||||||
baud: null,
|
baud: null, noRetrain: true,
|
||||||
|
|
||||||
async mount(ctx) {
|
async mount(ctx) {
|
||||||
S = { t: 0, done: false, built: false, building: false, buildAt: 0, talked: false, read: false,
|
S = { t: 0, done: false, built: false, building: false, buildAt: 0, talked: false, read: false,
|
||||||
rmsWaiting: false, metRms: false };
|
rmsWaiting: false, metRms: false, unameCount: 0, unamePatched: false, notedPatch: false };
|
||||||
window.__S = S; // debug handle
|
window.__S = S; // debug handle
|
||||||
|
|
||||||
ctx.scene.background = new THREE.Color(0x000000);
|
ctx.scene.background = new THREE.Color(0x000000);
|
||||||
@ -165,7 +180,7 @@ export default {
|
|||||||
await ctx.sleep(900);
|
await ctx.sleep(900);
|
||||||
ctx.phile('always-on');
|
ctx.phile('always-on');
|
||||||
|
|
||||||
ctx.hint('<k>who</k> <k>news</k> <k>talk</k> <k>make</k> <k>logout</k>');
|
ctx.hint('<k>who</k> <k>news</k> <k>talk</k> <k>make</k> <k>uname</k> <k>logout</k>');
|
||||||
ctx.say('type <b>help</b>. it is a real shell and it is on all the time.', 5000);
|
ctx.say('type <b>help</b>. it is a real shell and it is on all the time.', 5000);
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
@ -178,7 +193,7 @@ export default {
|
|||||||
if (!cmd) continue;
|
if (!cmd) continue;
|
||||||
|
|
||||||
if (cmd === 'help') {
|
if (cmd === 'help') {
|
||||||
term.writeln('|08 who news talk minna make uptime logout');
|
term.writeln('|08 who news talk minna make uname uptime logout');
|
||||||
term.writeln('|08 (and it will keep compiling while you do all of those.)');
|
term.writeln('|08 (and it will keep compiling while you do all of those.)');
|
||||||
} else if (cmd === 'who') {
|
} else if (cmd === 'who') {
|
||||||
WHO.forEach(([u, tty, at, from]) =>
|
WHO.forEach(([u, tty, at, from]) =>
|
||||||
@ -187,6 +202,21 @@ export default {
|
|||||||
term.writeln('|03 thirty-eight logged in. the board had forty-four users in nine years');
|
term.writeln('|03 thirty-eight logged in. the board had forty-four users in nine years');
|
||||||
term.writeln('|03 and could hold exactly one of them at a time.');
|
term.writeln('|03 and could hold exactly one of them at a time.');
|
||||||
ctx.phile('multiuser');
|
ctx.phile('multiuser');
|
||||||
|
} else if (cmd === 'uname') {
|
||||||
|
if (S.unamePatched) {
|
||||||
|
term.writeln(arg === '-a'
|
||||||
|
? '|11 GNU/Linux kruuna 0.12 #1 Sat Jan 4 12:33:16 EET 1992 i386 GNU/Linux'
|
||||||
|
: '|11 GNU/Linux');
|
||||||
|
if (!S.notedPatch) {
|
||||||
|
S.notedPatch = true;
|
||||||
|
term.writeln('|08 (it has said that since he rebuilt it. he did not ask anybody.)');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
term.writeln(arg === '-a'
|
||||||
|
? '|11 Linux kruuna 0.12 #1 Sat Jan 4 12:33:16 EET 1992 i386'
|
||||||
|
: '|11 Linux');
|
||||||
|
await this.interject(ctx, term);
|
||||||
|
}
|
||||||
} else if (cmd === 'uptime') {
|
} else if (cmd === 'uptime') {
|
||||||
term.writeln('|11 11:34am up 91 days, 3:12, 38 users, load average: 2.14, 1.88, 1.51');
|
term.writeln('|11 11:34am up 91 days, 3:12, 38 users, load average: 2.14, 1.88, 1.51');
|
||||||
term.writeln('|08 ninety-one days. it has not been switched off since february.');
|
term.writeln('|08 ninety-one days. it has not been switched off since february.');
|
||||||
@ -278,6 +308,44 @@ export default {
|
|||||||
ctx.say('he is right, and it took him nine years of saying it to be heard once.', 6000);
|
ctx.say('he is right, and it took him nine years of saying it to be heard once.', 6000);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// ── each one shorter and flatter than the last, until he gives up on you and
|
||||||
|
// goes and edits the program instead.
|
||||||
|
async interject(ctx, term) {
|
||||||
|
if (!S.metRms) return; // he has to have introduced himself first
|
||||||
|
const n = S.unameCount || 0;
|
||||||
|
if (n >= UNAME_AGAIN.length) return;
|
||||||
|
S.unameCount = n + 1;
|
||||||
|
const lines = UNAME_AGAIN[n];
|
||||||
|
await ctx.sleep(700);
|
||||||
|
term.writeln('');
|
||||||
|
term.writeln('|14Message from rms@gnu.ai.mit.edu on ttyp7 ...');
|
||||||
|
for (const line of lines) {
|
||||||
|
term.write('|14 ');
|
||||||
|
for (const ch of line) {
|
||||||
|
term.write(ch);
|
||||||
|
if (ch !== ' ') A.blip(1400 + Math.random() * 200, 0.004, 0.010);
|
||||||
|
// he types faster and flatter every time
|
||||||
|
await ctx.sleep(Math.max(6, 20 - n * 4));
|
||||||
|
}
|
||||||
|
term.writeln('');
|
||||||
|
await ctx.sleep(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (S.unameCount >= UNAME_AGAIN.length) {
|
||||||
|
// he stops trying to change your mind and changes the program. which is, in
|
||||||
|
// fairness, the strategy that actually worked.
|
||||||
|
await ctx.sleep(900);
|
||||||
|
term.writeln('');
|
||||||
|
await term.paint('|08 rms@ttyp7 is running: cd /usr/src/gnu/uname && make install\n', 1600);
|
||||||
|
await ctx.sleep(900);
|
||||||
|
await term.paint('|10 [rms has rebuilt uname. he did not ask anybody.]\n', 1400);
|
||||||
|
S.unamePatched = true;
|
||||||
|
A.burst(1320, 1760, 0.26, 0.16);
|
||||||
|
ctx.phile('uname');
|
||||||
|
ctx.say('he stopped arguing with you and edited the program. that is the entire<br>strategy, and it is the one that worked.', 6400);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
async logout(ctx, term) {
|
async logout(ctx, term) {
|
||||||
S.done = true;
|
S.done = true;
|
||||||
In.releaseText();
|
In.releaseText();
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import * as HUD from './hud.js';
|
|||||||
import { Link } from './link.js';
|
import { Link } from './link.js';
|
||||||
import { World } from './kit.js';
|
import { World } from './kit.js';
|
||||||
import { lookup as phileLookup, TOTAL as PHILE_TOTAL } from './philes.js';
|
import { lookup as phileLookup, TOTAL as PHILE_TOTAL } from './philes.js';
|
||||||
|
import * as Retrain from './retrain.js';
|
||||||
|
|
||||||
import coldboot from './levels/coldboot.js';
|
import coldboot from './levels/coldboot.js';
|
||||||
import stack from './levels/stack.js';
|
import stack from './levels/stack.js';
|
||||||
@ -194,6 +195,7 @@ export async function goto(id, { restart = false } = {}) {
|
|||||||
const mounting = (async () => { try { await nextLevel.mount(ctx); } catch (e) { console.error('mount failed:', id, e); } })();
|
const mounting = (async () => { try { await nextLevel.mount(ctx); } catch (e) { console.error('mount failed:', id, e); } })();
|
||||||
HUD.veil(0, 520);
|
HUD.veil(0, 520);
|
||||||
await mounting;
|
await mounting;
|
||||||
|
Retrain.arm(ctx, nextLevel);
|
||||||
refreshHud();
|
refreshHud();
|
||||||
saveGame();
|
saveGame();
|
||||||
if (fragNote) { HUD.say(fragNote, 3600); fragNote = null; }
|
if (fragNote) { HUD.say(fragNote, 3600); fragNote = null; }
|
||||||
@ -218,6 +220,8 @@ export function step(dt) {
|
|||||||
if (In.hit('KeyP') && !In.hasTextCapture()) toggleReader();
|
if (In.hit('KeyP') && !In.hasTextCapture()) toggleReader();
|
||||||
if (In.hit('Backquote')) toggleDev();
|
if (In.hit('Backquote')) toggleDev();
|
||||||
|
|
||||||
|
if (!game.paused) Retrain.update(dt, ctx);
|
||||||
|
|
||||||
if (!game.paused && game.level && game.level.update) {
|
if (!game.paused && game.level && game.level.update) {
|
||||||
try { game.level.update(dt, game.time, ctx); } catch (e) { console.error(e); game.level.update = null; }
|
try { game.level.update(dt, game.time, ctx); } catch (e) { console.error(e); game.level.update = null; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -725,6 +725,24 @@ he has been on ttyp7 since six in the morning. he saw who(1) go past. he
|
|||||||
waited until you called it an operating system.`,
|
waited until you called it an operating system.`,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'uname': {
|
||||||
|
title: 'he rebuilt uname',
|
||||||
|
from: 'ttyp7, having given up on you personally',
|
||||||
|
body: `five times you ran it and five times it said Linux and five times he said
|
||||||
|
something, each one shorter and flatter than the last, until he stopped.
|
||||||
|
|
||||||
|
then he went and edited the program so that it says GNU/Linux, and
|
||||||
|
installed it, and did not ask anybody.
|
||||||
|
|
||||||
|
which is, in fairness, the entire strategy. he did not win the argument
|
||||||
|
about naming and he was never going to. what he did instead was write a
|
||||||
|
compiler, a linker, a shell, a libc, a debugger and a text editor, put all
|
||||||
|
of them under a licence that forces everyone downstream to keep them free,
|
||||||
|
and wait.
|
||||||
|
|
||||||
|
that part worked completely. you are reading this on it.`,
|
||||||
|
},
|
||||||
|
|
||||||
// ── hop 45–50, the handshake ───────────────────────────────────────────
|
// ── hop 45–50, the handshake ───────────────────────────────────────────
|
||||||
|
|
||||||
'dtmf': {
|
'dtmf': {
|
||||||
@ -773,6 +791,21 @@ different speeds, and why the number it lands on is not a specification.
|
|||||||
it is a measurement of the copper between you.`,
|
it is a measurement of the copper between you.`,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'retrain': {
|
||||||
|
title: 'retrain',
|
||||||
|
from: 'the line, changing its mind',
|
||||||
|
body: `it did not stay the way you negotiated it.
|
||||||
|
|
||||||
|
the copper got wet, or somebody in the house picked up an extension, or
|
||||||
|
the exchange quietly rerouted you through a worse pair at three in the
|
||||||
|
morning — and the two modems stopped in the middle of whatever you were
|
||||||
|
doing and did the entire handshake again from the top.
|
||||||
|
|
||||||
|
sometimes you came back faster. usually you did not. and the number it
|
||||||
|
landed on the second time was the number you lived with, because nobody
|
||||||
|
was going to hang up and try again at those prices.`,
|
||||||
|
},
|
||||||
|
|
||||||
'connect': {
|
'connect': {
|
||||||
title: 'CONNECT 14400/ARQ/V32B/LAPM/V42BIS',
|
title: 'CONNECT 14400/ARQ/V32B/LAPM/V42BIS',
|
||||||
from: 'the line that told you what you got',
|
from: 'the line that told you what you got',
|
||||||
|
|||||||
125
src/retrain.js
Normal file
125
src/retrain.js
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
// retrain.js — the line does not stay the way you negotiated it.
|
||||||
|
//
|
||||||
|
// Real modems renegotiated mid-call. The copper got wet, somebody picked up an
|
||||||
|
// extension, the exchange rerouted you through a worse pair, and the two ends
|
||||||
|
// stopped and did the whole handshake again in the middle of your download.
|
||||||
|
//
|
||||||
|
// So: at some point after the handshake, wherever you happen to be, the line
|
||||||
|
// degrades and takes a baud rate off you. You get one chance to claw it back, and
|
||||||
|
// the chance is the ANSam beat you learned at hop 47 — press on the phase reversal,
|
||||||
|
// four times, at 450 ms, which is a real interval you have already been taught.
|
||||||
|
|
||||||
|
import * as A from './audio.js';
|
||||||
|
import * as In from './input.js';
|
||||||
|
import * as HUD from './hud.js';
|
||||||
|
|
||||||
|
const NEED = 4; // clean reversals to win the line back
|
||||||
|
const WINDOW = 0.20; // how close to the reversal you have to be
|
||||||
|
|
||||||
|
let R = {
|
||||||
|
armed: false, live: false, t: 0, cool: 0,
|
||||||
|
hits: 0, misses: 0, ans: null, sinceFlip: 0, phase: 1, done: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function reset() {
|
||||||
|
if (R.ans) { R.ans.stop(); R.ans = null; }
|
||||||
|
R = { armed: false, live: false, t: 0, cool: 0, hits: 0, misses: 0,
|
||||||
|
ans: null, sinceFlip: 0, phase: 1, done: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
// A level says whether it can survive being interrupted. Terminal levels cannot —
|
||||||
|
// SPACE is a character there, and stealing it mid-sentence is not a mechanic, it is
|
||||||
|
// a bug with a story attached.
|
||||||
|
export function arm(ctx, level) {
|
||||||
|
reset();
|
||||||
|
if (!level || level.noRetrain) return;
|
||||||
|
if (ctx.game.flags.retrained) return; // once a run. it is a beat, not a tax.
|
||||||
|
if (ctx.link.index <= 1) return; // nothing left to take
|
||||||
|
if (level.hop < 51) return; // only after you have earned a rate
|
||||||
|
R.armed = true;
|
||||||
|
R.cool = 22 + Math.random() * 40; // somewhere in the middle of the level
|
||||||
|
}
|
||||||
|
|
||||||
|
export function update(dt, ctx) {
|
||||||
|
if (!R.armed || R.done) return false;
|
||||||
|
R.t += dt;
|
||||||
|
|
||||||
|
if (!R.live) {
|
||||||
|
R.cool -= dt;
|
||||||
|
if (R.cool > 0) return false;
|
||||||
|
return begin(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── the ANSam beat. 450 ms, exactly as it was at hop 47.
|
||||||
|
R.sinceFlip += dt;
|
||||||
|
if (R.sinceFlip >= 0.45) {
|
||||||
|
R.sinceFlip -= 0.45;
|
||||||
|
R.phase = -R.phase;
|
||||||
|
R.flipAt = R.t;
|
||||||
|
R.counted = false;
|
||||||
|
R.flash = 0.42;
|
||||||
|
A.relayClick(0.13, 0.6); // the tick you are aiming at
|
||||||
|
}
|
||||||
|
R.flash = Math.max(0.14, (R.flash || 0.14) - dt * 3.1);
|
||||||
|
ctx.link.glitch = R.flash;
|
||||||
|
|
||||||
|
if (In.jumpHit()) {
|
||||||
|
const off = Math.abs(R.t - (R.flipAt ?? -9));
|
||||||
|
if (off < WINDOW && !R.counted) {
|
||||||
|
R.counted = true;
|
||||||
|
R.hits++;
|
||||||
|
A.burst(2100, 1050, 0.10, 0.16);
|
||||||
|
HUD.say(`<b>retrain</b> ${R.hits}/${NEED}`, 900);
|
||||||
|
if (R.hits >= NEED) return win(ctx);
|
||||||
|
} else {
|
||||||
|
R.misses++;
|
||||||
|
R.hits = 0;
|
||||||
|
A.burst(300, 220, 0.14, 0.14);
|
||||||
|
HUD.say('<b>retrain</b> off the reversal — again', 1200);
|
||||||
|
if (R.misses >= 6) return lose(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function begin(ctx) {
|
||||||
|
R.live = true;
|
||||||
|
R.sinceFlip = 0; R.flipAt = -9;
|
||||||
|
const before = ctx.link.index;
|
||||||
|
ctx.setBaud(Math.max(0, before - 1));
|
||||||
|
ctx.game.flags.retrained = true;
|
||||||
|
A.noCarrier();
|
||||||
|
R.flash = 0.5;
|
||||||
|
R.ans = A.ansam({ gain: 0.11 });
|
||||||
|
HUD.say('<b>the line is going.</b> somebody picked up an extension, or the pair got wet.<br>' +
|
||||||
|
`you are down to ${ctx.link.label}.`, 5600);
|
||||||
|
setTimeout(() => HUD.say('it is retraining. <b>SPACE on the phase reversal</b> — four hundred and fifty milliseconds,<br>the same beat as the bong. four clean ones and you get the line back.', 6600), 5800);
|
||||||
|
HUD.hint('<k>SPACE</k>on the reversal');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function win(ctx) {
|
||||||
|
finish(ctx);
|
||||||
|
const spec = ctx.setBaud(Math.min(6, ctx.link.index + 1));
|
||||||
|
A.burst(1200, 2400, 0.3, 0.18);
|
||||||
|
HUD.say(`<b>CONNECT ${spec.rate}</b> — you got it back. it will not always go like that.`, 5200);
|
||||||
|
ctx.phile('retrain');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function lose(ctx) {
|
||||||
|
finish(ctx);
|
||||||
|
A.burst(480, 620, 0.35, 0.16);
|
||||||
|
HUD.say('it settled where it settled. that is what the rest of this looks like now.', 5200);
|
||||||
|
ctx.phile('retrain');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function finish(ctx) {
|
||||||
|
R.done = true; R.live = false; R.armed = false;
|
||||||
|
if (R.ans) { R.ans.stop(); R.ans = null; }
|
||||||
|
ctx.link.glitch = 0;
|
||||||
|
HUD.hint('');
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isLive = () => R.live;
|
||||||
Loading…
Reference in New Issue
Block a user