Audit, minify, repair, or surgically insert vars and contingency_prompts in story-engine scenario JSON files. Use when: tuning scenario vars, removing unused vars, vars are bloated, scene changes are not triggering, contingency prompts never appear, conditionals fail silently, or you need a new gameplay flag. Trigger words: vars, contingency_prompts, scene change failing, conditional not firing, game flag.
Audit, minify, and surgically modify vars and contingency_prompts across story-engine scenario JSON files. Also diagnoses why scene changes and conditionals fail.
conditional or scene_change is not firing; a contingency_prompt is never appearingVars are map[string]string stored at scenario, scene, and runtime (GameState.Vars) levels. All values are strings. There are no typed fields, no operators beyond exact-match.
| Level | Where defined | Lifecycle |
|---|---|---|
| Scenario | scenario.vars | Set at game load; persist for entire game |
| Scene | scene.vars | Merged/overridden on LoadScene(); old vars NOT cleared |
| Runtime | Set via conditional.then.set_vars | Survive across scene transitions |
Scene vars are merged-in, they do not replace the whole map. A var set in Scene A persists into Scene B unless Scene B explicitly resets it.
Every var must be declared at scenario level. If a var is only written by contingency_rules at runtime and not pre-declared in scenario.vars, it does not exist in GameState.Vars until the LLM first writes it. Any when.vars check against an undeclared var will behave unpredictably. Always add an initializer entry for every var in the top-level "vars" block of the scenario, even if the initial value is never read by a conditional.
Use "false" for boolean flags. Use a descriptive string like "none" for multi-state vars that progress through named states (e.g., "none" → "started" → "secure"). Do not initialize multi-state vars to "false" — it is misleading and creates an extra state that is never set.
"conditionals": {
"my_event_id": {
"when": { "vars": { "flag": "true" }, "min_scene_turns": 1 },
"then": {
"scene_change": { "to": "next_scene", "reason": "conditional" },
"set_vars": { "flag": "used" },
"prompt": "..."
}
}
}
All when fields are AND logic. Supported when fields:
| Field | Match type |
|---|---|
vars | Exact string match per key (AND across keys) |
scene_turn_counter | Exact integer |
turn_counter | Exact integer |
min_scene_turns | Integer >= |
min_turns | Integer >= |
location | Exact location key string |
Injected into every LLM prompt when the when conditions pass. Supports plain strings or conditional objects:
"contingency_prompts": [
"Always show this.",
{
"prompt": "Only when flag is true.",
"when": { "vars": { "flag": "true" } }
}
]
Contingency prompts exist at five levels: scenario, PC, scene, NPC, location. All active ones are merged for each turn.
Goal: find unused, redundant, or over-specified vars.
vars maps (scenario + each scene).scenario.vars block. Any var that exists only in a scene vars block or is only ever written at runtime by set_vars/contingency_rules but has no top-level declaration is undeclared and must be added.conditional.when.vars — is this key checked anywhere?conditional.then.set_vars — is this key ever set?contingency_prompt.when.vars — is this key read by any prompt?scene_turn_counter: 2 when min_scene_turns: 2 is equivalent and more robustGoal: remove dead vars, collapse redundant ones, and simplify when conditions.
vars map and from any set_vars block that sets them.set_vars writes; if nothing reads the var, delete all writes.when.vars, set_vars, and contingency_prompt.when.vars to use it, then delete the duplicate.min_scene_turns over exact scene_turn_counter unless the exact turn matters.min_turns over turn_counter for time pressure gates.set_vars or scene vars init) and at least one reader (when.vars or contingency_prompt.when.vars).Goal: strip the scenario to zero vars.
vars fields at scenario and scene levels.set_vars fields inside conditional.then blocks.when.vars checks inside conditionals and contingency_prompts.when block is now empty {}, delete the entire when key (making the contingency always-on).conditional has an empty when: {}, decide with user whether to:
when entirely, orvars, set_vars, or when.vars references remain.Goal: add a minimal var to accomplish a specific gameplay goal.
<subject>_<state> (e.g., gate_unlocked, boss_defeated, npc_met).vars if it resets per scenevars if it must persist across scenesscenario.vars block with its default value, even for scene-scoped vars. This ensures the key exists in GameState.Vars from turn one and when.vars checks behave predictably.set_vars in the then block of the conditional that detects the triggering event.when.vars check on the conditional that gates the outcome.contingency_prompt that activates when the flag is set.Work through this checklist in order:
Step 1 — Find the conditional
scene_change conditional. Confirm then.scene_change.to matches an actual scene key in scenario.scenes.Step 2 — Check the when fields
when. For vars, confirm:
GameState.Vars at the time the conditional is evaluated (check scenario/scene vars initializers and any prior set_vars).scene_turn_counter / turn_counter: confirm it's an exact match, not a >=. If the turn has already passed, it will never match again. Switch to min_scene_turns / min_turns if the window shouldn't be this narrow.Step 3 — Check for setter
set_vars for the triggering var present in a then block that actually fires? If the var is never set to the expected value, the conditional never passes.Step 4 — Check for shadowing
LoadScene()? Check scene-level vars in subsequent scenes.Step 5 — Check contingency_prompts
set_vars."When the player does X, the system sets flag_name to 'true'." tells the LLM to create the delta that writes the var.Step 6 — Check target scene
then.scene_change.to and exists as a key in scenario.scenes.Common root causes summary:
| Symptom | Likely cause |
|---|---|
| Conditional never fires | Var never set to expected value |
| Scene change fires once then stops | scene_turn_counter exact match, window already passed |
| Scene change fires but resets | Target scene vars overwrites the flag |
| Contingency prompt never appears | Wrong level (should be at scene, not scenario), or when.vars value mismatch |
| LLM ignores var-setting instruction | Contingency prompt absent; LLM has no instruction to write the var |
"true" / "false" strings — never "yes", "no", "1", "0".min_scene_turns over exact scene_turn_counter unless a precise one-turn gate is needed.vars if its stale value from a prior scene could cause incorrect conditional matches.