An interactive mystery adventure game set in a haunted African kraal under a blood moon. Players awaken in this supernatural setting and must interact with ancestral spirits, solve their riddles, and escape before midnight strikes.
Play in your browser or terminal! The game features a rich visual web interface with character sprites, atmospheric backgrounds, and ambient audio, or can be played as a classic text adventure in the terminal.
You find yourself in a mysterious kraal under a blood moon, surrounded by ancestral spirits who guard the path to freedom. Each spirit presents a unique challenge—solve their riddles correctly to earn your escape, or fail and remain trapped forever in the haunted realm.
The game features:
- Visual web interface with character sprites, backgrounds, and ambient audio
- Atmospheric narrative with rich descriptions and immersive storytelling
- Branching choices that let you explore different locations
- Spirit encounters with unique personalities and backstories
- Riddle challenges that test your wisdom and intuition
- Multiple endings based on your performance
- Memory system that tracks your progress and decisions
- Responsive design that works on desktop, tablet, and mobile devices
- Ensure you have Node.js installed (version 14 or higher)
- Install dependencies:
npm install
The visual web interface provides the full immersive experience with graphics and sound:
-
Development mode (with hot reload):
npm run dev:web
Then open your browser to
http://localhost:5173 -
Production build:
npm run build:web npm run preview:web
Then open your browser to
http://localhost:4173
Web Features:
- 🎨 Character sprites for each spirit (Gogo Thandi, Tokoloshe, Sangoma)
- 🖼️ Atmospheric background images for different locations
- 🔊 Ambient audio that changes with each scene
- 📱 Responsive design for desktop, tablet, and mobile
- 🎭 Visual feedback for riddle answers
- 📊 Progress indicator showing your journey
For a classic text adventure experience:
-
Build the TypeScript code:
npm run build
-
Start the game:
npm start
Or run both steps together:
npm run play- Read the narrative - Immerse yourself in the atmospheric descriptions
- Make choices - Click buttons (web) or select numbered options (terminal)
- Meet spirits - Encounter ancestral beings with unique stories
- Solve riddles - Answer challenges correctly to prove your worth
- Escape or be trapped - Your performance determines your fate
Web Interface:
- Click buttons - Select choices and navigate
- Audio toggle - Mute/unmute ambient sounds
- Responsive - Works with mouse, touch, or keyboard
Terminal Interface:
- Number keys (1-4) - Select choices from menus
- Enter - Continue through narrative scenes
- Ctrl+C - Exit the game at any time
haunted-memory-trail/
├── .kiro/
│ ├── flows/ # YAML flow definitions
│ │ ├── intro_flow.yaml
│ │ ├── gogo_thandi_flow.yaml
│ │ ├── escape_ending.yaml
│ │ └── trapped_ending.yaml
│ └── personas/ # YAML persona definitions
│ └── gogo_thandi.yaml
├── assets/ # Visual and audio assets
│ ├── images/
│ │ ├── backgrounds/ # Scene backgrounds
│ │ └── characters/ # Character sprites
│ └── audio/ # Ambient sound files
├── src/
│ ├── index.ts # Terminal entry point
│ ├── GameEngine.ts # Core game loop
│ ├── FlowManager.ts # Flow/scene management
│ ├── PersonaManager.ts # Character data management
│ ├── MemoryManager.ts # State tracking
│ ├── PlayerInterface.ts # Terminal user interaction
│ └── web/ # Web application
│ ├── index.html # Web entry point
│ ├── main.ts # Web initialization
│ ├── styles.css # Visual styling
│ ├── WebPlayerInterface.ts # Browser UI
│ └── AssetManager.ts # Asset loading
├── dist/ # Compiled terminal version
├── dist-web/ # Compiled web version
├── package.json
├── tsconfig.json
├── vite.config.mjs # Web bundler config
└── README.md
The game uses a modular, YAML-driven architecture that separates content from code:
- Game Engine - Orchestrates the game loop and coordinates all managers
- Flow Manager - Loads YAML flows and manages scene transitions
- Persona Manager - Loads character data and provides spirit information
- Memory Manager - Tracks player state using key-value pairs
- Player Interface - Handles user interaction (terminal or web)
- Terminal Interface - Text-based with colored formatting
- Web Interface - Visual browser-based with graphics and audio
- WebPlayerInterface - Implements the browser UI with DOM manipulation
- AssetManager - Loads and caches images and audio files
- Vite Bundler - Packages the application for web deployment
All game content is defined in YAML files, making it easy to add new spirits, locations, and challenges without modifying code. The same YAML files power both the terminal and web versions.
Follow these steps to add a new ancestral spirit to the game:
Create a new YAML file in .kiro/personas/ (e.g., baba_themba.yaml):
persona_id: "baba_themba"
name: "Baba Themba"
archetype: "Warrior Chief"
tone: "Stern but honorable"
backstory: "Baba Themba led the kraal's warriors through countless battles. He fell defending his people from raiders, his spear still clutched in his ghostly hand."
introduction: "Halt! I am Baba Themba, guardian of this sacred ground. Prove your courage, or turn back now."
challenge:
type: "riddle"
question: "I am won without fighting, lost without defeat, and held without hands. What am I?"
choices:
- choice_id: "honor"
text: "Honor"
correct: true
memory_key: "solved_baba_riddle"
memory_value: true
- choice_id: "peace"
text: "Peace"
correct: false
memory_key: "solved_baba_riddle"
memory_value: false
- choice_id: "respect"
text: "Respect"
correct: false
memory_key: "solved_baba_riddle"
memory_value: false
success_message: "Well spoken! You understand what truly matters. Pass, brave one."
failure_message: "No... you have much to learn about what it means to be a warrior."Create a new YAML file in .kiro/flows/ (e.g., baba_themba_flow.yaml):
flow_id: "baba_themba_flow"
scenes:
- scene_id: "encounter"
type: "narrative"
text: "{{persona.introduction}}"
persona_id: "baba_themba"
background_image: "warrior_spirit.jpg"
ambient_audio: "drums.mp3"
memory_set:
- key: "met_baba_themba"
value: true
next: "challenge"
- scene_id: "challenge"
type: "challenge"
persona_id: "baba_themba"
challenge_type: "riddle"
on_success: "success_response"
on_failure: "failure_response"
- scene_id: "success_response"
type: "narrative"
text: "{{persona.challenge.success_message}}"
next: "continue_choice"
- scene_id: "failure_response"
type: "narrative"
text: "{{persona.challenge.failure_message}}"
next: "continue_choice"
- scene_id: "continue_choice"
type: "choice"
text: "What will you do now?"
choices:
- choice_id: "return"
text: "Return to the center of the kraal"
next_flow: "entry_choice_flow"
- choice_id: "continue"
text: "Continue exploring"
next_flow: "entry_choice_flow"Update .kiro/flows/entry_choice_flow.yaml to add the new spirit as a choice option:
flow_id: "entry_choice_flow"
scenes:
- scene_id: "main_choice"
type: "choice"
text: "The kraal stretches before you. Where will you go?"
choices:
- choice_id: "gogo_thandi"
text: "Visit the old healer's rondavel"
next_flow: "gogo_thandi_flow"
- choice_id: "baba_themba"
text: "Approach the warrior's memorial"
next_flow: "baba_themba_flow"
# ... other choicesTo add a new explorable location:
Create a new YAML file in .kiro/flows/ (e.g., sacred_tree_flow.yaml):
flow_id: "sacred_tree_flow"
scenes:
- scene_id: "arrival"
type: "narrative"
text: "You approach the ancient marula tree at the edge of the kraal. Its gnarled branches reach toward the blood moon like skeletal fingers. The air here feels thick with old magic."
background_image: "sacred_tree.jpg"
ambient_audio: "wind_whispers.mp3"
memory_set:
- key: "visited_sacred_tree"
value: true
next: "explore_choice"
- scene_id: "explore_choice"
type: "choice"
text: "What do you do?"
choices:
- choice_id: "examine"
text: "Examine the tree more closely"
next_scene: "examination"
- choice_id: "return"
text: "Return to the center of the kraal"
next_flow: "entry_choice_flow"
- scene_id: "examination"
type: "narrative"
text: "You notice strange carvings in the bark—symbols of protection and remembrance. This tree has witnessed generations of life and death."
next: "explore_choice"Update the entry choice flow to include the new location:
choices:
- choice_id: "sacred_tree"
text: "Walk to the sacred marula tree"
next_flow: "sacred_tree_flow"flow_id: "unique_flow_identifier"
scenes:
- scene_id: "unique_scene_id"
type: "narrative" | "choice" | "challenge"
text: "Scene text content"
# Optional fields
background_image: "image.jpg"
ambient_audio: "audio.mp3"
persona_id: "persona_reference"
# For narrative scenes
memory_set:
- key: "memory_key_name"
value: true | false | "string" | number
next: "next_scene_id" | null
# For choice scenes
choices:
- choice_id: "choice_identifier"
text: "Choice display text"
next_scene: "scene_id" # Stay in current flow
next_flow: "flow_id" # Transition to new flow
conditions: # Optional
key: "memory_key"
value: true
# For challenge scenes
challenge_type: "riddle"
on_success: "success_scene_id"
on_failure: "failure_scene_id"persona_id: "unique_persona_identifier"
name: "Spirit Display Name"
archetype: "Character archetype"
tone: "Dialogue tone description"
backstory: "Character background story"
introduction: "First encounter message"
challenge:
type: "riddle"
question: "The riddle or challenge question"
choices:
- choice_id: "choice_identifier"
text: "Answer option text"
correct: true | false
memory_key: "solved_spirit_riddle"
memory_value: true | false
success_message: "Message when player succeeds"
failure_message: "Message when player fails"The game uses these memory key patterns:
met_<spirit_name>- Boolean tracking spirit encounters (e.g.,met_gogo_thandi)solved_<spirit_name>_riddle- Boolean tracking challenge results (e.g.,solved_gogo_riddle)visited_<location>- Boolean tracking location visits (e.g.,visited_rondavel)spirits_solved- Integer count of successfully solved challengesgame_complete- Boolean indicating game has ended
The game has two possible endings based on your performance:
- Escape Ending - Solve all spirit riddles correctly to break free from the haunted kraal
- Trapped Ending - Fail or ignore any riddle and remain trapped in the spirit realm forever
The ending is determined by checking all solved_*_riddle memory keys when the game concludes.
Terminal version:
npm run buildWeb version:
npm run build:webRun the comprehensive test suite:
npm testThis runs all tests including:
- Unit tests for core components
- Property-based tests for correctness validation
- Integration tests for full game flows
- Web interface tests for UI components
- Asset loading tests
- YAML compatibility tests
Test coverage includes:
- 75+ tests across 6 test suites
- Property-based testing with 100+ iterations per property
- Full playthrough scenarios for both endings
- Cross-browser compatibility validation
Web development with hot reload:
npm run dev:webTerminal development:
npm run playCore:
- yaml - YAML file parsing for content files
- TypeScript - Type-safe development
Terminal:
- chalk - Terminal color formatting
- readline-sync - Synchronous user input
Web:
- vite - Fast build tool and dev server
- vitest - Testing framework
- jsdom - DOM testing environment
- fast-check - Property-based testing library
Place your assets in the appropriate directories:
Character sprites: assets/images/characters/
- Format: SVG or PNG
- Naming:
{persona_id}_sprite.svg - Example:
gogo_thandi_sprite.svg
Background images: assets/images/backgrounds/
- Format: SVG, PNG, or JPG
- Referenced in YAML flow files
Audio files: assets/audio/
- Format: MP3
- Referenced in YAML flow files
The AssetManager automatically loads and caches these files, with graceful fallbacks for missing assets.
The web version can be deployed to any static hosting service:
-
Build the production bundle:
npm run build:web
-
Deploy the
dist-webfolder to your hosting service:- GitHub Pages
- Netlify
- Vercel
- AWS S3
- Any static web host
The build includes all assets and is optimized for production with:
- Minified JavaScript and CSS
- Optimized asset loading
- Browser caching support
- Responsive design for all devices
Preview the production build locally:
npm run preview:webISC
The Haunted Memory Trail is a demonstration of YAML-driven interactive fiction, showcasing how content and code can be cleanly separated for easy extension and modification. The dual-interface design (terminal and web) demonstrates how the same game engine can power different presentation layers.
Technical Highlights:
- YAML-driven content architecture
- Dual interface (terminal + web) from single codebase
- Property-based testing for correctness validation
- Responsive web design with visual and audio assets
- Modular component architecture
May the ancestors guide your path through the haunted kraal! 🌙