{
  "contract": "timed-window",
  "version": 1,
  "origin": "https://opengdd.org/contracts/timed-window-1",
  "summary": "Covers named time windows such as invulnerability, cooldowns, status effects, and restart locks. It says what happens when a window opens again, pauses, ends early, or expires. What opens each window, and how long it lasts, stays with your game.",
  "mechanism": [
    "Each row declares one timed window. The row cites the game's own Fixed material for what opens it, its duration, and the clock that advances it; the contract owns none of those game-specific rules or numbers. An opening is an event of the entity the window is given to. Each entity that the game's rules give the window gets its own instance, opened, advanced, and closed by that entity's own events, unless the adoption's Fixed prose explicitly names the window as shared. This per-entity reading is binding for every sentence and generated check that speaks of open copies. The validator cannot check this; a reviewer does.",
    "When the cited opening occurs, the duration is read once and the window opens for that duration; later changes to the cited number or calculation do not move an open copy's endpoint. A fixed duration cites its tuning number; a duration computed from game state cites the Fixed prose that defines the calculation. A window includes its named last moment: it is still open exactly when the cited clock has advanced by that duration, and closed at any point after that, unless its row says it can end early.",
    "A row's on-retrigger answer applies when its opening occurs again while that window is open. ignore leaves the open copy unchanged. new-duration replaces its remaining time with the newly granted duration. keep-longer keeps whichever is longer: the remaining time or the newly granted duration. extend-to-cap adds the newly granted duration to the time remaining, but never leaves more than the row's cited cap. The same open copy continues under ignore, new-duration, keep-longer, and extend-to-cap, and its history shows one opening; only stack creates a new copy. stack opens another independent copy and leaves every open copy unchanged. A game whose extension has a largest permitted duration names that duration as its cap; a game that permits no largest duration states the exception in its own prose and adopts this contract as promised rather than checked. A game whose stacking has a largest number of open copies states that limit in its own prose and adopts this row as promised rather than checked; this contract does not count copies.",
    "Under expiry-only, nothing closes any open copy before its duration ends; a retrigger's own rewrite of an open copy's remaining time is not an early closure. Under declared-ending, the event or condition cited by the row closes every open copy on the entity the ending happens to, unless the cited prose names more; this covers copies being used, cancelled, dispelled, or otherwise ended by the game.",
    "stops means the row's clock does not advance while ordinary play is suspended. keeps-running means it does. The cited clock definition decides what ordinary play being suspended means; this contract does not define pause or catch-up behavior.",
    "The answers belong to each window row, not to the game as a whole. If one apparent window follows different repeat-trigger rules for different opening causes, this version does not model those causes separately; the game must name them as separate windows or state the exception in its own prose."
  ],
  "questions": {},
  "declares": {
    "rows": {
      "windows": {
        "description": "List every named window. An empty list is a claim its author must defend. A cap uses the window's unit and cannot be shorter than its normal duration. The duration unit must match the clock. A real-time window cannot use turns to avoid the suspension choice. The validator cannot check this; a reviewer does.",
        "record": {
          "id": {
            "type": "string",
            "required": true,
            "pattern": "kebab-case",
            "unique": true,
            "description": "The window's stable name as your game writes it. A poison effect might be named poison."
          },
          "opens-at": {
            "type": "citation",
            "required": true,
            "description": "Where your fixed game prose says what opens the window. A poison window might open when a poisoned attack lands."
          },
          "duration": {
            "type": "citation",
            "required": true,
            "description": "Where your game states how long the window lasts. Cite a number for a fixed duration, or fixed prose for a duration calculated from game state. The value is read when the window opens. For example, poison may read its turn count when the hit lands."
          },
          "duration-unit": {
            "type": "string",
            "required": true,
            "options": [
              "seconds",
              "frames",
              "ticks",
              "turns"
            ],
            "description": "The unit used by the duration and its clock. Seconds measure real time. Frames count updates. Ticks count clock steps. Turns count game turns. A poison effect might use turns."
          },
          "clock": {
            "type": "citation",
            "required": true,
            "description": "Where your fixed game prose names the clock that closes this window. A poison effect may use the turn clock."
          },
          "while-suspended": {
            "type": "string",
            "when": {
              "row": {
                "duration-unit": [
                  "seconds",
                  "frames",
                  "ticks"
                ]
              }
            },
            "options": [
              "stops",
              "keeps-running"
            ],
            "description": "For a window whose clock runs with play — seconds, frames, or ticks — whether that clock pauses with ordinary play. If it stops, an invulnerability window keeps all its remaining time during a pause. If it keeps running, the window can expire before play resumes."
          },
          "on-retrigger": {
            "type": "string",
            "required": true,
            "options": [
              "ignore",
              "new-duration",
              "keep-longer",
              "extend-to-cap",
              "stack"
            ],
            "description": "What a new opening does while this window is already open. Ignore leaves the poison window unchanged. New duration replaces its remaining time with the duration from the new poison hit. Keep longer uses whichever time is longer. Extend to cap adds time without passing its cap. Stack opens another independent poison copy. Only stacking creates a new copy. Every other choice continues the current copy and keeps one opening in its history."
          },
          "retrigger-cap": {
            "type": "citation",
            "when": {
              "row": {
                "on-retrigger": [
                  "extend-to-cap"
                ]
              }
            },
            "description": "Where your game states the largest amount of time that can remain after an extension. A poison effect may cap all extensions at its maximum duration. Add this only when repeat openings extend up to a cap. Use the same unit as the duration."
          },
          "ends-early": {
            "type": "string",
            "required": true,
            "options": [
              "expiry-only",
              "declared-ending"
            ],
            "description": "Whether the window can close before its duration ends. Expiry only keeps a poison effect open until time runs out. A declared ending lets a cure close every open poison copy on the entity it cures, unless the cited prose names more."
          },
          "early-end": {
            "type": "citation",
            "when": {
              "row": {
                "ends-early": [
                  "declared-ending"
                ]
              }
            },
            "description": "Where your fixed game prose names every event or condition that ends this window early. This includes using or cancelling it. For poison, this may be an antidote or cleansing the target. Add this only when the window can end early."
          }
        }
      }
    }
  },
  "pack": "sha256:0b00f8a7b2f1f165dbffc65ea394d7fad1cb9fdf6dc123b74bf78c675ad138ed",
  "answers": {},
  "_note_this_file": "This adoption describes a soulslike dodge roll. I-frames are the 0.4 seconds (400 milliseconds) when a rolling player cannot be hit. The dodge-roll-i-frames row makes that window open at roll start, follow the real-time clock, keep its current endpoint if another start is reported, and end only on expiry. The separate cooldown row keeps another roll unavailable until its own longer window expires. The cooldown makes a repeat opening unreachable, so `on-retrigger: ignore` is the declared answer for an event the game prevents.",
  "_note_numbers": "The tuning value player.dodge_roll_iframe_seconds is 0.4 seconds, or 400 milliseconds as shorthand. The tuning value player.dodge_roll_cooldown_seconds is 0.8 seconds, or 800 milliseconds as shorthand. The i-frame window is open at 0.4 seconds and closes on the first observable progress beyond 0.4. The cooldown is open at 0.8 seconds and closes on the first progress beyond 0.8. Both clocks stop during suspension, so a window paused with 0.2 seconds left resumes with 0.2 seconds left.",
  "values": {},
  "rows": {
    "windows": [
      {
        "id": "dodge-roll-i-frames",
        "opens-at": "02-mechanics.md#dodge-roll-start",
        "duration": "player.dodge_roll_iframe_seconds",
        "duration-unit": "seconds",
        "clock": "02-mechanics.md#real-time-clock",
        "while-suspended": "stops",
        "on-retrigger": "ignore",
        "ends-early": "expiry-only"
      },
      {
        "id": "dodge-roll-cooldown",
        "opens-at": "02-mechanics.md#dodge-roll-start",
        "duration": "player.dodge_roll_cooldown_seconds",
        "duration-unit": "seconds",
        "clock": "02-mechanics.md#real-time-clock",
        "while-suspended": "stops",
        "on-retrigger": "ignore",
        "ends-early": "expiry-only"
      }
    ]
  },
  "verification": {
    "declared-window-set": {
      "scope": "the player's dodge-roll windows: invulnerability from roll start through 400 milliseconds (0.4 seconds), and cooldown from roll start through 800 milliseconds (0.8 seconds); the four-unit and six-unit numbers in the suspension check are neutral teaching units, not this game's timing"
    }
  }
}
