Skip to content

fix(37-12): narrator re-declares confrontation on scene transitions#452

Merged
slabgorb merged 4 commits intodevelopfrom
feat/37-12-narrator-confrontation-redef
Apr 14, 2026
Merged

fix(37-12): narrator re-declares confrontation on scene transitions#452
slabgorb merged 4 commits intodevelopfrom
feat/37-12-narrator-confrontation-redef

Conversation

@slabgorb
Copy link
Copy Markdown
Owner

Summary

  • Adds a === TRANSITION CONFRONTATION === section to the narrator prompt whenever an encounter is active, telling the narrator it may re-emit the confrontation field when the scene shifts to a different type. Lists every OTHER confrontation type from the genre pack as a concrete target (current type filtered out — Case C redeclare is a no-op).
  • Removes the contradictory "Only emit confrontation on the turn the encounter STARTS" instruction from the is_none() branch. 37-13 built the encounter gate to route re-emits across Cases C/D/E; the prompt was actively telling the narrator not to send them.
  • Emits encounter.transition_guidance_injected (StateTransition type) with current_encounter_type and alternative_count fields so the GM panel can verify the narrator was shown N transition options on every turn with an active encounter.

Scope: prompt-string edit in build_prompt_context + one WatcherEventBuilder::send(). No new types, no new public API, no subsystem boundary changes. The block sits outside the inner find_confrontation_def() guard on purpose so a broken-def encounter can still receive a recovery menu — the existing ValidationWarning independently signals the broken state.

Efficiency: the TEA verify phase applied one simplify fix (replaced a Vec<&ConfrontationDef> collect+iterate with a direct filter+counter loop). Review phase applied four nits: code comment honesty (name all six gate cases explicitly), test bounded window via === END TRANSITION CONFRONTATION ===, narrowed phrase test to canonical re-emit, and a test docstring fix for a six-cases/five-names contradiction.

Test plan

  • 7/7 Story 37-12 source-scan integration tests pass (narrator_confrontation_redef_story_37_12_tests)
  • 419/419 sidequest-server integration suite green — zero regressions
  • cargo clippy -p sidequest-server --tests -- -D warnings clean
  • cargo fmt clean
  • Playtest verification: on next playtest, watch the GM panel's encounter filter for encounter.transition_guidance_injected events to confirm the block is actually reached at runtime. Source-scan tests prove the code is present and textually correct; only runtime observation proves the turn-loop exercises it.

Known follow-ups (not blocking)

  • encounter_gate.rs:10-12 doc comment incorrectly claims 37-13 is the "root cause fix" for 37-12 — should be softened to "dispatch-side prerequisite". Risk: a future agent dismisses related work as already-shipped.
  • prompt.rs:362 AVAILABLE CONFRONTATIONS block runs unconditionally; mid-encounter the narrator now sees both that list and the new TRANSITION CONFRONTATION list. Pre-existing design, not introduced by this PR.
  • Field format format!("- \"{}\" ...", alt.confrontation_type, ...) doesn't escape YAML-sourced quotes/newlines. Pre-existing at prompt.rs:372. Appropriate fix is validation at ConfrontationDef deserialization time.

🤖 Generated with Claude Code

slabgorb and others added 4 commits April 14, 2026 14:58
Source-scan tests on dispatch/prompt.rs verify the seven ACs for Story
37-12. Follows the Story 28-4 pattern (include_str! scan) since
DispatchContext is too heavy to instantiate in integration tests.

All 7 tests fail RED as expected:
- prompt_no_longer_tells_narrator_to_only_emit_on_start
- prompt_includes_transition_confrontation_section_marker
- prompt_instructs_narrator_to_reemit_on_scene_shift
- transition_block_iterates_confrontation_defs
- prompt_emits_transition_guidance_otel_event
- otel_transition_event_carries_alternative_count_field
- transition_guidance_is_below_build_prompt_context_declaration

412 unrelated tests still passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds a TRANSITION CONFRONTATION section to the narrator prompt when an
encounter is active, telling the narrator it may re-emit the
`confrontation` field when the scene shifts to a different type. Lists
the other confrontation types from the genre pack as concrete targets
and excludes the current type (handled by the 37-13 gate's Case C
redeclare no-op).

Emits `encounter.transition_guidance_injected` with
`current_encounter_type` and `alternative_count` fields so the GM panel
can verify the narrator was shown N transition options on each turn.

Removes the contradictory "Only emit confrontation on the turn the
encounter STARTS" instruction from the is_none() branch — the 37-13
encounter gate was built to route re-emits across six cases, but the
prompt was actively telling the narrator not to send them.

Scope: prompt-string edit plus one watcher event. No new types, no new
public API, no behavioral change to encounter_gate.rs.

7/7 Story 37-12 tests pass, 412 unrelated integration tests still pass,
clippy clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Applied one high-confidence simplify-efficiency finding from TEA verify:
the TRANSITION CONFRONTATION block in build_prompt_context() collected
filtered confrontation defs into an intermediate Vec, then iterated the
Vec to format list items. Replaced with a direct filter+counter loop
that produces the same string output and the same alternative_count
OTEL field without the intermediate allocation.

The cost of a ~30-element Vec allocation is tiny compared to the
downstream Claude CLI subprocess, but the counter variant is strictly
simpler and has no tradeoff.

7/7 Story 37-12 tests still pass, 419-test integration suite green,
clippy clean, cargo fmt no-op.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…stness

Post-review improvements from Colonel Potter's review team:

1. comment-analyzer #1: the prompt.rs code comment claimed 37-13 routes
   re-emits "on every case" but only listed three of six. Rewrote to
   name Cases C/D/E explicitly, note that A/B are reached via the
   is_none() branch, and document that the block sits outside the
   def-guard intentionally so a broken-def encounter can still be
   recovered via a transition menu.

2. edge-hunter #6 / test-analyzer #4: the
   transition_block_iterates_confrontation_defs test used a fixed
   2000-byte window past the marker, which was fragile to block growth
   and could sweep into neighbouring AVAILABLE CONFRONTATIONS iteration.
   Replaced with a semantic window bounded by === TRANSITION
   CONFRONTATION === and === END TRANSITION CONFRONTATION ===.

3. rule-checker check #6: the phrase test accepted any of six
   candidates, which made future wording swaps invisible to CI. Now
   that GREEN settled on the canonical "re-emit the `confrontation`"
   phrasing, narrowed the assertion to that exact string so any reword
   surfaces in code review.

4. comment-analyzer #2: the test module doc said "six observable cases"
   but enumerated only five outcome names. Rewrote to note that Cases
   A and B share the `Created` outcome name, resolving the
   six-cases-five-names contradiction.

All 7 Story 37-12 tests still pass, 419-test integration suite green,
clippy clean, cargo fmt no-op.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@slabgorb slabgorb merged commit 82a1102 into develop Apr 14, 2026
1 check failed
@slabgorb slabgorb deleted the feat/37-12-narrator-confrontation-redef branch April 14, 2026 19:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant