# Board generation — storyboard strip (pencil tier) → photoreal finish (full tier),
# StoryGate verdict + auto-reroll, and a per-call board-model A/B seam.
#
# THE board surface. Two-stage by design (REC-149): a HALF-SIZE pencil strip is
# generated + StoryGate-judged + human-approved FIRST; only an approved pencil is
# rendered to a FULL-SIZE photoreal finish (render_board_finish). The board ref
# path uses INDIVIDUAL char/prop refs (char turn-views front/profile + 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 (REC-213 C4)); the LOCATION
# ref is the composite SHEET when the board is locked to one sublocation +
# use_composite_sheets is on (REC-213 C3, same resolve_sheet_asset route as video) —
# else the sublocation plate. render_board_finish re-reads the same location SHEET
# from the board sidecar (sublocation_refs) for the photoreal finish. The board/video
# LOCATION-ref fork + its activation are the ref_resolution divergence
# ref_system_split (route board_ref_path), referenced here, NOT redefined.
#
# Capability state/canonical/deprecated live in ssot_manifest.yaml; the capability
# NODES are declared once in _capabilities.topo.yaml (the FK index). This file does
# NOT redeclare capability nodes — its routes/loops/entrypoints reference capability
# ids by string (prompt_building, ref_resolution, modality_registry). There is NO
# `board_generation` capability in the manifest; board build orchestration lives in
# board_builder.py, an entrypoint surface over those capabilities. The board ref
# junction (_collect_board_refs / route board_ref_path) is mapped in the
# ref_resolution subsystem — referenced here, not redefined.
subsystem: board_generation

entrypoints:
  - id: board_cli
    file: recoil/pipeline/cli/generate.py
    symbol: main
    invoke: "python3 recoil/pipeline/cli/generate.py <project> <episode> --storyboard <batch> [--auto-reroll --max-board-attempts N]"
    kind: cli
    summary: >
      THE board CLI surface. --storyboard builds a proposed pencil strip for one
      continuity/oner batch (via _run_storyboard_build → 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.
    capabilities: [prompt_building, ref_resolution, modality_registry]
    produces: [pencil_strip, photoreal_finish, board_record, story_gate_verdict]
    risk_level: high

  - id: build_and_dispatch_board
    file: recoil/pipeline/_lib/board_builder.py::build_and_dispatch_board
    symbol: build_and_dispatch_board
    invoke: "from recoil.pipeline._lib.board_builder import build_and_dispatch_board"
    kind: module
    summary: >
      Single-attempt pencil-tier board build+dispatch. Collects refs
      (_collect_board_refs — 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.
    capabilities: [prompt_building, ref_resolution, modality_registry]
    produces: [pencil_strip, board_record, story_gate_verdict]
    risk_level: high

  - id: render_board_finish
    file: recoil/pipeline/_lib/board_builder.py::render_board_finish
    symbol: render_board_finish
    invoke: "from recoil.pipeline._lib.board_builder import render_board_finish"
    kind: module
    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.
    capabilities: [prompt_building, modality_registry]
    produces: [photoreal_finish]
    risk_level: high

artifacts:
  - id: pencil_strip
    path: "<project_root>/prep/ep_NNN/storyboards/{batch_id}_vNN.png"
    written_by: [build_and_dispatch_board]
    read_by: [render_board_finish, board_cli]
    schema: "half-size pencil storyboard strip + .json generation sidecar (kind=storyboard)"
    risk_level: high

  - id: photoreal_finish
    path: "<project_root>/prep/ep_NNN/storyboards/{batch_id}_vNN.finish.png"
    written_by: [render_board_finish]
    read_by: [board_cli]
    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)"
    risk_level: high

  - id: story_gate_verdict
    path: "<project_root>/prep/ep_NNN/storyboards/{batch_stem}.verdict.json"
    written_by: [build_and_dispatch_board]
    read_by: [board_cli]            # also read by board_auto_reroll (retry_loops.topo.yaml)
    schema: "StoryGate shadow verdict; carries route + fix_notes; drives auto-reroll"
    risk_level: medium

  - id: board_record
    path: "manifest.execution.boards[shotset_hash]"
    written_by: [board_cli]
    read_by: [board_cli]
    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)"
    risk_level: high

# NOTE: the StoryGate auto-reroll loop, the story_gate_mode flag, and the
# board_reroll_story_gate_fork divergence are mapped in retry_loops.topo.yaml
# (the cross-cutting reroll subsystem). build_and_dispatch_board still judges
# under story_gate_mode=='shadow' and writes story_gate_verdict (artifact below);
# this file owns the board-CONTENT concerns — the two-stage pencil→finish split,
# the model A/B seam, the model fallback retry, and the board artifacts.
flags:
  - id: board_model_override
    defined: recoil/pipeline/_lib/board_provider.py::select_board_model
    default: "gpt-image-2"        # config get_model('storyboard','image') (REC-182)
    forks_capability: prompt_building
    gates_routes: [board_primary_model_route]
    summary: >
      The REC-182/188 board-model A/B seam. Precedence: explicit board_model param
      override → beat.beat_metadata['board_model'] → 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.

routes:
  - id: board_primary_model_route
    capability: prompt_building
    surface: board
    entry: recoil/pipeline/_lib/board_provider.py::select_board_model
    resolves_via: recoil/pipeline/_lib/prompt_engine.py::get_builder
    ref_kind: individual
    gated_by: board_model_override
    fallback_route: board_fallback_model_route
    risk_level: high

  - id: board_fallback_model_route
    capability: prompt_building
    surface: board
    entry: recoil/pipeline/_lib/board_provider.py::board_fallback_model
    resolves_via: recoil/pipeline/_lib/prompt_engine.py::get_builder
    ref_kind: individual          # NB2 (gemini-3.1-flash-image-preview) on refusal/422
    gated_by: null                # auto-triggered by is_board_refusal, not a config flag
    fallback_route: null          # second refusal = HARD block, never infinite fallback
    risk_level: high

  # The actual two-stage split (REC-149): pencil tier vs photoreal finish.
  - id: pencil_board_route
    capability: prompt_building
    surface: board
    entry: recoil/pipeline/_lib/board_builder.py::build_and_dispatch_board
    resolves_via: recoil/pipeline/_lib/board_builder.py::build_and_dispatch_board
    ref_kind: pencil_halfsize     # HALF-size pencil strip; StoryGate-judged; human-approval gate
    gated_by: null
    fallback_route: null
    risk_level: high

  - id: photoreal_finish_route
    capability: prompt_building
    surface: board
    entry: recoil/pipeline/_lib/board_builder.py::render_board_finish
    resolves_via: recoil/pipeline/_lib/board_builder.py::render_board_finish
    ref_kind: photoreal_fullsize  # FULL-size; approved pencil PNG as FIRST ref; NO StoryGate
    gated_by: null
    fallback_route: null
    risk_level: high

# The StoryGate auto-reroll loop (board_auto_reroll, driver build_with_auto_reroll,
# capped at max_attempts=3, reads story_gate_verdict) is owned by retry_loops.topo.yaml.
loops:
  - id: board_model_fallback_retry
    kind: retry
    capability: prompt_building
    trigger: "primary board dispatch RunResult is a refusal/422 (is_board_refusal)"
    driver: recoil/pipeline/_lib/board_provider.py::board_fallback_model
    bound:
      max: 1
      source: "single-shot: board_fallback_model returns fallback once; primary==fallback → no retry"
    exit:
      - "fallback dispatch succeeds → proceed (sidecar fallback_from set)"
      - "second refusal → HARD block (board dispatch failed)"
    retries_into: build_and_dispatch_board
    risk_level: high

divergences:
  - id: board_two_stage_split
    capability: prompt_building
    forks: [pencil_board_route, photoreal_finish_route]
    on_flag: null
    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 — only the
      pencil tier gates. Collapsing the two stages, or judging the finish, breaks
      the REC-149 contract.
    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).
    reduced_tier: always
    risk_level: high

symbols:
  - id: board_refcap_junction
    file: recoil/pipeline/_lib/board_builder.py::build_and_dispatch_board
    symbol: build_and_dispatch_board
    role: >
      The board ref-cap enforcement point: after _collect_board_refs assembles the
      refs (individual char front/profile + 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 (REC-213 C4); plus the location ref — 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.
    reduced_tier: always
    risk_level: high
