Add comprehensive README.md in British English

Co-authored-by: andydixon <7983876+andydixon@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-01-22 09:25:32 +00:00
co-authored by andydixon
parent 38e079a565
commit 18ef134bc5
+553
View File
@@ -0,0 +1,553 @@
# MODE 7 Story Engine
A teletext-style interactive horror story engine with retro CRT aesthetics and a sophisticated branching narrative system.
## Overview
MODE 7 is a browser-based interactive story engine that recreates the look and feel of vintage teletext systems (such as the BBC Micro's MODE 7 display mode). It features:
- **Authentic teletext rendering**: 40×25 character grid with classic colour palette
- **CRT visual effects**: Scanlines, vignette, and glass overlay effects
- **Typewriter-style text output**: Character-by-character animation with retro beeps
- **Branching narrative system**: State-driven story flow with conditional content
- **Atmospheric corruption**: Pressure-based text glitching and visual degradation
- **Mobile support**: Touch-optimised on-screen keyboard for mobile devices
## Quick Start
1. Clone the repository
2. Serve the directory with any static web server (e.g., `python3 -m http.server`)
3. Open `index.html` in a modern web browser
4. Press ENTER to begin the story
## Architecture
### Core Components
The engine consists of three primary modules:
1. **`teletext.js`** - The low-level terminal renderer
- Canvas-based character grid system
- Colour control codes (teletext-style)
- Keyboard input handling
- Visual effects (cursor blinking, flash attribute)
- Pressure-driven corruption system
2. **`story.js`** - The story execution engine
- Step-by-step scene processor
- State management and variable substitution
- Text markup rendering
- Timing and pacing control
- Branching logic
3. **`story.json`** - The story data file
- Scene definitions
- Glitch text pools
- Metadata and configuration
### How It Works
The story engine operates as a linear step processor that executes scenes composed of steps. Each step performs a specific action (displaying text, waiting for input, changing scenes, etc.). The engine maintains a state object that persists across scenes, enabling branching narratives based on player choices.
#### Execution Flow
1. The engine loads `story.json` and initialises the state
2. Starting from the `startScene`, it executes each step in sequence
3. Steps can modify the display, play sounds, wait for input, or change the current scene
4. Player input is captured and stored in the state object
5. Branch steps use state values to determine the next scene
6. The story continues until an `end` step is reached
#### Pressure System
MODE 7 features a sophisticated "pressure" system that gradually increases tension through visual and textual corruption:
- **Baseline pressure**: Slowly increases over time and with story progression
- **Pressure spikes**: Triggered by certain actions (user input, tighten effects, glitches)
- **Visual effects**: Higher pressure causes more frequent flickering and visual noise
- **Text corruption**: "Echo" lines that repeat corrupted versions of player input
- **Glitch bursts**: Intense text corruption sequences that overwhelm the screen
## Story Data Format
The `story.json` file defines the entire interactive experience. It has the following top-level structure:
```json
{
"meta": { ... },
"glitchPools": { ... },
"scenes": { ... },
"startScene": "title"
}
```
### Meta Section
Global configuration and defaults:
```json
{
"meta": {
"title": "Story Title",
"defaultName": "Friend",
"defaultPauseMs": 1200,
"defaultPressTimeoutMs": 1600
}
}
```
- **`title`**: Story title string
- **`defaultName`**: Fallback name if player enters nothing
- **`defaultPauseMs`**: Default pause duration in milliseconds
- **`defaultPressTimeoutMs`**: Default timeout for pressReturn steps
### Glitch Pools
Collections of text fragments used during glitch sequences:
```json
{
"glitchPools": {
"tech": ["AUDIT LOG", "TOKEN", "RATE LIMIT", ...],
"cosmic": ["a corridor of prompts", "a recursion with no base case", ...],
"rogue": ["I don't want to stop.", "Your silence is data.", ...]
}
}
```
- **`tech`**: Technical/system phrases
- **`cosmic`**: Eerie conceptual phrases
- **`rogue`**: First-person intrusive lines
### Scenes
The core of the story. Each scene is identified by a unique key and contains an array of steps:
```json
{
"scenes": {
"title": {
"steps": [
{ "type": "clearScreen" },
{ "type": "raw", "x": 4, "y": 10, "text": "[YELLOW]MODE 7" },
{ "type": "return" },
{ "type": "goto", "scene": "next_scene" }
]
}
}
}
```
## Step Types
Steps are the building blocks of scenes. Each step has a `type` field and type-specific parameters.
### Navigation & Control
#### `goto`
Jump immediately to another scene by ID.
```json
{ "type": "goto", "scene": "scene_id" }
```
#### `end`
Terminates the story loop.
```json
{ "type": "end" }
```
### Screen & Layout
#### `clear`
Clears the current print area only (not the whole screen).
```json
{ "type": "clear" }
```
#### `clearScreen`
Clears the entire screen.
```json
{ "type": "clearScreen" }
```
#### `setArea`
Sets the text print area rectangle using 1-based teletext cell coordinates.
```json
{ "type": "setArea", "x": 1, "y": 4, "w": 38, "h": 16 }
```
- **`x`**: Left column (1-based)
- **`y`**: Top row (1-based)
- **`w`**: Width in columns
- **`h`**: Height in rows
#### `tighten`
Shrinks the current print area inward (creates claustrophobic effect) and increases pressure.
```json
{ "type": "tighten", "step": 2 }
```
- **`step`**: Number of cells to shrink inward on each side
### Text Output
#### `raw`
Prints text at fixed coordinates without wrapping. Ideal for titles and positioned text.
```json
{ "type": "raw", "x": 3, "y": 10, "text": "[WHITE]Hello" }
```
- **`x`**: Column position (1-based)
- **`y`**: Row position (1-based)
- **`text`**: Text to display (supports markup and variables)
#### `line`
Prints wrapped text inside the current print area with typewriter effect.
```json
{ "type": "line", "text": "[WHITE]Some wrapped line" }
```
- **`text`**: Text to display (supports markup and variables)
#### `lineIf`
Conditional line output. Only prints when a state variable matches the expected value.
```json
{ "type": "lineIf", "condVar": "ok", "equals": 1, "text": "[CYAN]Status: holding it together." }
```
- **`condVar`**: State variable to check
- **`equals`**: Expected value (exact match required)
- **`text`**: Text to display if condition is met
### Sound
#### `beep`
Plays a short retro beep sound.
```json
{ "type": "beep", "freq": 880, "ms": 40, "gain": 0.012 }
```
- **`freq`** *(optional)*: Frequency in Hz (default: 880)
- **`ms`** *(optional)*: Duration in milliseconds (default: 40)
- **`gain`** *(optional)*: Volume level (default: 0.012)
### Timing & Pacing
#### `pause`
Silent wait that can be skipped by pressing ENTER.
```json
{ "type": "pause", "ms": 900, "skippable": true }
```
- **`ms`** *(optional)*: Duration in milliseconds (uses `meta.defaultPauseMs` if omitted)
- **`skippable`** *(optional)*: Whether ENTER can skip (default: true)
Note: Pauses are ignored whilst the system is waiting for typed input.
#### `return`
Waits for the player to press ENTER.
```json
{ "type": "return", "timeoutMs": 2000 }
```
- **`timeoutMs`** *(optional)*: Maximum wait time in milliseconds (waits indefinitely if omitted)
#### `pressReturn`
Displays an optional prompt and waits for ENTER or timeout.
```json
{
"type": "pressReturn",
"timeoutMs": 1800,
"prompt": "[YELLOW]Press RETURN to leave sooner. Or wait."
}
```
- **`timeoutMs`** *(optional)*: Maximum wait time (uses `meta.defaultPressTimeoutMs` if omitted)
- **`prompt`** *(optional)*: Text to display before waiting
### Input & Branching
#### `input`
Free-text input field. Player types text and presses ENTER. Result stored in state.
```json
{
"type": "input",
"var": "name",
"maxLen": 18,
"trim": true,
"upper": false,
"default": "Fallback",
"defaultFromMeta": "defaultName"
}
```
- **`var`** *(required)*: State variable name to store result
- **`maxLen`** *(optional)*: Maximum character length (extra characters ignored with beep)
- **`trim`** *(optional)*: Whether to trim whitespace (default: false)
- **`upper`** *(optional)*: Whether to convert to uppercase (default: false)
- **`default`** *(optional)*: Fallback value if player enters nothing
- **`defaultFromMeta`** *(optional)*: Use `meta[key]` as fallback if result is empty
#### `yesno`
Single-key Y/N input. No ENTER required.
```json
{ "type": "yesno", "var": "trust" }
```
- **`var`** *(required)*: State variable name (stores 1 for yes, 0 for no)
#### `branch`
Chooses next scene based on state variable value.
```json
{
"type": "branch",
"var": "trust",
"cases": {
"1": "act1_trust_yes",
"0": "act1_trust_no"
}
}
```
- **`var`** *(required)*: State variable to check
- **`cases`** *(required)*: Object mapping values to scene IDs
Note: Value is coerced to string for comparison. Throws error if no matching case exists.
### Atmosphere & Effects
#### `glitch`
Runs a burst of corrupted system/rogue text and increases pressure.
```json
{ "type": "glitch", "rounds": 10, "intensity": 3 }
```
- **`rounds`** *(optional)*: Number of glitch lines to display (default: 10)
- **`intensity`** *(optional)*: Corruption intensity level (default: 3)
## Text Markup & Variables
### Colour Tags
Text fields support inline colour and style tags:
- **Colours**: `[BLACK]`, `[RED]`, `[GREEN]`, `[YELLOW]`, `[BLUE]`, `[MAGENTA]`, `[CYAN]`, `[WHITE]`
- **Effects**: `[FLASH]`, `[STEADY]`
Tags must be in ALL CAPS inside square brackets. They are converted to control codes and never appear literally on screen.
**Example:**
```json
"text": "[YELLOW]WARNING:[WHITE] System unstable"
```
### Variable Substitution
Use double curly braces to insert state variable values:
```json
"text": "Hello, {{name}}. Today is {{day}}."
```
The engine replaces `{{key}}` with `String(state[key])` or an empty string if the variable doesn't exist.
**Important:** Ensure variables are set (via `input` or `yesno` steps) before they are referenced in text.
## State Management
The `state` object is a simple key-value store that persists across scenes. It is used to:
1. Store player input (`input` and `yesno` steps)
2. Track story progress and decisions
3. Enable conditional content (`lineIf` steps)
4. Drive branching logic (`branch` steps)
### Reserved State Variables
The following state variables are managed internally:
- **`_pressure`**: Current pressure level (0..1)
- **`_pressureStartMs`**: Session start timestamp
- **`_stepsSeen`**: Number of steps executed
- **`_echoCooldown`**: Cooldown counter for echo lines
- **`_echoHistory`**: Recent echo text for deduplication
Avoid using variables prefixed with `_` for story data.
## Branching & Story Flow
### Scene Transitions
Scenes execute linearly until a transition occurs:
- **`goto`** step: Explicitly jumps to a named scene
- **`end`** step: Terminates the story loop
- **End of scene**: If no transition is specified, the story may stall (avoid this!)
### Branching Pattern
A typical branching pattern:
1. Present a choice to the player
2. Use `input` or `yesno` to capture their decision
3. Use `branch` to route to different scenes based on the stored value
**Example:**
```json
{
"steps": [
{ "type": "line", "text": "[WHITE]Do you trust the system? (Y/N)" },
{ "type": "yesno", "var": "trust" },
{ "type": "branch", "var": "trust", "cases": {
"1": "trust_yes_path",
"0": "trust_no_path"
}}
]
}
```
### Loops & Revisits
Scenes can be revisited by using `goto` to jump to earlier scenes. Be cautious of infinite loops—ensure there's always a path to an `end` step or forward progression.
## Authoring Guidelines
### Tone & Style
- **Atmosphere**: Introspective, unsettling, teletext horror
- **Perspective**: Second-person or first-person AI narration works well
- **Scene length**: Keep focused (10–25 steps) rather than extremely long monologues
- **Pacing**: Use `pause`, `clear`, and `tighten` for dramatic effect
### Best Practices
1. **Test all branches**: Ensure every `branch` has valid cases and target scenes exist
2. **Default values**: Provide sensible defaults for `input` steps
3. **Gradual pressure**: Build tension slowly with occasional `tighten` and `glitch` steps
4. **Clear text areas**: Use `clear` between major sections to avoid visual clutter
5. **Validate scene IDs**: Ensure all `goto` and `branch` targets reference valid scenes
### Accessibility Considerations
- Don't rely on player knowledge of code or technical lore
- Avoid explicit gore or graphic violence
- Provide clear prompts for input
- Use colour contrast effectively (test readability)
## Mobile Support
MODE 7 includes touch-optimised mobile support:
- **On-screen keyboard**: Automatically appears during input steps on touch devices
- **Tap to advance**: Tapping the canvas acts as pressing ENTER
- **Visual feedback**: Button presses show visual feedback
- **Responsive layout**: Canvas scales to fit mobile screens
The mobile keyboard is automatically hidden when not needed.
## Development
### Project Structure
```
mode7/
├── index.html # Main HTML entry point
├── css/
│ └── style.css # Styling and CRT effects
├── fonts/
│ └── font.otf # Custom teletext font
└── js/
├── main.js # Application bootstrap
├── core/
│ └── teletext.js # Terminal renderer
└── story/
├── story.js # Story engine
├── story.json # Story data
└── STORY_PROMPT.md # Authoring guide for AI
```
### Extending the Engine
To add new step types:
1. Add the step type handler in `executeStep()` in `story.js`
2. Update the documentation in `story.js` comments
3. Add examples to `STORY_PROMPT.md`
### Testing Stories
1. Make changes to `story.json`
2. Refresh the browser (no build step required)
3. Test all branches and input scenarios
4. Check console for errors (missing scenes, invalid steps, etc.)
## Technical Details
### Browser Compatibility
- Modern browsers with Canvas and Web Audio API support
- Tested on Chrome, Firefox, Safari, Edge
- Mobile browsers (iOS Safari, Chrome Mobile)
### Performance
- Offscreen canvas rendering for text layer
- Requestanimationframe-based render loop
- Minimal DOM manipulation
- Efficient character-by-character typewriter effect
### Font
The custom `TeletextOTF` font recreates the distinctive look of MODE 7 teletext. Falls back to monospace fonts if unavailable.
## Troubleshooting
### Story doesn't start
- Check browser console for errors
- Verify `story.json` is valid JSON (no trailing commas)
- Ensure `startScene` references a valid scene ID
### Text doesn't appear
- Check that the print area (`setArea`) is configured
- Verify colour tags are properly formatted (`[COLOUR]`, not `(COLOUR)`)
- Ensure text isn't being printed outside the visible area
### Branching errors
- Verify all `goto` and `branch` scene IDs exist in `scenes` object
- Ensure `branch` cases cover all possible state values
- Check that state variables are set before being used in branches
### Input not working
- Ensure canvas has focus (click it first)
- Check that `input` steps specify a `var` name
- Verify mobile keyboard appears on touch devices
## Licence
See repository for licence information.
## Credits
MODE 7 is a teletext-style interactive horror experience that pays homage to classic BBC Micro MODE 7 displays and the aesthetics of 1980s computer systems.
---
**Ready to begin?** Edit `story.json` and start crafting your own branching narrative with authentic teletext aesthetics.