Dreams & Dice is a fan-made interactive visual novel set in a surreal Penacony-inspired dream archive. It combines branching dialogue, investigation, relationship changes, collectible evidence, alternate endings, music-driven atmosphere, and a visual archive of recovered story scenes.
This project is intended for entertainment and creative storytelling. It is not affiliated with, sponsored by, or endorsed by the original rights holders of any referenced characters or settings.
- Project overview
- Features
- Technology
- Requirements
- Installation
- Running the project
- Available commands
- Project structure
- Story and scene data
- How gameplay works
- Save data and persistence
- Audio system
- Gallery and recovered frames
- Flowchart
- Achievements
- Accessibility and controls
- Testing and validation
- Windows packaging and release
- Troubleshooting
- Development guidelines
- License and attribution
The application is a browser-based React game that can also be packaged as a Windows desktop application with Electron.
At startup, the app displays:
- A Yokailab brand/loading screen.
- A fan-project disclaimer.
- The main game menu.
The game then loads the central Game controller. This controller manages the active scene, dialogue, choices, audio, menus, saves, achievements, casebook data, gallery access, and other gameplay state.
- Scene-based story progression.
- Dialogue lines with speakers, emotions, backgrounds, music, and visual effects.
- Player choices that can:
- Move to another scene.
- Set story flags.
- Change relationship values.
- Add inventory items.
- Add evidence.
- Timed choices with configurable timeout routes.
- Keyboard navigation for dialogue and choices.
- Auto-play and skip modes.
The story is assembled from three authored JSON sources:
- Main story.
- The
Lost Dreamerside quest. - Alternate-ending story content.
The flowchart includes the main route, side-story route, alternate branch, and separate ending nodes, including:
- Tragic ending scenes.
- Insanity and other authored ending scenes.
- The side-story ending.
- The true alternate conclusion.
The casebook provides live views of:
- Evidence discovered in the story.
- Inventory items collected by the player.
- Relationship activity.
Casebook sections can scroll when their content exceeds the available space.
The Gallery is the visual archive for recovered story frames.
- Frames are locked until their corresponding scene is visited.
- Recovered frames display their actual scene background asset.
- Images are shown in monochrome for the archive aesthetic.
- Cards use a responsive 16:9 image ratio.
- Selecting a recovered frame opens a larger scene-detail preview.
- The preview uses a liquid-glass background and an opaque overlay so the underlying gallery does not show through.
The flowchart visualizes discovered story routes and connections.
- Visited scenes are shown as discovered nodes.
- Unvisited branches remain visually concealed or locked.
- Runtime-only connections are added for side-story and alternate routes.
- Ending nodes are marked separately.
- Zoom-out, zoom-in, and reset controls are available.
- The current zoom percentage is displayed.
The game persists progress in browser localStorage and Electron Chromium storage.
Persisted information includes:
- Autosave state.
- Manual save slots.
- Run history.
- Settings.
- Unlocked achievements.
- Main-story completion state used to unlock side content.
Settings such as the default main-menu music are saved immediately and are restored with precedence over older settings embedded in autosaves.
- Background music loops continuously.
- Scene-specific music can replace the current track.
- The selected default main-menu track is used when the player is in the main menu.
- BGM, SFX, and master volume settings are independent.
- Audio playback is started or resumed in response to browser/Electron autoplay rules.
Achievements can be triggered by:
- Reaching story scenes.
- Reaching ending scenes.
- Collecting evidence.
- Completing the visual gallery.
- Completing the authored story archive.
Achievement notifications are deduplicated and should only appear when an achievement is newly unlocked.
The Electron shell:
- Opens the built Vite application.
- Exposes a safe restart API through the preload script.
- Uses context isolation.
- Disables Node integration in the renderer.
- Locks the application window to 1600 x 900.
- Resets Chromium zoom to 100%.
- Blocks accidental Ctrl-plus/Ctrl-minus page zoom shortcuts.
- React 19
- TypeScript 5.8
- Vite 6
- Motion
- Lucide React
- Three.js and React Three Fiber
- Tailwind CSS
- Electron 36
- Electron Builder
- Vitest
- Testing Library for React
- Testing Library Jest DOM
- JSDOM
Install the following before working on the project:
- Node.js with npm.
- Windows 10 or newer for Windows packaging.
- A modern browser for web development.
- Windows build tools may be required by Electron Builder or native dependencies.
Use an active LTS version of Node.js when possible.
From the project directory:
npm installThe project does not require a runtime API server or external database. Story content and static assets are included in the repository.
npm run devVite starts the development server on port 3000 and listens on all interfaces.
Open:
http://localhost:3000/
npm run dev:electronThis command first creates a production Vite build and then opens it in Electron.
npm run build
npm run preview| Command | Purpose |
|---|---|
npm install |
Install project dependencies. |
npm run dev |
Start Vite development mode on port 3000. |
npm run dev:electron |
Build the web app and launch Electron. |
npm run build |
Create the production Vite build in dist/. |
npm run preview |
Serve the production build locally. |
npm run lint |
Run the TypeScript compiler in no-emit validation mode. |
npm test |
Run the Vitest test suite. |
npm run dist:win |
Build Windows Electron distributions with Electron Builder. |
npm run dist:portable |
Build the portable Windows executable. |
npm run release:windows -- 1.0.0 |
Run validation and create versioned portable, installer, and ZIP artifacts. |
npm run clean |
Remove selected generated build files. |
.
├── electron/
│ ├── main.cjs Electron main process and window setup
│ └── preload.cjs Safe renderer-to-main bridge
├── public/
│ ├── bgm/ Background music files
│ ├── backgrounds/ Scene background images
│ ├── characters_portraits/ Character portrait assets
│ └── icon.ico Windows application icon
├── scripts/
│ └── release-windows.cjs Windows release automation
├── story/
│ └── 1.0 story/
│ ├── main story/
│ └── side quest/
├── src/
│ ├── components/
│ │ ├── Game.tsx Main game controller and screen rendering
│ │ ├── FlowchartScreen.tsx
│ │ ├── AchievementsScreen.tsx
│ │ ├── RunHistoryScreen.tsx
│ │ ├── Gallery and archive UI in Game.tsx
│ │ └── Shared visual components
│ ├── achievements.ts Achievement definitions and conditions
│ ├── gameContent.ts Casebook, cast, and authored content helpers
│ ├── gameStorage.ts Save validation and localStorage helpers
│ ├── script.ts Story normalization and runtime SCRIPT map
│ ├── types.ts Shared game, scene, dialogue, and choice types
│ ├── App.tsx Startup sequence and Game mounting
│ ├── main.tsx React entry point
│ ├── index.css Global layout and visual styling
│ └── App.test.tsx Application tests
├── backgrounds.json Background metadata and asset mapping
├── character.json Character metadata and portrait mapping
├── index.html Browser entry document
├── package.json Scripts, dependencies, and Electron Builder config
└── README.md Project documentation
Story JSON is imported and merged in src/script.ts:
story/1.0 story/main story/story.json
story/1.0 story/side quest/side-quest-01-lost-dreamer.json
story/1.0 story/main story/story-alt-ending.json
The files are merged into one raw story object. Each object key is a scene ID and each value is an array of story lines.
A story line may contain:
{
"speaker": "Sparkle",
"text": "A line of dialogue.",
"emotion": "smile",
"bg": "bg_dream_lounge",
"bgm": "First_Dream.mp3",
"nextScene": "act1_next",
"timeLimit": 5,
"timeoutChoiceId": "ending_insanity",
"choices": [
{
"label": "Choose an answer",
"nextScene": "act1_branch"
}
]
}Supported story metadata includes:
speaker: Character name or narrator/system speaker.text: Dialogue or narration text.emotion: Portrait emotion key.bg: Background ID frombackgrounds.json.bgm: Music filename frompublic/bgm/.nextScene: Scene to enter after the line.choices: Player choices shown at the end of a scene or line.timeLimit: Timed-choice duration in seconds.timeoutChoiceId: Choice ID or destination scene used when time expires.
src/script.ts converts raw JSON into the runtime SCRIPT map. During normalization it:
- Generates stable dialogue IDs.
- Resolves character names to character IDs.
- Applies background fallbacks.
- Applies scene music overrides.
- Infers an emotion when no emotion is authored.
- Adds visual effects for specific text or emotion patterns.
- Creates terminal
end_creditscontent. - Builds the gallery scene list from scenes with authored backgrounds.
When adding a new scene, ensure every nextScene target exists in the merged story data.
The main game state includes:
- Current screen.
- Player name.
- Current scene ID.
- Dialogue index.
- Dialogue history.
- Visited scene IDs.
- Unlocked achievement IDs.
- Dialogue log.
- Flags.
- Relationship values.
- Inventory.
- Evidence.
- Settings.
When a choice is selected, the game:
- Records the current location in history.
- Applies flags.
- Adds relationship changes.
- Deduplicates inventory items.
- Deduplicates evidence.
- Moves to the destination scene or advances the current dialogue.
Scene transitions update the visited-scene list. The gallery, flowchart, achievements, and menu statistics derive their information from this live state.
Storage keys are defined in src/gameStorage.ts:
| Key | Contents |
|---|---|
penacony_autosave |
Most recent automatic progress snapshot. |
penacony_saves |
Manual save slots. |
penacony_run_history |
Up to 25 archived run snapshots. |
penacony_settings |
Dedicated persistent settings record. |
penacony_main_story_completed |
Main-story completion flag used for side content unlocks. |
Before a save is loaded, the game checks that:
- The value is an object.
- The scene ID exists in
SCRIPT. - The dialogue index is an integer.
- The dialogue index is not negative.
Loaded state is normalized and dialogue indexes are clamped to the selected scene's available dialogue range.
The Preferences screen's Reset progress action removes:
- Autosave.
- Manual saves.
- Run history.
- Settings.
- Main-story completion state.
It also resets the in-memory game state and UI overlays.
Music files are stored in public/bgm/ and referenced by filename.
The BGM synchronizer:
- Chooses menu music from
settings.defaultMainMenuBgm. - Uses scene-specific BGM when authored.
- Uses act-level fallbacks when no scene BGM is specified.
- Loops every active track.
- Fades between tracks.
- Applies master and BGM volume settings.
If a track does not play:
- Confirm the exact filename and capitalization in
public/bgm/. - Confirm the reference includes the
.mp3extension. - Check the browser/Electron console for a failed media request.
- Confirm the volume sliders are above zero.
- Interact with the page if the browser has blocked autoplay.
Gallery metadata is generated from STORY_GALLERY_SCENES in src/script.ts.
Each gallery entry contains:
sceneIdlabelbackgrounddescription
Background IDs are resolved through backgrounds.json before being used as CSS image URLs. This is important: bg_dream_lounge is a logical ID, while backgrounds/bg_dream_lounge.png is the actual browser asset.
Recovered-frame presentation:
- Cards use monochrome filtering.
- Cards use
aspect-ratio: 16 / 9. - Unvisited frames remain sealed.
- Recovered frames can open in a modal preview.
- The preview uses a liquid-glass overlay and an opaque backdrop.
FlowchartScreen.tsx creates a graph from:
- Explicit scene choices.
- Explicit
nextScenetransitions. - Runtime edges for content that is authored separately but connected conceptually at runtime.
Ending IDs are explicitly recognized so separate endings remain separate nodes. If a new side route or ending is added:
- Add the scene to the appropriate story JSON file.
- Ensure an authored or runtime edge can reach it.
- Add it to ending detection if it should be marked as an ending.
- Confirm it appears in the flowchart after rebuilding.
Achievement definitions live in src/achievements.ts.
Achievement conditions are functions receiving the current game state. Common conditions include:
- Visiting a scene.
- Reaching an ending.
- Collecting evidence.
- Visiting every gallery scene.
- Visiting every authored story scene.
Achievement unlock handling is idempotent:
- Already-unlocked achievements are ignored.
- Newly unlocked IDs are deduplicated.
- Notifications are announced once.
- Unlocks are persisted immediately to autosave.
| Input | Action |
|---|---|
Space |
Advance dialogue or finish typing. |
1 to 4 |
Select the corresponding choice. |
Escape |
Open the pause menu during gameplay. |
Ctrl+S |
Create a quick save. |
Ctrl+L |
Open save/load. |
F11 |
Toggle browser fullscreen when supported. |
Ctrl+0 |
Reset browser zoom in Electron. |
Ctrl+Plus / Ctrl+Minus |
Blocked in Electron to prevent accidental permanent scaling. |
The game includes options for:
- Text speed.
- Auto-play speed.
- Master volume.
- BGM volume.
- SFX volume.
- Reduced motion.
- UI scale.
- Text size.
- High contrast.
- Color-blind-safe markers.
- Focus mode.
- Default main-menu music.
Run TypeScript validation:
npm run lintRun the full test suite:
npm testCreate a production build:
npm run buildValidate the Electron main process syntax:
node --check electron/main.cjsBefore publishing, run at least:
npm run lint
npm test
npm run build
node --check electron/main.cjsThe production build may report a Vite warning about a JavaScript chunk larger than 500 kB. This is a bundle-size warning, not a build failure. Code splitting can be introduced later if startup performance requires it.
Build the standard Windows targets:
npm run dist:winBuild a portable executable:
npm run dist:portableThe release script validates the version, updates package.json, runs TypeScript validation and the production build, then creates:
- Portable
.exe. - NSIS installer
.exe. - ZIP containing both executables.
Run it with a semantic version:
npm run release:windows -- 1.0.0Prerelease versions are also supported:
npm run release:windows -- 1.0.0-beta.1Artifacts are written to:
release/<version>/
The release script uses temporary staging folders and removes them after packaging.
In the Electron build, restart the application. The main process resets Chromium zoom level and zoom factor to 100%. Ctrl+0 also resets zoom.
Open Preferences and select the desired default main-menu track. Settings are saved to penacony_settings. If an old autosave contains a different music setting, the dedicated settings record takes precedence.
All synchronized BGM tracks are configured to loop. Check the media filename and browser/Electron console if playback still stops.
Confirm that:
- The scene has a valid
bgID. - The ID exists in
backgrounds.json. - The mapped asset exists under
public/backgrounds/. - The production build includes the asset.
Invalid scene IDs, negative dialogue indexes, and malformed save objects are rejected. Use the save screen to delete a corrupted slot and create a new save.
Some side content depends on main-story completion. Resetting progress removes the completion flag and intentionally locks that content again.
The Electron command loads the dist/ directory. Rebuild before launching:
npm run build
npx electron .When adding story content:
- Use stable, descriptive scene IDs.
- Keep destination scene IDs valid.
- Add backgrounds to
backgrounds.json. - Add music to
public/bgm/before referencing it. - Add character portrait mappings when introducing new emotions.
- Check the flowchart for new branches.
- Check the gallery if the scene has a recoverable background.
- Add achievement logic only when the unlock condition is deterministic.
- Run TypeScript validation and a production build.
- Test a fresh game, a resumed game, reset progress, and a route ending.
When modifying UI:
- Preserve keyboard and screen-reader labels.
- Keep scrollable sections constrained with
min-height: 0. - Avoid broad CSS overrides that unintentionally affect modal layers.
- Verify z-index and opaque backgrounds for overlays.
- Test both browser development mode and Electron production mode.
The project is governed by the project-specific copyright and fan-work notice
in [LICENSE].
YOKAILAB owns the original source code, writing, story structure, scene design, game logic, user-interface work, and other material created specifically for this project, subject to any asset-specific notices.
The character identities, names, designs, portraits, and character artwork derived from Honkai: Star Rail are third-party material. They are used only in this unofficial, non-commercial fan project and are not claimed as YOKAILAB's property. Honkai: Star Rail and related intellectual property belong to their respective rights holders, including HoYoverse/COGNOSPHERE and their licensors, as applicable.
Dreams & Dice is not affiliated with, sponsored by, endorsed by, or approved
by HoYoverse, COGNOSPHERE, or any other rights holder. The license does not
grant permission to copy, distribute, modify, or commercially use third-party
Honkai: Star Rail material. Review the complete [LICENSE]
before publishing or redistributing the project.