{
  "nodes": {
    "artifacts": [
      {
        "id": "pencil_strip",
        "path": "<project_root>/prep/ep_NNN/storyboards/{batch_id}_vNN.png",
        "read_by": [
          "render_board_finish",
          "board_cli"
        ],
        "risk_level": "high",
        "schema": "half-size pencil storyboard strip + .json generation sidecar (kind=storyboard)",
        "written_by": [
          "build_and_dispatch_board"
        ]
      },
      {
        "id": "photoreal_finish",
        "path": "<project_root>/prep/ep_NNN/storyboards/{batch_id}_vNN.finish.png",
        "read_by": [
          "board_cli"
        ],
        "risk_level": "high",
        "schema": "full-size photoreal finish (kind=storyboard_finish, finish_of=<pencil>); board.photoreal_artifact; consumed downstream as the board video dispatch reads (preferred_board_artifact)",
        "written_by": [
          "render_board_finish"
        ]
      },
      {
        "id": "story_gate_verdict",
        "path": "<project_root>/prep/ep_NNN/storyboards/{batch_stem}.verdict.json",
        "read_by": [
          "board_cli"
        ],
        "risk_level": "medium",
        "schema": "StoryGate shadow verdict; carries route + fix_notes; drives auto-reroll",
        "written_by": [
          "build_and_dispatch_board"
        ]
      },
      {
        "id": "board_record",
        "path": "manifest.execution.boards[shotset_hash]",
        "read_by": [
          "board_cli"
        ],
        "risk_level": "high",
        "schema": "board decision SSOT keyed by shotset_hash (status/artifact/photoreal_artifact/source_sha256/model/fallback_from); stamped via derivation_manifest.stamp_board (_stamp_board_ssot)",
        "written_by": [
          "board_cli"
        ]
      },
      {
        "id": "board_review_comments_json",
        "path": "{project}/prep/ep_NNN/storyboards/board_comments.json",
        "read_by": [
          "board_review_surface"
        ],
        "risk_level": "medium",
        "schema": "human board-review comments keyed by board/panel \u2014 distinct from the auto-reroll lineage sidecar",
        "written_by": [
          "board_comments_api"
        ]
      },
      {
        "id": "receipts_jsonl",
        "path": "$RECOIL_ROOT/_dispatch_logs/receipts.jsonl",
        "read_by": [],
        "risk_level": "medium",
        "schema": "recoil/pipeline/core/receipts.py::GenerationReceipt",
        "written_by": [
          "recoil/pipeline/core/dispatch.py::_emit_receipt_jsonl"
        ]
      },
      {
        "id": "video_take_mp4",
        "path": "renders/ep_NNN/*.mp4",
        "read_by": [
          "step_runner"
        ],
        "risk_level": "medium",
        "schema": "canonical video_naming grammar (build_filename)",
        "written_by": [
          "step_runner"
        ]
      },
      {
        "id": "video_sidecar_json",
        "path": "renders/ep_NNN/*.mp4.json",
        "risk_level": "medium",
        "schema": "populate_sidecar SSOT (recoil/pipeline/_lib/sidecar.py) \u2014 refs_used/seed/cost/dispatch_path",
        "written_by": [
          "step_runner"
        ]
      },
      {
        "id": "seeddance_meta_yaml",
        "path": "renders/ep_NNN/{shot_id}_meta.yaml",
        "risk_level": "low",
        "schema": "SeedDance generation metadata (model in name, endpoint, prompt, cost, latency)",
        "written_by": [
          "step_runner"
        ]
      },
      {
        "id": "keyframe_png",
        "path": "prep/ep_NNN/*.png",
        "read_by": [
          "step_runner"
        ],
        "risk_level": "low",
        "written_by": [
          "step_runner"
        ]
      },
      {
        "id": "boundary_frames",
        "path": "renders/ep_NNN/boundary_frames/*_segNN.jpg",
        "read_by": [
          "step_runner"
        ],
        "risk_level": "low",
        "schema": "ffmpeg -ss per-segment extraction for video identity gates",
        "written_by": [
          "step_runner"
        ]
      },
      {
        "id": "observability_log",
        "path": "_pipeline/state/visual/ (provider call rows)",
        "risk_level": "medium",
        "schema": "recoil/execution/providers/observability.py::record_call \u2014 provider/model/tier/status/cost/latency",
        "written_by": [
          "video_model_client"
        ]
      },
      {
        "id": "persisted_scene",
        "path": "_pipeline/state/orchestration/scenes/{episode}_{scene_id}.json   (e.g. ep_001_BATCH_004.json, ep_001_ONER_002.json, ep_001_<pass_id>.json)",
        "read_by": [
          "batch_reroll_selector",
          "board_ref_path",
          "video_ref_path"
        ],
        "reduced_tier": "always",
        "risk_level": "critical",
        "schema": "Scene{beats[].beat_metadata: {scene_id, modality, grouping{strategy,ordinal}, shot|batch_shots[](CanonicalShot dicts w/ location_id+scene_index), inputs_fingerprint}}",
        "written_by": [
          "episode_runner_module",
          "generate_cli",
          "reroll_route"
        ]
      },
      {
        "id": "prompt_bible",
        "path": "recoil/**/prompt_bible*.{json,yaml}",
        "read_by": [
          "prompt_engine_smoke_cli",
          "seedance_i2v_route",
          "kling_i2v_route"
        ],
        "reduced_tier": "auto",
        "risk_level": "medium",
        "schema": "per-model {optimal_word_range, max_chars, shared_lexicon, formatter_limits}",
        "written_by": []
      },
      {
        "id": "flash_system_prompt_files",
        "path": "flash_base_instructions.txt + flash_to_{model_target}_v{ver}.txt",
        "read_by": [
          "flash_enrichment_lockterm_retry"
        ],
        "reduced_tier": "never",
        "risk_level": "low",
        "schema": "free-text system-prompt templates for Flash enrichment per target model",
        "written_by": []
      },
      {
        "id": "composite_sheet_v1",
        "path": "char=assets/char/<id>/base/sheets/sheet_v1.png | loc=assets/loc/<id>/sheets/sheet_v1.png | prop=assets/prop/<id>/sheet_v1.png",
        "read_by": [
          "video_sheet_route",
          "board_location_sheet_route",
          "render_board_finish"
        ],
        "reduced_tier": "always",
        "risk_level": "high",
        "schema": "single multi-angle composite PNG per entity; ProjectPaths.sheet_path resolves the canonical location PER KIND (char has base/sheets/, loc has sheets/ (no base/), prop has sheet_v1.png at the subject root); resolve_sheet_asset loud-fails (SheetIntegrityError) on 1x1/truncated clobber",
        "written_by": [
          "generate_composite_sheet_cli"
        ]
      },
      {
        "id": "board_ref_layout",
        "path": "in-memory ref_layout dict \u2192 board generation sidecar (sublocation_refs / sublocation_ref / prop entries)",
        "read_by": [
          "collect_reference_images_entry"
        ],
        "reduced_tier": "auto",
        "risk_level": "medium",
        "schema": "ordered refs[] + ref_layout index map; sidecar persists sublocation_refs + shared_ref for board\u2192video reuse",
        "written_by": [
          "collect_board_refs"
        ]
      },
      {
        "id": "video_ref_manifest",
        "path": "in-memory {logical_key: 1-based ImageN index} returned to prompt builders",
        "read_by": [],
        "reduced_tier": "auto",
        "risk_level": "high",
        "schema": "{identity_N: pos, scene_1: pos}; sheet path mirrors angle path so @Image{identity_1} \u2192 @Image1 in both routes",
        "written_by": [
          "collect_reference_images_entry"
        ]
      },
      {
        "id": "ops_log",
        "path": "<project_root>/_pipeline/state/ops/*.ndjson",
        "read_by": [],
        "risk_level": "low",
        "schema": "ndjson events: retry_strategy_selected, retry_cost_warning, beat_strategy_exhausted, beat_exhausted, strategy_engine_error",
        "written_by": [
          "strategy_selection_loop",
          "beat_take_loop"
        ]
      },
      {
        "id": "board_comments",
        "path": "<project_root>/prep/<ep_NNN>/storyboards/board_comments.json",
        "read_by": [],
        "risk_level": "low",
        "schema": "reroll_lineage + per-attempt verdict/route persisted via _finalize_reroll_board; distinct from human board_review_comments",
        "written_by": [
          "board_autoreroll_loop"
        ]
      },
      {
        "id": "ssot_manifest",
        "path": "recoil/architecture/ssot_manifest.yaml",
        "read_by": [
          "build_topology",
          "all_topology_nodes"
        ],
        "reduced_tier": "always",
        "risk_level": "critical",
        "schema": "capability map: {capability_id: {canonical, state, deprecated_paths, ...}}",
        "written_by": [
          "human",
          "spec_register_capability"
        ]
      },
      {
        "id": "mention_ledger",
        "path": "{project}/prep/ep_NNN/breakdown/mention_ledger.json",
        "read_by": [
          "breakdown_gate_cli",
          "coverage_validator"
        ],
        "risk_level": "high",
        "schema": "scenes[].mentions[] with kind in {location, sublocation, prop, prop_state, wardrobe_change}; common required: kind, surface_text, span_quote, scene_id, scene_hash. Gate-A cache, NOT SSOT.",
        "written_by": [
          "breakdown_extract_cli"
        ]
      },
      {
        "id": "location_registry",
        "path": "{project}/assets/loc/{location_id}/base/location.json",
        "read_by": [
          "sublocation_registry",
          "ref_resolution"
        ],
        "risk_level": "high",
        "schema": "sublocations{name: {ref, source_sha256}}; absence = undecomposed = everything permitted (fail-OPEN); empty-but-present = decomposed-with-nothing-allowed.",
        "written_by": [
          "gen_sublocations_cli"
        ]
      },
      {
        "id": "asset_pool_refs",
        "path": "{project}/assets/{cls}/{subject}/base/pool/{kind}/",
        "read_by": [
          "project_path_construction",
          "ref_resolution"
        ],
        "reduced_tier": "always",
        "risk_level": "critical",
        "schema": "v3 canonical ref pool, kind in {identity, turn, expr, loc, prop, scene}; resolve_ref reads HERE.",
        "written_by": []
      },
      {
        "id": "composite_sheets",
        "path": "per-kind via ProjectPaths.sheet_path \u2014 char {project}/assets/char/<subject>/base/sheets/, loc {project}/assets/loc/<subject>/sheets/, prop {project}/assets/prop/<subject>/ (loc/prop do NOT use base/sheets/)",
        "read_by": [
          "ref_resolution"
        ],
        "risk_level": "high",
        "schema": "v3 composite multi-angle sheets (the Seedance/Kling lock). The per-kind layout is intentional and has ONE home: ProjectPaths.sheet_path (REC-213 C1+C2) \u2014 reader (resolve_sheet_asset) and writer (_sheet_dest) both call it.",
        "written_by": [
          "generate_composite_sheet._sheet_dest"
        ]
      },
      {
        "id": "promoted_hero_top_level",
        "path": "{project}/assets/{cls}/{subject}/{role|hero}.{ext}",
        "read_by": [],
        "reduced_tier": "always",
        "risk_level": "critical",
        "schema": "subject TOP-LEVEL promotion target (NOT base/pool/). The production resolver_ref does NOT read here \u2192 promoted heroes are orphaned. This is the ref_promotion KNOWN-BROKEN root.",
        "written_by": [
          "prep_character_angles_cli",
          "set_hero_junction",
          "promote_grid_junction"
        ]
      }
    ],
    "capabilities": [
      {
        "id": "ref_resolution",
        "reduced_tier": "always",
        "risk_level": "critical"
      },
      {
        "id": "board_review_comments",
        "risk_level": "medium"
      },
      {
        "id": "payload_assembly",
        "risk_level": "high"
      },
      {
        "id": "dispatch_entry_point",
        "risk_level": "critical"
      },
      {
        "id": "modality_registry",
        "risk_level": "high"
      },
      {
        "id": "prompt_building",
        "risk_level": "medium"
      },
      {
        "id": "worksurface_execution",
        "risk_level": "high"
      },
      {
        "id": "typed_payload",
        "reduced_tier": "always",
        "risk_level": "high"
      },
      {
        "id": "orchestration",
        "reduced_tier": "always",
        "risk_level": "critical"
      },
      {
        "id": "scene_beat_take",
        "reduced_tier": "always",
        "risk_level": "high"
      },
      {
        "id": "workflow_take_model",
        "reduced_tier": "auto",
        "risk_level": "medium"
      },
      {
        "id": "strategy_engine",
        "reduced_tier": "always",
        "risk_level": "high"
      },
      {
        "id": "budget_guard",
        "reduced_tier": "always",
        "risk_level": "high"
      },
      {
        "id": "output_layout",
        "reduced_tier": "always",
        "risk_level": "high"
      },
      {
        "id": "project_path_construction",
        "reduced_tier": "always",
        "risk_level": "high"
      },
      {
        "id": "ref_promotion",
        "reduced_tier": "always",
        "risk_level": "critical"
      }
    ],
    "divergences": [
      {
        "capability": "prompt_building",
        "forks": [
          "pencil_board_route",
          "photoreal_finish_route"
        ],
        "id": "board_two_stage_split",
        "invariant": "render_board_finish MUST NOT run on a non-approved board (asserts board.status=='approved') and MUST feed the approved pencil PNG as the FIRST reference; the photoreal finish is FULL-size while the pencil is HALF-size (_apply_iteration_size). The finish is judged by NO StoryGate \u2014 only the pencil tier gates. Collapsing the two stages, or judging the finish, breaks the REC-149 contract.\n",
        "on_flag": null,
        "reduced_tier": "always",
        "risk": "An engineer wiring \"render the board\" could skip the pencil approval gate and dispatch a full-size finish directly, bypassing StoryGate + human approval and the spend gate (preferred_board_artifact picks photoreal only after approval).\n",
        "risk_level": "high"
      },
      {
        "capability": "modality_registry",
        "forks": [
          "video_i2v_dispatch_path",
          "r2v_multi_execute_pass_path"
        ],
        "id": "modality_runner_fork",
        "invariant": "dispatch() MUST resolve the runner ONLY via get_runner(modality) from the RunnerRegistry bootstrapped by register_default_runners \u2014 no modality may get a second dispatch surface. r2v_multi legitimately bypasses build_dispatch_payload's per-shot author/cap path by delegating to StepRunner.execute_pass inside R2VMultiRunner.run; that bypass MUST stay behind the registry (i.e. still entered through dispatch()), never become a direct execute_pass call that skips _validate_payload + the receipt emit.\n",
        "on_flag": null,
        "reduced_tier": "always",
        "risk": "An engineer adding a new video sub-modality could register a parallel runner or call execute_pass directly, splitting the dispatch surface and dropping the receipts.jsonl audit line + aspect_ratio/model-profile guards silently.\n",
        "risk_level": "critical"
      },
      {
        "capability": "ref_resolution",
        "forks": [
          "composite_sheet_ref_path",
          "two_pass_angle_ref_path"
        ],
        "id": "video_ref_collection_fork",
        "invariant": "_collect_reference_images MUST try the composite-sheet collector (_collect_sheet_refs \u2192 resolve_sheet_asset per entity) FIRST and fall back to the two-pass angle collector only on None; assert_refs_complete MUST run on BOTH return branches (sheet and angle). The sheet branch returning the per-kind sheet path currently MASKS the known-broken angle/pool resolver (ssot_manifest ref_resolution.current_broken_behavior) \u2014 unifying onto one canonical ref source (REC-213=C) must not silently drop the pre-spend gate.\n",
        "on_flag": "use_composite_sheets",
        "reduced_tier": "always",
        "risk": "With sheets enabled the broken pool-scan fallback never fires, so the resolver bug stays invisible in production; with sheets disabled the same shot silently degrades to angle refs the broken resolver may not find. Two ref shapes for one surface, switched by an env var / project config.\n",
        "risk_level": "high"
      },
      {
        "capability": "prompt_building",
        "forks": [
          "author_strategy_route",
          "deterministic_template_route"
        ],
        "id": "prompt_authoring_fork",
        "invariant": "_build_author_aware_prompt MUST converge every failure mode onto the deterministic-template prompt (AuthorInputError/AuthorCallError/BindAssertion/ verify_block all \u2192 deterministic(...)), EXCEPT the systemic 3-consecutive author_call breaker which MUST raise and halt before further spend. A new strategy MUST NOT introduce an authored-prose path with no deterministic fallback, and MUST NOT bypass the _reset_breaker_state() accounting.\n",
        "on_flag": null,
        "reduced_tier": "always",
        "risk": "Authored prose forks (directed_prose vs shot_spec, \u00b1world_state_pass) reach different verify/cap loops; a strategy added without a deterministic floor would let an author outage block production instead of degrading, or let the breaker counter leak and false-halt a healthy run. (forks listed are the two ref routes the prompt is bound against \u2014 the authoring fork rides the same _build_author_aware_prompt junction; the load-bearing record is the invariant.)\n",
        "risk_level": "high"
      },
      {
        "capability": "worksurface_execution",
        "forks": [
          "seedance_via_flora",
          "seedance_via_fal"
        ],
        "id": "fallback_declared_not_wired",
        "invariant": "resolve_fallback() returning a (fb_adapter, fb_tier) MUST NOT be read as a live retry: VideoModelClient._finalize_failed logs the fallback but performs NO inline dispatch. Any code asserting \"primary failed \u2192 fallback ran\" is wrong; fallback retry must be caller-orchestrated by re-invoking submit() with the original dict.\n",
        "on_flag": null,
        "reduced_tier": "always",
        "risk": "REC-122 \u2014 the old \"retrying on fallback\" log wording claimed a dispatch that never happened, sending 2026-06-09 EP001 debugging to fal for requests that were never made. provider_strategy.json declares fallback=fal for every seeddance entry; an engineer trusting the strategy file assumes fal coverage on flora failure that does not exist.\n",
        "risk_level": "critical"
      },
      {
        "capability": "worksurface_execution",
        "forks": [
          "seedance_via_flora",
          "seedance_via_fal",
          "seedance_via_atlas"
        ],
        "id": "seedance_provider_three_way",
        "invariant": "The SAME model_id (seeddance-2.0) routes to flora (strategy primary), fal, OR atlas (RECOIL_PROVIDER_OVERRIDE). resolve_adapter's precedence is override > capability_exceptions > primary ONLY \u2014 the declared \"fallback\" is a SEPARATE resolve_fallback() lookup that is logged, NOT auto-dispatched (see fallback_declared_not_wired). Provider-specific ref handling (Flora upload_local_refs vs fal fal-storage upload vs atlas CDN reuse) MUST stay inside each adapter's build_submit \u2014 StepRunner emits ONE unified payload and never branches by provider.\n",
        "on_flag": "provider_override",
        "reduced_tier": "always",
        "risk": "Same payload, three transports with different ref-upload + cost models; only Flora has orphan-recovery wiring (flora_orphan_recovery loop is _is_flora_recovery_candidate-gated, so a fal/atlas timeout has NO recovery). A silent override flips the whole run's provider, cost model, and recovery behavior with no payload-visible signal.\n",
        "risk_level": "high"
      },
      {
        "capability": "typed_payload",
        "forks": [
          "video_dispatch_path",
          "pass_r2v_path"
        ],
        "id": "payload_hints_dict_vs_typed",
        "invariant": "StepRunnerHints is extra='allow' and _dict_to_unified passes a typed PayloadHints THROUGH but defensively COPIES a legacy dict. Every adapter read of payload.hints MUST go through coerce_to_dict() at the boundary; reading the wrong key silently drops fields (REC-38: tier read from 'provider_hints' instead of merged 'hints' \u2192 tier always None \u2192 UPGRADE_FAST_TO_PRO never reached VideoModelClient).\n",
        "on_flag": null,
        "reduced_tier": "always",
        "risk": "typed_payload is transitioning (expires 2026-06-30) and rides seed/tier to Flora via extra='allow'; untyped keys that bypass StepRunnerHints.model_fields silently vanish at the StepRunner\u2192adapter seam with no validation error.\n",
        "risk_level": "high"
      },
      {
        "capability": "modality_registry",
        "forks": [
          "video_dispatch_path",
          "kling_video_path"
        ],
        "id": "multi_shot_native_vs_sequential",
        "invariant": "execute_multi_shot routes to the native MultiShotPayload branch ONLY when model_profiles.get_api_pattern(model) is in {'kling_rest'}; every other model (Veo/SeedDance) falls through to _execute_sequential_shots (the canonical route). REC-235: the native branch is UNWIRED \u2014 KlingAdapter native multi-shot is not plumbed (VideoModelClient only accepts UnifiedVideoPayload-shaped dicts and drops shots/start_frame_bytes via _dict_to_unified) \u2014 so the kling_rest branch now RAISES NotImplementedError (fail-loud) instead of silently dispatching. The get_api_pattern / _MULTI_SHOT_PATTERNS routing seam is preserved as the future native wire-up entry point.\n",
        "on_flag": null,
        "reduced_tier": "always",
        "risk": "The native kling_rest branch is retired to a fail-loud raise: a caller targeting the native Kling multi-shot path gets NotImplementedError instead of a silent shot-drop. The working route is the sequential fallback. CP-3 revisits native multi-shot once UnifiedVideoPayload.hints['multi_shots'] is wired through KlingAdapter.\n",
        "risk_level": "medium"
      },
      {
        "capability": "worksurface_execution",
        "forks": [
          "video_dispatch_path",
          "keyframe_image_path"
        ],
        "id": "action_inference_per_adapter",
        "invariant": "Action (t2v/i2v/r2v/f2v and t2i/i2i/is2i) is inferred INDEPENDENTLY inside each adapter from payload shape \u2014 fal._infer_action, flora._infer_action, kling._resolve_endpoint \u2014 NOT decided by StepRunner. The promotion rules MUST agree on the shared payload contract (image\u2192i2v, reference_images\u2192r2v); flora additionally promotes image-model t2i\u2192i2i/is2i and adds f2v (image+image_tail), which fal/kling video paths do not.\n",
        "on_flag": null,
        "reduced_tier": "always",
        "risk": "Three separate shape\u2192action mappers over one UnifiedVideoPayload; a field added to the payload (e.g. a new ref slot) must be taught to every adapter's inferer or it silently routes to the wrong endpoint. Divergent f2v support means the same payload yields different actions on flora vs fal.\n",
        "risk_level": "medium"
      },
      {
        "capability": "orchestration",
        "forks": [
          "grouping_coverage",
          "grouping_continuity",
          "grouping_oner"
        ],
        "id": "grouping_strategy_fork",
        "invariant": "The grouping STRATEGY chosen at run time forks BOTH the persisted scene-id namespace AND the dispatch modality, and the resulting {episode}_{scene_id}.json is the SSOT the board + r2v re-read. A scene persisted under one strategy MUST be re-derived (or --batch-targeted) under the SAME strategy: continuity scenes are named BATCH_NNN but carry grouping.strategy='continuity' (the CONT selector token), and the per-strategy ordinal must round-trip through verify_scene_grouping_metadata before any reroll spend.\n",
        "on_flag": "grouping_strategy",
        "reduced_tier": "always",
        "risk": "An engineer adding a grouping strategy or rerolling under the wrong --grouping can mismatch the selector\u2192scene mapping (CONT\u2192BATCH vs ONER\u2192ONER), feed the wrong persisted beat to dispatch, or silently flip modality (r2v_multi\u2194video_i2v) and lose the [Xs-Ys] batch timestamp annotations.\n",
        "risk_level": "critical"
      },
      {
        "capability": "scene_beat_take",
        "forks": [
          "grouping_continuity",
          "grouping_continuity_per_shot"
        ],
        "id": "batch_modality_fork",
        "invariant": "A continuity Batch with len(shots) < min_batch_size (3) is flagged below_threshold and MUST dispatch as per-shot video_i2v, not r2v_multi; the same is true for a 1-shot oner. cluster_shots_into_batches owns this decision \u2014 callers MUST NOT force a below-threshold batch onto r2v_multi.\n",
        "on_flag": null,
        "reduced_tier": "always",
        "risk": "single_batch_from_shots deliberately DEFEATS the below_threshold/heuristic splitting (a coverage pass is one billed Flora run regardless of shot count), so the two clusterers diverge on whether to fall back. Mixing them \u2014 using cluster_shots_into_batches where a deliberate pass-as-batch was intended, or vice versa \u2014 silently changes batching, billing, and modality.\n",
        "risk_level": "high"
      },
      {
        "capability": "orchestration",
        "forks": [
          "grouping_coverage",
          "batch_reroll_selector"
        ],
        "id": "scene_version_append",
        "invariant": "The sole RE-DERIVATION writer APPENDS a new structure-immutable ep_NNN_BATCH_NNN.v{N+1}.json body (a candidate version) in the per-batch ep_NNN_BATCH_NNN.versions.json manifest; it MUST NOT rewrite the shot STRUCTURE of any existing version and MUST NOT move active_version. In-place take/board STATUS updates to the ACTIVE version body remain allowed (the existing save_scene caller surface); only SceneVersionStore.conform/revert moves the pointer. Every prior version's structure stays intact and re-pointable.\n",
        "reduced_tier": "always",
        "risk": "A re-derivation writer that OVERWRITES an existing version's shot structure, or any code that moves the pointer outside conform/revert, silently destroys approved shot structure \u2014 the exact loss this design prevents.\n",
        "risk_level": "high"
      },
      {
        "capability": "prompt_building",
        "forks": [
          "seedance_i2v_route",
          "kling_i2v_route",
          "gpt_image_2_storyboard_route",
          "previz_keyframe_route"
        ],
        "id": "builder_dispatch_table",
        "invariant": "EVERY production prompt MUST resolve through get_builder((model_id, modality)) against the single BUILDERS table (prompt_engine.py ~L7956-8042) \u2014 no parallel builder module, no direct-import that bypasses the (model,modality) key. get_builder raises KeyError (never silently substitutes) on an unregistered key. New (model,modality) pairs are added ONLY by appending to BUILDERS, kept in sync with provider_strategy.json (asserted by test_keys_match_provider_strategy_snapshot).\n",
        "on_flag": null,
        "reduced_tier": "always",
        "risk": "The table aliases distinct models onto shared builders (happy-horse-* \u2192 build_wan_*, gpt-image-2 \u2192 build_previs_prompt) whose token syntax differs (HappyHorse uses character1/character2 positional, not @Image1). An engineer adding a model by reusing a builder slot can ship a syntactically-wrong prompt that passes get_builder and only fails at the provider. The build_seeddance_i2v_multishot_prompt key is intentionally OMITTED \u2014 a tools/ import references a non-existent symbol (single-d \"seedance\" typo).\n",
        "risk_level": "high"
      },
      {
        "capability": "prompt_building",
        "forks": [
          "seedance_i2v_route",
          "kling_i2v_route"
        ],
        "id": "prompt_length_split",
        "invariant": "Prompt-side _enforce_prompt_length (prompt_engine.py L443-495) TRUNCATES at the bible's per-model max_chars (last sentence boundary); the Flora video adapter (recoil/execution/providers/flora.py L489-498, _FLORA_MAX_PROMPT_CHARS=2500) RAISES ValueError on any video prompt >2500 chars rather than truncating. These two guards MUST stay reconciled: if a model's bible max_chars is unset or >2500, prompt_engine emits an over-length prompt that flora.py hard-fails. The seedance builders must keep prompts under 2500 organically (boards+sheets carry the visual), never rely on back-truncation.\n",
        "on_flag": null,
        "reduced_tier": "always",
        "risk": "REC-123: Flora's preprocessing silently REPLACES any >2500-char prompt with '. _ .' (blankPrompt) and bills the run with no prompt. flora.py fails loud to prevent the silent burn \u2014 but if _enforce_prompt_length truncates a seedance prompt to a bible max_chars that is LARGER than 2500, the run reaches flora.py over-length and aborts; if smaller, the cinema tail is lopped. The two limits live in different files and can drift apart unnoticed.\n",
        "risk_level": "high"
      },
      {
        "capability": "prompt_building",
        "forks": [
          "seedance_r2v_multi_route",
          "seedance_i2v_route"
        ],
        "id": "name_binding_modality_fork",
        "invariant": "bind_named_prose / _assert_bound_prompt (prompt_engine.py L5399-5530) forks by modality: 'r2v_multi' bound prose MUST carry @ImageN tokens, every one backed by the ref manifest; 'video_i2v' bound prose MUST NOT contain ANY @ImageN token (raises BindAssertionError). No bound prompt of either modality may leak a configured character proper noun, and beat count MUST equal len(timing_segments).\n",
        "on_flag": null,
        "reduced_tier": "always",
        "risk": "A2 content-policy leak: single-shot i2v/r2v had never run through the name-strip (\"Option C\") path. Mixing the two contracts \u2014 emitting @ImageN tokens on the i2v path, or leaving a character name un-stripped on either \u2014 fails the bind assertion at build time, but a builder change that routes i2v prose through the r2v binder would inject illegal @ImageN tokens that the provider mis-maps.\n",
        "risk_level": "high"
      },
      {
        "capability": "ref_resolution",
        "forks": [
          "board_ref_path",
          "video_ref_path"
        ],
        "id": "ref_system_split",
        "invariant": "for a LOCKED board with use_composite_sheets ENABLED, board_ref_path resolves the LOCATION sheet via the SAME recoil/core/ref_resolver.py::resolve_sheet_asset as video, gated by the SAME composite_sheets_enabled flag (REC-213 C3 \u2014 one activation SSOT, no board/video fork); board CHARACTER refs stay individual (resolve_character_bundle) by design \u2014 an intentional per-surface choice, not a silent fork.",
        "on_flag": "use_composite_sheets",
        "reduced_tier": "always",
        "risk": "PR#135 \u2014 the board path fed individual plates while video uses composite SHEETS; an engineer extending board refs could not see the split, fed the wrong location ref, and mis-injected the debt_counter WARDROBE item as a prop.",
        "risk_level": "critical"
      },
      {
        "capability": "orchestration",
        "forks": [
          "board_autoreroll_route",
          "board_single_build_route"
        ],
        "id": "board_reroll_story_gate_fork",
        "invariant": "build_with_auto_reroll MUST run only under story_gate_mode='shadow'; it RAISES BoardBuilderError on 'off' AND on 'enforce' (enforce ships in v1.1, NotImplementedError). The reroll loop is the ONLY path that consumes story-gate HARD verdicts + fix-notes \u2014 a board built via the single-build route bypasses gate-driven reroll entirely.",
        "on_flag": "story_gate_mode",
        "reduced_tier": "always",
        "risk": "An engineer enabling story_gate_mode='enforce' expecting stricter boards instead crashes the build (raises in StoryGate.__init__ and in build_with_auto_reroll's mode check). And board generation silently has NO auto-reroll whenever mode is 'off' (the default) \u2014 HARD board failures pass through un-rerolled.",
        "risk_level": "high"
      },
      {
        "capability": "ref_promotion",
        "forks": [
          "promotion_writer_route",
          "production_reader_route"
        ],
        "id": "ref_promotion_three_writers",
        "invariant": "ref_promotion MUST land refs where the production resolver reads. Today the three promotion mechanisms (set_hero, promote_grid, prep_character_angles.process_intake) ALL write to assets/{cls}/{subject}/ top-level, while resolve_entity_refs (the production payload resolver) reads assets/{cls}/{subject}/base/pool/{kind}/ \u2014 so no promotion output reaches production. Consolidation = one promotion writer + one resolver onto base/pool via a manifest pointer (REC-76 follow-up).",
        "on_flag": null,
        "reduced_tier": "always",
        "risk": "ssot_manifest marks ref_promotion canonical:null KNOWN-BROKEN. THREE incompatible mechanisms (asset_ops.set_hero copies, ref_image_ops.promote_grid copies w/ dual names, prep_character_angles renames) all promote toward subject top-level with non-conformant names; production resolver does not read the pool nor the top level it expects \u2192 promoted refs are silently orphaned. An operator promoting a hero sees success but production never picks it up.",
        "risk_level": "critical"
      },
      {
        "capability": "project_path_construction",
        "forks": [
          "char_sheet_path_route",
          "loc_sheet_path_route"
        ],
        "id": "location_sheets_path_split",
        "invariant": "the per-kind sheet layout (char assets/char/<n>/base/sheets/, loc assets/loc/<n>/sheets/ \u2014 loc intentionally drops base/) has exactly ONE home: ProjectPaths.sheet_path (REC-213 C1+C2). Reader (resolve_sheet_asset) and writer (_sheet_dest) both go through it, so the per-kind branch cannot drift across code paths.",
        "on_flag": null,
        "reduced_tier": "always",
        "risk": "RESOLVED by C1+C2: the per-kind difference is intentional and SSOT'd in ProjectPaths.sheet_path. Note loc composite sheets (sheets/) still live under a different parent than the loc registry (base/location.json) \u2014 a layout fact, no longer a code-path split.",
        "risk_level": "low"
      }
    ],
    "entrypoints": [
      {
        "capabilities": [
          "prompt_building",
          "ref_resolution",
          "modality_registry"
        ],
        "file": "recoil/pipeline/cli/generate.py",
        "id": "board_cli",
        "invoke": "python3 recoil/pipeline/cli/generate.py <project> <episode> --storyboard <batch> [--auto-reroll --max-board-attempts N]",
        "kind": "cli",
        "produces": [
          "pencil_strip",
          "photoreal_finish",
          "board_record",
          "story_gate_verdict"
        ],
        "risk_level": "high",
        "summary": "THE board CLI surface. --storyboard builds a proposed pencil strip for one continuity/oner batch (via _run_storyboard_build \u2192 build_with_auto_reroll when --auto-reroll, else build_and_dispatch_board). --approve-board triggers the photoreal finish (render_board_finish); --reject-board / --revalidate-board manage the board decision SSOT. board_provider.py NOTE: no board-model CLI flag exists; the A/B seam is the board_model param + beat_metadata, not argv.\n",
        "symbol": "main"
      },
      {
        "capabilities": [
          "prompt_building",
          "ref_resolution",
          "modality_registry"
        ],
        "file": "recoil/pipeline/_lib/board_builder.py::build_and_dispatch_board",
        "id": "build_and_dispatch_board",
        "invoke": "from recoil.pipeline._lib.board_builder import build_and_dispatch_board",
        "kind": "module",
        "produces": [
          "pencil_strip",
          "board_record",
          "story_gate_verdict"
        ],
        "risk_level": "high",
        "summary": "Single-attempt pencil-tier board build+dispatch. Collects refs (_collect_board_refs \u2014 individual char/prop refs + the location ref, which is the composite SHEET when locked + use_composite_sheets on per REC-213 C3, else the sublocation plate), enforces the gpt-image-2 16-ref cap, builds the storyboard prompt via get_builder(board_model,'storyboard'), dispatches at the HALF-SIZE iteration tier (_iteration_tier / _apply_iteration_size), runs the shadow StoryGate, and on a primary-model refusal/422 retries ONCE against board_fallback_model. Persists proposed board metadata.\n",
        "symbol": "build_and_dispatch_board"
      },
      {
        "capabilities": [
          "prompt_building",
          "modality_registry"
        ],
        "file": "recoil/pipeline/_lib/board_builder.py::render_board_finish",
        "id": "render_board_finish",
        "invoke": "from recoil.pipeline._lib.board_builder import render_board_finish",
        "kind": "module",
        "produces": [
          "photoreal_finish"
        ],
        "risk_level": "high",
        "summary": "Stage-2 (REC-149): render a FULL-SIZE photoreal finish for an already-APPROVED pencil board. Requires board.status==approved; feeds the approved pencil PNG as the FIRST (composition) reference plus copied identity refs from its sidecar (_finish_refs_from_sidecar) into get_builder(board_model,'storyboard_finish'). Same single-shot fallback-model retry on refusal. NO StoryGate on the finish.\n",
        "symbol": "render_board_finish"
      },
      {
        "capabilities": [
          "orchestration"
        ],
        "file": "recoil/pipeline/cli/run_overnight.py",
        "id": "run_overnight_cli",
        "invoke": "python3 recoil/pipeline/cli/run_overnight.py <project> <ep> [...]",
        "kind": "cli",
        "produces": [
          "persisted_scene",
          "ops_log"
        ],
        "reduced_tier": "always",
        "risk_level": "high",
        "summary": "Autonomous overnight production entry over EpisodeRunner \u2014 the unattended sibling of generate.py. Loops episode batches without an interactive operator.\n",
        "symbol": "main"
      },
      {
        "capabilities": [
          "dispatch_entry_point",
          "worksurface_execution"
        ],
        "file": "recoil/pipeline/tools/dispatch_cli.py",
        "id": "dispatch_cli",
        "invoke": "python3 recoil/pipeline/tools/dispatch_cli.py --project <p> --model <m> [--shot|--shots|--start-frame ...] [--ref-video --audio-url --generate-audio]",
        "kind": "cli",
        "produces": [
          "video_take_mp4",
          "receipts_jsonl"
        ],
        "reduced_tier": "always",
        "risk_level": "high",
        "summary": "Major manual/test generation entry over dispatch()/StepRunner \u2014 single-shot i2v, r2v_multi, keyframe, talking-shot, ad-hoc refs. The hand-dispatch path that bypasses plan loading.\n",
        "symbol": "main"
      },
      {
        "capabilities": [
          "ref_resolution"
        ],
        "file": "recoil/pipeline/tools/generate_composite_sheet.py",
        "id": "generate_composite_sheet_cli",
        "invoke": "python3 recoil/pipeline/tools/generate_composite_sheet.py --project <p> --entity <id> ...",
        "kind": "cli",
        "produces": [
          "composite_sheets"
        ],
        "risk_level": "medium",
        "summary": "THE writer for the composite-sheet ref system (sheets/sheet_v1.png) that the video path consumes via resolve_sheet_asset (the bundle sheet route). Without a generated sheet, use_composite_sheets silently falls back to the two-pass angle path.\n",
        "symbol": "main"
      },
      {
        "capabilities": [
          "output_layout"
        ],
        "file": "recoil/pipeline/tools/breakdown_gate_cli.py",
        "id": "breakdown_gate_cli",
        "invoke": "python3 recoil/pipeline/tools/breakdown_gate_cli.py --project <p> --episode <n>",
        "kind": "cli",
        "produces": [
          "mention_ledger"
        ],
        "risk_level": "medium",
        "summary": "Gate over the mention_ledger (Gate-A breakdown readiness) \u2014 the breakdown counterpart to the extractor; ratifies the per-episode scene mention ledger before board/r2v consume it.\n",
        "symbol": "main"
      },
      {
        "capabilities": [
          "dispatch_entry_point",
          "modality_registry"
        ],
        "file": "recoil/pipeline/core/dispatch.py::dispatch",
        "id": "dispatch",
        "invoke": "from recoil.pipeline.core.dispatch import dispatch; dispatch(modality, payload, context=ctx)",
        "kind": "module",
        "produces": [
          "receipts_jsonl"
        ],
        "risk_level": "critical",
        "summary": "CP-5 single generation entry point. Lazy-bootstraps runners per StepRunner (_ensure_bootstrap), _validate_payload (modality-keyed contract guard), injects aspect_ratio from Project SSOT, runs model-profile constraint check, calls runner.run(payload), wraps RunResult in a GenerationReceipt, and emits the receipts.jsonl audit line. Every generation call goes through here EXCEPT previs and the r2v_multi execute_pass body (see divergences).\n",
        "symbol": "dispatch"
      },
      {
        "capabilities": [
          "payload_assembly"
        ],
        "file": "recoil/pipeline/tools/audit_dispatch.py::main",
        "id": "audit_dispatch_cli",
        "invoke": "python3 -m recoil.pipeline.tools.audit_dispatch --project <slug> --episode ep_NNN",
        "kind": "cli",
        "risk_level": "medium",
        "summary": "Pre-spend payload audit. Calls build_dispatch_payload(..., dry_run=True) per modality (\u2192 _build_audit_payload) to materialize the payload shape WITHOUT authoring prose or spending; the canonical dry-run payload surface. Does NOT call dispatch().\n",
        "symbol": "main"
      },
      {
        "capabilities": [
          "typed_payload",
          "worksurface_execution"
        ],
        "file": "recoil/execution/step_runner.py::StepRunner",
        "id": "step_runner",
        "invoke": "from recoil.execution.step_runner import StepRunner",
        "kind": "module",
        "risk_level": "high",
        "summary": "Single source of truth for all generation execution. Synchronous, thread-safe (store mutations via ExecutionStore lock). execute_video (I2V/T2V/V2V), execute_pass (SeedDance R2V coverage), execute_keyframe (image t2i/i2i/is2i + gate retry loop), execute_previz, execute_wan_r2v, execute_multi_shot. Emits ONE unified dict payload; provider quirks live in adapters.\n",
        "symbol": "StepRunner"
      },
      {
        "capabilities": [
          "worksurface_execution",
          "typed_payload"
        ],
        "file": "recoil/execution/video_model_client.py::VideoModelClient",
        "id": "video_model_client",
        "invoke": "from recoil.execution.video_model_client import VideoModelClient",
        "kind": "module",
        "risk_level": "high",
        "summary": "Thin client atop the adapter registry. dict \u2192 UnifiedVideoPayload, resolve_adapter, submit/poll/finalize, observability rows on every terminal event. On failure it LOGS the configured fallback but does NOT auto-dispatch it (see fallback_declared_not_wired). Returns a core.jobs.Job.\n",
        "symbol": "VideoModelClient"
      },
      {
        "capabilities": [
          "orchestration",
          "scene_beat_take"
        ],
        "file": "recoil/pipeline/cli/generate.py",
        "id": "generate_cli",
        "invoke": "python3 recoil/pipeline/cli/generate.py <project> <ep> --grouping {coverage|continuity|oner|auto} [--pass <id>] [--batch EP{ep}_{CONT|ONER}_{ord}] [--new-take] [--dry-run]",
        "kind": "cli",
        "produces": [
          "persisted_scene",
          "ops_log"
        ],
        "reduced_tier": "always",
        "risk_level": "critical",
        "summary": "Live production entry. Loads the plan, builds ExecutionStore + StepRunner, cold-starts LearningEngine\u2192StrategyEngine, constructs EpisodeRunner, acquires a lockfile, then asyncio.run(run_episode_batches()). The --batch path BYPASSES coverage-pass loading + plan reassembly and dispatches a single persisted scene directly (not run_episode_batches).\n",
        "symbol": "main"
      },
      {
        "capabilities": [
          "orchestration",
          "scene_beat_take",
          "workflow_take_model"
        ],
        "file": "recoil/pipeline/orchestrator/episode_runner.py",
        "id": "episode_runner_module",
        "invoke": "from recoil.pipeline.orchestrator.episode_runner import EpisodeRunner; await EpisodeRunner(...).run_episode_batches(...)",
        "kind": "module",
        "produces": [
          "persisted_scene"
        ],
        "reduced_tier": "always",
        "risk_level": "critical",
        "summary": "Clusters shots into groups via the grouping registry, converts each Group to a Scene of Beats, runs Beats under an asyncio.Semaphore, performs stale-take recovery + phantom-success invalidation + current-ref fingerprint revalidation before dispatch, and persists each Scene through the guarded writer.\n",
        "symbol": "EpisodeRunner.run_episode_batches"
      },
      {
        "capabilities": [
          "orchestration",
          "scene_beat_take"
        ],
        "file": "recoil/pipeline/api/routes/reroll.py",
        "id": "reroll_route",
        "invoke": "POST /reroll {project, episode, batch_id: EP{ep}_{CONT|ONER}_{ord}}",
        "kind": "tool",
        "produces": [
          "persisted_scene"
        ],
        "reduced_tier": "auto",
        "risk_level": "high",
        "summary": "HTTP reroll surface. Parses the batch selector to the SAME persisted scene id as --batch (batch_selector), board-gate pre-scans BEFORE any mutating call, then prepare_beat_for_reroll clears a stale primary and dispatches one new take.\n",
        "symbol": "reroll"
      },
      {
        "capabilities": [
          "prompt_building"
        ],
        "file": "recoil/pipeline/_lib/prompt_engine.py",
        "id": "prompt_engine_smoke_cli",
        "invoke": "python -m recoil.pipeline._lib.prompt_engine",
        "kind": "cli",
        "reduced_tier": "never",
        "risk_level": "low",
        "summary": "__main__ smoke harness: loads RECOIL_ROOT/DEFAULT_PROJECT storyboard_ep_001.json + project_config.json + breakdown.json and prints compiled prompts. Dev smoke only \u2014 production callers import get_builder()/compile_all_prompts() directly (EpisodeRunner \u2192 build_dispatch_payload \u2192 builders). Not the live dispatch path.\n",
        "symbol": "__main__"
      },
      {
        "capabilities": [
          "ref_resolution"
        ],
        "file": "recoil/pipeline/_lib/board_builder.py",
        "id": "collect_board_refs",
        "invoke": "from recoil.pipeline._lib.board_builder import _collect_board_refs",
        "kind": "module",
        "produces": [
          "board_ref_layout"
        ],
        "reduced_tier": "always",
        "risk_level": "high",
        "summary": "Board surface ref collector \u2014 char refs per-kind INDIVIDUAL (char bundle turn-views); prop refs individual prop-identity for NON-worn props (a WORN prop, bible attached_to a char in the shot, renders as PROSE on the carrier with NO standalone ref \u2014 REC-213 C4); the LOCATION ref is the composite SHEET (resolve_sheet_asset) when locked + use_composite_sheets on (REC-213 C3), else the sublocation plate.",
        "symbol": "_collect_board_refs"
      },
      {
        "capabilities": [
          "ref_resolution"
        ],
        "file": "recoil/pipeline/_lib/dispatch_payload.py",
        "id": "collect_reference_images_entry",
        "invoke": "from recoil.pipeline._lib.dispatch_payload import _collect_reference_images",
        "kind": "module",
        "produces": [
          "video_ref_manifest"
        ],
        "reduced_tier": "always",
        "risk_level": "high",
        "summary": "Video/r2v surface ref collector. Tries composite sheets first (when enabled), else two-pass angle collection capped at 9 refs. Emits ref_manifest position map.",
        "symbol": "_collect_reference_images"
      },
      {
        "capabilities": [
          "scene_beat_take",
          "orchestration"
        ],
        "file": "recoil/pipeline/cli/generate.py",
        "id": "generate_cli_new_take",
        "invoke": "python3 recoil/pipeline/cli/generate.py --project P --episode N --pass PASS_X --new-take [--strategy NAME] [--seed N] [--make-primary]",
        "kind": "cli",
        "summary": "Manual single-pass reroll. force_new_take=True derives grouping from the primary take and runs ONE more Take through _dispatch_one_beat (break after first). Coverage-pass rerolls only (non-coverage deferred REC-111).",
        "symbol": "main"
      },
      {
        "file": "recoil/pipeline/tools/breakdown_extract_cli.py",
        "id": "breakdown_extract_cli",
        "invoke": "python3 recoil/pipeline/tools/breakdown_extract_cli.py --project <slug> --episode <N> [--dry-run]",
        "kind": "cli",
        "produces": [
          "mention_ledger"
        ],
        "risk_level": "medium",
        "summary": "Extract/refresh the per-episode mention ledger via extract_mention_ledger; carries forward unchanged scenes by scene_hash.",
        "symbol": "main"
      },
      {
        "file": "recoil/pipeline/tools/gen_sublocations.py",
        "id": "gen_sublocations_cli",
        "invoke": "python3 recoil/pipeline/tools/gen_sublocations.py --project <slug> --location <id> [--probe --dry-run --adjacency-confirm --restamp]",
        "kind": "cli",
        "produces": [
          "location_registry"
        ],
        "risk_level": "medium",
        "summary": "SOLE writer of base/location.json sublocation registry (post-2026-06-11 retirement of other writers); generates derived sublocation refs and reconciles source_sha256 to bible description hashes.",
        "symbol": "main"
      },
      {
        "capabilities": [
          "ref_promotion"
        ],
        "file": "recoil/pipeline/tools/prep_character_angles.py",
        "id": "prep_character_angles_cli",
        "invoke": "python3 recoil/pipeline/tools/prep_character_angles.py  # Path A: rotate an intake/MJ hero into angle refs",
        "kind": "cli",
        "risk_level": "high",
        "summary": "ref_promotion mechanism #1 \u2014 process_intake copies a hero to assets/char/<slug>/hero.<ext> (subject top-level, NOT base/pool); then generates angles. Non-conformant: resolver does not read this path.",
        "symbol": "prep_character"
      }
    ],
    "flags": [
      {
        "default": "gpt-image-2",
        "defined": "recoil/pipeline/_lib/board_provider.py::select_board_model",
        "forks_capability": "prompt_building",
        "gates_routes": [
          "board_primary_model_route"
        ],
        "id": "board_model_override",
        "summary": "The REC-182/188 board-model A/B seam. Precedence: explicit board_model param override \u2192 beat.beat_metadata['board_model'] \u2192 config default gpt-image-2. NO CLI flag (board_provider docstring). Selects the prompt builder registered for (model_id,'storyboard'); gpt-image-2 and gemini-3.1-flash-image-preview both register build_storyboard_strip_prompt.\n"
      },
      {
        "default": false,
        "defined": "recoil/pipeline/_lib/dispatch_payload.py::board_gated",
        "gates_routes": [
          "board_artifact_video_injection_route"
        ],
        "id": "board_gated",
        "risk_level": "medium",
        "summary": "PayloadContext.board_gated \u2014 when true AND a board_ref_path is present, the approved board image is appended to the video payload as the final board_1 ref (dispatch_payload.py:1265,2252).\n"
      },
      {
        "default": false,
        "defined": "recoil/pipeline/_lib/dispatch_payload.py::composite_sheets_enabled",
        "forks_capability": "ref_resolution",
        "gates_routes": [
          "composite_sheet_ref_path",
          "board_location_sheet_route"
        ],
        "id": "use_composite_sheets",
        "summary": "Env RECOIL_USE_COMPOSITE_SHEETS=1 (wins) OR project_config.use_composite_sheets:true \u2192 video ref collection returns composite SHEETS, AND a LOCKED board's LOCATION ref becomes the composite SHEET (REC-213 C3, board_location_sheet_route); else two-pass angle refs / the sublocation plate. ONE public activation gate (composite_sheets_enabled) shared by both surfaces \u2014 the single activation SSOT (no board/video fork).\n"
      },
      {
        "default": false,
        "defined": "recoil/pipeline/_lib/dispatch_payload.py::_verify_spatial_pre_spend",
        "forks_capability": "payload_assembly",
        "gates_routes": [],
        "id": "spatial_override",
        "summary": "RECOIL_SPATIAL_OVERRIDE=1 downgrades REC-111 spatial pre-spend BLOCK\u2192WARN (logged loudly) instead of raising DispatchPayloadError. r2v_multi only, when primitive.location_id is set.\n"
      },
      {
        "default": false,
        "defined": "recoil/pipeline/_lib/dispatch_payload.py::_build_author_aware_prompt",
        "forks_capability": "prompt_building",
        "gates_routes": [],
        "id": "world_state_pass",
        "summary": "RECOIL_WORLD_STATE_PASS=1 \u2192 _inject_world_state_settings clones the primitive with derived per-segment settings before author_pass; r2v_multi + shot_spec only.\n"
      },
      {
        "default": false,
        "defined": "recoil/pipeline/core/dispatch.py::_emit_eventbus_hook",
        "forks_capability": "dispatch_entry_point",
        "gates_routes": [],
        "id": "eventbus_enabled",
        "summary": "RECOIL_EVENTBUS_ENABLED=1 \u2192 dispatch() emits to recoil.api.eventbus.BUS after each receipt. Default OFF keeps the engine\u2192api import edge out of the default env. No-op-on-failure; never blocks dispatch.\n"
      },
      {
        "default": null,
        "defined": "recoil/execution/providers/registry.py::resolve_adapter",
        "forks_capability": "worksurface_execution",
        "gates_routes": [
          "seedance_via_atlas"
        ],
        "id": "provider_override",
        "risk_level": "high",
        "summary": "RECOIL_PROVIDER_OVERRIDE forces a single provider_id for ALL models (highest routing precedence), still capability-enforced. RECOIL_PROVIDER_MODE=test swaps live adapters for providers.testing.mock_* of the same Protocol.\n"
      },
      {
        "default": "default",
        "defined": "recoil/execution/providers/registry.py::resolve_adapter",
        "forks_capability": "worksurface_execution",
        "id": "provider_tier",
        "risk_level": "medium",
        "summary": "Tier is ALWAYS passed as a kwarg from StepRunner (CP-2 edit #7), NOT via env \u2014 RECOIL_PROVIDER_TIER_OVERRIDE races under ProductionLoop's ThreadPoolExecutor. execute_pass defaults tier='standard_720p'; execute_video reads hints['tier'].\n"
      },
      {
        "default": "coverage",
        "defined": "recoil/pipeline/_lib/grouping.py::get_grouping",
        "forks_capability": "orchestration",
        "gates_routes": [
          "grouping_coverage",
          "grouping_continuity",
          "grouping_oner"
        ],
        "id": "grouping_strategy",
        "summary": "--grouping {coverage|continuity|oner|solo|auto} selects a GROUPING_REGISTRY assembler. Each forks the persisted scene-id namespace AND the modality (coverage\u2192pass_id/r2v_multi; continuity\u2192BATCH_NNN/r2v_multi|video_i2v; oner\u2192ONER_NNN). The default production strategy is coverage.\n"
      },
      {
        "default": false,
        "defined": "recoil/pipeline/cli/generate.py::main",
        "forks_capability": "scene_beat_take",
        "gates_routes": [
          "batch_reroll_selector"
        ],
        "id": "force_new_take",
        "summary": "--new-take requires exactly one r2v_multi batch target and reroll grouping derived from the primary take; --batch requires --new-take; --retry is incompatible with --batch.\n"
      },
      {
        "default": false,
        "defined": "recoil/pipeline/_lib/prompt_engine.py::enrich_prompt",
        "forks_capability": "prompt_building",
        "gates_routes": [],
        "id": "skip_flash_enrichment",
        "summary": "enrich_prompt early-returns the raw builder prompt (tag 'bypass_config') when project_config.skip_flash_enrichment is set, OR (tag 'bypass_env') when is_env=True \u2014 Flash hallucinates humans into empty rooms, so ENV shots never enrich.\n"
      },
      {
        "default": false,
        "defined": "recoil/pipeline/_lib/prompt_engine.py::build_wan_i2v_prompt",
        "forks_capability": "prompt_building",
        "gates_routes": [
          "wan_inbetween_route"
        ],
        "id": "has_end_frame",
        "summary": "has_end_frame=True switches build_wan_i2v_prompt to In-Between transition prose (start\u2192end interpolation, cede-control); False is standard single-anchor i2v.\n"
      },
      {
        "default": "off",
        "defined": "recoil/pipeline/_lib/story_gate.py::story_gate_mode",
        "forks_capability": "orchestration",
        "gates_routes": [
          "board_autoreroll_route"
        ],
        "id": "story_gate_mode",
        "summary": "off | shadow | enforce. build_with_auto_reroll REQUIRES 'shadow' (raises BoardBuilderError on 'off', raises on 'enforce' \u2014 not yet supported). Forks board generation into a reroll loop vs a single build."
      }
    ],
    "loops": [
      {
        "bound": {
          "max": 1,
          "source": "single-shot: board_fallback_model returns fallback once; primary==fallback \u2192 no retry"
        },
        "capability": "prompt_building",
        "driver": "recoil/pipeline/_lib/board_provider.py::board_fallback_model",
        "exit": [
          "fallback dispatch succeeds \u2192 proceed (sidecar fallback_from set)",
          "second refusal \u2192 HARD block (board dispatch failed)"
        ],
        "id": "board_model_fallback_retry",
        "kind": "retry",
        "retries_into": "build_and_dispatch_board",
        "risk_level": "high",
        "trigger": "primary board dispatch RunResult is a refusal/422 (is_board_refusal)"
      },
      {
        "bound": {
          "max": 3,
          "source": "max_attempts = 3 if provider cap is not None else 2"
        },
        "capability": "prompt_building",
        "driver": "recoil/pipeline/_lib/dispatch_payload.py::_build_author_aware_prompt",
        "exit": [
          "verify passes (no blocking result) AND prompt within provider cap \u2192 bind + return",
          "attempt >= max_attempts on verify BLOCK \u2192 deterministic('verify_block') fallback"
        ],
        "id": "prose_author_verify_retry",
        "kind": "retry",
        "retries_into": "prose_author_verify_retry",
        "risk_level": "medium",
        "trigger": "authored prose fails verify_authored_prose with a BLOCKING result"
      },
      {
        "bound": {
          "max": 3,
          "source": "max_attempts (same loop as verify retry; cap-present \u21d2 3)"
        },
        "capability": "prompt_building",
        "driver": "recoil/pipeline/_lib/dispatch_payload.py::_build_author_aware_prompt",
        "exit": [
          "len(prompt_text) <= cap \u2192 break, return authored prompt",
          "attempt >= max_attempts still over cap \u2192 raise PromptTooLongError"
        ],
        "id": "prompt_cap_reauthor_retry",
        "kind": "retry",
        "retries_into": "prompt_cap_reauthor_retry",
        "risk_level": "high",
        "trigger": "bound prompt length > provider max_prompt_chars cap"
      },
      {
        "bound": {
          "max": 3,
          "source": "module-global _consecutive_author_call_failures >= 3"
        },
        "capability": "prompt_building",
        "driver": "recoil/pipeline/_lib/dispatch_payload.py::_reset_breaker_state",
        "exit": [
          "any non-author_call success path \u2192 _reset_breaker_state() zeroes the counter",
          "3rd consecutive AuthorCallError \u2192 raise DispatchPayloadError (halt before further spend)",
          "< 3 consecutive \u2192 deterministic('author_call') fallback for this shot"
        ],
        "id": "author_call_breaker",
        "kind": "convergence",
        "retries_into": "author_call_breaker",
        "risk_level": "high",
        "trigger": "AuthorCallError (prose-author transport/auth failure) raised in the attempt loop"
      },
      {
        "bound": {
          "max": 1800,
          "source": "step_runner timeout_s arg (model-dependent)"
        },
        "capability": "worksurface_execution",
        "driver": "recoil/execution/video_model_client.py::VideoModelClient.wait_for_job",
        "exit": [
          "status==COMPLETED \u2192 finalize",
          "status==FAILED \u2192 finalize_failed",
          "timeout \u2192 TIMEOUT + best-effort cancel"
        ],
        "id": "poll_until_terminal",
        "kind": "poll",
        "reads_artifact": "observability_log",
        "risk_level": "high",
        "trigger": "job submitted; status not in {COMPLETED, FAILED, CANCELLED}"
      },
      {
        "bound": {
          "max": 1200,
          "source": "_orphan_recovery_deadline_s()"
        },
        "capability": "worksurface_execution",
        "driver": "recoil/execution/video_model_client.py::VideoModelClient._recover_orphaned_run",
        "exit": [
          "COMPLETED+video_url \u2192 finalize_completed",
          "terminal FAILED \u2192 finalize_failed",
          "deadline \u2192 ORPHANED (recoverable server-side)"
        ],
        "id": "flora_orphan_recovery",
        "kind": "poll",
        "retries_into": "poll_until_terminal",
        "risk_level": "high",
        "trigger": "Flora-only: poll TIMEOUT while still RUNNING, OR FAILED with no provider error, OR COMPLETED with no video_url (_is_flora_recovery_candidate + _should_recover_*).\n"
      },
      {
        "bound": {
          "max": 3,
          "source": "execute_keyframe(max_gate_retries=3)"
        },
        "capability": "worksurface_execution",
        "driver": "recoil/execution/step_runner.py::StepRunner.execute_keyframe",
        "exit": [
          "all gates pass \u2192 keyframe_generated",
          "non-retriable/semantic \u2192 keyframe_semantic_failed",
          "no feedback fix \u2192 icu_escalated",
          "retries exhausted \u2192 keyframe_mechanical_failed"
        ],
        "id": "keyframe_gate_retry",
        "kind": "retry",
        "risk_level": "high",
        "trigger": "keyframe gate verdict not passed AND verdict.retriable AND attempt < max_gate_retries"
      },
      {
        "bound": {
          "max": 3,
          "source": "execute_keyframe gate loop"
        },
        "capability": "worksurface_execution",
        "driver": "recoil/execution/feedback/agent.py::FeedbackAgent",
        "exit": [
          "fix applied \u2192 continue retry (negative-prompt / ref-prune)",
          "no fix \u2192 autopsy + icu_escalated"
        ],
        "id": "keyframe_feedback_retry",
        "kind": "strategy",
        "retries_into": "keyframe_gate_retry",
        "risk_level": "medium",
        "trigger": "keyframe gate failed AND a retry attempt remains \u2014 FeedbackAgent.diagnose() consulted before re-render"
      },
      {
        "bound": {
          "max": 3,
          "source": "EpisodeRunner.max_takes (default 3; --max-takes CLI override propagates to existing scenes on resume)"
        },
        "capability": "scene_beat_take",
        "driver": "recoil/pipeline/orchestrator/episode_runner.py::EpisodeRunner._dispatch_one_beat",
        "exit": [
          "beat.approved or beat.is_exhausted (max_takes reached \u2192 max_takes_reached)",
          "StrategyEngine strategy set exhausted (_strategy_exhausted)",
          "BudgetExhaustedError (budget_guard.would_exceed)"
        ],
        "id": "beat_take_retry",
        "kind": "retry",
        "reads_artifact": "persisted_scene",
        "reduced_tier": "always",
        "retries_into": "grouping_coverage",
        "risk_level": "high",
        "trigger": "Take fails (or no primary_take yet) inside _dispatch_one_beat; loop while not beat.is_exhausted and not beat.approved and not strategy_exhausted."
      },
      {
        "bound": {
          "max": 3,
          "source": "shares max_takes with beat_take_retry; strategy set size also bounds it"
        },
        "capability": "workflow_take_model",
        "driver": "recoil/pipeline/orchestrator/episode_runner.py::EpisodeRunner._dispatch_one_beat",
        "exit": [
          "strategy set exhausted (_strategy_exhausted)",
          "Take succeeds and becomes primary"
        ],
        "id": "strategy_retry",
        "kind": "strategy",
        "reduced_tier": "auto",
        "retries_into": "beat_take_retry",
        "risk_level": "medium",
        "trigger": "With a StrategyEngine injected, a failed Take is classified \u2192 StrategyEngine.select_and_apply \u2192 StrategyDiff mutates the next workflow. Without StrategyEngine retries are blind (same workflow)."
      },
      {
        "bound": {
          "max": "per-shot attempt count vs policy max",
          "source": "RetryDispatcher retry budget (per-shot attempts vs policy)"
        },
        "capability": "orchestration",
        "driver": "recoil/pipeline/orchestrator/retry_dispatcher.py::RetryDispatcher.dispatch",
        "exit": [
          "classify_failure \u2192 permanent (returns None)",
          "retry budget exhausted \u2192 permanent"
        ],
        "id": "dispatch_retry_backoff",
        "kind": "retry",
        "reduced_tier": "auto",
        "risk_level": "medium",
        "trigger": "A dispatched shot fails; classify_failure decides retryable vs permanent, then queue with exponential backoff."
      },
      {
        "bound": {
          "max": 1,
          "source": "hardcoded second _call_flash_enrichment(temp=0.0) in enrich_prompt"
        },
        "capability": "prompt_building",
        "driver": "recoil/pipeline/_lib/prompt_engine.py::enrich_prompt",
        "exit": [
          "all locked_terms present \u2192 return (enriched, 'v{version}')",
          "retry still missing terms \u2192 return (base_prompt, 'fallback_validation')",
          "Flash returns empty \u2192 return (base_prompt, 'fallback_error'/'fallback_retry_error')"
        ],
        "id": "flash_enrichment_lockterm_retry",
        "kind": "retry",
        "reads_artifact": "flash_system_prompt_files",
        "retries_into": "previz_keyframe_route",
        "risk_level": "medium",
        "trigger": "enrich_prompt: Flash output dropped a locked_term (missing != [])"
      },
      {
        "bound": {
          "max": 3,
          "source": "Beat.max_takes (recoil/pipeline/core/take.py:206, default 3; CLI --new-take runs exactly one) \u2014 effective limit extended by phantom_recovery_loop"
        },
        "capability": "scene_beat_take",
        "driver": "recoil/pipeline/orchestrator/episode_runner.py::_dispatch_one_beat",
        "exit": [
          "beat.primary_take set (a Take succeeded \u2192 primary selected)",
          "beat.is_exhausted (len(takes) >= effective_max, no primary)",
          "beat.approved",
          "_strategy_exhausted (StrategyEngine returned None or ESCALATE_TO_HUMAN)",
          "force_new_take path breaks after the single reroll attempt",
          "BudgetExhaustedError raised by the inner budget gate \u2192 propagates, HALTS scene"
        ],
        "id": "beat_take_loop",
        "kind": "retry",
        "retries_into": "strategy_selection_loop",
        "risk_level": "high",
        "trigger": "A Take fails (status != succeeded) and the Beat has no primary_take; loop attempts the next Take."
      },
      {
        "bound": {
          "max": 6.0,
          "source": "RetryCostPolicy.max_retry_spend_usd (recoil/pipeline/orchestrator/production_types.py:80, default $6.00/pass; warn at $4.00) \u2014 PLUS a hard 3-accumulated-free-strategy cap before forced escalation"
        },
        "capability": "strategy_engine",
        "driver": "recoil/pipeline/orchestrator/strategy_registry.py::select_and_apply",
        "exit": [
          "projected cumulative_retry_cost > max_retry_spend_usd for every remaining strategy \u2192 _escalate_to_human_diff",
          ">=3 free/cheap strategies already tried and no affordable expensive one \u2192 ESCALATE_TO_HUMAN",
          "static escalation chain for the FailureMode exhausted (all already_tried)",
          "StrategyDiff returned with strategy_name == ESCALATE_TO_HUMAN \u2192 caller sets _strategy_exhausted"
        ],
        "id": "strategy_selection_loop",
        "kind": "strategy",
        "reads_artifact": "ops_log",
        "risk_level": "high",
        "trigger": "A failed Take is classified by detect_failure_mode; StrategyEngine.select_and_apply picks the next strategy off the failure-mode escalation chain (learned reorder or static chain)."
      },
      {
        "bound": {
          "max": 3,
          "source": "build_with_auto_reroll(max_attempts=3) (recoil/pipeline/_lib/board_builder.py:794); requires story_gate_mode='shadow' (raises BoardBuilderError otherwise)"
        },
        "capability": "orchestration",
        "driver": "recoil/pipeline/_lib/board_builder.py::build_with_auto_reroll",
        "exit": [
          "_attempt_is_clean \u2192 selected, status approved",
          "attempt not rerollable (_attempt_is_rerollable False) \u2192 best attempt rejected",
          "attempt_number >= max_attempts \u2192 stopped_reason 'attempt_cap_reached', rejected",
          "no fix-notes extractable from the verdict \u2192 rejected",
          "abort-class verdict \u2192 _best_attempt_for_abort"
        ],
        "id": "board_autoreroll_loop",
        "kind": "reroll",
        "reads_artifact": "board_comments",
        "risk_level": "medium",
        "trigger": "A storyboard strip gets a HARD story-gate verdict that is rerollable (fix-notes present); fix-notes are fed back and the board is regenerated."
      },
      {
        "bound": {
          "max": 2,
          "source": "Beat._MAX_PHANTOM_RECOVERIES (recoil/pipeline/core/take.py:212, ClassVar=2, REC-19); each grant extends Beat.is_exhausted effective_max by one slot"
        },
        "capability": "scene_beat_take",
        "driver": "recoil/pipeline/core/take.py::grant_phantom_recovery",
        "exit": [
          "phantom_recovery_count >= _MAX_PHANTOM_RECOVERIES \u2192 grant_phantom_recovery returns False (no further extension)",
          "extended take attempt succeeds \u2192 primary selected, beat_take_loop exits"
        ],
        "id": "phantom_recovery_loop",
        "kind": "retry",
        "retries_into": "beat_take_loop",
        "risk_level": "medium",
        "trigger": "invalidate_phantom_succeeded_takes finds a status=succeeded Take whose artifact is missing/evicted; _demote_take(phantom=True) demotes it and grants one extra take slot."
      },
      {
        "bound": {
          "max": 50.0,
          "source": "EpisodeRunner.budget_usd \u2192 BudgetGuard(limit_usd=...) (episode_runner.py:382, default $50.0; CLI --budget overrides). Per-beat reservations released on no-dispatch / zero-cost failure (REC-122)."
        },
        "capability": "budget_guard",
        "driver": "recoil/pipeline/_lib/budget_manager.py::would_exceed",
        "exit": [
          "would_exceed(est) True (or est<=0 and spent>=budget) \u2192 BudgetExhaustedError \u2192 scene saved + halted",
          "scene completes within budget (all beats dispatched or skipped)"
        ],
        "id": "budget_kill_switch",
        "kind": "convergence",
        "risk_level": "high",
        "trigger": "Before each non-dry-run Take attempt, _estimate_take_cost is reserved via would_exceed; over-cap raises BudgetExhaustedError (this is a HALT, not a retry \u2014 the outer bound on all loops above)."
      }
    ],
    "out_of_scope_capabilities": [
      "atom_read_model",
      "board_read_model",
      "composition_manifest",
      "director_notes_ledger",
      "dispatch_dashboard_render",
      "per_atom_regeneration",
      "plan_overrides",
      "scene_active_body_persistence",
      "scene_version_manifest_writer",
      "scene_version_read_model",
      "script_edit_snapshot"
    ],
    "phases": [
      {
        "entrypoints": [
          "breakdown_extract_cli",
          "breakdown_gate_cli"
        ],
        "id": "script_breakdown",
        "next": [
          "scene_assembly"
        ],
        "produces": [
          "mention_ledger"
        ],
        "summary": "Episode script (ep_NNN.md) is broken down into the per-episode mention ledger \u2014 the scene\u2192 location/sublocation/prop/wardrobe/character mentions that downstream ref resolution reads.\n"
      },
      {
        "entrypoints": [
          "prep_character_angles_cli",
          "generate_composite_sheet_cli",
          "gen_sublocations_cli"
        ],
        "id": "asset_prep",
        "next": [
          "storyboard",
          "payload_dispatch"
        ],
        "produces": [
          "composite_sheets",
          "location_registry",
          "promoted_hero_top_level",
          "asset_pool_refs"
        ],
        "summary": "Reference assets are produced + promoted: character angle sheets, composite ref SHEETS, and the sublocation/location registry. Feeds both board and video ref resolution. (Currently fragmented across three promotion writers \u2014 see ref_promotion_three_writers divergence.)\n"
      },
      {
        "entrypoints": [
          "episode_runner_module",
          "run_overnight_cli",
          "generate_cli"
        ],
        "id": "scene_assembly",
        "next": [
          "storyboard",
          "payload_dispatch"
        ],
        "produces": [
          "persisted_scene"
        ],
        "summary": "Shots are clustered into scenes/beats/takes by the grouping strategy and persisted as the scene SSOT (BATCH/ONER ids). Board + video both re-read these persisted scenes.\n"
      },
      {
        "entrypoints": [
          "board_cli",
          "build_and_dispatch_board",
          "render_board_finish"
        ],
        "id": "storyboard",
        "next": [
          "payload_dispatch"
        ],
        "produces": [
          "pencil_strip",
          "photoreal_finish",
          "story_gate_verdict",
          "board_record"
        ],
        "summary": "Storyboard generation: a pencil-tier strip is proposed + story-gated, then (on approval) a full-size photoreal finish. Board CHAR/prop refs are per-kind individual; the LOCATION ref is the composite SHEET when locked + use_composite_sheets on (REC-213 C3, same resolve_sheet_asset route as video \u2014 ref_system_split).\n"
      },
      {
        "entrypoints": [
          "dispatch",
          "audit_dispatch_cli",
          "dispatch_cli"
        ],
        "id": "payload_dispatch",
        "next": [
          "generation"
        ],
        "produces": [
          "receipts_jsonl",
          "video_ref_manifest"
        ],
        "summary": "Typed payload assembly + the single dispatch entry point: refs collected (composite sheets for video), prompt authored, payload validated, modality runner resolved. Emits the receipt log.\n"
      },
      {
        "entrypoints": [
          "step_runner",
          "video_model_client",
          "generate_cli"
        ],
        "id": "generation",
        "next": [
          "review_reroll"
        ],
        "produces": [
          "video_take_mp4",
          "video_sidecar_json",
          "keyframe_png",
          "observability_log"
        ],
        "summary": "Generation execution: StepRunner drives the provider adapters (Flora primary / fal / atlas) to render video takes + keyframes; polls to terminal; writes takes + sidecars.\n"
      },
      {
        "entrypoints": [
          "reroll_route",
          "generate_cli_new_take"
        ],
        "id": "review_reroll",
        "next": [],
        "produces": [
          "board_review_comments_json"
        ],
        "summary": "Takes are story-gated / human-reviewed; failures re-enter via the reroll surface + the strategy engine (structured retry under the budget guard) until approved or exhausted.\n"
      }
    ],
    "routes": [
      {
        "capability": "prompt_building",
        "entry": "recoil/pipeline/_lib/board_provider.py::select_board_model",
        "fallback_route": "board_fallback_model_route",
        "gated_by": "board_model_override",
        "id": "board_primary_model_route",
        "ref_kind": "individual",
        "resolves_via": "recoil/pipeline/_lib/prompt_engine.py::get_builder",
        "risk_level": "high",
        "surface": "board"
      },
      {
        "capability": "prompt_building",
        "entry": "recoil/pipeline/_lib/board_provider.py::board_fallback_model",
        "fallback_route": null,
        "gated_by": null,
        "id": "board_fallback_model_route",
        "ref_kind": "individual",
        "resolves_via": "recoil/pipeline/_lib/prompt_engine.py::get_builder",
        "risk_level": "high",
        "surface": "board"
      },
      {
        "capability": "prompt_building",
        "entry": "recoil/pipeline/_lib/board_builder.py::build_and_dispatch_board",
        "fallback_route": null,
        "gated_by": null,
        "id": "pencil_board_route",
        "ref_kind": "pencil_halfsize",
        "resolves_via": "recoil/pipeline/_lib/board_builder.py::build_and_dispatch_board",
        "risk_level": "high",
        "surface": "board"
      },
      {
        "capability": "prompt_building",
        "entry": "recoil/pipeline/_lib/board_builder.py::render_board_finish",
        "fallback_route": null,
        "gated_by": null,
        "id": "photoreal_finish_route",
        "ref_kind": "photoreal_fullsize",
        "resolves_via": "recoil/pipeline/_lib/board_builder.py::render_board_finish",
        "risk_level": "high",
        "surface": "board"
      },
      {
        "capability": "ref_resolution",
        "entry": "recoil/pipeline/_lib/dispatch_payload.py::_collect_reference_images",
        "fallback_route": null,
        "gated_by": "board_gated",
        "id": "board_artifact_video_injection_route",
        "ref_kind": "board_plate",
        "resolves_via": "recoil/pipeline/_lib/dispatch_payload.py::_resolve_board_artifact",
        "risk_level": "medium",
        "surface": "video"
      },
      {
        "capability": "ref_resolution",
        "entry": "recoil/execution/step_runner.py::StepRunner.execute_keyframe",
        "fallback_route": null,
        "gated_by": null,
        "id": "keyframe_lookbundle_route",
        "ref_kind": "look_bundle",
        "resolves_via": "recoil/execution/step_runner.py::StepRunner._resolve_look_bundle",
        "risk_level": "medium",
        "surface": "keyframe"
      },
      {
        "capability": "dispatch_entry_point",
        "entry": "recoil/pipeline/core/dispatch.py::dispatch",
        "fallback_route": null,
        "gated_by": null,
        "id": "video_i2v_dispatch_path",
        "ref_kind": "individual",
        "resolves_via": "recoil/pipeline/core/runners/video_runner.py::VideoRunner.run",
        "risk_level": "high",
        "surface": "video"
      },
      {
        "capability": "modality_registry",
        "entry": "recoil/pipeline/core/dispatch.py::dispatch",
        "fallback_route": null,
        "gated_by": null,
        "id": "r2v_multi_execute_pass_path",
        "ref_kind": "individual",
        "resolves_via": "recoil/pipeline/core/runners/r2v_multi_runner.py::R2VMultiRunner.run",
        "risk_level": "high",
        "surface": "r2v"
      },
      {
        "capability": "ref_resolution",
        "entry": "recoil/pipeline/_lib/dispatch_payload.py::_collect_reference_images",
        "fallback_route": "two_pass_angle_ref_path",
        "gated_by": "use_composite_sheets",
        "id": "composite_sheet_ref_path",
        "ref_kind": "composite_sheet",
        "resolves_via": "recoil/core/ref_resolver.py::resolve_sheet_asset",
        "risk_level": "high",
        "surface": "video"
      },
      {
        "capability": "ref_resolution",
        "entry": "recoil/pipeline/_lib/dispatch_payload.py::_collect_reference_images",
        "fallback_route": null,
        "gated_by": null,
        "id": "two_pass_angle_ref_path",
        "ref_kind": "individual",
        "resolves_via": "recoil/core/ref_resolver.py::resolve_character_bundle",
        "risk_level": "high",
        "surface": "video"
      },
      {
        "capability": "prompt_building",
        "entry": "recoil/pipeline/_lib/dispatch_payload.py::_build_author_aware_prompt",
        "fallback_route": "deterministic_template_route",
        "gated_by": null,
        "id": "author_strategy_route",
        "ref_kind": "authored_prose",
        "resolves_via": "recoil/pipeline/_lib/author_strategies.py::resolve_strategy",
        "risk_level": "high",
        "surface": "video"
      },
      {
        "capability": "prompt_building",
        "entry": "recoil/pipeline/_lib/dispatch_payload.py::_build_author_aware_prompt",
        "fallback_route": null,
        "gated_by": null,
        "id": "deterministic_template_route",
        "ref_kind": "deterministic_template",
        "resolves_via": "recoil/pipeline/_lib/dispatch_payload.py::_build_deterministic_template_prompt",
        "risk_level": "high",
        "surface": "video"
      },
      {
        "capability": "worksurface_execution",
        "entry": "recoil/execution/step_runner.py::StepRunner.execute_video",
        "fallback_route": null,
        "gated_by": null,
        "id": "video_dispatch_path",
        "ref_kind": "i2v_t2v_v2v",
        "resolves_via": "recoil/execution/video_model_client.py::VideoModelClient.submit",
        "risk_level": "high",
        "surface": "video"
      },
      {
        "capability": "worksurface_execution",
        "entry": "recoil/execution/step_runner.py::StepRunner.execute_pass",
        "fallback_route": null,
        "gated_by": null,
        "id": "pass_r2v_path",
        "ref_kind": "composite_sheet",
        "resolves_via": "recoil/execution/video_model_client.py::VideoModelClient.submit",
        "risk_level": "high",
        "surface": "r2v"
      },
      {
        "capability": "worksurface_execution",
        "entry": "recoil/execution/step_runner.py::StepRunner.execute_keyframe",
        "fallback_route": null,
        "gated_by": null,
        "id": "keyframe_image_path",
        "ref_kind": "individual",
        "resolves_via": "recoil/execution/providers/registry.py::resolve_adapter",
        "risk_level": "high",
        "surface": "image"
      },
      {
        "capability": "worksurface_execution",
        "entry": "recoil/execution/step_runner.py::StepRunner.execute_previz",
        "fallback_route": null,
        "gated_by": null,
        "id": "previz_image_path",
        "ref_kind": "individual",
        "resolves_via": "recoil/execution/providers/registry.py::resolve_adapter",
        "risk_level": "medium",
        "surface": "previz"
      },
      {
        "capability": "worksurface_execution",
        "entry": "recoil/execution/providers/registry.py::resolve_adapter",
        "fallback_route": "seedance_via_fal",
        "gated_by": null,
        "id": "seedance_via_flora",
        "ref_kind": "provider_primary",
        "resolves_via": "recoil/execution/providers/flora.py::FloraAdapter.build_submit",
        "risk_level": "high",
        "surface": "video"
      },
      {
        "capability": "worksurface_execution",
        "entry": "recoil/execution/providers/registry.py::resolve_fallback",
        "fallback_route": null,
        "gated_by": null,
        "id": "seedance_via_fal",
        "ref_kind": "provider_fallback",
        "resolves_via": "recoil/execution/providers/fal.py::FalAdapter.build_submit",
        "risk_level": "critical",
        "surface": "video"
      },
      {
        "capability": "worksurface_execution",
        "entry": "recoil/execution/providers/registry.py::resolve_adapter",
        "fallback_route": null,
        "gated_by": "provider_override",
        "id": "seedance_via_atlas",
        "ref_kind": "provider_override",
        "resolves_via": "recoil/execution/providers/atlas.py::AtlasAdapter.build_submit",
        "risk_level": "high",
        "surface": "video"
      },
      {
        "capability": "worksurface_execution",
        "entry": "recoil/execution/providers/registry.py::resolve_adapter",
        "fallback_route": null,
        "gated_by": null,
        "id": "kling_video_path",
        "ref_kind": "endpoint_table",
        "resolves_via": "recoil/execution/providers/kling.py::KlingAdapter.build_submit",
        "risk_level": "high",
        "surface": "video"
      },
      {
        "capability": "orchestration",
        "entry": "recoil/pipeline/_lib/grouping.py::_assemble_coverage",
        "fallback_route": null,
        "gated_by": "grouping_strategy",
        "id": "grouping_coverage",
        "ref_kind": "coverage_pass",
        "resolves_via": "recoil/pipeline/orchestrator/coverage_planner.py::build_passes",
        "risk_level": "high",
        "surface": "coverage"
      },
      {
        "capability": "orchestration",
        "entry": "recoil/pipeline/_lib/grouping.py::_assemble_continuity",
        "fallback_route": "grouping_continuity_per_shot",
        "gated_by": "grouping_strategy",
        "id": "grouping_continuity",
        "ref_kind": "batch",
        "resolves_via": "recoil/pipeline/_lib/scene_clusterer.py::cluster_shots_into_batches",
        "risk_level": "high",
        "surface": "continuity"
      },
      {
        "capability": "orchestration",
        "entry": "recoil/pipeline/_lib/grouping.py::_assemble_continuity",
        "fallback_route": null,
        "gated_by": "grouping_strategy",
        "id": "grouping_continuity_per_shot",
        "ref_kind": "per_shot_i2v",
        "resolves_via": "recoil/pipeline/_lib/scene_clusterer.py::cluster_shots_into_batches",
        "risk_level": "medium",
        "surface": "continuity"
      },
      {
        "capability": "orchestration",
        "entry": "recoil/pipeline/_lib/grouping.py::_assemble_oner",
        "fallback_route": null,
        "gated_by": "grouping_strategy",
        "id": "grouping_oner",
        "ref_kind": "oner",
        "resolves_via": "recoil/pipeline/_lib/grouping.py::_split_oner_scene_for_caps",
        "risk_level": "high",
        "surface": "oner"
      },
      {
        "capability": "scene_beat_take",
        "entry": "recoil/pipeline/orchestrator/batch_selector.py::parse_batch_selector",
        "fallback_route": null,
        "gated_by": null,
        "id": "batch_reroll_selector",
        "ref_kind": "persisted_scene_id",
        "resolves_via": "recoil/pipeline/orchestrator/batch_selector.py::verify_scene_grouping_metadata",
        "risk_level": "high",
        "surface": "reroll"
      },
      {
        "capability": "prompt_building",
        "entry": "recoil/pipeline/_lib/prompt_engine.py::get_builder",
        "fallback_route": null,
        "gated_by": null,
        "id": "seedance_i2v_route",
        "ref_kind": "composite_sheet",
        "resolves_via": "recoil/pipeline/_lib/prompt_engine.py::build_seeddance_i2v_prompt",
        "risk_level": "high",
        "surface": "video"
      },
      {
        "capability": "prompt_building",
        "entry": "recoil/pipeline/_lib/prompt_engine.py::get_builder",
        "fallback_route": null,
        "gated_by": null,
        "id": "seedance_r2v_multi_route",
        "ref_kind": "composite_sheet",
        "resolves_via": "recoil/pipeline/_lib/prompt_engine.py::build_seeddance_r2v_prompt_multi",
        "risk_level": "high",
        "surface": "r2v"
      },
      {
        "capability": "prompt_building",
        "entry": "recoil/pipeline/_lib/prompt_engine.py::get_builder",
        "fallback_route": null,
        "gated_by": null,
        "id": "kling_i2v_route",
        "ref_kind": "composite_sheet",
        "resolves_via": "recoil/pipeline/_lib/prompt_engine.py::build_kling_i2v_prompt",
        "risk_level": "medium",
        "surface": "video"
      },
      {
        "capability": "prompt_building",
        "entry": "recoil/pipeline/_lib/prompt_engine.py::get_builder",
        "fallback_route": null,
        "gated_by": null,
        "id": "kling_t2v_route",
        "ref_kind": "individual",
        "resolves_via": "recoil/pipeline/_lib/prompt_engine.py::build_kling_t2v_prompt",
        "risk_level": "medium",
        "surface": "video"
      },
      {
        "capability": "prompt_building",
        "entry": "recoil/pipeline/_lib/prompt_engine.py::compile_all_prompts",
        "fallback_route": null,
        "gated_by": "has_end_frame",
        "id": "wan_inbetween_route",
        "ref_kind": "turn_view",
        "resolves_via": "recoil/pipeline/_lib/prompt_engine.py::build_wan_i2v_prompt",
        "risk_level": "medium",
        "surface": "f2v"
      },
      {
        "capability": "prompt_building",
        "entry": "recoil/pipeline/_lib/prompt_engine.py::get_builder",
        "fallback_route": null,
        "gated_by": null,
        "id": "gpt_image_2_storyboard_route",
        "ref_kind": "individual",
        "resolves_via": "recoil/pipeline/_lib/prompt_engine.py::build_storyboard_strip_prompt",
        "risk_level": "medium",
        "surface": "storyboard"
      },
      {
        "capability": "prompt_building",
        "entry": "recoil/pipeline/_lib/prompt_engine.py::get_builder",
        "fallback_route": null,
        "gated_by": null,
        "id": "previz_keyframe_route",
        "ref_kind": "individual",
        "resolves_via": "recoil/pipeline/_lib/prompt_engine.py::build_previs_prompt",
        "risk_level": "low",
        "surface": "previz"
      },
      {
        "capability": "ref_resolution",
        "entry": "recoil/pipeline/_lib/board_builder.py::_collect_board_refs",
        "fallback_route": null,
        "gated_by": null,
        "id": "board_ref_path",
        "ref_kind": "individual",
        "resolves_via": "recoil/core/ref_resolver.py::resolve_character_bundle",
        "risk_level": "high",
        "surface": "board"
      },
      {
        "capability": "ref_resolution",
        "entry": "recoil/pipeline/_lib/dispatch_payload.py::_collect_reference_images",
        "fallback_route": "video_twopass_route",
        "gated_by": "use_composite_sheets",
        "id": "video_ref_path",
        "ref_kind": "composite_sheet",
        "resolves_via": "recoil/core/ref_resolver.py::resolve_sheet_asset",
        "risk_level": "high",
        "surface": "video"
      },
      {
        "capability": "ref_resolution",
        "entry": "recoil/pipeline/_lib/board_builder.py::_collect_board_refs",
        "fallback_route": null,
        "gated_by": null,
        "id": "board_char_route",
        "ref_kind": "turn_view",
        "resolves_via": "recoil/core/ref_resolver.py::resolve_character_bundle",
        "risk_level": "high",
        "surface": "board"
      },
      {
        "capability": "ref_resolution",
        "entry": "recoil/pipeline/_lib/board_builder.py::_phase_for_char",
        "fallback_route": "board_char_route",
        "gated_by": null,
        "id": "board_wardrobe_route",
        "ref_kind": "individual",
        "resolves_via": "recoil/core/ref_resolver.py::resolve_reference_bundle",
        "risk_level": "medium",
        "surface": "board"
      },
      {
        "capability": "ref_resolution",
        "entry": "recoil/pipeline/_lib/board_builder.py::_append_sublocation_ref",
        "fallback_route": "board_location_route",
        "gated_by": "use_composite_sheets",
        "id": "board_location_sheet_route",
        "ref_kind": "composite_sheet",
        "resolves_via": "recoil/core/ref_resolver.py::resolve_sheet_asset",
        "risk_level": "high",
        "surface": "board"
      },
      {
        "capability": "ref_resolution",
        "entry": "recoil/pipeline/_lib/board_builder.py::_append_sublocation_ref",
        "fallback_route": null,
        "gated_by": null,
        "id": "board_location_route",
        "ref_kind": "plate",
        "resolves_via": "recoil/pipeline/_lib/sublocation_registry.py::sublocation_ref",
        "risk_level": "high",
        "surface": "board"
      },
      {
        "capability": "ref_resolution",
        "entry": "recoil/pipeline/_lib/board_builder.py::_append_prop_refs",
        "fallback_route": null,
        "gated_by": null,
        "id": "board_prop_route",
        "ref_kind": "individual",
        "resolves_via": "recoil/pipeline/_lib/board_builder.py::_prop_identity_ref",
        "risk_level": "high",
        "surface": "board"
      },
      {
        "capability": "ref_resolution",
        "entry": "recoil/pipeline/_lib/dispatch_payload.py::_collect_reference_images",
        "fallback_route": "video_twopass_route",
        "gated_by": "use_composite_sheets",
        "id": "video_sheet_route",
        "ref_kind": "composite_sheet",
        "resolves_via": "recoil/core/ref_resolver.py::resolve_sheet_asset",
        "risk_level": "critical",
        "surface": "video"
      },
      {
        "capability": "ref_resolution",
        "entry": "recoil/pipeline/_lib/dispatch_payload.py::_collect_reference_images",
        "fallback_route": null,
        "gated_by": null,
        "id": "video_twopass_route",
        "ref_kind": "individual",
        "resolves_via": "recoil/core/ref_resolver.py::resolve_character_bundle",
        "risk_level": "high",
        "surface": "video"
      },
      {
        "capability": "ref_resolution",
        "entry": "recoil/pipeline/_lib/dispatch_payload.py::_collect_reference_images",
        "fallback_route": null,
        "gated_by": null,
        "id": "video_location_route",
        "ref_kind": "individual",
        "resolves_via": "recoil/core/ref_resolver.py::resolve_location_refs",
        "risk_level": "critical",
        "surface": "video"
      },
      {
        "capability": "orchestration",
        "entry": "recoil/pipeline/_lib/board_builder.py::build_with_auto_reroll",
        "fallback_route": "board_single_build_route",
        "gated_by": "story_gate_mode",
        "id": "board_autoreroll_route",
        "ref_kind": "board_rerolled",
        "resolves_via": "recoil/pipeline/_lib/board_builder.py::build_with_auto_reroll",
        "risk_level": "high",
        "surface": "board"
      },
      {
        "capability": "orchestration",
        "entry": "recoil/pipeline/_lib/board_builder.py::build_and_dispatch_board",
        "fallback_route": null,
        "gated_by": null,
        "id": "board_single_build_route",
        "ref_kind": "board",
        "resolves_via": "recoil/pipeline/_lib/board_builder.py::build_and_dispatch_board",
        "risk_level": "low",
        "surface": "board"
      },
      {
        "capability": "ref_promotion",
        "entry": "recoil/pipeline/_lib/asset_ops.py::set_hero",
        "gated_by": null,
        "id": "promotion_writer_route",
        "ref_kind": "top_level_subject_dir",
        "resolves_via": "recoil/pipeline/_lib/asset_ops.py::set_hero",
        "risk_level": "critical",
        "surface": "promotion"
      },
      {
        "capability": "ref_promotion",
        "entry": "recoil/core/ref_resolver.py::resolve_entity_refs",
        "gated_by": null,
        "id": "production_reader_route",
        "ref_kind": "base_pool_dir",
        "resolves_via": "recoil/core/ref_resolver.py::resolve_entity_refs",
        "risk_level": "critical",
        "surface": "production"
      },
      {
        "capability": "project_path_construction",
        "entry": "recoil/core/paths.py::get_character_sheets_dir",
        "gated_by": null,
        "id": "char_sheet_path_route",
        "ref_kind": "char_base_sheets",
        "resolves_via": "recoil/core/paths.py::get_character_sheets_dir",
        "risk_level": "medium",
        "surface": "char"
      },
      {
        "capability": "project_path_construction",
        "entry": "recoil/core/paths.py::get_location_sheets_dir",
        "gated_by": null,
        "id": "loc_sheet_path_route",
        "ref_kind": "loc_sheets",
        "resolves_via": "recoil/core/paths.py::get_location_sheets_dir",
        "risk_level": "medium",
        "surface": "loc"
      }
    ],
    "schema": [
      {
        "fields": [
          {
            "dup_tier": "never",
            "meaning": "Unique wardrobe or appearance phase identifier.",
            "name": "phase_id",
            "semantic": null,
            "type": "str"
          },
          {
            "dup_tier": "never",
            "meaning": "First episode where this phase applies.",
            "name": "start_ep",
            "semantic": null,
            "type": "int"
          },
          {
            "dup_tier": "never",
            "meaning": "Last episode where this phase applies.",
            "name": "end_ep",
            "semantic": null,
            "type": "int"
          },
          {
            "dup_tier": "never",
            "meaning": "Legacy prose reason the phase changed.",
            "name": "phase_trigger_event",
            "semantic": null,
            "type": "str"
          },
          {
            "dup_tier": "never",
            "meaning": "Legacy full wardrobe description for the phase.",
            "name": "wardrobe_description",
            "semantic": null,
            "type": "str"
          },
          {
            "dup_tier": "always",
            "meaning": "Structured wardrobe additions, removals, or modifications from the prior phase.",
            "name": "wardrobe_arc_delta",
            "semantic": "wardrobe_arc",
            "type": "str"
          },
          {
            "dup_tier": "always",
            "meaning": "Wardrobe items carried forward unchanged from the prior phase.",
            "name": "wardrobe_arc_carries",
            "semantic": "wardrobe_arc",
            "type": "str"
          },
          {
            "dup_tier": "never",
            "meaning": "Hair and makeup notes for this phase.",
            "name": "hair_makeup",
            "semantic": null,
            "type": "str"
          },
          {
            "dup_tier": "always",
            "meaning": "Mutable scars, injuries, or accumulated damage for this phase.",
            "name": "distinguishing_marks",
            "semantic": "marks",
            "type": "str"
          },
          {
            "dup_tier": "never",
            "meaning": "Structured evidence-backed trigger for the phase.",
            "name": "trigger",
            "semantic": null,
            "type": "Optional['PhaseTrigger']"
          },
          {
            "dup_tier": "never",
            "meaning": "Structured phase appearance block alongside legacy prose.",
            "name": "appearance",
            "semantic": null,
            "type": "Optional['PhaseAppearance']"
          }
        ],
        "file": "recoil/pipeline/_lib/render_schema.py::CharacterPhase",
        "id": "schema_character_phase",
        "model": "CharacterPhase",
        "reduced_tier": "never"
      },
      {
        "fields": [
          {
            "dup_tier": "always",
            "meaning": "Never-changing canonical hair color.",
            "name": "hair_color",
            "semantic": "identity_lock",
            "type": "str"
          },
          {
            "dup_tier": "always",
            "meaning": "Never-changing canonical eye color.",
            "name": "eye_color",
            "semantic": "identity_lock",
            "type": "str"
          },
          {
            "dup_tier": "always",
            "meaning": "Never-changing canonical skin tone.",
            "name": "skin_tone",
            "semantic": "identity_lock",
            "type": "str"
          },
          {
            "dup_tier": "always",
            "meaning": "Never-changing canonical body build.",
            "name": "build",
            "semantic": "identity_lock",
            "type": "str"
          },
          {
            "dup_tier": "always",
            "meaning": "Never-changing distinguishing identity traits.",
            "name": "distinguishing",
            "semantic": "marks",
            "type": "list[str]"
          }
        ],
        "file": "recoil/pipeline/_lib/render_schema.py::IdentityInvariants",
        "id": "schema_identity_invariants",
        "model": "IdentityInvariants",
        "reduced_tier": "never"
      },
      {
        "fields": [
          {
            "dup_tier": "never",
            "meaning": "Whether the phase trigger is a script event or episode range.",
            "name": "type",
            "semantic": null,
            "type": "Literal['script_event', 'ep_range']"
          },
          {
            "dup_tier": "never",
            "meaning": "Scene reference anchoring a script-event trigger.",
            "name": "scene_ref",
            "semantic": null,
            "type": "str"
          },
          {
            "dup_tier": "never",
            "meaning": "Human-readable trigger description.",
            "name": "description",
            "semantic": null,
            "type": "str"
          },
          {
            "dup_tier": "never",
            "meaning": "Hash of the evidence supporting this trigger.",
            "name": "evidence_hash",
            "semantic": null,
            "type": "str"
          }
        ],
        "file": "recoil/pipeline/_lib/render_schema.py::PhaseTrigger",
        "id": "schema_phase_trigger",
        "model": "PhaseTrigger",
        "reduced_tier": "never"
      },
      {
        "fields": [
          {
            "dup_tier": "never",
            "meaning": "Named wardrobe item.",
            "name": "piece",
            "semantic": null,
            "type": "str"
          },
          {
            "dup_tier": "never",
            "meaning": "Visual descriptor for the wardrobe item.",
            "name": "descriptor",
            "semantic": null,
            "type": "str"
          },
          {
            "dup_tier": "always",
            "meaning": "Current worn, removed, damaged, or torn state.",
            "name": "state",
            "semantic": "prop_carrier",
            "type": "Literal['worn', 'removed', 'damaged', 'torn']"
          },
          {
            "dup_tier": "never",
            "meaning": "Whether this item is visually important enough to surface.",
            "name": "salient",
            "semantic": null,
            "type": "bool"
          }
        ],
        "file": "recoil/pipeline/_lib/render_schema.py::WardrobePiece",
        "id": "schema_wardrobe_piece",
        "model": "WardrobePiece",
        "reduced_tier": "never"
      },
      {
        "fields": [
          {
            "dup_tier": "never",
            "meaning": "Structured wardrobe pieces for the phase.",
            "name": "wardrobe",
            "semantic": null,
            "type": "list[WardrobePiece]"
          },
          {
            "dup_tier": "never",
            "meaning": "Structured hair presentation state.",
            "name": "hair_state",
            "semantic": null,
            "type": "Literal['loose', 'tucked', 'tied', 'covered']"
          },
          {
            "dup_tier": "always",
            "meaning": "Gear visible on the character in this phase.",
            "name": "visible_gear",
            "semantic": "prop_carrier",
            "type": "list[str]"
          },
          {
            "dup_tier": "always",
            "meaning": "Structured notable marks visible in this phase.",
            "name": "notable_marks",
            "semantic": "marks",
            "type": "list[str]"
          }
        ],
        "file": "recoil/pipeline/_lib/render_schema.py::PhaseAppearance",
        "id": "schema_phase_appearance",
        "model": "PhaseAppearance",
        "reduced_tier": "never"
      },
      {
        "fields": [
          {
            "dup_tier": "never",
            "meaning": "Canonical character identifier.",
            "name": "char_id",
            "semantic": null,
            "type": "str"
          },
          {
            "dup_tier": "never",
            "meaning": "Human-readable character name.",
            "name": "display_name",
            "semantic": null,
            "type": "str"
          },
          {
            "dup_tier": "never",
            "meaning": "Core visual identity description.",
            "name": "visual_description",
            "semantic": null,
            "type": "str"
          },
          {
            "dup_tier": "never",
            "meaning": "Optional character height in centimeters.",
            "name": "height_cm",
            "semantic": null,
            "type": "Optional[int]"
          },
          {
            "dup_tier": "never",
            "meaning": "Optional relative scale prompt fragment.",
            "name": "scale_prompt_fragment",
            "semantic": null,
            "type": "Optional[str]"
          },
          {
            "dup_tier": "always",
            "meaning": "One-sentence thesis for wardrobe progression.",
            "name": "wardrobe_arc_thesis",
            "semantic": "wardrobe_arc",
            "type": "str"
          },
          {
            "dup_tier": "never",
            "meaning": "Whether the wardrobe thesis is director-approved.",
            "name": "wardrobe_arc_thesis_approved",
            "semantic": null,
            "type": "bool"
          },
          {
            "dup_tier": "never",
            "meaning": "Source of the wardrobe thesis decision.",
            "name": "wardrobe_arc_thesis_source",
            "semantic": null,
            "type": "str"
          },
          {
            "dup_tier": "always",
            "meaning": "Director free-text wardrobe vision notes.",
            "name": "wardrobe_arc_vision",
            "semantic": "wardrobe_arc",
            "type": "str"
          },
          {
            "dup_tier": "never",
            "meaning": "Wardrobe and appearance phases for this character.",
            "name": "phases",
            "semantic": null,
            "type": "list[CharacterPhase]"
          },
          {
            "dup_tier": "never",
            "meaning": "Episodes where this character appears.",
            "name": "episodes",
            "semantic": null,
            "type": "list[int]"
          },
          {
            "dup_tier": "always",
            "meaning": "Never-changing identity block for this character.",
            "name": "identity_invariants",
            "semantic": "identity_lock",
            "type": "Optional[IdentityInvariants]"
          },
          {
            "dup_tier": "always",
            "meaning": "Optional declared transient visual states.",
            "name": "transients",
            "semantic": "identity_lock",
            "type": "Optional[list[str]]"
          }
        ],
        "file": "recoil/pipeline/_lib/render_schema.py::BibleCharacter",
        "id": "schema_bible_character",
        "model": "BibleCharacter",
        "reduced_tier": "never"
      },
      {
        "fields": [
          {
            "dup_tier": "never",
            "meaning": "Primary practical light source for the location.",
            "name": "primary_source",
            "semantic": null,
            "type": "str"
          },
          {
            "dup_tier": "never",
            "meaning": "Default light direction.",
            "name": "direction",
            "semantic": null,
            "type": "LightDirection"
          },
          {
            "dup_tier": "never",
            "meaning": "Default light quality.",
            "name": "quality",
            "semantic": null,
            "type": "LightQuality"
          },
          {
            "dup_tier": "never",
            "meaning": "Default light color temperature.",
            "name": "color_temp",
            "semantic": null,
            "type": "LightColorTemp"
          }
        ],
        "file": "recoil/pipeline/_lib/render_schema.py::LocationLightingProfile",
        "id": "schema_location_lighting_profile",
        "model": "LocationLightingProfile",
        "reduced_tier": "never"
      },
      {
        "fields": [
          {
            "dup_tier": "never",
            "meaning": "Canonical location identifier.",
            "name": "location_id",
            "semantic": null,
            "type": "str"
          },
          {
            "dup_tier": "never",
            "meaning": "Super-category zone for the location.",
            "name": "habitat_zone",
            "semantic": null,
            "type": "str"
          },
          {
            "dup_tier": "never",
            "meaning": "Original scene headings mapped to this location.",
            "name": "aliases",
            "semantic": null,
            "type": "list[str]"
          },
          {
            "dup_tier": "never",
            "meaning": "Full visual location description.",
            "name": "description",
            "semantic": null,
            "type": "str"
          },
          {
            "dup_tier": "never",
            "meaning": "Default lighting profile for the location.",
            "name": "lighting_profile",
            "semantic": null,
            "type": "Optional[LocationLightingProfile]"
          },
          {
            "dup_tier": "never",
            "meaning": "Location color palette as hex codes.",
            "name": "color_palette",
            "semantic": null,
            "type": "list[str]"
          },
          {
            "dup_tier": "never",
            "meaning": "Atmospheric or environmental notes.",
            "name": "atmosphere",
            "semantic": null,
            "type": "str"
          },
          {
            "dup_tier": "never",
            "meaning": "Semantic named sublocations owned by the bible.",
            "name": "sublocations",
            "semantic": "subloc",
            "type": "Optional[dict[str, 'BibleSublocation']]"
          }
        ],
        "file": "recoil/pipeline/_lib/render_schema.py::BibleLocation",
        "id": "schema_bible_location",
        "model": "BibleLocation",
        "reduced_tier": "never"
      },
      {
        "fields": [
          {
            "dup_tier": "never",
            "meaning": "Semantic description of the named sublocation.",
            "name": "description",
            "semantic": "subloc",
            "type": "str"
          }
        ],
        "file": "recoil/pipeline/_lib/render_schema.py::BibleSublocation",
        "id": "schema_bible_sublocation",
        "model": "BibleSublocation",
        "reduced_tier": "never"
      },
      {
        "fields": [
          {
            "dup_tier": "never",
            "meaning": "Operative prop state description.",
            "name": "description",
            "semantic": null,
            "type": "str"
          },
          {
            "dup_tier": "always",
            "meaning": "Visual delta that distinguishes this prop state.",
            "name": "visual_delta",
            "semantic": "prop_state",
            "type": "str"
          }
        ],
        "file": "recoil/pipeline/_lib/render_schema.py::PropState",
        "id": "schema_prop_state",
        "model": "PropState",
        "reduced_tier": "never"
      },
      {
        "fields": [
          {
            "dup_tier": "always",
            "meaning": "Source prop state; serialized with alias \"from\".",
            "name": "from_state",
            "semantic": "prop_state",
            "type": "str"
          },
          {
            "dup_tier": "always",
            "meaning": "Destination prop state.",
            "name": "to",
            "semantic": "prop_state",
            "type": "str"
          },
          {
            "dup_tier": "always",
            "meaning": "Whether the transition can be reversed.",
            "name": "reversible",
            "semantic": "prop_state",
            "type": "bool"
          },
          {
            "dup_tier": "always",
            "meaning": "Optional scene that triggers the transition.",
            "name": "trigger_scene",
            "semantic": "prop_state",
            "type": "Optional[str]"
          }
        ],
        "file": "recoil/pipeline/_lib/render_schema.py::BiblePropTransition",
        "id": "schema_bible_prop_transition",
        "model": "BiblePropTransition",
        "reduced_tier": "never"
      },
      {
        "fields": [
          {
            "dup_tier": "never",
            "meaning": "Canonical prop identifier.",
            "name": "prop_id",
            "semantic": null,
            "type": "str"
          },
          {
            "dup_tier": "never",
            "meaning": "Visual description of the prop.",
            "name": "description",
            "semantic": null,
            "type": "str"
          },
          {
            "dup_tier": "always",
            "meaning": "Annotation notes; operative states live in states and transitions.",
            "name": "state_notes",
            "semantic": "prop_state",
            "type": "str"
          },
          {
            "dup_tier": "always",
            "meaning": "Character IDs that use this prop, not necessarily carriers.",
            "name": "associated_characters",
            "semantic": "associated_characters",
            "type": "list[str]"
          },
          {
            "dup_tier": "never",
            "meaning": "Episodes where this prop appears.",
            "name": "episodes",
            "semantic": null,
            "type": "list[int]"
          },
          {
            "dup_tier": "always",
            "meaning": "Whether the prop is baked into character identity refs.",
            "name": "is_permanent_attachment",
            "semantic": "prop_carrier",
            "type": "bool"
          },
          {
            "dup_tier": "always",
            "meaning": "Character ID this prop is permanently attached to or worn by.",
            "name": "attached_to",
            "semantic": "prop_carrier",
            "type": "Optional[str]"
          },
          {
            "dup_tier": "always",
            "meaning": "Operative prop state machine keyed by state_id.",
            "name": "states",
            "semantic": "prop_state",
            "type": "dict[str, PropState | str]"
          },
          {
            "dup_tier": "always",
            "meaning": "Initial state_id for the prop state machine.",
            "name": "initial_state",
            "semantic": "prop_state",
            "type": "str"
          },
          {
            "dup_tier": "never",
            "meaning": "Allowed directed transitions between prop states.",
            "name": "transitions",
            "semantic": null,
            "type": "list['BiblePropTransition']"
          },
          {
            "dup_tier": "always",
            "meaning": "Whether the prop can be transiently carried by a character.",
            "name": "carriable",
            "semantic": "prop_carrier",
            "type": "bool"
          }
        ],
        "file": "recoil/pipeline/_lib/render_schema.py::BibleProp",
        "id": "schema_bible_prop",
        "model": "BibleProp",
        "reduced_tier": "never"
      },
      {
        "fields": [
          {
            "dup_tier": "never",
            "meaning": "Canonical lighting motif identifier.",
            "name": "motif_id",
            "semantic": null,
            "type": "str"
          },
          {
            "dup_tier": "never",
            "meaning": "Visual and thematic motif description.",
            "name": "description",
            "semantic": null,
            "type": "str"
          },
          {
            "dup_tier": "never",
            "meaning": "Motif color temperature.",
            "name": "color_temp",
            "semantic": null,
            "type": "LightColorTemp"
          },
          {
            "dup_tier": "never",
            "meaning": "Location IDs associated with this lighting motif.",
            "name": "associated_locations",
            "semantic": null,
            "type": "list[str]"
          },
          {
            "dup_tier": "always",
            "meaning": "Character IDs associated with this lighting motif.",
            "name": "associated_characters",
            "semantic": "associated_characters",
            "type": "list[str]"
          }
        ],
        "file": "recoil/pipeline/_lib/render_schema.py::LightingMotif",
        "id": "schema_lighting_motif",
        "model": "LightingMotif",
        "reduced_tier": "never"
      },
      {
        "fields": [
          {
            "dup_tier": "never",
            "meaning": "Persisted-shape schema version.",
            "name": "schema_version",
            "semantic": null,
            "type": "int"
          },
          {
            "dup_tier": "never",
            "meaning": "Scratchpad analysis stripped before Pass 2.",
            "name": "structural_analysis",
            "semantic": null,
            "type": "str"
          },
          {
            "dup_tier": "never",
            "meaning": "Project identifier.",
            "name": "project",
            "semantic": null,
            "type": "str"
          },
          {
            "dup_tier": "never",
            "meaning": "Total episode count.",
            "name": "total_episodes",
            "semantic": null,
            "type": "int"
          },
          {
            "dup_tier": "never",
            "meaning": "UTC generation timestamp.",
            "name": "generated_at",
            "semantic": null,
            "type": "datetime"
          },
          {
            "dup_tier": "never",
            "meaning": "Series-level wardrobe philosophy.",
            "name": "wardrobe_philosophy",
            "semantic": null,
            "type": "str"
          },
          {
            "dup_tier": "never",
            "meaning": "Whether the wardrobe philosophy is director-approved.",
            "name": "wardrobe_philosophy_approved",
            "semantic": null,
            "type": "bool"
          },
          {
            "dup_tier": "never",
            "meaning": "Bible characters keyed by char_id.",
            "name": "characters",
            "semantic": null,
            "type": "dict[str, BibleCharacter]"
          },
          {
            "dup_tier": "never",
            "meaning": "Bible locations keyed by location_id.",
            "name": "locations",
            "semantic": null,
            "type": "dict[str, BibleLocation]"
          },
          {
            "dup_tier": "never",
            "meaning": "Bible props keyed by prop_id.",
            "name": "props",
            "semantic": null,
            "type": "dict[str, BibleProp]"
          },
          {
            "dup_tier": "never",
            "meaning": "Recurring lighting motifs.",
            "name": "lighting_motifs",
            "semantic": null,
            "type": "list[LightingMotif]"
          }
        ],
        "file": "recoil/pipeline/_lib/render_schema.py::GlobalBible",
        "id": "schema_global_bible",
        "model": "GlobalBible",
        "reduced_tier": "never"
      },
      {
        "fields": [
          {
            "dup_tier": "never",
            "meaning": "Filesystem path to the reference asset.",
            "name": "path",
            "semantic": null,
            "type": "Path"
          },
          {
            "dup_tier": "never",
            "meaning": "Reference role such as identity, board, location, prop, or sheet.",
            "name": "role",
            "semantic": "ref_role",
            "type": "RefRole"
          },
          {
            "dup_tier": "never",
            "meaning": "Subject identifier the reference depicts.",
            "name": "subject",
            "semantic": "ref_subject",
            "type": "str"
          },
          {
            "dup_tier": "never",
            "meaning": "Reference kind or view family.",
            "name": "kind",
            "semantic": "ref_kind",
            "type": "str"
          },
          {
            "dup_tier": "never",
            "meaning": "Whether this is the canonical hero identity reference.",
            "name": "is_hero",
            "semantic": "ref_hero",
            "type": "bool"
          },
          {
            "dup_tier": "never",
            "meaning": "Optional content hash for the asset.",
            "name": "content_hash",
            "semantic": null,
            "type": "Optional[str]"
          },
          {
            "dup_tier": "never",
            "meaning": "Origin of the reference asset.",
            "name": "source",
            "semantic": "ref_source",
            "type": "RefSource"
          },
          {
            "dup_tier": "never",
            "meaning": "Optional turn-view qualifier.",
            "name": "view",
            "semantic": "ref_view",
            "type": "Optional[str]"
          }
        ],
        "file": "recoil/core/ref_types.py::RefAsset",
        "id": "schema_ref_asset",
        "model": "RefAsset",
        "reduced_tier": "never"
      },
      {
        "fields": [
          {
            "dup_tier": "never",
            "meaning": "Ordered role-tagged references for one shot.",
            "name": "assets",
            "semantic": null,
            "type": "tuple[RefAsset, ...]"
          }
        ],
        "file": "recoil/core/ref_types.py::ReferenceBundle",
        "id": "schema_reference_bundle",
        "model": "ReferenceBundle",
        "reduced_tier": "never"
      }
    ],
    "symbols": [
      {
        "file": "recoil/pipeline/_lib/board_builder.py::build_and_dispatch_board",
        "id": "board_refcap_junction",
        "reduced_tier": "always",
        "risk_level": "high",
        "role": "The board ref-cap enforcement point: after _collect_board_refs assembles the refs (individual char front/profile + prop identity for NON-worn props \u2014 a WORN prop, bible attached_to a char in the shot, renders as PROSE on the carrier with NO standalone ref (REC-213 C4); plus the location ref \u2014 the composite SHEET when locked + use_composite_sheets on per REC-213 C3, else the sublocation plate; route board_ref_path / board_location_sheet_route), this raises if len(refs) > 16 (the gpt-image-2 reference cap). Adding a new board ref kind that pushes past 16 silently breaks here; the cap is a hard architectural ceiling on board ref breadth.\n",
        "symbol": "build_and_dispatch_board"
      },
      {
        "file": "recoil/pipeline/_lib/dispatch_payload.py::build_dispatch_payload",
        "id": "build_dispatch_payload",
        "risk_level": "high",
        "role": "Thin signature-stable wrapper (reimpl 2026-05-25): branches dry_run \u2192 _build_audit_payload, else validates modality \u2208 {video_i2v, r2v_multi}, builds a PayloadContext, delegates to build_unified_payload. The junction every production payload (EpisodeRunner) and audit pass crosses.\n",
        "symbol": "build_dispatch_payload"
      },
      {
        "file": "recoil/pipeline/_lib/dispatch_payload.py::build_unified_payload",
        "id": "build_unified_payload",
        "risk_level": "high",
        "role": "The real assembly body: resolves start_frame, calls _collect_reference_images (refs + manifest), drives _build_author_aware_prompt (prompt fork), and emits the modality-keyed payload dict. The single fan-in for refs+prompt+routing.\n",
        "symbol": "build_unified_payload"
      },
      {
        "file": "recoil/pipeline/_lib/dispatch_payload.py::_collect_reference_images",
        "id": "collect_reference_images",
        "risk_level": "high",
        "role": "VIDEO-surface ref owner (FK ref_resolution). Tries composite sheets first (_collect_sheet_refs \u2192 ref_resolver.resolve_sheet_asset per entity); on None, runs the two-pass angle collector (Pass 1 hero-per-char \u2192 location block \u2192 Pass 2 round-robin angles, cap 9) via resolve_character_bundle / resolve_location_refs. Gated by an assert_refs_complete pre-spend gate on every return.\n",
        "symbol": "_collect_reference_images"
      },
      {
        "file": "recoil/pipeline/_lib/shot_primitive.py::primitive_from_payload_context",
        "id": "primitive_from_payload_context",
        "risk_level": "high",
        "role": "Shared PayloadContext \u2192 ShotPrimitive coercion used by BOTH the video path (_build_author_aware_prompt) and the board path (board_builder); the prompt builder's single shot-model input. A change here forks both surfaces.\n",
        "symbol": "primitive_from_payload_context"
      },
      {
        "file": "recoil/pipeline/core/dispatch.py::dispatch",
        "id": "dispatch_runner_run",
        "risk_level": "critical",
        "role": "runner = get_runner(modality); runner.run(payload). The modality\u2192runner indirection \u2014 the point where video_i2v\u2192VideoRunner.run diverges from r2v_multi\u2192R2VMultiRunner.run (which delegates to execute_pass, bypassing this file's per-shot author/cap path for batch coverage).\n",
        "symbol": "dispatch"
      },
      {
        "file": "recoil/execution/video_model_client.py::_dict_to_unified",
        "id": "dict_to_unified",
        "reduced_tier": "always",
        "risk_level": "high",
        "role": "The dict\u2192UnifiedVideoPayload coercion seam \u2014 every StepRunner payload crosses here; the typed-vs-dict hints fork lives at this boundary.",
        "symbol": "_dict_to_unified"
      },
      {
        "file": "recoil/execution/providers/registry.py::resolve_adapter",
        "id": "resolve_adapter",
        "reduced_tier": "always",
        "risk_level": "high",
        "role": "THE routing junction: (model_id, payload, tier) \u2192 (adapter, tier) under fixed precedence (override > exceptions > primary). Capability-enforces; raises ProviderCapabilityError loud.",
        "symbol": "resolve_adapter"
      },
      {
        "file": "recoil/execution/providers/registry.py::resolve_fallback",
        "id": "resolve_fallback",
        "reduced_tier": "always",
        "risk_level": "critical",
        "role": "Returns the DECLARED fallback adapter \u2014 but its result is logged, not dispatched, by _finalize_failed (the fallback_declared_not_wired divergence).",
        "symbol": "resolve_fallback"
      },
      {
        "file": "recoil/execution/video_model_client.py::VideoModelClient._submit_via",
        "id": "submit_via_sentinel",
        "risk_level": "high",
        "role": "Sentinel-URL interception: fal-sdk:// (Wan i2v/r2v) and googleapi:// (image/veo) bypass HTTP transport and call adapter.direct_subscribe_*/direct_submit_* instead \u2014 without this branch urllib crashes on dispatch.",
        "symbol": "_submit_via"
      },
      {
        "file": "recoil/pipeline/orchestrator/episode_runner.py",
        "id": "episode_runner_dispatch_one_beat",
        "reduced_tier": "always",
        "risk_level": "high",
        "role": "The take-loop junction \u2014 owns max_takes, StrategyEngine mutation, budget gate, and primary_take selection; where Scene\u2192Beat\u2192Take becomes dispatch.",
        "symbol": "EpisodeRunner._dispatch_one_beat"
      },
      {
        "file": "recoil/pipeline/orchestrator/episode_runner.py",
        "id": "scene_from_group",
        "reduced_tier": "always",
        "risk_level": "high",
        "role": "Group\u2192Scene materialization: stamps beat_metadata.grouping + batch_shots (CanonicalShot dicts w/ location_id+scene_index) that the board/r2v and fingerprint revalidation read back.",
        "symbol": "EpisodeRunner._scene_from_group"
      },
      {
        "file": "recoil/pipeline/_lib/grouping.py",
        "id": "get_grouping",
        "reduced_tier": "always",
        "risk_level": "high",
        "role": "GROUPING_REGISTRY dispatch \u2014 the single seam that forks the persisted scene namespace + modality per strategy.",
        "symbol": "get_grouping"
      },
      {
        "file": "recoil/pipeline/_lib/prompt_engine.py",
        "id": "get_builder_junction",
        "reduced_tier": "always",
        "risk_level": "high",
        "role": "THE single resolution point for prompt_building \u2014 (model_id, modality) \u2192 builder via the BUILDERS table. Every production prompt traverses here; the SSOT chokepoint that forbids parallel builders. Raises KeyError (never silent) on an unregistered key.\n",
        "symbol": "get_builder"
      },
      {
        "file": "recoil/pipeline/_lib/prompt_engine.py",
        "id": "enforce_prompt_length_junction",
        "reduced_tier": "always",
        "risk_level": "high",
        "role": "The prompt-side length guard called as the LAST step of nearly every builder. Reads bible max_chars + optimal_word_range; truncates at sentence boundary. Counterpart to flora.py's 2500-char hard-fail (see divergence prompt_length_split).\n",
        "symbol": "_enforce_prompt_length"
      },
      {
        "file": "recoil/pipeline/_lib/prompt_engine.py",
        "id": "assert_bound_prompt_junction",
        "reduced_tier": "always",
        "risk_level": "high",
        "role": "Post-bind invariant gate for named prose \u2014 enforces the modality-forked @ImageN contract, no-character-name-leak, and beat==timing_segments. Raises BindAssertionError. The junction that keeps r2v_multi and i2v prose from cross-contaminating (see name_binding_modality_fork).\n",
        "symbol": "_assert_bound_prompt"
      },
      {
        "file": "recoil/pipeline/_lib/prompt_engine.py",
        "id": "indexed_ref_entries_junction",
        "reduced_tier": "auto",
        "risk_level": "medium",
        "role": "Normalizes ref_layout sublocation_refs/prop_refs (dict OR list) into index-sorted entries that build_storyboard_strip_prompt / build_gpt_image_2_storyboard_finish_prompt render into the \"REFERENCE MAPPING:\" block \u2014 the bridge from resolved ref spans to prompt @ImageN text.\n",
        "symbol": "_indexed_ref_entries"
      },
      {
        "file": "recoil/core/ref_resolver.py",
        "id": "resolve_reference_bundle",
        "reduced_tier": "always",
        "risk_level": "critical",
        "role": "THE canonical resolver (manifest canonical_target). Precedence shelf\u2192pool\u2192legacy-flat; returns a ReferenceBundle. Only reached on the char path (via resolve_character_bundle); video-location + board-prop + board-loc bypass it.",
        "symbol": "resolve_reference_bundle"
      },
      {
        "file": "recoil/core/ref_resolver.py",
        "id": "resolve_character_bundle",
        "reduced_tier": "always",
        "risk_level": "high",
        "role": "Char-only wrapper over the canonical resolver: hero + fullbody + \u22643 turn-views, capped at 6 assets. The ONE shared resolver across board AND video char routes (the convergence point REC-213=C would unify location/prop onto).",
        "symbol": "resolve_character_bundle"
      },
      {
        "file": "recoil/core/ref_resolver.py",
        "id": "resolve_entity_refs",
        "reduced_tier": "always",
        "risk_level": "high",
        "role": "manifest `canonical` symbol \u2014 but it is just _resolve_legacy_flat_refs; resolve_character_refs / resolve_location_refs / resolve_prop_refs are thin wrappers over it. Three of four manifest deprecated_paths collapse to this one legacy function.",
        "symbol": "resolve_entity_refs"
      },
      {
        "file": "recoil/core/ref_types.py",
        "id": "reference_bundle_type",
        "reduced_tier": "auto",
        "risk_level": "medium",
        "role": "The role-tagged ordered ref container (RefAsset: role/subject/kind/is_hero/view/source). by_role/hero_subjects/view_path projections. The target type any consolidation onto one resolver must emit for all kinds.",
        "symbol": "ReferenceBundle"
      },
      {
        "file": "recoil/core/paths.py",
        "id": "sheet_path",
        "reduced_tier": "auto",
        "risk_level": "medium",
        "role": "The single per-kind composite-sheet LAYOUT home (char base/sheets/, loc sheets/, prop subject-root). Reader (ref_resolver.resolve_sheet_asset) and writer (generate_composite_sheet._sheet_dest) both call it, so they cannot drift (REC-213 C1+C2).",
        "symbol": "sheet_path"
      },
      {
        "file": "recoil/core/paths.py",
        "id": "project_paths",
        "reduced_tier": "always",
        "risk_level": "high",
        "role": "v3 project-scoped path SSOT. Root via projects_root() \u2192 RECOIL_PROJECTS_ROOT env (CLAUDE_DATA) then pipeline_config.json, else raises. asset_look_dir/pool_dir/episode_breakdown_dir/get_location_sheets_dir all hang off here; asset_kind_dir + sequences_dir raise DeprecatedPathAPIError. THE construction junction every artifact path traverses.",
        "symbol": "ProjectPaths"
      },
      {
        "file": "recoil/core/paths.py",
        "id": "resolve_ref_reader",
        "risk_level": "critical",
        "role": "The path-layer ref reader: scans asset_look_dir(base)/ for candidate stems, NOT base/pool/. A second resolver alongside core/ref_resolver.py \u2014 the version-split this whole topology forces (ssot_manifest ref_resolution: transitioning). Reads a DIFFERENT location than every promotion writer.",
        "symbol": "ProjectPaths.resolve_ref"
      },
      {
        "file": "recoil/pipeline/_lib/asset_ops.py",
        "id": "set_hero_junction",
        "risk_level": "high",
        "role": "ref_promotion mechanism #2 \u2014 copies source \u2192 asset_subject_dir/<phase>_hero.<ext> (subject top-level) with a sidecar manifest + op log. Different target than promote_grid and prep_character_angles.",
        "symbol": "set_hero"
      },
      {
        "file": "recoil/pipeline/_lib/ref_image_ops.py",
        "id": "promote_grid_junction",
        "risk_level": "high",
        "role": "ref_promotion mechanism #3 \u2014 splits a grid, writes BOTH asset_subject_dir/<role>.png (canonical) AND <slug>_<role>.png (legacy) at subject top-level. Yet another non-pool target; resolver prefers the canonical-named top-level file but production reads base/pool.",
        "symbol": "promote_grid"
      }
    ]
  },
  "version": 1
}
