87 lines
3.3 KiB
JavaScript
87 lines
3.3 KiB
JavaScript
/**
|
|
* The "decode" scramble — the shared type-out effect of the console.
|
|
*
|
|
* The main menu uses it on the Galaxy Seed field (it "decodes" in as the
|
|
* panel assembles); the game uses it on the system dossier HUD — the
|
|
* system name and every data line below it type themselves in as the
|
|
* ship arrives. One effect, two scenes.
|
|
*
|
|
* Over `dur` ms the target string reveals left-to-right while the
|
|
* unrevealed slots hold random glyphs from the decode alphabet — the
|
|
* console pulling a signal out of static. With `reverse = true` the
|
|
* same reveal plays BACKWARDS (value → shrinking stable prefix → static
|
|
* → '') — the console dropping the signal, used to deconstruct the
|
|
* dossier when it folds away.
|
|
*
|
|
* Pure (no Phaser): build one per line with a start time, poll
|
|
* `display(time)` from the scene's `update()`, and drop it once
|
|
* `finished(time)`. The time base is the engine loop time the scene's
|
|
* `update(time, delta)` receives (=== `this.time.now` after that frame).
|
|
*
|
|
* const dec = new ScrambleDecode('Kepler-9', t0, DECODE_DURATION);
|
|
* // each frame: if (dec.started(time)) text.setText(dec.display(time));
|
|
* // done when: dec.finished(time)
|
|
*/
|
|
|
|
/** The alphabet unrevealed slots draw from (no vowels, no 0/1 — signal, not language). */
|
|
export const DECODE_CHARS = 'abcdefghjkmnpqrstuvwxyz23456789#%+*<>?';
|
|
|
|
/** The menu's seed-field window (ms) — the canonical decode timing. */
|
|
export const DECODE_DURATION = 620;
|
|
|
|
export class ScrambleDecode {
|
|
/**
|
|
* @param {string} value the final text
|
|
* @param {number} t0 absolute start time (scene time, in ms)
|
|
* @param {number} [dur] reveal window in ms
|
|
* @param {boolean} [reverse] false = construct ('' → static → value);
|
|
* true = deconstruct (value → static → ''), the same reveal in reverse
|
|
*/
|
|
constructor(value, t0, dur = DECODE_DURATION, reverse = false) {
|
|
this.value = String(value ?? '');
|
|
this.t0 = t0;
|
|
this.dur = dur > 0 ? dur : 1;
|
|
this.reverse = !!reverse;
|
|
}
|
|
|
|
started(time) {
|
|
return time >= this.t0;
|
|
}
|
|
|
|
finished(time) {
|
|
return (time - this.t0) / this.dur >= 1;
|
|
}
|
|
|
|
/**
|
|
* The display string at `time`: a stable prefix of the target with the
|
|
* remaining slots scrambled. Forward: the prefix grows. Reverse: it
|
|
* shrinks. Before the start: forward is '', reverse holds the value
|
|
* (the line is already showing it). After the finish: forward holds
|
|
* the value, reverse is '' (the reveal lands at ~87% of the window,
|
|
* then holds).
|
|
*/
|
|
display(time) {
|
|
if (!this.started(time)) return this.reverse ? this.value : '';
|
|
if (this.finished(time)) return this.reverse ? '' : this.value;
|
|
const u = (time - this.t0) / this.dur;
|
|
const progress = this.reverse
|
|
? Math.max(0, Math.min(1, (1 - u) * 1.15))
|
|
: Math.max(0, Math.min(1, u * 1.15));
|
|
const reveal = Math.floor(progress * this.value.length);
|
|
let out = '';
|
|
for (let i = 0; i < this.value.length; i++) {
|
|
out += i < reveal ? this.value[i] : DECODE_CHARS[(Math.random() * DECODE_CHARS.length) | 0];
|
|
}
|
|
return out;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Per-line window for multi-line decodes (the system dossier): ~70 ms per
|
|
* character — the menu's feel — floored so short lines don't flicker and
|
|
* capped so long ones don't drag.
|
|
*/
|
|
export function decodeDur(length = 0) {
|
|
return Math.max(420, Math.min(880, 320 + length * 46));
|
|
}
|