Step 2 - Build The Stuck Note Incident
You built the pop in Step 1. Now add the first thing that can go wrong.
This step adds the first concrete incident in Kurzz: Stuck Note.
The goal is to create one musical problem that is:
- legible to non-musicians
- solvable by player or AI behavior
- interesting enough to create different play styles
This tutorial is content/scripting-first. No Rust changes are required. Use Lua scripts first; use Rhai only if your scenario runtime is configured for Rhai.
Before You Start
This step builds on Step 1. scenarios/kurzz/pop/follow_the_leader.yaml must
exist and be loadable.
If you have not done Step 1, read 01_lets_pop.md first. The incident spec in this step adds fields to that pop file.
What We Are Building
Player-facing premise:
"A note is stuck in the room. Keep playing through it until the room releases."
Core clear rule:
- after trigger, count matching plays of one stuck pitch
- clear when total matching plays reaches 7
- any participant can contribute (player or AI)
This guarantees eventual recovery while still allowing strategy.
The incident also has survival stakes: while it is unresolved, Rally escalation pressure climbs. On clear, trust and stability trend back up. This is not just musical flavor -- it connects to the player's logistics situation in the larger game.
Why This Design Works
Stuck Note supports multiple valid responses:
- Fast clear (spam)
Hammer the stuck pitch to clear quickly.
- Drone rebuild (coherent ensemble play)
Use the stuck pitch as a drone, rebuild phrase coherence around it, and clear as a side effect.
- Intentional hold (creative risk)
Delay clearing on purpose to hold tension, then resolve later.
"Not clearing yet" is not automatic failure. It is a strategic posture until survival pressure forces a change.
Files You Will Touch
Minimum expected files for this step:
scenarios/kurzz/pop/follow_the_leader.yaml(from Step 1; you are adding incident fields)scenarios/kurzz/scripts/musician.stuck_note.lua(new)
Optional (if you separate host script concerns):
scenarios/kurzz/scripts/musician.tutorial_cue.lua(existing cue script)
You are defining incident policy in spec plus script, not in engine code.
Spec Surface (YAML)
Add incident metadata to the pop rule set so downstream systems can react.
Suggested fields to emit while the incident is active:
incident_kind: stuck_noteincident_active: true|falseincident_stuck_pitch_midi: <int>incident_clear_progress_count: <int>incident_clear_target_count: 7
If your current rule shape cannot hold all of these directly, keep only the minimum and let script emit the rest as events.
Concrete Rally consequence example (for readability):
- while active and unresolved,
escalationpressure trends up - on clear,
trustorstabilitytrends up and escalation flattens
This gives the player a visible survival consequence, not just musical flavor.
Script Surface (Lua Preferred)
Create scenarios/kurzz/scripts/musician.stuck_note.lua.
Responsibilities:
- Start incident (choose stuck pitch, set active state)
- Watch note stream contribution events
- Increment progress when a matching pitch arrives
- Emit progress events
- Emit clear event exactly once at count >= 7
Pseudocode shape:
function tick()
local active = host.get("incident", "stuck_note_active")
if not active then
-- choose pitch and start once
-- host.set("incident", "stuck_note_active", true)
-- host.set("incident", "stuck_pitch_midi", 60)
-- host.set("incident", "clear_progress_count", 0)
-- host.emit("incident.stuck_note.started.v1", {...})
return
end
-- inspect notes since last tick (implementation-specific host access)
-- for each matching pitch: increment and emit progress
local progress = host.get("incident", "clear_progress_count")
if progress >= 7 then
-- set inactive and emit cleared exactly once
-- host.emit("incident.stuck_note.cleared.v1", {...})
end
end
Rhai fallback:
- keep identical state fields and event names
- port syntax only; preserve behavior contract
Event Contract To Keep Stable
Use the same event names and payload intent from the workflow plan:
incident.stuck_note.started.v1incident.stuck_note.progress.v1incident.stuck_note.cleared.v1
Keep these stable so Walker/UI work can iterate without script churn.
UX Language Guidelines
Use stage language, not telemetry language.
Good:
- "Stuck note C4: 3/7 cleared. Mira is helping."
- "You can hammer C4 to clear, or build around it."
- "Still active. You are holding tension on purpose."
Avoid:
- "incident_progress = 0.42"
- "state mutation success"
Test Pass (Content/Scripting)
Before handing this off, verify these runs:
-
Player-clear run
-
player repeats stuck pitch
-
clear occurs exactly at 7
-
AI/mixed clear run
-
player does not force clear
-
AI or mixed contribution reaches 7 and clears
-
Intentional delayed clear run
-
incident remains active for a while
- no immediate fail state
- clear later by chosen strategy
All three should produce legible event traces and human-readable feedback text.
Done When
- incident can start from spec+script
- progress updates on matching note plays from any participant
- clear happens exactly once at 7
- at least one run each for player clear, AI/mixed clear, delayed clear
- consequences are visible as Rally pressure/stability change
What Comes Next
After this step, the next tutorial should connect the incident stream to Walker feedback panels and result language so players see cause/effect immediately.
References
- `plantangenet/music/docs/MUSIC_IN_PLANTANGENET.md -- How "music" works in plantangeent.
plantangenet/music/docs/NEW_POP.md-- How to Develop a new popscenarios/kurzz/docs/A_new_drummer.md-- lore and voice reference for the Kurzz scenario