This commit is contained in:
Dan 2026-01-21 17:44:05 +00:00
parent 3e2d6c6a5c
commit cd58c07c6b
4 changed files with 362 additions and 23 deletions

View file

@ -173,6 +173,11 @@ class SceneManager {
continue; continue;
} }
if (block.type === "glitch") {
await this._renderGlitch(block);
continue;
}
// Text with optional className (supports html: true for HTML content) // Text with optional className (supports html: true for HTML content)
if (block.text !== undefined) { if (block.text !== undefined) {
if (block.html) { if (block.html) {
@ -611,4 +616,126 @@ class SceneManager {
await Promise.all(fadePromises); await Promise.all(fadePromises);
this.activeSounds.clear(); this.activeSounds.clear();
} }
// Render glitching text effect
async _renderGlitch(block) {
const text = this._interpolateText(block.text || "");
const intensity = block.intensity || 0.3; // How much glitch (0-1)
const spread = block.spread || 2; // How many lines above/below to infect
const speed = block.speed || 50; // Animation speed in ms
const duration = block.duration || 2000; // How long the glitch lasts
const className = block.className || "glitch-text";
// Glitch character pool - mix of unicode, symbols, and corrupted chars
const glitchChars = [
"▓", "▒", "░", "█", "▀", "▄", "▌", "▐", "║", "╬", "╣", "╠",
"¡", "¿", "‽", "※", "§", "¶", "†", "‡", "∞", "≈", "≠", "±",
"░", "▒", "▓", "█", "▀", "▄", "▌", "▐", "╔", "╗", "╚", "╝",
"Ω", "∑", "∏", "∫", "√", "∂", "∆", "∇", "∈", "∉", "∩", "",
"̴", "̵", "̶", "̷", "̸", "̡", "̢", "̧", "̨", "̛", "̖", "̗",
"É", "È", "Ê", "Ë", "Á", "À", "Â", "Ã", "Ä", "Å", "Æ", "Ç",
"Ñ", "Õ", "Ö", "Ø", "Ú", "Ù", "Û", "Ü", "Ý", "Þ", "ß", "ð",
"!", "@", "#", "$", "%", "^", "&", "*", "?", "~", "`", "|"
];
// Generate the glitched text
const generateGlitch = (baseText, glitchAmount) => {
let result = "";
for (let i = 0; i < baseText.length; i++) {
if (Math.random() < glitchAmount && baseText[i] !== " ") {
// Replace character with glitch
result += glitchChars[Math.floor(Math.random() * glitchChars.length)];
} else {
result += baseText[i];
}
}
return result;
};
// Generate random glitch lines that "infect" surrounding area
const generateInfectionLines = (baseLength) => {
const lines = [];
for (let i = 0; i < spread; i++) {
const lineLength = Math.floor(baseLength * Math.random() * 0.7);
const offset = Math.floor(Math.random() * baseLength * 0.3);
const glitchLine = " ".repeat(offset) +
Array.from({ length: lineLength }, () =>
glitchChars[Math.floor(Math.random() * glitchChars.length)]
).join("");
lines.push(glitchLine);
}
return lines;
};
// Create container for animated glitch
const containerId = `glitch-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
this.adapter.printHTML(
`<div id="${containerId}" class="${className}"></div>`
);
const container = this.adapter.terminal.output.querySelector(`#${containerId}`);
if (!container) return;
// Animation loop
const startTime = Date.now();
let finalLinesAbove = [];
let finalLinesBelow = [];
const animate = () => {
const elapsed = Date.now() - startTime;
if (elapsed >= duration) {
// Final state - show original text with minimal glitch, keep infection lines
const finalGlitch = generateGlitch(text, intensity * 0.1);
let output = "";
finalLinesAbove.forEach(line => {
output += `<div class="glitch-line glitch-above">${line}</div>`;
});
output += `<div class="glitch-line glitch-main">${finalGlitch}</div>`;
finalLinesBelow.forEach(line => {
output += `<div class="glitch-line glitch-below">${line}</div>`;
});
container.innerHTML = output;
return;
}
// Current glitch intensity (ramps up then down)
const progress = elapsed / duration;
const currentIntensity = intensity * Math.sin(progress * Math.PI);
// Generate infected lines above
const linesAbove = generateInfectionLines(text.length);
const linesBelow = generateInfectionLines(text.length);
// Store for final state
finalLinesAbove = linesAbove;
finalLinesBelow = linesBelow;
// Generate glitched main text
const glitchedText = generateGlitch(text, currentIntensity);
// Build output
let output = "";
linesAbove.forEach(line => {
output += `<div class="glitch-line glitch-above">${line}</div>`;
});
output += `<div class="glitch-line glitch-main">${glitchedText}</div>`;
linesBelow.forEach(line => {
output += `<div class="glitch-line glitch-below">${line}</div>`;
});
container.innerHTML = output;
// Continue animation
setTimeout(animate, speed);
};
// Start animation
animate();
// Wait for animation to complete
await this._sleep(duration);
}
} }

View file

@ -34,6 +34,7 @@ const boxingDayGame = {
read_new_message: false, read_new_message: false,
found_number: false, found_number: false,
dialed_lighthouse: false, dialed_lighthouse: false,
seen_archive_glitch: false,
// deletion flags (this needs to persist across game sessions somehow... TBD) // deletion flags (this needs to persist across game sessions somehow... TBD)
archives_deleted: false, archives_deleted: false,
@ -621,15 +622,26 @@ const boxingDayGame = {
}, },
board_archives: { board_archives: {
title: "The Archives", // title: "The Archives",
content: [ content: [
"═══ THE ARCHIVES ═══", {
{ text: "Historical posts - Read only", className: "info" }, type: "table",
"", title: "THE ARCHIVES",
" [1998] The Lighthouse Project - NightWatchman", headers: ["#", "SUBJECT", "OP", "LAST"],
" [1999] Frequencies and Patterns - Signal_Lost", rows: [
" [1999] RE: Has anyone heard from Keeper? - Anonymous", ["1", "The Lighthouse Project", "NightWatchman", "1998"],
"", ["2", "Frequencies and Patterns", "Signal_Lost", "1999"],
["3", "RE: Has anyone heard from Keeper?", "[UNKNOWN]", "1999"],
],
widths: [4, 35, 16, 8],
align: ["right", "left", "right", "left"],
style: "single",
},
{
text: "Historical posts, read only...",
italic: true,
className: "info",
},
{ {
condition: { not: "visited.archive_warning" }, condition: { not: "visited.archive_warning" },
content: [ content: [
@ -655,14 +667,17 @@ const boxingDayGame = {
}, },
archive_lighthouse: { archive_lighthouse: {
title: "The Lighthouse Project", //title: "The Lighthouse Project",
content: [ content: [
"═══ The Lighthouse Project ═══",
{ {
text: "Posted by: NightWatchman - November 15, 1998", type: "table",
className: "info", title: "The Lighthouse Project",
headers: ["FROM", "TO", "DATE"],
rows: [["NightWatchman [SYSOP]", "All", "15/11/98"]],
widths: [25, 15, 10],
align: ["left", "left", "left"],
style: "single",
}, },
"",
"Some of you have asked about the secondary BBS.", "Some of you have asked about the secondary BBS.",
"Yes, it exists. No, I can't give you the number.", "Yes, it exists. No, I can't give you the number.",
"", "",
@ -670,14 +685,13 @@ const boxingDayGame = {
"He said he found something in the noise between stations.", "He said he found something in the noise between stations.",
"Patterns that shouldn't exist.", "Patterns that shouldn't exist.",
"", "",
"I hosted his board as a favor.", "I set up his board as a favor.",
"Then one day, he stopped logging in.", "Then one day, he stopped logging in.",
"", "",
"The board is still there. Still running.", "The board is still there. Still running.",
"I check it sometimes. The files he left behind...", "I check it sometimes. The files he left behind...",
"", "",
"Some doors are better left closed.", "Some doors are better left closed.",
"",
{ text: "- NW", className: "info" }, { text: "- NW", className: "info" },
], ],
options: [{ text: "Back", next: "board_archives" }], options: [{ text: "Back", next: "board_archives" }],
@ -710,10 +724,17 @@ const boxingDayGame = {
}, },
archive_keeper: { archive_keeper: {
title: "RE: Has anyone heard from Keeper?", //title: "RE: Has anyone heard from Keeper?",
content: [ content: [
"═══ RE: Has anyone heard from Keeper? ═══", {
{ text: "Posted by: Anonymous - December 20, 1999", className: "info" }, type: "table",
title: "RE: Has anyone heard from Keeper?",
headers: ["FROM", "TO", "DATE"],
rows: [["[UNKNOWN]", "All", "20/12/99"]],
widths: [25, 15, 10],
align: ["left", "left", "left"],
style: "single",
},
"", "",
"He's still there.", "He's still there.",
"In The Lighthouse.", "In The Lighthouse.",
@ -722,17 +743,61 @@ const boxingDayGame = {
"The cascade is ready.", "The cascade is ready.",
"It just needs carriers.", "It just needs carriers.",
"", "",
"555-0237", {
type: "glitch",
text: "ERROR: MEMORY FAULT AT 0x555f0237",
intensity: 0.7,
spread: 0,
speed: 200,
duration: 2000,
className: "error glitch-text",
},
"", "",
"Before midnight on the 31st.", "Before midnight on the 31st.",
"The alignment only happens once.", "The alignment only happens once.",
"", "",
{ {
text: "[This post was flagged for removal but persists]", text: "[This post was flagged for removal but persists]",
className: "error", className: "error",
}, },
{
html: true,
text: "<br /><br />",
},
{
condition: { not: "seen_archive_glitch" },
content: [
{
text: "What the hell was that...",
italic: true,
className: "info",
},
],
else: [
{
text: "The glitch persists...",
italic: true,
className: "info",
},
],
},
{
condition: "found_number",
content: {
text: "The memory location looks oddly like the phone number... 555-0237",
italic: true,
className: "warning",
},
},
],
onAfterRender: [
// Decided to move the phone number out of discovery here..
// Not sure if it should be found in two places
// Message should be enough, surely?
// { set: "found_number", value: true },
{ set: "seen_archive_glitch", value: true },
], ],
onEnter: [{ set: "found_number", value: true }],
options: [{ text: "Back", next: "board_archives" }], options: [{ text: "Back", next: "board_archives" }],
}, },

View file

@ -62,9 +62,56 @@ const testAdventureGame = {
condition: { not: { path: "visited.cursor-talk" } }, condition: { not: { path: "visited.cursor-talk" } },
next: "cursor-talk", next: "cursor-talk",
}, },
{ text: "Test glitch effect", next: "glitch-demo" },
], ],
}, },
"glitch-demo": {
title: "Glitch Effect Demo",
content: [
"The terminal screen begins to distort...",
{ type: "delay", ms: 800 },
"",
{
type: "glitch",
text: "REALITY CORRUPTING",
intensity: 0.5,
spread: 3,
duration: 2500,
},
"",
{ type: "delay", ms: 500 },
"The corruption spreads across nearby lines...",
{ type: "delay", ms: 600 },
"",
{
type: "glitch",
text: "ERROR: MEMORY FAULT AT 0x7FFFFFFF",
intensity: 0.7,
spread: 4,
speed: 40,
duration: 3000,
className: "error glitch-text",
},
"",
{ type: "delay", ms: 500 },
"A subtle glitch flickers briefly...",
{ type: "delay", ms: 400 },
"",
{
type: "glitch",
text: "Connection unstable",
intensity: 0.2,
spread: 1,
duration: 1500,
},
"",
{ type: "delay", ms: 300 },
"The screen stabilizes again.",
],
options: [{ text: "Back to start", next: "start" }],
},
"look-around": { "look-around": {
title: "Searching...", title: "Searching...",
content: ["You search the area carefully...", { type: "delay", ms: 500 }], content: ["You search the area carefully...", { type: "delay", ms: 500 }],

View file

@ -0,0 +1,100 @@
# Glitch Content Type - Usage Guide
The `glitch` content type creates an animated glitching text effect that makes text appear to corrupt and "infect" surrounding lines with random glitch characters.
## Basic Usage
```javascript
{
type: "glitch",
text: "SYSTEM CORRUPTED"
}
```
## Configuration Options
### Required
- **text** (string): The text to glitch
### Optional
- **intensity** (number, 0-1, default: 0.3): How much the text glitches. Higher = more characters replaced
- **spread** (number, default: 2): Number of "infection" lines to show above and below the main text
- **speed** (number, default: 50): Animation frame speed in milliseconds
- **duration** (number, default: 2000): How long the glitch effect lasts in milliseconds
- **className** (string, default: "glitch-text"): CSS class to apply to the container
## Examples
### Subtle Glitch
```javascript
{
type: "glitch",
text: "Connection unstable...",
intensity: 0.2,
spread: 1,
duration: 1500
}
```
### Intense Corruption
```javascript
{
type: "glitch",
text: "ERROR: MEMORY FAULT",
intensity: 0.7,
spread: 4,
speed: 30,
duration: 3000,
className: "error glitch-text"
}
```
### Quick Flicker
```javascript
{
type: "glitch",
text: "Reality fragmenting",
intensity: 0.4,
spread: 2,
speed: 25,
duration: 1000
}
```
### In Scene Content
```javascript
content: [
"The terminal screen begins to distort...",
{ type: "delay", ms: 500 },
{
type: "glitch",
text: "CASCADE.EXE EXECUTING",
intensity: 0.6,
spread: 3,
duration: 2500
},
{ type: "delay", ms: 500 },
"Something is very wrong."
]
```
## Visual Effect Description
The glitch effect:
1. Replaces characters in the text with random glitch symbols (unicode blocks, special chars, etc.)
2. Creates random "infection" lines above and below the main text
3. Animates over time with a sine wave intensity (ramps up, then down)
4. Ends with the text showing minimal corruption
The infection lines are offset randomly and contain random glitch characters, making it look like the corruption is spreading to surrounding content.
## Character Pool
The glitch uses a pool of:
- Block drawing characters (▓▒░█)
- Box drawing characters (║╬╣╠╔╗╚╝)
- Mathematical symbols (Ω∑∏∫√∂∆∇)
- Accented characters (É È Ê Ë Á À)
- Special symbols (¡¿‽※§¶†‡∞≈≠±)
- Combining diacritics (creates zalgo-like text)
- Standard symbols (!@#$%^&*)