{
  "contract": "camera-framing",
  "version": 2,
  "summary": "This contract covers the views used during play and what each view keeps visible. It records fixed, follow, and free-camera modes, manual panning, view size, travel limits, safe areas, clearance, camera activities, and failure handling. This contract does not cover camera feel, physical input mappings, unit commands, HUD layout, or 3D occlusion. The Control options contract covers physical input mappings. The Pointer command contract covers unit commands.",
  "questions": {
    "content-visibility": {
      "asks": "In normal play, must these cameras keep anything from your game on screen?",
      "rationale": "A follow target or camera travel limit does not prove that game content remains visible. This choice states whether these cameras make that promise.",
      "options": {
        "declared-content": {
          "meaning": "The cameras keep named game content visible. A party view keeps every active hero on screen.",
          "semantics": "Every visibility promise is a coverage-promises row. Every effect on a visibility promise is a camera-activities row. Nothing outside those rows is promised."
        },
        "no-content-guarantee": {
          "meaning": "The cameras make no promise that game content stays visible. In an endless runner, the character may leave the view during a fast fall.",
          "semantics": "No visibility promise exists; the promise and activity row sets are declared empty."
        }
      }
    }
  },
  "declares": {
    "values": {},
    "rows": {
      "cameras": {
        "description": "The play views of this adoption and the rules that move or limit each view.",
        "when-empty": "An adoption without a camera row describes no play view, and a reviewer rejects it.",
        "record": {
          "id": {
            "type": "string",
            "required": true,
            "pattern": "kebab-case",
            "unique": true,
            "description": "A short name for this camera in your game's words, such as board-camera or tactical-view."
          },
          "active-when": {
            "type": "citation",
            "required": true,
            "description": "Where your game's rules state the complete rule for when this camera supplies a displayed play view."
          },
          "view-declared-in": {
            "type": "citation",
            "required": true,
            "description": "Where your game's rules name this camera's final play region, aspect handling, and display sizes. For a fixed view, the rules also name the authored base position. For a fixed size, the rules also name the authored view size."
          },
          "follow-mode": {
            "type": "string",
            "required": true,
            "options": [
              "fixed-view",
              "locked-follow",
              "slack-follow",
              "free-view",
              "free-with-recenter"
            ],
            "description": "Fixed view: the base position never moves and follows nothing, and no input moves it, as in a view of one room. Only a declared activity can offset it. Locked follow: the view keeps its target at the center, for example a hero. Slack follow: the target, for example a runner, can move inside an allowed area before the view follows. Free view: the view starts from the position that the player moves it to, as in a tactical view. Free with recenter: the view is a free view, and one action can return it to a target, as when a map returns to a selected group."
          },
          "follow-target-declared-in": {
            "type": "citation",
            "when": {
              "row": {
                "follow-mode": [
                  "locked-follow",
                  "slack-follow",
                  "free-with-recenter"
                ]
              }
            },
            "description": "Where your game's rules name the followed target or the target used by recenter. For slack follow, the rules also name the allowed positions around that target."
          },
          "manual-pan": {
            "type": "string",
            "when": {
              "row": {
                "follow-mode": [
                  "free-view",
                  "free-with-recenter"
                ]
              }
            },
            "options": [
              "edge-scroll",
              "drag-view",
              "move-view-actions",
              "several-methods"
            ],
            "description": "Edge scroll: the view moves when the pointer is at the screen edge, as in a strategy game. Drag view: the player drags the view, as on a mission map. Move-view actions: game actions move the view, as with a tactics camera. Several methods: the camera offers at least two of these methods, as on a world map."
          },
          "pan-actions-declared-in": {
            "type": "citation",
            "when": {
              "row": {
                "follow-mode": [
                  "free-view",
                  "free-with-recenter"
                ]
              }
            },
            "description": "Where your game's rules name the game actions that pan this camera. For several methods, the rules name at least two methods. The Control options contract covers platform inputs."
          },
          "recenter-action-declared-in": {
            "type": "citation",
            "when": {
              "row": {
                "follow-mode": [
                  "free-with-recenter"
                ]
              }
            },
            "description": "Where your game's rules name the game action that returns this view to its cited target."
          },
          "travel-limit": {
            "type": "string",
            "when": {
              "row": {
                "follow-mode": [
                  "locked-follow",
                  "slack-follow",
                  "free-view",
                  "free-with-recenter"
                ]
              }
            },
            "options": [
              "no-camera-travel-limit",
              "one-world-region",
              "changes-by-situation"
            ],
            "description": "No camera travel limit: the camera position has no limit, as in a runner view that keeps moving. One world region: the camera position stays inside one region, as a board view stays inside its room. Changes by situation: the region depends on the situation, as when a tactical map has a different region in each mission phase. This field is not asked for a fixed view, because a fixed view does not move."
          },
          "travel-limit-declared-in": {
            "type": "citation",
            "when": {
              "row": {
                "travel-limit": [
                  "one-world-region",
                  "changes-by-situation"
                ]
              }
            },
            "description": "Where your game's rules state the complete region for the camera position and, when the region changes by situation, the rule that selects the current region."
          },
          "view-size": {
            "type": "string",
            "required": true,
            "options": [
              "fixed-size",
              "player-held-zoom",
              "automatic-fit"
            ],
            "description": "Fixed size keeps a runner at its authored view size. Player-held zoom lets a strategy player hold a chosen zoom level. Automatic fit widens a co-op view until the party and margins fit."
          },
          "view-size-rule-declared-in": {
            "type": "citation",
            "when": {
              "row": {
                "view-size": [
                  "player-held-zoom",
                  "automatic-fit"
                ]
              }
            },
            "description": "Where your game's rules state the rule for the view size. For player-held zoom, the rules name the zoom actions and the allowed range. For automatic fit, the rules name four items. They name the allowed range. They name the response to the aspect ratio. They name the update timing. They name the rule for promises that apply at the same time."
          },
          "failure-response": {
            "type": "string",
            "when": {
              "flag": {
                "content-visibility": [
                  "declared-content"
                ]
              }
            },
            "options": [
              "hold-valid-frame",
              "keep-priority-content",
              "show-framing-failure",
              "change-world-to-restore-frame",
              "failure-unreachable"
            ],
            "description": "This field says what happens when the required content cannot fit. Hold valid frame: the game keeps showing the last valid view, as in a puzzle transition. Keep priority content: the game keeps the content with the higher priority visible and records each promise that did not fit as failed, as when a tactics view keeps the leader visible and records hidden allies as failed. Show framing failure: the game shows the best view that it can and adds a warning, as in a strategy view. Change world to restore frame: a game rule tethers, defeats, or moves content so that the content fits again, as in a co-op game. Failure unreachable: your rules explain why this failure can never happen, as when a fixed arena fits on every display size that the game supports."
          },
          "failure-handling-declared-in": {
            "type": "citation",
            "when": {
              "flag": {
                "content-visibility": [
                  "declared-content"
                ]
              }
            },
            "description": "Where your game's rules state the exact wait, priority, warning, or game rule used after failure, or the complete argument that failure cannot be reached."
          }
        }
      },
      "coverage-promises": {
        "description": "The subjects or areas each camera keeps visible in named play situations.",
        "when-empty": "No camera in this adoption promises to keep game content visible.",
        "record": {
          "id": {
            "type": "string",
            "required": true,
            "pattern": "kebab-case",
            "unique": true,
            "description": "A short name for this promise in your game's words, such as whole-board or selected-group."
          },
          "camera-id": {
            "type": "string",
            "required": true,
            "pattern": "kebab-case",
            "description": "The camera that keeps this promise, by the name you gave it."
          },
          "subject": {
            "type": "citation",
            "required": true,
            "description": "Where your game's rules name the subject or area that stays visible."
          },
          "applies-when": {
            "type": "citation",
            "required": true,
            "description": "Where your game's rules state the complete play conditions in which this promise applies."
          },
          "coverage": {
            "type": "string",
            "required": true,
            "options": [
              "whole-extent",
              "reference-point"
            ],
            "description": "Whole extent keeps every edge of a selected squad visible. Reference point keeps a runner's cited center visible even when the sprite reaches past the view."
          },
          "reference-point": {
            "type": "citation",
            "when": {
              "row": {
                "coverage": [
                  "reference-point"
                ]
              }
            },
            "description": "Where your game's rules name the exact point that stays visible."
          },
          "visibility-area": {
            "type": "string",
            "required": true,
            "options": [
              "rendered-view",
              "hud-safe-area"
            ],
            "description": "Rendered view measures a runner from the final view edges, so HUD overlap alone does not make the promise fail. HUD safe area measures a tactics objective from the cited unobscured area, so HUD-covered space counts as outside the area."
          },
          "safe-area-declared-in": {
            "type": "citation",
            "when": {
              "row": {
                "visibility-area": [
                  "hud-safe-area"
                ]
              }
            },
            "description": "Where your game's rules state the final unobscured rectangle or shape after the HUD elements named by this promise's conditions are placed."
          },
          "margin-declared-in": {
            "type": "citation",
            "required": true,
            "description": "Where your game's rules state the top, right, bottom, and left clearance inside the chosen visibility area, with units. A clearance of zero is stated explicitly. A camera clamped at its travel limit may hold less than this clearance on the clamped side, but the subject still stays inside the area."
          }
        }
      },
      "camera-activities": {
        "description": "The situations that change the frame. Each row says whether the situation keeps or suspends promises, and whether it affects every promise on its camera or only the named promises.",
        "when-empty": "No camera activity has a visibility-promise effect in this adoption. Under the answer declared-content, the cameras that have a promise use none of the seven listed activities.",
        "record": {
          "id": {
            "type": "string",
            "required": true,
            "pattern": "kebab-case",
            "unique": true,
            "description": "A short name for this activity in your game's words, such as sprint-look-ahead or boss-hit-shake."
          },
          "camera-id": {
            "type": "string",
            "required": true,
            "pattern": "kebab-case",
            "description": "The camera this activity changes, by the name you gave it."
          },
          "activity-kind": {
            "type": "string",
            "required": true,
            "options": [
              "look-ahead",
              "manual-pan",
              "aim-or-peek-offset",
              "player-zoom",
              "shake",
              "temporary-zoom",
              "scripted-move"
            ],
            "description": "Look-ahead: the view moves ahead of the target, for example ahead of a runner. Manual pan: the player moves the view, as in a tactical view. Aim or peek offset: the view of a follow camera moves toward the point that the player aims at or peeks at. Player zoom: the player holds a chosen view size, as in a strategy view. Shake: the view shakes, for example on a boss hit. Temporary zoom: the view size changes for a limited time, for example in combat. Scripted move: the game moves the view, for example to show a dialogue reveal."
          },
          "applies-when": {
            "type": "citation",
            "required": true,
            "description": "Where your game's rules state the complete start and end conditions for this camera activity."
          },
          "extent-declared-in": {
            "type": "citation",
            "when": {
              "row": {
                "activity-kind": [
                  "look-ahead",
                  "aim-or-peek-offset",
                  "shake",
                  "temporary-zoom",
                  "scripted-move"
                ]
              }
            },
            "description": "Where your game's rules name this activity's strongest displacement or size change. Manual pan is bounded by the travel limit and the controller instead. Player zoom is bounded by the view-size citation."
          },
          "actions-declared-in": {
            "type": "citation",
            "when": {
              "row": {
                "activity-kind": [
                  "aim-or-peek-offset"
                ]
              }
            },
            "description": "Where your game's rules name the game actions that produce this offset. The Control options contract covers platform inputs."
          },
          "promises": {
            "type": "citation",
            "description": "Where your game's rules list the promise names that this activity affects. When this field is absent, the activity affects every promise on its camera."
          },
          "promise-effect": {
            "type": "string",
            "required": true,
            "options": [
              "keeps-promise",
              "may-suspend"
            ],
            "description": "Keeps promise preserves the affected rows, as when runner look-ahead stays inside its margin. May suspend allows those rows to fail only for this activity, as during a boss reveal."
          }
        }
      }
    }
  },
  "rules": {},
  "origin": "https://opengdd.org/contracts/camera-framing-2",
  "mechanism": [
    "This text decides the order of the steps for each displayed play moment: which camera is active, what must remain visible, who moves the view, what may suspend a promise, how travel and view size are applied, what happens when content cannot fit, and when a suspended promise returns. The questions and rows supply choices and cited game rules. They do not change the order.",
    "A **displayed play moment** is the final play image presented at one moment, after follow motion, manual pan, aim or peek offset, player-held zoom, shake, temporary zoom, scripted movement, viewport scaling, and HUD composition. A **play region** is the screen area named by `view-declared-in`; two regions are the same region whenever their screen areas overlap at all. A coverage row's **visibility area** is either the final rendered play region or that row's cited HUD-safe area. A **valid frame** satisfies every applicable, unsuspended coverage row at its settled margin. A **framing failure** exists only when no camera position and view size allowed by the camera can produce a valid frame.",
    "### Select the view and promises",
    "1. Read every camera whose `active-when` rule holds. Each active camera supplies the final play region named by `view-declared-in`. At most one camera may be active in one play region. Simultaneous split views settle independently only when their regions are distinct.",
    "2. For each active camera, collect its coverage rows whose `applies-when` rules hold. `whole-extent` contributes the subject's complete displayed extent. For a set-valued subject, the whole extent is the union of its members' displayed extents. `reference-point` contributes only the cited point.",
    "3. Construct each row's visibility area after display scaling and the HUD placement named by that row's conditions. HUD-covered space is outside a HUD-safe area; a rendered-view row deliberately ignores HUD overlap. Inset the area by the cited top, right, bottom, and left margin. This contract adds one condition for the edges: a later travel clamp may reduce only the margin on its clamped side. It never reduces the underlying visibility area. A whole extent fits only when all of it lies inside the settled inset area. A reference point fits when that point lies inside it. A subject or point lying exactly on the boundary of the settled inset area fits.",
    "### Apply activity permissions",
    "4. Read every active activity row for the camera. Its `promises` citation, when present, selects the affected applicable promises; when absent, it selects every applicable promise on that camera. An active `may-suspend` row suspends only that set for its cited interval. Active `keeps-promise` rows do not cancel a different active suspension affecting the same promise.",
    "5. A suspended promise is not tested and cannot create a framing failure. It becomes required again on the first displayed play moment after the last suspending activity affecting it ends. On a free-with-recenter camera, a suspending manual-pan interval begins when the view leaves its recentered state and ends on recenter or camera deactivation.",
    "6. The player proposes motion and held zoom through the cited game actions. The camera controller decides the final frame. `free-view` and `free-with-recenter` name who initiates position changes, not who decides the displayed result. A `keeps-promise` manual-pan or aim-or-peek row therefore requires the controller to stop, redirect, or resize the proposed view before an affected promise fails.",
    "### Build and test the frame",
    "7. Build the candidate position in two stages. Stage one selects the base position inside the follow mode's set. A fixed view has only the base position named by `view-declared-in`. A locked follow has only its target point. A slack follow may use the allowed positions its citation names around the target. A free view starts from the player's proposal; a free-with-recenter view does the same until recenter selects its target point. The controller may search only within the set supplied by that mode. Apply any travel limit to the base position; a fixed view declares none. It constrains camera position, not the visible region, and it never proves that a subject is visible. Resolve the step-3 edge condition now: when this clamp alone prevents the subject from meeting a margin, reduce only the clamped-side margin by the exact shortfall, no lower than zero. The subject must still lie inside the underlying, uninset visibility area. Stage two applies each active position-changing activity as an offset to the settled base, bounded by its cited extent. An activity offset is outside the mode's search set and may carry the view past the travel limit; the final test in step 9 still decides every promise.",
    "8. Settle view size once. `fixed-size` uses the size named by `view-declared-in`. `player-held-zoom` starts from the player's held level inside the cited range; its `player-zoom` activity row's effect decides whether the controller must keep every promise or may let the named ones suspend; with no such row, the controller must keep every applicable promise at every held level. `automatic-fit` applies the cited fit rule, choosing the largest allowed size at which the extent, the margins, the strongest cited displacement of every position-changing `keeps-promise` activity, and the largest cited size of every size-changing one fit together. On any view-size answer, an active temporary-zoom activity may propose its cited extent as a temporary size; an accepted temporary size replaces the size the answer would otherwise settle, for the activity's cited interval, and the authored size, held level, or fit result returns on the first displayed moment after it ends. The controller decides the result of every proposal under the activity permission and all unsuspended promises together. The travel region still constrains camera position only: if fitting needs a center outside it, clamp the center and continue fitting inside the allowed zoom range, even when the visible region extends beyond the travel region. If no allowed size and clamped position fits, step 11 applies.",
    "9. Compose the final view and HUD, then test every unsuspended promise against its inset visibility area. This final test decides the outcome; a follow trigger, recenter action, camera travel limit, or earlier candidate frame never substitutes for it.",
    "10. If every row passes, display the valid frame. A `keeps-promise` activity is demonstrated only when this final test passes on every displayed moment of its cited interval, including the strongest displacement or size change its extent citation names.",
    "### Settle a framing failure and recovery",
    "11. Only after no allowed candidate can pass, create one framing failure containing the camera id, failed promise ids, display size, visibility areas, settled margins, attempted position and view size, travel limit, active activities, and selected failure response. A failure response is not an ordinary precondition and cannot weaken a coverage row before this step.",
    "12. `hold-valid-frame` does not display the invalid candidate. It keeps the last valid displayed frame or, before one exists, the cited non-play transition surface; the cited game rule decides whether play or a transition advances. `keep-priority-content` applies the cited total priority, displays the best allowed frame, and records each promise that did not fit. `show-framing-failure` displays the best allowed frame with its cited player-facing failure state. `change-world-to-restore-frame` applies the cited game rule to tether, defeat, or move content, then reruns steps 2–10 before another play moment is displayed; the cited rule may not change the active camera set or create a play region. `failure-unreachable` declares no runtime handling: reaching this step contradicts its cited argument and invalidates the design claim.",
    "13. A failure remains a failure until a later settlement produces a valid frame or the camera deactivates. Priority, a visible notice, or starting a cited world change does not by itself turn an invalid frame into a kept promise.",
    "14. When a suspension ends, run steps 2–10 before displaying the next play moment. The first displayed moment without an active suspension must be valid or must enter steps 11–13. At a camera handover, run steps 1–10: the incoming camera brings its own promise set and visibility areas, and its first frame follows this same first-valid-frame recovery path. Switching cannot insert an unchecked frame or leave two cameras active in one region.",
    "The order of the steps is: active play region, applicable promise, each coverage row's visibility area and margin, activity suspension, player proposal and controller decision, camera-position travel limit, allowed position and view-size settlement, final composed-view test, failure response. Later stages never rewrite an earlier declaration."
  ],
  "pack": "sha256:41d8bf86673c8ea92e50b19543f6f1f0b8cde5f74ca61cf0545f44b4279ab8ed",
  "answers": {
    "content-visibility": "declared-content"
  },
  "values": {},
  "rows": {
    "cameras": [
      {
        "id": "combat-follow-camera",
        "active-when": "party.combat-view-active",
        "view-declared-in": "presentation.combat-play-view",
        "follow-mode": "locked-follow",
        "follow-target-declared-in": "party.active-party-centroid",
        "travel-limit": "one-world-region",
        "travel-limit-declared-in": "combat.current-arena-region",
        "view-size": "fixed-size",
        "failure-response": "change-world-to-restore-frame",
        "failure-handling-declared-in": "party.tether-or-defeat-to-restore-frame"
      },
      {
        "id": "tactical-camera",
        "active-when": "party.tactical-view-active",
        "view-declared-in": "presentation.tactical-play-view",
        "follow-mode": "free-view",
        "manual-pan": "edge-scroll",
        "pan-actions-declared-in": "controls.tactical-edge-pan-action",
        "travel-limit": "changes-by-situation",
        "travel-limit-declared-in": "battle.tactical-regions",
        "view-size": "player-held-zoom",
        "view-size-rule-declared-in": "controls.tactical-zoom-actions-and-range",
        "failure-response": "show-framing-failure",
        "failure-handling-declared-in": "presentation.tactical-framing-warning"
      }
    ],
    "coverage-promises": [
      {
        "id": "active-party",
        "camera-id": "combat-follow-camera",
        "subject": "party.active-heroes",
        "applies-when": "party.combat-control-active",
        "coverage": "whole-extent",
        "visibility-area": "rendered-view",
        "margin-declared-in": "presentation.party-view-margin"
      },
      {
        "id": "selected-group",
        "camera-id": "tactical-camera",
        "subject": "battle.selected-unit-group",
        "applies-when": "battle.selection-can-receive-commands",
        "coverage": "whole-extent",
        "visibility-area": "hud-safe-area",
        "safe-area-declared-in": "presentation.tactical-safe-area",
        "margin-declared-in": "presentation.selected-group-margin"
      }
    ],
    "camera-activities": [
      {
        "id": "hero-sprint-look-ahead",
        "camera-id": "combat-follow-camera",
        "activity-kind": "look-ahead",
        "applies-when": "party.leader-sprinting",
        "extent-declared-in": "party.sprint-look-ahead-lead",
        "promise-effect": "keeps-promise"
      },
      {
        "id": "hero-hit-shake",
        "camera-id": "combat-follow-camera",
        "activity-kind": "shake",
        "applies-when": "combat.heavy-hit-shake",
        "extent-declared-in": "combat.heavy-hit-shake-strength",
        "promise-effect": "may-suspend"
      },
      {
        "id": "boss-approach-punch-in",
        "camera-id": "combat-follow-camera",
        "activity-kind": "temporary-zoom",
        "applies-when": "combat.boss-approach-punch-in",
        "extent-declared-in": "combat.punch-in-zoom-extent",
        "promise-effect": "keeps-promise"
      },
      {
        "id": "leader-aim-peek",
        "camera-id": "combat-follow-camera",
        "activity-kind": "aim-or-peek-offset",
        "applies-when": "party.leader-aiming",
        "extent-declared-in": "party.aim-peek-reach",
        "actions-declared-in": "controls.leader-aim-actions",
        "promise-effect": "keeps-promise"
      },
      {
        "id": "tactical-edge-pan",
        "camera-id": "tactical-camera",
        "activity-kind": "manual-pan",
        "applies-when": "battle.edge-pan-moving-view",
        "promise-effect": "keeps-promise"
      },
      {
        "id": "tactical-held-zoom",
        "camera-id": "tactical-camera",
        "activity-kind": "player-zoom",
        "applies-when": "battle.zoom-level-held",
        "promise-effect": "keeps-promise"
      },
      {
        "id": "battlefield-reveal",
        "camera-id": "tactical-camera",
        "activity-kind": "scripted-move",
        "applies-when": "battle.reinforcement-reveal",
        "extent-declared-in": "battle.reveal-move-path",
        "promise-effect": "may-suspend"
      }
    ]
  }
}
