Files
BreakEscape/docs/GLOBAL_VARIABLES.md

352 lines
10 KiB
Markdown
Raw Permalink Normal View History

# Global Ink Variables System
## Overview
This document describes the global variable system that allows narrative state to be shared across all NPC conversations in a scenario. Global variables are stored in `window.gameState.globalVariables` and are automatically synced to all loaded Ink stories.
## How It Works
### Single Source of Truth
`window.gameState.globalVariables` is the authoritative store for all global narrative state. When a variable changes in any NPC's story, it updates here and is then synced to all other loaded stories.
### Data Flow
```
┌─────────────────────────────────┐
│ window.gameState.globalVariables│ ← Single source of truth
│ { player_joined_organization... }│
└──────────────┬──────────────────┘
┌───────┴────────┐
│ On Load/Sync │
└───────┬────────┘
┌──────────────────────┐
│ NPC Ink Stories │
│ - test_npc_back │
│ - equipment_officer │
│ - helper_npc │
└──────────────────────┘
```
### Initialization Flow
1. **Game Start** (`js/core/game.js` - `create()`)
- Scenario JSON is loaded with `globalVariables` section
- `window.gameState.globalVariables` is initialized from scenario defaults
2. **Story Load** (`js/systems/npc-manager.js` - `getInkEngine()`)
- Story JSON is compiled from Ink source
- Auto-discovers `global_*` variables not in scenario
- Syncs all global variables FROM window.gameState INTO the story
- Sets up variable change listener to sync back
3. **Variable Change Detection** (`js/systems/npc-conversation-state.js`)
- Ink's `variableChangedEvent` fires when any variable changes
- If variable is global, updates window.gameState
- Broadcasts change to all other loaded stories
## Declaring Global Variables
### Method 1: Scenario JSON (Recommended)
Add a `globalVariables` section to your scenario file:
```json
{
"scenario_brief": "My Scenario",
"globalVariables": {
"player_joined_organization": false,
"main_quest_complete": false,
"player_reputation": 0
},
"startRoom": "lobby",
...
}
```
**Advantages:**
- Centralized location for all narrative state
- Visible to designers and developers
- Type-safe (defaults define types)
- Clear which variables are shared
### Method 2: Naming Convention (Fallback)
Add variables starting with `global_` to any Ink file:
```ink
VAR global_research_complete = false
VAR global_alliance_formed = false
```
**Advantages:**
- Quick prototyping without editing scenario file
- Third-party Ink files can declare their own globals
- Graceful degradation for scenarios without globalVariables section
## Using Global Variables in Ink
Global variables are automatically synced to Ink stories on load. Just declare them with the same name:
```ink
// Will be synced from window.gameState.globalVariables automatically
VAR player_joined_organization = false
=== check_status ===
{player_joined_organization:
This NPC recognizes you as a member!
- else:
Welcome, outsider.
}
```
### Conditional Choice Display
To show/hide choices based on global variables, use the conditional syntax directly in choice brackets:
```ink
// Shows this choice only if player_joined_organization is true
+ {player_joined_organization} [Show me everything]
-> show_inventory
// Regular choice always visible
* [Show me specialist items]
-> show_filtered
```
**Important:** The syntax is `+ {variable} [choice text]`, NOT `{variable: + [choice text]}`
## Accessing Global Variables from JavaScript/Phaser
Read global variables:
```javascript
const hasJoined = window.gameState.globalVariables.player_joined_organization;
```
Write global variables (syncs automatically to next conversation):
```javascript
window.gameState.globalVariables.player_joined_organization = true;
```
Get all global variables:
```javascript
console.log(window.gameState.globalVariables);
```
## How State Persistence Works
When an NPC conversation ends:
- `npcConversationStateManager.saveNPCState()` captures:
- Full story state (if mid-conversation)
- NPC-specific variables only
- **Snapshot of global variables**
On next conversation:
- `npcConversationStateManager.restoreNPCState()`:
- Restores global variables first
- Loads full story state or just variables
- Syncs globals into the story
## Critical Syncing Points
For global variables to work correctly, syncing must happen at specific times:
1. **After Player Choice** (`person-chat-minigame.js` - `handleChoice()`)
- Reads all global variables that changed in the Ink story
- Updates `window.gameState.globalVariables`
- Broadcasts changes to other loaded stories
2. **Before Showing Dialogue** (`person-chat-minigame.js` - `start()`)
- Re-syncs all globals into the current story
- Critical because Ink evaluates conditionals at `continue()` time
- Ensures conditional choices reflect current state from other NPCs
3. **On Story Load** (`npc-manager.js` - `getInkEngine()`)
- Initial sync of globals into newly loaded story
- Sets up listeners for future changes
## Implementation Details
### Key Files
- **`js/main.js`** (line 46-52)
- Initializes `window.gameState` with `globalVariables`
- **`js/core/game.js`** (line 461-467)
- Loads scenario and initializes `window.gameState.globalVariables`
- **`js/systems/npc-conversation-state.js`**
- `getGlobalVariableNames()` - Lists all global variables
- `isGlobalVariable(name)` - Checks if a variable is global
- `discoverGlobalVariables(story)` - Auto-discovers `global_*` variables
- `syncGlobalVariablesToStory(story)` - Syncs FROM window → Ink
- `syncGlobalVariablesFromStory(story)` - Syncs FROM Ink → window
- `observeGlobalVariableChanges(story, npcId)` - Sets up listeners
- `broadcastGlobalVariableChange()` - Propagates changes to all stories
- **`js/systems/npc-manager.js`** (line 702-712)
- Calls sync methods after loading each story
### Type Handling
Ink's `Value.Create()` is used through the indexer to ensure proper type wrapping:
```javascript
story.variablesState[variableName] = value; // Uses Ink's Value.Create internally
```
This handles:
- `boolean``BoolValue`
- `number``IntValue` or `FloatValue`
- `string``StringValue`
### Loop Prevention
When broadcasting changes to other stories, the event listener is temporarily disabled to prevent infinite loops:
```javascript
const oldHandler = story.variablesState.variableChangedEvent;
story.variablesState.variableChangedEvent = null;
story.variablesState[variableName] = value;
story.variablesState.variableChangedEvent = oldHandler;
```
## Example: Equipment Officer Scenario
### Scenario File (`npc-sprite-test2.json`)
```json
{
"globalVariables": {
"player_joined_organization": false
},
...
}
```
### First NPC (`test2.ink`)
```ink
VAR player_joined_organization = false
=== player_closing ===
# speaker:player
* [I'd love to join your organization!]
~ player_joined_organization = true
Excellent! Welcome aboard.
```
### Second NPC (`equipment-officer.ink`)
```ink
VAR player_joined_organization = false // Synced from test2.ink
=== hub ===
// This option only appears if player joined organization
+ {player_joined_organization} [Show me everything]
-> show_inventory
```
**Result:**
- Player talks to first NPC, chooses to join
- `player_joined_organization``true` in window.gameState
- Player talks to second NPC
- Variable is synced into their story
- Full inventory option now appears!
## Debugging & Troubleshooting
### Conditional Choices Not Appearing?
**Most Common Cause:** Ink files must be **recompiled** after editing.
```bash
# Recompile the Ink file:
inklecate -ojv scenarios/compiled/equipment-officer.json scenarios/ink/equipment-officer.ink
```
Then **hard refresh** the browser:
- Windows/Linux: `Ctrl + Shift + R`
- Mac: `Cmd + Shift + R`
### Variable Changed But Choices Still Wrong?
**Cause:** Conditionals evaluated before variable synced.
**Solution:** Ensure you're using the correct Ink syntax:
```ink
// ✅ CORRECT - conditional in choice brackets
+ {player_joined_organization} [Show me everything]
// ❌ WRONG - wrapping entire choice block
{player_joined_organization:
+ [Show me everything]
}
```
### Check Global Variables
```javascript
window.gameState.globalVariables
```
### Enable Debug Mode
```javascript
window.npcConversationStateManager._log('debug', 'message', data);
```
### Verify Scenario Loaded Correctly
```javascript
window.gameScenario.globalVariables
```
### Check Cached Stories
```javascript
window.npcManager.inkEngineCache
```
### View Console Logs
Look for these patterns in browser console:
- `✅ Synced player_joined_organization = true to story` - Variable synced successfully
- `🔄 Global variable player_joined_organization changed from false to true` - Variable changed
- `🌐 Synced X global variable(s) after choice` - Changes propagated after player choice
## Best Practices
1. **Declare in Scenario** - Use the `globalVariables` section for main narrative state
2. **Consistent Naming** - Use snake_case: `player_joined_organization`, `quest_complete`
3. **Type Consistency** - Keep the same type (bool, number, string) across all uses
4. **Document Intent** - Add comments in Ink files explaining what globals mean
5. **Test State Persistence** - Verify globals persist across page reloads
6. **Avoid Circular Logic** - Don't create mutually-dependent conditional branches
## Migration Guide
### Adding Global Variables to Existing Scenarios
1. Add `globalVariables` section to scenario JSON:
```json
{
"globalVariables": {
"new_variable": false
},
...
}
```
2. Add to Ink files that use it:
```ink
VAR new_variable = false
```
3. Use in conditionals or assignments:
```ink
{new_variable:
Conditions when variable is true
}
```
### No Breaking Changes
- Scenarios without `globalVariables` work fine (empty object)
- Existing variables remain NPC-specific unless added to `globalVariables`
- `global_*` convention works for quick prototyping