# D&D Combat Scene Semantic Validation ## Purpose Add an optional LLM-backed validator for `dnd/scene-descriptions` that checks whether the proposed scene kind correctly represents substantive active combat in the current transcript chunk. The validator will improve the reliability of the existing downstream rule that combat turns and enemy events run only for scenes classified as `combat`, without moving scene classification into the chunker or broadening the durable scene-description contract. This roadmap defines the intended final state and the code and documentation changes required to reach it. ## Current State - `dnd/scenes` produces a complete, gap-free scene plan containing source ranges. It does not classify those ranges. - `dnd/scene-descriptions` processes one accepted chunk at a time and produces exactly one scene with a deterministic chunk ID and source range plus the model-supplied `kind`, `title`, and `summary`. - The supported scene kinds are `combat`, `narrative`, `recap`, and `meta`. - The production extract chain validates shape, source identity, durable JSON schema, and advisory lexical relatedness. None of those checks can decide whether active combat was classified correctly. - Combat-turn and enemy-event extraction already require an exact matching scene description whose kind is `combat`. A missing, ambiguous, or non-combat classification does not run those LLM extractors. - The validation framework already supports typed LLM-backed validators, PromptKit structured-output repair, bounded validator execution retries, aggregated semantic correction guidance, and configurable terminal handling of validator rejection or execution failure. - There are currently no production LLM-backed validators. The new module will establish the first concrete D&D implementation of that existing framework capability. ## User Intent And Policy The validator exists to catch both consequential classification errors: 1. a scene containing substantive active combat is assigned a non-combat kind; and 2. a scene without substantive active combat is assigned `combat`. “Substantive active combat” means that an in-session encounter is materially organized around participants taking or resolving hostile actions, such as initiative or turn exchanges, attacks, combat spells, damage, saves, movement, or similarly sustained conflict. The following do not establish active combat by themselves: - planning or preparing for a possible fight; - threats, hostile dialogue, or a tense confrontation; - immediate aftermath, looting, healing, or discussion of a completed fight; - a recap or in-world recollection of earlier combat; or - out-of-character rules discussion without active encounter play. A chunk with substantive active combat remains combat when it also contains brief setup, rules clarification, interruption, phase transition, or immediate aftermath. The validator judges whether the supplied chunk's proposed combat status is supported; it does not redesign the scene boundary or review the quality of the title and summary. The model must receive only semantically useful material: the transcript chunk, the proposed scene kind, and the combat decision policy. Transcript unit IDs may remain visible as part of the ordinary source presentation, but the model must not be asked to reproduce them or the chunk ID, source range, hashes, validator keys, reason codes, or any other opaque application identifier. Application code owns all mapping, provenance, and correction metadata deterministically. ## Target End State ### Validator Module Add a typed validator package at `internal/modules/dnd/validate/scenedescriptions/combat_semantics` with the registered key `extract/dnd/scene-descriptions/combat_semantics`. The module will: - register for `dnd.SceneDescriptionListKind`; - declare `contracts.ExecutionClassLLMBacked`; - accept no module-specific options and reject unknown options; - require an extract-stage request containing one valid scene and the current transcript chunk; - call the run-scoped scheduled `StructuredLLMClient` supplied through `pipeline.BuildRequest`; - forward the resolved validator `llm_profile`, session ID, and structured output repair override without inventing a separate concurrency or provider path; - expose prompt/schema/policy fingerprints through the existing manifest and checkpoint metadata conventions; and - return an ordinary `contracts.ValidationResult` whose semantic meaning is fully owned by the module while terminal disposition remains framework and pipeline policy. The module is intentionally extract-stage-specific. If it is configured for a merged or normalized value, receives no current chunk, receives anything other than exactly one scene, or cannot establish the required input invariants, it must return a contextual execution error rather than make a semantic judgment against incomplete or ambiguous material. ### LLM Decision Contract The private LLM response will contain exactly two required fields: - `verdict`, one of `approved`, `combat_should_be_added`, or `combat_should_be_removed`; and - `explanation`, a concise, transcript-grounded explanation of the decision. The JSON schema must reject unknown fields and require every declared field. It must contain no optional properties and no `uniqueItems` keyword. The prompt must force a best judgment and must not request a confidence score or an “uncertain” verdict; model-estimated uncertainty is a quality signal, not a process warning. The adapter will validate verdict consistency deterministically: - `approved` accepts either a supported combat classification or a supported non-combat classification; - `combat_should_be_added` is valid only when the proposed kind is not `combat`; and - `combat_should_be_removed` is valid only when the proposed kind is `combat`. An inconsistent verdict, blank or over-limit explanation, malformed final response, missing prompt input, or completion error is a validator execution failure. PromptKit may repair structurally invalid output within its configured budget, and the framework may re-execute the validator within its distinct validator retry budget. The validator will not create a recursive semantic validation or feedback loop for its own response. On semantic mismatch, application code will assign stable internal reason codes and build bounded, actionable correction guidance. The guidance will state whether the corrected scene must be `combat` or must use the appropriate non-combat kind and will include the model's safe, bounded evidence explanation. It will not expose validator keys or reason codes to the producer. The existing producer retry mechanism will append that guidance to the exact defective scene-description response and request one complete replacement. ### Prompt Assets Store the validator's LLM-facing assets under `assets/dnd/scene-descriptions/validate/combat-semantics/`, using the existing content-only root assets package and module-owned prompt registration pattern. The asset set will include a consistently named `prompt.yaml`, one concise module instruction file, and one private v1 response schema. The prompt declaration will use `dnd.scene_descriptions.validate_combat` as its prompt ID and `dnd-extraction` as its default profile. Stable shared and module policy messages will precede variable inputs so PromptKit backends can reuse identical prefixes. The prompt will reuse the shared D&D system and chunk-transcript assets rather than copying their contents. A small local input asset will present only the proposed scene kind; the complete scene artifact, its deterministic ID, and its source reference will not be rendered. Factor the combat/non-combat decision policy currently embedded in the scene-description extractor instructions into one `common-dnd-` shared asset selected by both the extractor and validator manifests. Keep the non-combat `narrative`, `recap`, and `meta` selection rules local to the extractor. The shared combat policy must be byte-identical for both consumers; do not create two near-duplicate definitions. The manifests, asset hashes, and prompt-cache tests must make the resulting ownership and message ordering explicit without using brittle exact-length assertions. ### Registration And Configuration Register the validator builder and its prompt/schema assets through the D&D registrar. Catalog inspection must report its typed artifact kind and `llm_backed` execution class. The validator will be selectable in an extract validator override, but it will not be added to the production default chain by this feature. An operator who opts in must place it after the existing deterministic shape, source-reference, and durable-schema checks so malformed candidates are rejected before a paid LLM call. It should be last in the chain after advisory relatedness unless evaluation demonstrates a concrete reason to change that order. The validator uses the ordinary profile precedence and validator binding fields already documented by Notarius. It introduces no new configuration field, retry budget, failure policy, scheduler, or environment variable. Validator rejection and execution failure continue to follow the configured stage validation policy. Under the application defaults, exhausted semantic rejection fails the run, while exhausted validator execution failure may advance with an actionable process warning and incomplete-validation provenance. ### Default-Chain Promotion Gate The final state for this roadmap is a production-quality, documented, operator-selectable validator that remains opt-in. It must not enter the registered production default chain until provider-backed evaluation shows that its accuracy, retry value, latency, and token cost justify enabling an additional model call for every scene-description chunk. Promotion, if later selected, requires a separate deliberate change to the default chain and maintained configuration expectations. It does not require a new architecture decision so long as the validator remains at the scene-description extract stage. ## Required Code Changes The implementation must make the following coherent changes: - add the new typed validator, strict option decoder, LLM response model, response interpretation, error wrapping, fingerprints, and registration; - add and register its embedded prompt, instruction, input, and response-schema assets; - factor the combat classification policy into a shared prompt fragment if doing so is necessary to keep the extractor and validator language exactly identical; - update D&D registrar tests, prompt-asset registration tests, prompt-cache ordering tests, and catalog/spec assertions for the new selectable key; - add focused validator tests with a fake structured LLM client for approval, both rejection directions, inconsistent verdicts, malformed or failed completions, input preconditions, profile/session/repair forwarding, bounded explanations, correction guidance, and immutable candidate handling; - add an integration test proving that a semantic rejection from this validator supplies useful correction guidance to a retry-capable `dnd/scene-descriptions` producer and that the corrected candidate is revalidated before acceptance; - retain and extend downstream contract coverage proving that combat-turn and enemy-event extraction still runs only for an exact accepted `combat` scene; and - update current configuration and D&D internal documentation to list the key, describe its opt-in status and recommended chain position, explain its process-failure behavior, and distinguish it from a future chunk-boundary validator. No durable artifact schema or integration contract needs a version change. The validator produces validation provenance and correction behavior, not a new published artifact. ## Evaluation Requirements Maintain a small human-reviewed evaluation set that exercises at least: - ordinary active combat and initiative-like exchanges; - combat preceded by setup or followed by immediate aftermath; - multi-phase encounters and brief rules interruptions; - planning, threats, hostile dialogue, and tense confrontations without active combat; - aftermath, looting, and healing after combat has ended; - prior-session recap and in-world recollection of combat; and - sustained out-of-character combat rules discussion. Default automated tests must remain deterministic, offline, and provider-free. They should test prompt inputs, response interpretation, retry integration, and failure policy with fakes rather than asserting exact natural-language output. Provider-backed evaluation is an explicit manual or opt-in task. Record false acceptance, false rejection, successful producer correction, added calls, latency, and token use before proposing default-chain promotion. ## Documentation And Architecture Record Update `docs/config.md` as the canonical owner of the selectable validator key, binding behavior, and chain placement. Update `docs/internal/dnd.md` as the canonical owner of the D&D-specific decision policy, prompt ownership, and module boundary. Link rather than duplicate the generic retry, diagnostic, and failure-policy contracts owned by their existing architecture and operator documentation. No new ADR is required for this scope. It applies the accepted generic validation and feedback-aware retry architecture without changing stage ownership or a durable contract. Create or supersede an ADR only if later work moves classification into `dnd/scenes`, adds annotations to the chunk plan, or otherwise transfers ownership across pipeline stages. ## Out Of Scope - changing scene boundaries or deciding whether one combat was fragmented across multiple chunks; - adding an LLM-backed chunk-plan validator; - moving scene kind into chunk-plan annotations; - changing the scene-description, combat-turn, or enemy-event durable schemas; - replacing the deterministic combat-only downstream extraction gate; - validating title or summary quality with this module; - adding confidence scores or surfacing model uncertainty as warnings; - adding a validator-specific concurrency or retry subsystem; and - enabling the validator in the production default chain before evaluation. ## Acceptance Criteria - The new LLM-backed validator is registered for scene-description artifacts and can be selected only through the existing validator binding mechanism. - It reviews exactly one extract-stage scene against the corresponding transcript chunk and detects both missing and unsupported `combat` tags. - Its response contract and candidate presentation do not ask the model to reproduce transcript unit IDs, chunk IDs, source ranges, hashes, validator keys, or reason codes. - Its strict structured schema has only required fields, rejects unknown fields, and avoids unsupported JSON Schema keywords. - Rejections produce bounded, transcript-grounded corrective guidance that the existing scene-description producer retry can use. - Validator contract or execution failures remain distinct from semantic rejection and follow existing configured policy. - The existing deterministic validator chain and downstream exact-combat gate remain intact. - Prompt definitions share identical combat policy content rather than maintaining near-duplicate instructions. - Automated coverage is deterministic and provider-free; an explicit human-reviewed evaluation protocol exists for later default-chain review. - Current documentation describes implemented selection and behavior without claiming that the validator is enabled by default.