Pools & meters

Stamina

stamina · an adoption of Ranged value, version 2

Tests included

Use in your package

The first button opens the authoring tool with these answers and numbers already filled in. You still add the rules and tuning of your own game.

What is in the zip file

The zip file holds the adoption and its acceptance tests. In the authoring tool, choose Add contract and pick this zip file. If you edit your package outside the authoring tool, unpack the zip file in your package folder. The files of the zip file go into contracts/.

The contract that this adoption uses

This contract covers health, stamina, oxygen, XP and other values with a marked bottom and top. It decides what happens to a change at those limits, how named lines fire (report that the value crossed them), how costs are paid, and how the value can move on its own. This contract does not cover the sources of changes, their rates, the display of the value, or the effects of its reports.

Read the full Ranged value contract →

Questions

The supplied answer is marked on each question. Pick other answers to see what changes. Nothing is saved here: the zip file and the authoring tool use the supplied answers.

Can this value hold a fraction, or is it always a whole number?

Choices for Can this value hold a fraction, or is it always a whole number?
Supplied answer The stored value is always whole. A health bar can show 7 or 8, never 7.5.

A change with a fractional part passes through small-change before any limit or mark reads it. Every absolute stored target and mark must resolve to a whole number.

Fractions stay exactly as sent. An oxygen meter can rest at 7.5.

Every finite change lands at full precision. No saved-fraction store exists.

Why this is asked

Whole and fractional values handle small changes differently. A health bar may show only whole points while oxygen keeps decimals.

A change contains part of a point. What happens to that part?

Asked when
Number kind is Whole number.
If not asked
This value keeps fractional changes exactly as sent.
Choices for A change contains part of a point. What happens to that part?
Use the nearer whole change, with an exact half going to the higher number. A health gain of 2.5 becomes 3.

Round the signed change to the nearest integer; ties go toward positive infinity. Discard the difference.

Use the nearer whole change, with an exact half going away from zero. A karma loss of 2.5 becomes −3.

Round to the nearest integer; ties choose the integer with the greater absolute magnitude. Discard the difference.

Drop the fractional part toward zero. A poison drain of 0.7 health does nothing that step.

Remove the signed fractional part toward zero and discard it.

Every nonzero change moves at least one whole point. Chip damage of 0.1 removes 1 health.

Round every non-integer signed change away from zero and discard the difference.

Supplied answer Save the fractional parts until they make a whole point. Two hits worth 0.5 poise each add 1 poise together.

Move the whole part now. Add only the signed fractional remainder to one saved-fraction store. Remove complete multiples of fraction-step toward zero, move by that many whole units, and keep the remainder. Sets always clear the store; named resets and starting over at a limit use their own clearing choices.

The value lands exactly on a line or a limit. Does the value count as having reached it?

Choices for The value lands exactly on a line or a limit. Does the value count as having reached it?
Supplied answer Yes, landing exactly on it counts. Health falling to exactly 25 fires a warning set at 25.

A rising mark at t is reached at value greater than or equal to t; a falling mark is reached at value less than or equal to t. An exact landing may consume a limit.

No, the value must pass it. Health must fall below 25 before a warning set at 25 fires.

A rising mark at t is reached only above t; a falling mark is reached only below t. An exact landing does not consume a limit.

A change would push this value above its marked top. What happens?

Choices for A change would push this value above its marked top. What happens?
Supplied answer Stop at the top and drop the extra. A large health pickup fills the bar, and the rest of the pickup is dropped.

Store max. Drop the extra and do not report it. In every other respect the change counts as applied.

Refuse the whole gain. A health pickup that would overfill the bar adds no health.

Leave the value and saved fraction unchanged and report the refusal. Fire no mark.

Fill to the top and hand back the extra. A healing rule can pass unused healing to a shield.

Store max and report the positive extra to the caller. This contract does not decide where the extra goes. A change with nobody acting has no caller, so its extra is dropped.

The marked top does not stop the value. It stays a named point that other rules can refer to. A score can keep rising after it passes its goal.

Store the complete result above max. The declared top remains citable and still defines shares.

Start a lap at the top. In a lap, the value starts again from a number that you name. Filling an XP bar reports a level and returns the bar to that number.

Reaching is decided by exact-landing. Report a lap and move to lap-lands-on. The mechanism text alone defines leftover handling and report order (see its numbered steps and its paragraph Lap loop).

A change would pull this value below its marked bottom. What happens?

Choices for A change would pull this value below its marked bottom. What happens?
Supplied answer Stop at the bottom and drop the shortfall. A lethal hit leaves health at zero, and the rest of the damage is dropped.

Store min. Drop the shortfall and do not report it.

Refuse the whole loss. A hit that would cross the bottom deals no damage.

Leave the value and saved fraction unchanged and report the refusal. Fire no mark.

Empty to the bottom and hand back the shortfall. A shield can report how much damage is left for another rule.

Store min and report the positive shortfall to the caller. This contract does not decide where the shortfall goes. A change with nobody acting has no caller, so the shortfall is dropped.

The marked bottom does not stop the value. It stays a named point that other rules can refer to. A debt meter can keep falling below zero.

Store the complete result below min. The declared bottom remains citable.

Start a lap at the bottom. In a lap, the value starts again from a number that you name. Losing the last combo point can report a break and refill the meter.

Reaching is decided by exact-landing. Report a lap and move to lap-lands-on. The mechanism text alone defines leftover handling and report order (see its numbered steps and its paragraph Lap loop).

One move passes more than one mark (a line or a band edge). Which marks report?

Asked when
the lines list has at least one row, or the bands list has at least one row.
If not asked
No mark exists, so no move can pass one.
Choices for One move passes more than one mark (a line or a band edge). Which marks report?
Supplied answer Report every crossed mark in travel order. One large hit can report wounded, critical, then empty health.

Report every armed line and band edge whose reached state changes in its direction. Order by position along the path; row order decides a tie. Update all mark state.

Report only the crossed mark nearest the landing value. One large hit can report empty health and suppress earlier warnings.

Among the crossed lines and band edges, find the one nearest to the landing value. Row order decides a tie. Report only that mark, but update every reached, arm, and band state along the full path.

The bottom or top moves while this value holds a number. What happens to the value?

Asked when
the min value is supplied as a citation, or the max value is supplied as a citation.
If not asked
The bottom and top are fixed numbers, so neither limit moves.
Choices for The bottom or top moves while this value holds a number. What happens to the value?
Supplied answer Follow a moving top by filling to it. Raising maximum health from 100 to 120 also raises current health to 120.

A max change stores the new max. A min-only change keeps the value unless it falls below the new min, then stores that min.

Keep the current number while it is still inside the range. Raising maximum health from 100 to 120 leaves current health at 70.

Preserve the value inside the new range; otherwise clamp it to the limit it is outside, even if an ordinary change may pass that limit.

Keep the same share of the range. Raising a half-full health bar from 100 to 200 moves its health from 50 to 100.

Compute the old normalized share immediately before the limit move and store the same share of the new range. An empty old range uses the new min. Whole-number targets round to nearest with ties away from zero. Saved fraction is unchanged.

Keep the number if it is still inside the range. Otherwise move the value to a number that you name. When the stamina top is lowered, a value that is now outside the range can move to 80.

Preserve the value inside the new range. Otherwise store out-of-range-lands-on, which must resolve inside the new range.

Do not move the held number now. Lowering a score's marked top to 100 can leave a held score of 120 unchanged.

A held number outside a moved limit is legal only where the at-the-ceiling or at-the-floor answer for that limit lets the value pass the limit.

Something sets this value to a number outside its range. What happens?

Asked when
the sets list has at least one row.
If not asked
No named set puts this value directly at a target number.
Choices for Something sets this value to a number outside its range. What happens?
Move to the limit the target passed. A refill aimed at 120 leaves a 100-point health bar at 100.

Above max store max; below min store min. The target outside the range is not a raw result. A set does not hand back extra or start a lap.

Refuse the set and keep the old number. An invalid checkpoint refill leaves health unchanged.

Leave the value and saved fraction unchanged, fire no mark, and report refusal to the caller.

Store the target even outside the range. A score set to 120 can stay above its marked top of 100.

Store the target exactly. It is legal beyond a limit only where the at-the-ceiling or at-the-floor answer for that limit lets the value pass the limit.

A set jumps across one or more marks. Do they report?

Asked when
the sets list has at least one row.
If not asked
No named set crosses a line or changes a band.
Choices for A set jumps across one or more marks. Do they report?
Report crossed marks after a successful set. Refilling oxygen to full can clear the low-air warnings on the way up.

After storing the accepted target, test lines and band edges along the movement. jumped-lines selects reports. A refused or clamped target is never a raw result.

Update mark state without reports. Respawning at full health does not replay every health warning.

Recompute line reached state and line-geometry band state from the stored target. Event-edged bands keep their state. Report nothing.

Starting over at a limit is a lap. After a lap, what happens to the extra?

Asked when
At the ceiling is Start a lap, or At the floor is Start a lap.
If not asked
Neither limit starts this value over, so no lap leaves extra.
Choices for Starting over at a limit is a lap. After a lap, what happens to the extra?
Keep using the extra after the lap. An XP award of 120 can fill a 100-point bar and put 20 into the next level.

Keep the amount past the consuming limit and apply it in the same direction after the lap movement. The mechanism text defines the sequence (see its numbered steps and its paragraph Lap loop).

Drop the extra after the lap. A 120-point stagger hit fills one 100-point meter, and the remaining 20 are dropped.

Drop the leftover after one lap and do not report it (see the numbered steps and the paragraph Lap loop of the mechanism text).

After a lap, the extra can reach the same limit again. Can one change cause more than one lap?

Asked when
At the ceiling is Start a lap, or At the floor is Start a lap.
If not asked
Neither limit starts this value over, so one change cannot lap it.
Choices for After a lap, the extra can reach the same limit again. Can one change cause more than one lap?
Start over again while enough extra remains. A 250-point XP award can report two levels on a 100-point bar.

Repeat while carried leftover reaches the limit, up to the cap of 32 laps in the paragraph Lap loop of the mechanism text. This requires carrying the extra.

Allow at most one lap from each change. A 250-point combo gain reports one lap even if more would fit.

The originating change causes at most one lap (see the numbered steps and the paragraph Lap loop of the mechanism text).

Does a lap clear any saved fraction?

Asked when
Small change is Save the fraction and At the ceiling is Start a lap, or Small change is Save the fraction and At the floor is Start a lap.
If not asked
No lap can clear saved fractions; either this value does not lap or it does not save fractions.
Choices for Does a lap clear any saved fraction?
Clear the saved fraction after every lap. Breaking a stagger meter removes the 0.4 poise that was saved.

A lap clears the saved fraction.

Keep the saved fraction across a lap. Leveling up keeps the 0.4 XP that was saved.

A lap leaves the signed saved-fraction store unchanged.

This value cannot pay the full cost of an action, because not enough room is left before the limit. What happens?

Asked when
the costs list has at least one row.
If not asked
No cost checks this value before changing it; every change simply happens, and bands and limits still apply.
Choices for This value cannot pay the full cost of an action, because not enough room is left before the limit. What happens?
Supplied answer Refuse the action and spend nothing. A tired player cannot dodge without the full stamina cost.

Report refusal and shortage to the caller. Leave the value, saved fraction, and every mark unchanged.

Spend what remains and report the amount paid. A tired player can use the last stamina for a weaker dodge.

Move to the limit in the direction of the cost, test marks normally, and report the approved positive magnitude. The game's rule decides the partial action.

A drift is a change that happens with nobody acting. Two or more drifts can run in the same step. How do they combine?

Asked when
the drifts list has at least two rows.
If not asked
Fewer than two drifts can run together, so no combination choice applies.
Choices for A drift is a change that happens with nobody acting. Two or more drifts can run in the same step. How do they combine?
Add the drifts' signed amounts and move once. Oxygen drain of 3 and recovery of 1 produce one loss of 2.

Clip each eligible contribution to its own stop, sum the clipped amounts, resolve small-change once, and test marks once.

Run only the largest drift. With health regeneration of 4 and poison damage of 2, only the regeneration runs in that step.

Compare clipped absolute magnitudes; row order decides a tie. A skipped drift whose amount grows while it runs does not advance its growth for that step.

Run each drift in written order. Poison can damage health before regeneration heals it in the same step.

Settle every eligible row separately in row order. Each row follows the numbered steps of the mechanism text from step 1 to step 8.

A change would pass a limit. Are marks tested against the number before the limit is applied, or against the number that the value keeps?

Asked when
the lines list has at least one row, or the bands list has at least one row.
If not asked
No line or band exists, so nothing reads a result beyond a limit.
Choices for A change would pass a limit. Are marks tested against the number before the limit is applied, or against the number that the value keeps?
Supplied answer Marks are tested against the number that the value keeps. A hit aimed at −20 leaves health at 0, so warnings are tested against 0.

The outward part of the move tests its stored endpoint, or the consuming limit on a lap. It never tests beyond a limit.

Marks are tested against the number before the limit is applied. A hit aimed at −20 lets health warnings be tested against −20 before health returns to 0.

The outward part of the move tests the raw candidate. It may observe a number the value never stores. A refused change has no observable raw result.

Numbers7 numbers

Each value is either a number set here or a reference to a decided number in your tuning. It cannot reference an open number.

Min = 0

fixed as written

min

This value's marked bottom. Keep it even when the value may fall lower.

Max = tuning.stamina.max

read from your tuning

max

This value's marked top. Keep it even when the value may rise higher.

Initial = tuning.stamina.max

read from your tuning

initial

The number that this value holds when it is created. When you point to a number in your game's rules instead, that number is read at creation. Later resets are listed separately.

Fraction step = 1

fixed as written

fraction-step

How much saved fraction releases one whole point.

Anchor

anchor

The resting point a drift approaches from either side.

Not used by this adoption. It applies when some drifts row has direction Toward the anchor.

Lap lands on

lap-lands-on

Where the value lands during a lap.

Not used by this adoption. It applies when At the ceiling is Start a lap, or At the floor is Start a lap.

Out of range lands on

out-of-range-lands-on

Where the value lands when a moved limit leaves it outside the new range.

Not used by this adoption. It applies when When a limit moves is Pull it to a declared number.

Rules13 rules

A rule is a check between the numbers. Validation reports a rule that fails.

  • max > min
  • initial >= min
  • initial <= max
  • fraction-step > 0
  • anchor >= min
  • anchor <= max
  • lap-lands-on >= min
  • lap-lands-on <= max
  • out-of-range-lands-on >= min
  • out-of-range-lands-on <= max
  • The answer keep-the-share needs a bottom and a top that the value cannot pass. Change the answer no-ceiling or no-floor, or choose another answer to when-a-limit-moves.

    Forbidden when When a limit moves is Keep the share and At the ceiling is No ceiling, or When a limit moves is Keep the share and At the floor is No floor.

  • The answer refill-to-the-new-ceiling can put the value exactly on a limit whose answer is start-a-lap. A moving limit never starts a lap. Change one of these two answers.

    Forbidden when When a limit moves is Refill to the new ceiling and At the ceiling is Start a lap, or When a limit moves is Refill to the new ceiling and At the floor is Start a lap.

  • The answer lap-again-while-it-fits needs the extra that remains after the first lap. Choose carry-it-onto-the-new-value, or choose one-lap-per-change.

    Forbidden when More than one lap is Lap again while it fits and The extra after a lap is Drop it.

Lists9 lists

Each list holds the rows this adoption supplies. A list can be empty.

Reading notes

reading-notes

Read first
02-mechanics.md#stamina

Quantity unit

quantity-unit

Unit
stamina points

Lines

lines

IdValue kindValueValue keyLine unitDirectionRefireTimesRearmRearm declared inRepeatCounts at lineStarts armed
windednumber0.2—share-of-the-ceilingbothevery-crossing——————
second-windnumber0——fallingonce—on-reset02-mechanics.md#respawn———

Sets

sets

This adoption declares no sets.

Bands

bands

IdEnter onEnter value kindEnter valueEnter value keyEnter unitEnter directionEnter declared inLeave onLeave value kindLeave valueLeave value keyLeave unitLeave directionLeave declared inRepeatStartsBlocks changeCounts at line
exhaustedlinenumber0—numberfalling—linenumber0.3—share-of-the-ceilingrising——outblocks-falls—

Costs

costs

IdDirectionAmount keyAction declared inAnswer
attackfallingtuning.stamina.attack-cost02-mechanics.md#attack-action—
sprintfallingtuning.stamina.sprint-cost02-mechanics.md#sprint-actiontake-what-fits

Drifts

drifts

IdDirectionCadenceMoment declared inStep amount keyAmount kindAmount declared inStops at keyAt stop
recoveryrisesper-real-second—tuning.stamina.regen-ratefixed—contracts.stamina.maxlands-exactly

Interrupts

interrupts

IdDriftKindCondition declared inEvent declared inPause length keyPause length kindPause length declared inRepeat event
after-exertionrecoverypaused-after-event—02-mechanics.md#stamina-spenttuning.stamina.regen-delayfixed—restart-the-wait

Resets

resets

IdHappens atSets value to keyClears saved fractionRe arms linesResets bands
respawn02-mechanics.md#respawncontracts.stamina.maxyesyesreturn-to-starts
Test inputsscope and seeds

Some tests need a scope or seeds from the adoption before they can run.

This adoption supplies no test inputs. Every test uses its default inputs.

Acceptance tests37 tests apply

The contract comes with 96 tests. A test that runs once per row is counted once for each row. Tests that do not apply to these answers are still listed, with the reason.

change-applies

an in-range change applies exactly

scenarioonce

Applies to stamina.

An in-range change moves stamina by its amount, resolved to whole units as this value's small-change answer directs, in one commit with no partly settled state observable. A mark whose reached state did not change does not report. The limits are stated at min = 0 and max = tuning.stamina.max; this test restates neither number.

Test steps and diagnostics
Given

stamina holding a stored value strictly between min = 0 and max = tuning.stamina.max, with enough room that the change below stays in range, and with no active band blocking the change's direction

When
  • one imposed change with signed amount d is applied to stamina
Then
  • the stored stamina value changes by d, resolved to whole units as this value's small-change answer directs
  • the value and residue, when residue exists, commit together and no partly settled state is observable
  • no mark whose reached state did not change reports
Diagnostics
  • stamina-value-before-after
  • stamina-mark-log

fractional-change-resolves

a fractional change resolves as declared

scenarioonce

Does not apply to stamina: small change is save the fraction, not nearest or nearest away from zero or drop the fraction or always a whole unit.

fractional-change-saves-residue

saved fractions release whole multiples toward zero

scenarioonce

Applies to stamina.

stamina splits a fractional change: the whole part moves at once and the signed remainder is saved as residue. Whenever that residue contains complete multiples of fraction-step = 1, those multiples are removed from residue toward zero and move the value by that many whole units, and the remainder stays. A whole change leaves residue unchanged. The test restates neither the step nor any limit.

Test steps and diagnostics
Given

stamina holding a whole stored value with signed residue zero, enough room for the changes below, and a record naming each offered amount, movement, and residue afterwards, and with no active band blocking the change's direction

When
  • a run of positive and negative fractional changes is applied, including a whole change and residue that reaches at least one complete multiple of fraction-step = 1
Then
  • the whole part of each fractional change moves stamina at once and only its signed fractional remainder enters residue
  • complete multiples of fraction-step = 1 are removed from residue toward zero and move that many whole units in the matching direction
  • a whole change moves in full and leaves residue unchanged
  • the stored value remains whole
  • residue after each change equals the offered signed fractional remainders minus the complete multiples of fraction-step = 1 already released, and each released multiple moved that many whole units
Diagnostics
  • stamina-change-record
  • stamina-residue
  • stamina-value-before-after

fractional-value-keeps-no-store

a fractional value has no saved-fraction store

scenarioonce

Does not apply to stamina: number kind is whole number, not fractional.

comparisons-read-stored-value

every comparison reads the stored value

scenarioonce

Applies to stamina.

Whatever stamina shows the player, every comparison reads its stored value at full precision. Lines, bands, limits, drift stops, and costs never read a rounded display number, and each cited position is read again at each comparison.

Test steps and diagnostics
Given

a moment when the number shown for stamina differs from the stored value because the display is rounded, shortened, or abbreviated; where the display always matches the stored value, this test checks nothing

When
  • a line, band edge, limit, drift stop, or cost comparison is made
Then
  • the comparison reads the stored stamina value at full precision
  • every cited position is read again for that comparison
  • the displayed number decides nothing
Diagnostics
  • stamina-comparison-log
  • stamina-value-before-after

creation-is-silent

creation stores mark state silently

scenarioonce

Applies to stamina.

Creation is not a movement and reports nothing. stamina silently stores every line's reached state, gives lines their declared arm state or the armed default, and gives bands their declared starts.

Test steps and diagnostics
Given

creation of stamina at initial = tuning.stamina.max

When
  • initial line and band state is stored
Then
  • creation is not a movement and reports nothing
  • every line's reached state is stored silently
  • lines take their declared arm state, or armed when starts-armed is omitted
  • bands take their declared starts
Diagnostics
  • stamina-line-state
  • stamina-line-allowance
  • stamina-band-state
  • stamina-mark-log

exact-landing-counts

an exact landing counts as reached

scenarioonce

Applies to stamina.

For stamina, landing exactly on a line or consuming limit counts as reaching it. An armed line at that position, matching the direction of travel and with allowance remaining, reports on that landing, and the change consumes a limit whose answer is start-a-lap. This default answer is tested at a line or band that declares no counts-at-line of its own.

Test steps and diagnostics
Given

stamina on the unreached side of a declared line or consuming limit, at a line or band that declares no counts-at-line of its own

When
  • one change lands exactly on that position
Then
  • the position counts as reached in the direction of travel
  • an armed line at that position, matching the direction of travel and with allowance remaining, reports on that landing
  • the change consumes a limit whose answer is start-a-lap
Diagnostics
  • stamina-mark-log
  • stamina-operation-record

exact-landing-must-pass

an exact landing does not count as reached

scenarioonce

Does not apply to stamina: exact landing is at the line counts, not must pass the line.

ceiling-stops-change

the value stops at the top and the extra is dropped

scenarioonce

Applies to stamina.

A rising change that would pass max = tuning.stamina.max leaves stamina exactly on that limit. The extra is dropped and reported to no one. This test restates neither number.

Test steps and diagnostics
Given

stamina holding any value at or below max = tuning.stamina.max

When
  • an imposed rising change would take stamina above max = tuning.stamina.max
Then
  • the stored value is exactly max = tuning.stamina.max
  • the extra is dropped and reported to no one
  • in every other respect the change counts as applied
Diagnostics
  • stamina-value-after
  • stamina-edge-record

floor-stops-change

the value stops at the bottom and the shortfall is dropped

scenarioonce

Applies to stamina.

A falling change that would pass min = 0 leaves stamina exactly on that limit. The shortfall is dropped and reported to no one. This test restates neither number.

Test steps and diagnostics
Given

stamina holding any value at or above min = 0

When
  • an imposed falling change would take stamina below min = 0
Then
  • the stored value is exactly min = 0
  • the shortfall is dropped and reported to no one
Diagnostics
  • stamina-value-after
  • stamina-edge-record

ceiling-refuses-change

a change that would pass the top is refused whole

scenarioonce

Does not apply to stamina: at the ceiling is stop at the ceiling, not refuse the whole change.

floor-refuses-change

a change that would pass the bottom is refused whole

scenarioonce

Does not apply to stamina: at the floor is stop at the floor, not refuse the whole change.

ceiling-returns-extra

the extra above the top is handed back

scenarioonce

Does not apply to stamina: at the ceiling is stop at the ceiling, not hand back the extra.

floor-returns-extra

the shortfall below the bottom is handed back

scenarioonce

Does not apply to stamina: at the floor is stop at the floor, not hand back the extra.

ceiling-is-landmark

the top does not stop the value

scenarioonce

Does not apply to stamina: at the ceiling is stop at the ceiling, not no ceiling.

floor-is-landmark

the bottom does not stop the value

scenarioonce

Does not apply to stamina: at the floor is stop at the floor, not no floor.

ceiling-starts-lap

reaching the top starts a lap

scenarioonce

Does not apply to stamina: at the ceiling is stop at the ceiling, not start a lap.

floor-starts-lap

reaching the bottom starts a lap

scenarioonce

Does not apply to stamina: at the floor is stop at the floor, not start a lap.

lap-carries-extra

extra continues after a lap

scenarioonce

Does not apply to stamina: the extra after a lap is not asked for stamina.

lap-drops-extra

extra is dropped after a lap

scenarioonce

Does not apply to stamina: the extra after a lap is not asked for stamina.

lap-repeats-with-cap

carried extra laps again up to the runtime cap

scenarioonce

Does not apply to stamina: more than one lap is not asked for stamina.

lap-stops-after-one

one change causes at most one lap

scenarioonce

Does not apply to stamina: more than one lap is not asked for stamina.

lap-stops-after-one-and-drops

one change drops leftover after its only lap

scenarioonce

Does not apply to stamina: more than one lap is not asked for stamina.

lap-clears-residue

a lap clears saved residue

scenarioonce

Does not apply to stamina: a lap clears saved fraction is not asked for stamina.

lap-keeps-residue

a lap keeps saved residue

scenarioonce

Does not apply to stamina: a lap clears saved fraction is not asked for stamina.

line-crosses-fixed-position · winded

the winded line fires on crossing

scenarioper lines row

Applies to the winded row.

The winded line uses the position the row declares, read as its line-unit directs — the declared number itself unless the row declares share-of-the-ceiling, where it is that share of the value at max = tuning.stamina.max, read again at each comparison. A movement in either direction reports when its reached state changes from false to true, provided the line is armed and has allowance. Its declared starting arm state applies; omission means armed.

Test steps and diagnostics
Given

stamina holding a value on the unreached side of winded at the position the row declares, read as its line-unit directs — the declared number itself unless the row declares share-of-the-ceiling, where it is that share of the value at max = tuning.stamina.max, read again at each comparison, with the line in its declared starting arm state; omission of starts-armed means armed

When
  • one movement in either direction changes winded from unreached to reached
Then
  • winded reports exactly once if it is armed and still has allowance
  • the report carries before, after, direction, leg position, operation id, and row id
  • reached state is recomputed from the stored endpoint in both directions
Diagnostics
  • stamina-mark-log
  • stamina-line-state
  • stamina-value-before-after

line-crosses-fixed-position · second-wind

the second-wind line fires on crossing

scenarioper lines row

Applies to the second-wind row.

The second-wind line uses the position the row declares, read as its line-unit directs — the declared number itself unless the row declares share-of-the-ceiling, where it is that share of the value at max = tuning.stamina.max, read again at each comparison. A falling movement reports when its reached state changes from false to true, provided the line is armed and has allowance. Its declared starting arm state applies; omission means armed.

Test steps and diagnostics
Given

stamina holding a value on the unreached side of second-wind at the position the row declares, read as its line-unit directs — the declared number itself unless the row declares share-of-the-ceiling, where it is that share of the value at max = tuning.stamina.max, read again at each comparison, with the line in its declared starting arm state; omission of starts-armed means armed

When
  • one falling movement changes second-wind from unreached to reached
Then
  • second-wind reports exactly once if it is armed and still has allowance
  • the report carries before, after, direction, leg position, operation id, and row id
  • reached state is recomputed from the stored endpoint in both directions
Diagnostics
  • stamina-mark-log
  • stamina-line-state
  • stamina-value-before-after

line-crosses-cited-position

the Row.id line follows and fires at its cited position

scenarioper lines row

Does not apply to stamina: no row in lines matches value-kind citation.

line-uses-ceiling-share · winded

the winded line is a share of the top

scenarioper lines row

Applies to the winded row.

winded states a share rather than an absolute position. Its line follows that share of max = tuning.stamina.max, read again at each comparison; this test restates neither number.

Test steps and diagnostics
Given

the marked top at max = tuning.stamina.max recorded before comparison and winded armed

When
  • a movement in either direction crosses the share declared by winded
Then
  • the line position is the row's declared share multiplied by the value at max = tuning.stamina.max, read again at that comparison
  • the position may be fractional; it is compared, never stored
  • the crossing reports once if allowance remains
Diagnostics
  • stamina-comparison-log
  • stamina-mark-log

line-exact-landing-override

Row.id uses its own exact-landing answer

scenarioper lines row

Does not apply to stamina: no row in lines matches counts-at-line at the line counts or must pass the line.

line-refires-every-crossing · winded

winded fires again on each crossing

scenarioper lines row

Applies to the winded row.

For winded, each leg whose reached state for a reported direction goes false to true fires exactly one report, while a true-to-false leg reports nothing. the described sequence reports three times, once per crossing, because a both line keeps one reached state per direction.

Test steps and diagnostics
Given

winded armed and unreached

When
  • stamina crosses winded in a reporting direction, returns to unreached, and crosses again
Then
  • each leg whose reached state for a reported direction goes false to true fires exactly one report for that leg, and a leg whose transition is true to false reports nothing
  • the described sequence reports three times, once per crossing, because a both line keeps one reached state per direction
Diagnostics
  • stamina-mark-log
  • stamina-line-state

line-both-directions-keep-state · winded

winded keeps separate reached state for both directions

scenarioper lines row

Applies to the winded row.

A both-direction line keeps one reached state per direction. In a cross-down, cross-up, cross-down sequence, each qualifying false-to-true transition reports while refire allowance remains; one shared state is not legal.

Test steps and diagnostics
Given

winded armed with allowance and its rising and falling reached states recorded separately

When
  • stamina crosses winded down, up, and down again
Then
  • each direction keeps its own reached state
  • each qualifying false-to-true transition in either direction reports while the line's refire allowance remains
  • no shared single reached state is legal
Diagnostics
  • stamina-crossing-log
  • stamina-mark-log
  • stamina-line-state

line-fires-once-per-arm · second-wind

second-wind fires once, then waits to re-arm

scenarioper lines row

Applies to the second-wind row.

second-wind reports once per arm. Across two crossings without re-arming, only the first reports. Its re-arm rule is: the reset cited at 02-mechanics.md#respawn occurs.

Test steps and diagnostics
Given

second-wind armed with its full allowance

When
  • stamina makes two qualifying crossings without re-arming, then the reset cited at 02-mechanics.md#respawn occurs
Then
  • only the first crossing reports before re-arming
  • after the declared re-arm, the next qualifying crossing reports once again
Diagnostics
  • stamina-mark-log
  • stamina-line-allowance

line-fires-set-number

Row.id fires its declared number of times per arm

scenarioper lines row

Does not apply to stamina: no row in lines matches refire a set number of times and rearm on reset or on event or after a wait.

line-wait-rearm-repeat

Row.id handles another crossing during its re-arm wait

scenarioper lines row

Does not apply to stamina: no row in lines matches rearm after a wait.

jumped-lines-reports

a move crossing several marks reports as declared

scenarioonce

Applies to stamina.

When one leg of stamina crosses several marks, every crossed armed mark reports in travel order, with row order deciding a tie. every crossed mark updates all its state. A crossed band edge updates its band state under both answers whether or not its report is selected. Where this adoption has fewer than two marks, this test checks nothing.

Test steps and diagnostics
Given

one leg that crosses at least two armed bare lines or band edges with allowance; where the adoption has fewer than two marks, this test checks nothing

When
  • the leg settles
Then
  • every crossed armed mark reports in travel order, with row order deciding a tie
  • every crossed mark updates all its state
  • a band edge crossed on the leg updates its band state under both answers, whether or not its report is the selected one
Diagnostics
  • stamina-mark-log
  • stamina-mark-state

marks-read-final-value

marks read the final value at a limit

scenarioonce

Applies to stamina.

When a change passes a limit, stamina marks read the final outward endpoint: the stored value, or the consuming limit on a lap. A mark at or past the reached limit never observes the raw candidate, and every report from that leg carries the stored endpoint or consuming limit as its after value.

Test steps and diagnostics
Given

a change whose raw candidate passes a limit and a mark at or past the limit the change reaches

When
  • the change settles without refusal
Then
  • outward marks test the stored endpoint or the consuming limit
  • no outward mark observes a number beyond the limit
  • every report from that leg carries the stored endpoint, or the consuming limit, as its after value — never the raw candidate
Diagnostics
  • stamina-operation-record
  • stamina-mark-log

marks-read-raw-result

marks read the raw result at a limit

scenarioonce

Does not apply to stamina: lines read is the final value, not the raw result.

band-enters-at-fixed-line · exhausted

the exhausted band enters at its fixed line

scenarioper bands row

Applies to the exhausted row.

exhausted enters when a falling leg reaches the entering line at 0, read as its enter-unit directs — the declared number itself unless the row declares share-of-the-ceiling, where it is that share of the value at max = tuning.stamina.max, read again at each comparison. It reports the state change subject to the value's jumped-lines answer.

Test steps and diagnostics
Given

exhausted out and stamina on the non-entered side of the entering line at 0, read as its enter-unit directs — the declared number itself unless the row declares share-of-the-ceiling, where it is that share of the value at max = tuning.stamina.max, read again at each comparison

When
  • one falling leg changes the entering edge from unreached to reached
Then
  • exhausted enters and reports once, subject to jumped-lines
  • the report carries the row id and operation evidence
Diagnostics
  • stamina-band-state
  • stamina-mark-log

band-enters-at-cited-line

the Row.id band enters at its cited line

scenarioper bands row

Does not apply to stamina: no row in bands matches enter-on line and enter-value-kind citation.

band-leaves-at-fixed-line · exhausted

the exhausted band leaves at its fixed line

scenarioper bands row

Applies to the exhausted row.

exhausted leaves when a rising leg reaches the leaving line at 0.3, read as its leave-unit directs — the declared number itself unless the row declares share-of-the-ceiling, where it is that share of the value at max = tuning.stamina.max, read again at each comparison.

Test steps and diagnostics
Given

exhausted in and stamina on the non-leaving side of the leaving line at 0.3, read as its leave-unit directs — the declared number itself unless the row declares share-of-the-ceiling, where it is that share of the value at max = tuning.stamina.max, read again at each comparison

When
  • one rising leg changes the leaving edge from unreached to reached
Then
  • exhausted leaves and reports once, subject to jumped-lines
Diagnostics
  • stamina-band-state
  • stamina-mark-log

band-leaves-at-cited-line

the Row.id band leaves at its cited line

scenarioper bands row

Does not apply to stamina: no row in bands matches leave-on line and leave-value-kind citation.

band-event-enters

the Row.id band enters on its event

scenarioper bands row

Does not apply to stamina: no row in bands matches enter-on event.

band-event-or-wait-leaves

the Row.id band leaves on its declared event or wait

scenarioper bands row

Does not apply to stamina: no row in bands matches leave-on event or after a wait.

band-wait-repeat

the Row.id band handles another entry during its wait

scenarioper bands row

Does not apply to stamina: no row in bands matches leave-on after a wait.

band-starts-as-declared · exhausted

the exhausted band starts as declared

scenarioper bands row

Applies to the exhausted row.

At creation, exhausted starts out and reports nothing. The starting value is cited at initial = tuning.stamina.max; this test restates no number.

Test steps and diagnostics
Given

creation of stamina at initial = tuning.stamina.max

When
  • the initial band state is stored
Then
  • exhausted starts out
  • creation reports no mark
Diagnostics
  • stamina-band-state
  • stamina-mark-log

band-starts-from-initial

the Row.id band reads its starting state from the initial value

scenarioper bands row

Does not apply to stamina: no row in bands matches starts read from initial and enter-on line and leave-on line.

event-entered-band-starts-out

the event-entered Row.id band starts out

scenarioper bands row

Does not apply to stamina: no row in bands matches starts read from initial and enter-on event.

event-left-band-starts-out

the event-left Row.id band starts out

scenarioper bands row

Does not apply to stamina: no row in bands matches starts read from initial and leave-on event or after a wait.

band-blocks-selected-movement · exhausted

the exhausted band blocks its selected movements

scenarioper bands row

Applies to the exhausted row.

While exhausted is in, blocks-rises blocks rising imposed changes, costs, and drifts; blocks-falls blocks falling ones; blocks-all blocks both directions — each as far as this adoption can construct the attempt. Blocking happens before settlement, and a blocked drift records the block but has no receiver. Each set, reset, limit move, and lap movement this adoption can construct settles unblocked. Where it can construct none of them, this test checks nothing for that part.

Test steps and diagnostics
Given

exhausted in

When
  • of the movements this adoption can construct: where this row answers blocks-rises, a rising change, cost, and drift are each attempted; where it answers blocks-falls, a falling change, cost, and drift are each attempted; where it answers blocks-all, changes, costs, and drifts are attempted in both directions; for a movement kind that this adoption cannot construct, this test checks nothing
  • each of a set, a reset, a limit move, and a lap movement that this adoption can construct settles; where this adoption can construct none of them, this test checks nothing in this step
Then
  • each attempted selected imposed change, cost, and drift is discarded before fractional handling and records blocked
  • a blocked drift has no receiver and contributes nothing to a composed batch
  • each of a set, a reset, a limit move, and a lap movement that this adoption can construct settles unblocked; where this adoption can construct none of them, this test checks nothing in this step
Diagnostics
  • stamina-blocked-record
  • stamina-operation-record

event-entered-band-survives-silent-recompute

the event-entered Row.id band keeps state through silent recomputes

scenarioper bands row

Does not apply to stamina: no row in bands matches enter-on event.

event-left-band-survives-silent-recompute

the event-edged Row.id band keeps state through silent recomputes

scenarioper bands row

Does not apply to stamina: no row in bands matches leave-on event or after a wait.

line-once-never-rearms

Row.id fires once and never re-arms

scenarioper lines row

Does not apply to stamina: no row in lines matches refire once and rearm never.

line-set-number-never-rearms

Row.id has one fixed lifetime allowance

scenarioper lines row

Does not apply to stamina: no row in lines matches refire a set number of times and rearm never.

line-starts-armed-as-declared

Row.id uses its explicit starting arm state

scenarioper lines row

Does not apply to stamina: no row in lines matches starts-armed armed or disarmed.

band-enter-share-follows-ceiling

the Row.id entering line is a share of the top

scenarioper bands row

Does not apply to stamina: no row in bands matches enter-unit share of the ceiling.

band-leave-share-follows-ceiling · exhausted

the exhausted leaving line is a share of the top

scenarioper bands row

Applies to the exhausted row.

exhausted places its leaving line at a declared share of max = tuning.stamina.max, read again for the comparison. This test restates neither number.

Test steps and diagnostics
Given

exhausted in and the marked top at max = tuning.stamina.max recorded before comparison

When
  • a movement crosses the leaving share declared by the row
Then
  • the leaving position is the row's share multiplied by the value at max = tuning.stamina.max, read again at that comparison
  • the leaving position may be fractional; it is compared, never stored
  • exhausted leaves if the directional reached state changes
Diagnostics
  • stamina-comparison-log
  • stamina-band-state

band-exact-landing-override

Row.id uses its own exact-landing answer

scenarioper bands row

Does not apply to stamina: no row in bands matches counts-at-line at the line counts or must pass the line.

set-stores-target-without-raw-result

the Row.id set stores its target without a raw result

scenarioper sets row

Does not apply to stamina: sets has no rows.

outside-set-clamps

an outside set stores the nearer limit

scenarioonce

Does not apply to stamina: a set outside the range is not asked for stamina.

outside-set-refuses

an outside set is refused whole

scenarioonce

Does not apply to stamina: a set outside the range is not asked for stamina.

outside-set-stores-as-sent

an outside set stores its target as sent

scenarioonce

Does not apply to stamina: a set outside the range is not asked for stamina.

set-fires-marks-as-change

a successful set reports crossed marks

scenarioonce

Does not apply to stamina: does a set fire lines is not asked for stamina.

set-updates-marks-silently

a successful set recomputes marks without reports

scenarioonce

Does not apply to stamina: does a set fire lines is not asked for stamina.

successful-set-clears-residue

the Row.id set clears residue when it succeeds

scenarioper sets row

Does not apply to stamina: sets has no rows.

cost-pays-exactly · attack

the affordable attack cost pays exactly

scenarioper costs row

Applies to the attack row.

When affordable and unblocked, attack moves stamina toward min = 0 by the amount at tuning.stamina.attack-cost after small-change resolves it, updating residue accordingly. The report to 02-mechanics.md#attack-action carries no refusal and no shortage. This test names the addresses and restates no number.

Test steps and diagnostics
Given

stamina with enough room toward min = 0 to pay the non-negative amount at tuning.stamina.attack-cost, and with no active band blocking the change's direction

When
  • the action at 02-mechanics.md#attack-action attempts attack
Then
  • the stored value moves toward min = 0 by the amount at tuning.stamina.attack-cost after small-change resolves it, and residue updates accordingly
  • the report to the action carries no refusal and no shortage
  • marks test the paid movement normally
Diagnostics
  • stamina-value-before-after
  • stamina-residue-before-after
  • stamina-cost-record
  • stamina-mark-log

cost-pays-exactly · sprint

the affordable sprint cost pays exactly

scenarioper costs row

Applies to the sprint row.

When affordable and unblocked, sprint moves stamina toward min = 0 by the amount at tuning.stamina.sprint-cost after small-change resolves it, updating residue accordingly. The report to 02-mechanics.md#sprint-action carries no refusal and no shortage. This test names the addresses and restates no number.

Test steps and diagnostics
Given

stamina with enough room toward min = 0 to pay the non-negative amount at tuning.stamina.sprint-cost, and with no active band blocking the change's direction

When
  • the action at 02-mechanics.md#sprint-action attempts sprint
Then
  • the stored value moves toward min = 0 by the amount at tuning.stamina.sprint-cost after small-change resolves it, and residue updates accordingly
  • the report to the action carries no refusal and no shortage
  • marks test the paid movement normally
Diagnostics
  • stamina-value-before-after
  • stamina-residue-before-after
  • stamina-cost-record
  • stamina-mark-log

value-wide-cost-refuses

the default cost answer refuses an unaffordable action

scenarioonce

Applies to stamina.

For a cost row without an override, an unaffordable cost is refused whole. Movement and provisional residue are discarded together; value, residue, and every mark stay unchanged, and the caller receives the shortage.

Test steps and diagnostics
Given

a cost row without an answer override, insufficient room for its full provisional movement, stored value v, residue r when present, and unchanged mark state m

When
  • the cost is attempted
Then
  • the action is refused with its positive shortage
  • movement and provisional residue are discarded together
  • stored value v, residue r, and all mark state m remain unchanged
Diagnostics
  • stamina-cost-record
  • stamina-value-before-after
  • stamina-residue-before-after
  • stamina-mark-state

value-wide-cost-takes-what-fits

the default cost answer takes what fits

scenarioonce

Does not apply to stamina: not enough room is refuse the action, not take what fits.

cost-row-override-refuses

the Row.id override refuses an unaffordable action

scenarioper costs row

Does not apply to stamina: not enough room is refuse the action, not take what fits.

cost-row-override-partial · sprint

the sprint override takes what fits

scenarioper costs row

Applies to the sprint row.

sprint uses its own answer, not the default cost answer. When full payment does not fit, stamina moves to the limit in the direction of the cost, recomputes residue from the approved part only, and reports the positive amount paid to 02-mechanics.md#sprint-action.

Test steps and diagnostics
Given

insufficient room to pay sprint in full

When
  • the action at 02-mechanics.md#sprint-action attempts the amount at tuning.stamina.sprint-cost
Then
  • the row's take-what-fits answer applies, not the default answer
  • stamina moves to the limit in the direction of the cost and reports the positive amount paid
  • movement and residue are recomputed from only that approved amount
  • marks test that movement normally
Diagnostics
  • stamina-cost-record
  • stamina-value-before-after
  • stamina-residue-before-after
  • stamina-mark-log

drift-moves-value

Row.id moves the value on its own

scenarioper drifts row

Does not apply to stamina: no matching row in drifts has every field this test uses (amount-declared-in).

continuous-drift-uses-elapsed-time · recovery

recovery accrues continuously from elapsed clock time

scenarioper drifts row

Applies to the recovery row.

recovery accrues continuously. It multiplies the rate at tuning.stamina.regen-rate by elapsed cited-clock time, not frame count, and sends fractional accrual through small-change. This test restates no rate.

Test steps and diagnostics
Given

recovery eligible over equal cited-clock durations rendered with different frame counts

When
  • each duration settles
Then
  • each contribution is the rate at tuning.stamina.regen-rate multiplied by elapsed cited-clock time
  • equal elapsed time produces equal provisional movement regardless of frame count
  • fractional accrual passes through small-change
Diagnostics
  • stamina-clock-trace
  • stamina-drift-log

named-moment-drift-uses-citation

Row.id runs only at its named moment

scenarioper drifts row

Does not apply to stamina: no row in drifts matches cadence at a named moment.

declared-drift-amount-rule

Row.id uses its declared changing amount rule

scenarioper drifts row

Does not apply to stamina: no row in drifts matches amount-kind depends on state or grows while it runs.

drift-stops · recovery

recovery stops where its row says it stops

scenarioper drifts row

Applies to the recovery row.

Both at-stop answers leave stamina stored at contracts.stamina.max, so the stored value cannot distinguish them. This test reads the at-stop step record: the offered contribution is clipped so no value beyond contracts.stamina.max exists in the step record. Once stored at that address, recovery is ineligible and offers nothing. The test restates no stop or amount.

Test steps and diagnostics
Given

stamina less than one recovery contribution from contracts.stamina.max, recovery eligible and alone, and an at-stop step record naming the offered amount, every intermediate value, the correction, and the stored value afterwards

When
  • the next offered contribution would pass contracts.stamina.max
Then
  • the step record names the amount offered before clipping or correction
  • the offered contribution is clipped so no value beyond contracts.stamina.max exists in the step record
  • the stored value is exactly contracts.stamina.max under either answer
  • after an overshoot, correction reports no mark and recomputes reached state from the stored stop
  • recovery is ineligible and offers nothing while the value rests at its stop
Diagnostics
  • stamina-at-stop-step-record
  • stamina-offered-amount
  • stamina-intermediate-value
  • stamina-value-after

drift-clips-at-consuming-ceiling

Row.id clips at the consuming top without lapping

scenarioper drifts row

Does not apply to stamina: at the ceiling is stop at the ceiling, not start a lap.

drift-clips-at-consuming-floor

Row.id clips at the consuming bottom without lapping

scenarioper drifts row

Does not apply to stamina: at the floor is stop at the floor, not start a lap.

drift-approaches-anchor

Row.id approaches the anchor from either side

scenarioper drifts row

Does not apply to stamina: anchor is not in use for stamina.

drifts-add-together

eligible drifts add before one settlement

scenarioonce

Does not apply to stamina: drifts together is not asked for stamina.

largest-drift-wins

only the largest eligible drift settles

scenarioonce

Does not apply to stamina: drifts together is not asked for stamina.

drifts-settle-in-row-order

eligible drifts settle separately in row order

scenarioonce

Does not apply to stamina: drifts together is not asked for stamina.

condition-interrupt-controls-drift

Row.id controls its drift through a condition

scenarioper interrupts row

Does not apply to stamina: no row in interrupts matches kind off while condition or runs only while condition.

pause-interrupt-controls-drift · after-exertion

after-exertion pauses its drift after an event

scenarioper interrupts row

Applies to the after-exertion row.

After 02-mechanics.md#stamina-spent, after-exertion pauses recovery and uses the fixed pause at tuning.stamina.regen-delay. A repeated event restarts the wait from the repeated event. Every row naming the drift must allow it before it resumes.

Test steps and diagnostics
Given

recovery otherwise allowed by every interrupt row

When
  • the event at 02-mechanics.md#stamina-spent occurs twice before the pause completes
Then
  • after-exertion uses the fixed pause at tuning.stamina.regen-delay and prevents recovery from running during the wait
  • the second event restarts the wait from the repeated event
  • recovery runs again only after this and every other row naming it allow it
Diagnostics
  • stamina-interrupt-state
  • stamina-pause-record
  • stamina-drift-log

state-dependent-pause-length

Row.id reads its state-dependent pause rule

scenarioper interrupts row

Does not apply to stamina: no row in interrupts matches pause-length-kind depends on state.

interrupt-conjunction-holds · after-exertion

after-exertion combines with every interrupt on its drift

scenarioper interrupts row

Applies to the after-exertion row.

Even while after-exertion allows recovery, another row for that drift can forbid it. The drift runs only when every row naming it allows it; where this adoption has no such pair, this test checks nothing.

Test steps and diagnostics
Given

another interrupt row naming recovery and currently forbidding it while after-exertion allows it; where this adoption has no such pair, this test checks nothing

When
  • the drift reaches an occurrence while those row states hold
Then
  • recovery does not run
  • every interrupt row for one drift must allow it before it runs
Diagnostics
  • stamina-interrupt-state
  • stamina-drift-log

reset-returns-silently · respawn

the respawn reset returns the value silently

scenarioper resets row

Applies to the respawn row.

At 02-mechanics.md#respawn, respawn silently returns stamina to its named target, or to initial = tuning.stamina.max when sets-value-to-key is omitted (with a cited initial, the number read at creation). Where this adoption declares any line or band, crossing one on the return path reports nothing. The reset does not lap or pass through small-change.

Test steps and diagnostics
Given

stamina away from the target used by respawn

When
  • the moment at 02-mechanics.md#respawn occurs
Then
  • respawn stores its sets-value-to-key target when present, otherwise initial = tuning.stamina.max (with a cited initial, the number read at creation)
  • where this adoption declares any line or band, the return path crossing one reports nothing
  • the reset reports no lap
  • the return is one absolute repositioning and does not pass through small-change
Diagnostics
  • stamina-reset-record
  • stamina-value-after
  • stamina-mark-log

reset-handles-residue · respawn

the respawn reset handles residue as declared

scenarioper resets row

Applies to the respawn row.

When respawn settles, it clears residue to zero.

Test steps and diagnostics
Given

stamina holding nonzero signed residue before respawn

When
  • the reset at 02-mechanics.md#respawn settles
Then
  • respawn clears residue to zero in the same silent commit
Diagnostics
  • stamina-reset-record
  • stamina-residue-before-after

reset-handles-lines-and-bands · respawn

the respawn reset restores mark state as declared

scenarioper resets row

Applies to the respawn row.

respawn restores full allowance to every line whose rearm-declared-in cites this reset's moment; every other line keeps its current arm state and allowance. Band states respond as the row's resets-bands answer directs; a row that omits resets-bands keeps every band's state, which is this definition's documented default. All state changes are silent.

Test steps and diagnostics
Given

changed line allowances and band states before respawn

When
  • the reset at 02-mechanics.md#respawn settles
Then
  • respawn restores full allowance to every line whose rearm-declared-in cites this reset's moment; every other line keeps its current arm state and allowance
  • band states respond as the row's resets-bands answer directs, and a row that omits resets-bands keeps every band's state, which is this definition's documented default
  • every line's reached state is recomputed and stored from the reset target
  • all state changes are silent
Diagnostics
  • stamina-reset-record
  • stamina-line-allowance
  • stamina-band-state
  • stamina-mark-log

reset-applies-explicit-band-choice · respawn

the respawn reset applies its explicit band choice

scenarioper resets row

Applies to the respawn row.

An explicit return-to-starts restores declared band starts; recompute-from-target recomputes line geometry and keeps event-edged bands; keep preserves every band's prior state unchanged. This test is not generated for a row without resets-bands. The general reset test checks that such a row keeps every band's state.

Test steps and diagnostics
Given

for return-to-starts, band states different from their declared starts; for recompute-from-target, line-geometry band states different from what the reset target would produce; for keep, a prior band state that differs from what both return-to-starts and recompute-from-target would produce, where this adoption can construct one; where it cannot, keep is checked only as no band state changing

When
  • the reset at 02-mechanics.md#respawn settles
Then
  • where this row declares return-to-starts, it restores every band to its declared starts state
  • where this row declares recompute-from-target, it recomputes every line-geometry band from the reset target and keeps every event-edged band
  • where this row declares keep, it preserves every band's prior state unchanged
  • the change is silent
Diagnostics
  • stamina-reset-record
  • stamina-band-state
  • stamina-mark-log

limit-move-refills-ceiling

a moving top refills the value

scenarioonce

Applies to stamina.

Where max = tuning.stamina.max is cited and moves, stamina fills to the new max. Where min = 0 is cited and moves alone, the held value stays unless it falls below the new min. An adoption that cites only one limit is tested only for that limit. A limit move never laps and never clears residue.

Test steps and diagnostics
Given

stamina holding any value while each cited limit this adoption can move is exercised separately

When
  • each cited limit this adoption can move settles in its own case
Then
  • where max = tuning.stamina.max is a cited limit that moves, the new max is stored as the value
  • where min = 0 is cited and moves alone, the value is kept unless it falls below the new min
  • an adoption that cites only one limit is tested only for that limit
  • neither movement starts a lap and residue is unchanged
Diagnostics
  • stamina-limit-move-record
  • stamina-value-before-after

limit-move-keeps-number

a moving limit keeps the held number while it is inside the new range

scenarioonce

Does not apply to stamina: when a limit moves is refill to the new ceiling, not keep the number.

limit-move-keeps-share

a moving limit preserves the value's share

scenarioonce

Does not apply to stamina: when a limit moves is refill to the new ceiling, not keep the share.

limit-move-uses-declared-number

an outside value moves to its declared number

scenarioonce

Does not apply to stamina: when a limit moves is refill to the new ceiling, not pull it to a declared number.

limit-move-leaves-number-alone

a moving limit does not change the held number

scenarioonce

Does not apply to stamina: when a limit moves is refill to the new ceiling, not leave the number alone.

limit-move-never-fires-bare-line · winded

a limit move never fires the winded bare line

scenarioper lines row

Applies to the winded row.

After a limit move, stamina recomputes the reached state of bare line winded but never fires it. Where either limit answers start-a-lap, a limit move landing the value exactly on that limit does not consume it.

Test steps and diagnostics
Given

a cited limit move that changes winded reached state

When
  • the limit and any resulting held-value change finish settling
Then
  • winded stores its recomputed reached state but reports nothing
  • where either limit answers start-a-lap, a limit move landing the value exactly on that limit does not consume it
Diagnostics
  • stamina-limit-move-record
  • stamina-mark-log
  • stamina-mark-state

limit-move-never-fires-bare-line · second-wind

a limit move never fires the second-wind bare line

scenarioper lines row

Applies to the second-wind row.

After a limit move, stamina recomputes the reached state of bare line second-wind but never fires it. Where either limit answers start-a-lap, a limit move landing the value exactly on that limit does not consume it.

Test steps and diagnostics
Given

a cited limit move that changes second-wind reached state

When
  • the limit and any resulting held-value change finish settling
Then
  • second-wind stores its recomputed reached state but reports nothing
  • where either limit answers start-a-lap, a limit move landing the value exactly on that limit does not consume it
Diagnostics
  • stamina-limit-move-record
  • stamina-mark-log
  • stamina-mark-state

limit-move-recomputes-band · exhausted

a limit move recomputes the exhausted band as its edge kind directs

scenarioper bands row

Applies to the exhausted row.

After a limit move, line-geometry band exhausted recomputes and stores its state. Where that state changed, it reports the change with cause limit-moved; an unchanged state reports nothing.

Test steps and diagnostics
Given

a cited limit move with the prior exhausted state recorded

When
  • the limit and any resulting held-value change finish settling
Then
  • exhausted recomputes and stores its line-geometry state against the new positions
  • where that recomputed state differs from the prior recorded state, exhausted reports the change with cause limit-moved; where it does not differ, exhausted reports nothing
Diagnostics
  • stamina-limit-move-record
  • stamina-mark-log
  • stamina-mark-state

limits-hold

the limits hold after every operation

generalonce

Applies to stamina.

For every operation in this adoption's verification scope — sequences of legal imposed changes, costs, sets, drift steps and batches, laps, resets, and limit moves over one run — the stamina value is greater than or equal to min = 0, and the stamina value is less than or equal to max = tuning.stamina.max. A cited limit whose answer is no-floor or no-ceiling remains a named point that other rules can cite. The adoption supplies scope and seeds through verification inputs; each of the two conditions is the one that the answer for that limit permits.

Test steps and diagnostics
Holds

after every operation, the stamina value is greater than or equal to min = 0, and the stamina value is less than or equal to max = tuning.stamina.max. A cited limit whose answer is no-floor or no-ceiling remains a named point that other rules can cite

Seeds

["audit-a","audit-b"]

Scope

sequences of legal imposed changes, costs, sets, drift steps and batches, laps, resets, and limit moves over one run

Diagnostics
  • stamina-value-trace
  • stamina-first-violating-operation
JSONthe adoption as one file

Answers you try on this page do not change this file. To change an adoption, open it in the authoring tool.

{
  "contract": "ranged-value",
  "version": 2,
  "summary": "This contract covers health, stamina, oxygen, XP and other values with a marked bottom and top. It decides what happens to a change at those limits, how named lines fire (report that the value crossed them), how costs are paid, and how the value can move on its own. This contract does not cover the sources of changes, their rates, the display of the value, or the effects of its reports.",
  "questions": {
    "number-kind": {
      "asks": "Can this value hold a fraction, or is it always a whole number?",
      "rationale": "Whole and fractional values handle small changes differently. A health bar may show only whole points while oxygen keeps decimals.",
      "options": {
        "whole-number": {
          "meaning": "The stored value is always whole. A health bar can show 7 or 8, never 7.5.",
          "semantics": "A change with a fractional part passes through small-change before any limit or mark reads it. Every absolute stored target and mark must resolve to a whole number."
        },
        "fractional": {
          "meaning": "Fractions stay exactly as sent. An oxygen meter can rest at 7.5.",
          "semantics": "Every finite change lands at full precision. No saved-fraction store exists."
        }
      }
    },
    "small-change": {
      "asks": "A change contains part of a point. What happens to that part?",
      "when": {
        "flag": {
          "number-kind": [
            "whole-number"
          ]
        }
      },
      "otherwise": "This value keeps fractional changes exactly as sent.",
      "options": {
        "nearest": {
          "meaning": "Use the nearer whole change, with an exact half going to the higher number. A health gain of 2.5 becomes 3.",
          "semantics": "Round the signed change to the nearest integer; ties go toward positive infinity. Discard the difference."
        },
        "nearest-away-from-zero": {
          "meaning": "Use the nearer whole change, with an exact half going away from zero. A karma loss of 2.5 becomes −3.",
          "semantics": "Round to the nearest integer; ties choose the integer with the greater absolute magnitude. Discard the difference."
        },
        "drop-the-fraction": {
          "meaning": "Drop the fractional part toward zero. A poison drain of 0.7 health does nothing that step.",
          "semantics": "Remove the signed fractional part toward zero and discard it."
        },
        "always-a-whole-unit": {
          "meaning": "Every nonzero change moves at least one whole point. Chip damage of 0.1 removes 1 health.",
          "semantics": "Round every non-integer signed change away from zero and discard the difference."
        },
        "save-the-fraction": {
          "meaning": "Save the fractional parts until they make a whole point. Two hits worth 0.5 poise each add 1 poise together.",
          "semantics": "Move the whole part now. Add only the signed fractional remainder to one saved-fraction store. Remove complete multiples of fraction-step toward zero, move by that many whole units, and keep the remainder. Sets always clear the store; named resets and starting over at a limit use their own clearing choices."
        }
      }
    },
    "exact-landing": {
      "asks": "The value lands exactly on a line or a limit. Does the value count as having reached it?",
      "options": {
        "at-the-line-counts": {
          "meaning": "Yes, landing exactly on it counts. Health falling to exactly 25 fires a warning set at 25.",
          "semantics": "A rising mark at t is reached at value greater than or equal to t; a falling mark is reached at value less than or equal to t. An exact landing may consume a limit."
        },
        "must-pass-the-line": {
          "meaning": "No, the value must pass it. Health must fall below 25 before a warning set at 25 fires.",
          "semantics": "A rising mark at t is reached only above t; a falling mark is reached only below t. An exact landing does not consume a limit."
        }
      }
    },
    "at-the-ceiling": {
      "asks": "A change would push this value above its marked top. What happens?",
      "options": {
        "stop-at-the-ceiling": {
          "meaning": "Stop at the top and drop the extra. A large health pickup fills the bar, and the rest of the pickup is dropped.",
          "semantics": "Store max. Drop the extra and do not report it. In every other respect the change counts as applied."
        },
        "refuse-the-whole-change": {
          "meaning": "Refuse the whole gain. A health pickup that would overfill the bar adds no health.",
          "semantics": "Leave the value and saved fraction unchanged and report the refusal. Fire no mark."
        },
        "hand-back-the-extra": {
          "meaning": "Fill to the top and hand back the extra. A healing rule can pass unused healing to a shield.",
          "semantics": "Store max and report the positive extra to the caller. This contract does not decide where the extra goes. A change with nobody acting has no caller, so its extra is dropped."
        },
        "no-ceiling": {
          "meaning": "The marked top does not stop the value. It stays a named point that other rules can refer to. A score can keep rising after it passes its goal.",
          "semantics": "Store the complete result above max. The declared top remains citable and still defines shares."
        },
        "start-a-lap": {
          "meaning": "Start a lap at the top. In a lap, the value starts again from a number that you name. Filling an XP bar reports a level and returns the bar to that number.",
          "semantics": "Reaching is decided by exact-landing. Report a lap and move to lap-lands-on. The mechanism text alone defines leftover handling and report order (see its numbered steps and its paragraph Lap loop)."
        }
      }
    },
    "at-the-floor": {
      "asks": "A change would pull this value below its marked bottom. What happens?",
      "options": {
        "stop-at-the-floor": {
          "meaning": "Stop at the bottom and drop the shortfall. A lethal hit leaves health at zero, and the rest of the damage is dropped.",
          "semantics": "Store min. Drop the shortfall and do not report it."
        },
        "refuse-the-whole-change": {
          "meaning": "Refuse the whole loss. A hit that would cross the bottom deals no damage.",
          "semantics": "Leave the value and saved fraction unchanged and report the refusal. Fire no mark."
        },
        "hand-back-the-extra": {
          "meaning": "Empty to the bottom and hand back the shortfall. A shield can report how much damage is left for another rule.",
          "semantics": "Store min and report the positive shortfall to the caller. This contract does not decide where the shortfall goes. A change with nobody acting has no caller, so the shortfall is dropped."
        },
        "no-floor": {
          "meaning": "The marked bottom does not stop the value. It stays a named point that other rules can refer to. A debt meter can keep falling below zero.",
          "semantics": "Store the complete result below min. The declared bottom remains citable."
        },
        "start-a-lap": {
          "meaning": "Start a lap at the bottom. In a lap, the value starts again from a number that you name. Losing the last combo point can report a break and refill the meter.",
          "semantics": "Reaching is decided by exact-landing. Report a lap and move to lap-lands-on. The mechanism text alone defines leftover handling and report order (see its numbered steps and its paragraph Lap loop)."
        }
      }
    },
    "jumped-lines": {
      "asks": "One move passes more than one mark (a line or a band edge). Which marks report?",
      "when": {
        "any": [
          {
            "row-count": {
              "lines": "non-empty"
            }
          },
          {
            "row-count": {
              "bands": "non-empty"
            }
          }
        ]
      },
      "otherwise": "No mark exists, so no move can pass one.",
      "options": {
        "all-in-order": {
          "meaning": "Report every crossed mark in travel order. One large hit can report wounded, critical, then empty health.",
          "semantics": "Report every armed line and band edge whose reached state changes in its direction. Order by position along the path; row order decides a tie. Update all mark state."
        },
        "only-the-nearest": {
          "meaning": "Report only the crossed mark nearest the landing value. One large hit can report empty health and suppress earlier warnings.",
          "semantics": "Among the crossed lines and band edges, find the one nearest to the landing value. Row order decides a tie. Report only that mark, but update every reached, arm, and band state along the full path."
        }
      }
    },
    "when-a-limit-moves": {
      "asks": "The bottom or top moves while this value holds a number. What happens to the value?",
      "when": {
        "any": [
          {
            "value-form": {
              "min": [
                "citation"
              ]
            }
          },
          {
            "value-form": {
              "max": [
                "citation"
              ]
            }
          }
        ]
      },
      "otherwise": "The bottom and top are fixed numbers, so neither limit moves.",
      "options": {
        "refill-to-the-new-ceiling": {
          "meaning": "Follow a moving top by filling to it. Raising maximum health from 100 to 120 also raises current health to 120.",
          "semantics": "A max change stores the new max. A min-only change keeps the value unless it falls below the new min, then stores that min."
        },
        "keep-the-number": {
          "meaning": "Keep the current number while it is still inside the range. Raising maximum health from 100 to 120 leaves current health at 70.",
          "semantics": "Preserve the value inside the new range; otherwise clamp it to the limit it is outside, even if an ordinary change may pass that limit."
        },
        "keep-the-share": {
          "meaning": "Keep the same share of the range. Raising a half-full health bar from 100 to 200 moves its health from 50 to 100.",
          "semantics": "Compute the old normalized share immediately before the limit move and store the same share of the new range. An empty old range uses the new min. Whole-number targets round to nearest with ties away from zero. Saved fraction is unchanged."
        },
        "pull-it-to-a-declared-number": {
          "meaning": "Keep the number if it is still inside the range. Otherwise move the value to a number that you name. When the stamina top is lowered, a value that is now outside the range can move to 80.",
          "semantics": "Preserve the value inside the new range. Otherwise store out-of-range-lands-on, which must resolve inside the new range."
        },
        "leave-the-number-alone": {
          "meaning": "Do not move the held number now. Lowering a score's marked top to 100 can leave a held score of 120 unchanged.",
          "semantics": "A held number outside a moved limit is legal only where the at-the-ceiling or at-the-floor answer for that limit lets the value pass the limit."
        }
      }
    },
    "a-set-outside-the-range": {
      "asks": "Something sets this value to a number outside its range. What happens?",
      "when": {
        "row-count": {
          "sets": "non-empty"
        }
      },
      "otherwise": "No named set puts this value directly at a target number.",
      "options": {
        "pull-it-to-the-nearer-limit": {
          "meaning": "Move to the limit the target passed. A refill aimed at 120 leaves a 100-point health bar at 100.",
          "semantics": "Above max store max; below min store min. The target outside the range is not a raw result. A set does not hand back extra or start a lap."
        },
        "refuse-the-set": {
          "meaning": "Refuse the set and keep the old number. An invalid checkpoint refill leaves health unchanged.",
          "semantics": "Leave the value and saved fraction unchanged, fire no mark, and report refusal to the caller."
        },
        "store-it-as-sent": {
          "meaning": "Store the target even outside the range. A score set to 120 can stay above its marked top of 100.",
          "semantics": "Store the target exactly. It is legal beyond a limit only where the at-the-ceiling or at-the-floor answer for that limit lets the value pass the limit."
        }
      }
    },
    "does-a-set-fire-lines": {
      "asks": "A set jumps across one or more marks. Do they report?",
      "when": {
        "row-count": {
          "sets": "non-empty"
        }
      },
      "otherwise": "No named set crosses a line or changes a band.",
      "options": {
        "treat-it-as-a-change": {
          "meaning": "Report crossed marks after a successful set. Refilling oxygen to full can clear the low-air warnings on the way up.",
          "semantics": "After storing the accepted target, test lines and band edges along the movement. jumped-lines selects reports. A refused or clamped target is never a raw result."
        },
        "silent": {
          "meaning": "Update mark state without reports. Respawning at full health does not replay every health warning.",
          "semantics": "Recompute line reached state and line-geometry band state from the stored target. Event-edged bands keep their state. Report nothing."
        }
      }
    },
    "the-extra-after-a-lap": {
      "asks": "Starting over at a limit is a lap. After a lap, what happens to the extra?",
      "when": {
        "any": [
          {
            "flag": {
              "at-the-ceiling": [
                "start-a-lap"
              ]
            }
          },
          {
            "flag": {
              "at-the-floor": [
                "start-a-lap"
              ]
            }
          }
        ]
      },
      "otherwise": "Neither limit starts this value over, so no lap leaves extra.",
      "options": {
        "carry-it-onto-the-new-value": {
          "meaning": "Keep using the extra after the lap. An XP award of 120 can fill a 100-point bar and put 20 into the next level.",
          "semantics": "Keep the amount past the consuming limit and apply it in the same direction after the lap movement. The mechanism text defines the sequence (see its numbered steps and its paragraph Lap loop)."
        },
        "drop-it": {
          "meaning": "Drop the extra after the lap. A 120-point stagger hit fills one 100-point meter, and the remaining 20 are dropped.",
          "semantics": "Drop the leftover after one lap and do not report it (see the numbered steps and the paragraph Lap loop of the mechanism text)."
        }
      }
    },
    "more-than-one-lap": {
      "asks": "After a lap, the extra can reach the same limit again. Can one change cause more than one lap?",
      "when": {
        "any": [
          {
            "flag": {
              "at-the-ceiling": [
                "start-a-lap"
              ]
            }
          },
          {
            "flag": {
              "at-the-floor": [
                "start-a-lap"
              ]
            }
          }
        ]
      },
      "otherwise": "Neither limit starts this value over, so one change cannot lap it.",
      "options": {
        "lap-again-while-it-fits": {
          "meaning": "Start over again while enough extra remains. A 250-point XP award can report two levels on a 100-point bar.",
          "semantics": "Repeat while carried leftover reaches the limit, up to the cap of 32 laps in the paragraph Lap loop of the mechanism text. This requires carrying the extra."
        },
        "one-lap-per-change": {
          "meaning": "Allow at most one lap from each change. A 250-point combo gain reports one lap even if more would fit.",
          "semantics": "The originating change causes at most one lap (see the numbered steps and the paragraph Lap loop of the mechanism text)."
        }
      }
    },
    "a-lap-clears-saved-fraction": {
      "asks": "Does a lap clear any saved fraction?",
      "when": {
        "any": [
          {
            "flag": {
              "small-change": [
                "save-the-fraction"
              ],
              "at-the-ceiling": [
                "start-a-lap"
              ]
            }
          },
          {
            "flag": {
              "small-change": [
                "save-the-fraction"
              ],
              "at-the-floor": [
                "start-a-lap"
              ]
            }
          }
        ]
      },
      "otherwise": "No lap can clear saved fractions; either this value does not lap or it does not save fractions.",
      "options": {
        "clear-it": {
          "meaning": "Clear the saved fraction after every lap. Breaking a stagger meter removes the 0.4 poise that was saved.",
          "semantics": "A lap clears the saved fraction."
        },
        "keep-it": {
          "meaning": "Keep the saved fraction across a lap. Leveling up keeps the 0.4 XP that was saved.",
          "semantics": "A lap leaves the signed saved-fraction store unchanged."
        }
      }
    },
    "not-enough-room": {
      "asks": "This value cannot pay the full cost of an action, because not enough room is left before the limit. What happens?",
      "when": {
        "row-count": {
          "costs": "non-empty"
        }
      },
      "otherwise": "No cost checks this value before changing it; every change simply happens, and bands and limits still apply.",
      "options": {
        "refuse-the-action": {
          "meaning": "Refuse the action and spend nothing. A tired player cannot dodge without the full stamina cost.",
          "semantics": "Report refusal and shortage to the caller. Leave the value, saved fraction, and every mark unchanged."
        },
        "take-what-fits": {
          "meaning": "Spend what remains and report the amount paid. A tired player can use the last stamina for a weaker dodge.",
          "semantics": "Move to the limit in the direction of the cost, test marks normally, and report the approved positive magnitude. The game's rule decides the partial action."
        }
      }
    },
    "drifts-together": {
      "asks": "A drift is a change that happens with nobody acting. Two or more drifts can run in the same step. How do they combine?",
      "when": {
        "row-count": {
          "drifts": "at-least-two"
        }
      },
      "otherwise": "Fewer than two drifts can run together, so no combination choice applies.",
      "options": {
        "add-them-up": {
          "meaning": "Add the drifts' signed amounts and move once. Oxygen drain of 3 and recovery of 1 produce one loss of 2.",
          "semantics": "Clip each eligible contribution to its own stop, sum the clipped amounts, resolve small-change once, and test marks once."
        },
        "only-the-largest": {
          "meaning": "Run only the largest drift. With health regeneration of 4 and poison damage of 2, only the regeneration runs in that step.",
          "semantics": "Compare clipped absolute magnitudes; row order decides a tie. A skipped drift whose amount grows while it runs does not advance its growth for that step."
        },
        "in-row-order": {
          "meaning": "Run each drift in written order. Poison can damage health before regeneration heals it in the same step.",
          "semantics": "Settle every eligible row separately in row order. Each row follows the numbered steps of the mechanism text from step 1 to step 8."
        }
      }
    },
    "lines-read": {
      "asks": "A change would pass a limit. Are marks tested against the number before the limit is applied, or against the number that the value keeps?",
      "when": {
        "any": [
          {
            "row-count": {
              "lines": "non-empty"
            }
          },
          {
            "row-count": {
              "bands": "non-empty"
            }
          }
        ]
      },
      "otherwise": "No line or band exists, so nothing reads a result beyond a limit.",
      "options": {
        "the-final-value": {
          "meaning": "Marks are tested against the number that the value keeps. A hit aimed at −20 leaves health at 0, so warnings are tested against 0.",
          "semantics": "The outward part of the move tests its stored endpoint, or the consuming limit on a lap. It never tests beyond a limit."
        },
        "the-raw-result": {
          "meaning": "Marks are tested against the number before the limit is applied. A hit aimed at −20 lets health warnings be tested against −20 before health returns to 0.",
          "semantics": "The outward part of the move tests the raw candidate. It may observe a number the value never stores. A refused change has no observable raw result."
        }
      }
    }
  },
  "declares": {
    "values": {
      "min": {
        "description": "This value's marked bottom. Keep it even when the value may fall lower.",
        "forms": [
          "number",
          "citation"
        ]
      },
      "max": {
        "description": "This value's marked top. Keep it even when the value may rise higher.",
        "forms": [
          "number",
          "citation"
        ]
      },
      "initial": {
        "description": "The number that this value holds when it is created. When you point to a number in your game's rules instead, that number is read at creation. Later resets are listed separately.",
        "forms": [
          "number",
          "citation"
        ]
      },
      "fraction-step": {
        "description": "How much saved fraction releases one whole point.",
        "when": {
          "flag": {
            "small-change": [
              "save-the-fraction"
            ]
          }
        }
      },
      "anchor": {
        "description": "The resting point a drift approaches from either side.",
        "forms": [
          "number",
          "citation"
        ],
        "when": {
          "row-has": {
            "drifts": {
              "direction": [
                "toward-the-anchor"
              ]
            }
          }
        }
      },
      "lap-lands-on": {
        "description": "Where the value lands during a lap.",
        "forms": [
          "number",
          "citation"
        ],
        "when": {
          "any": [
            {
              "flag": {
                "at-the-ceiling": [
                  "start-a-lap"
                ]
              }
            },
            {
              "flag": {
                "at-the-floor": [
                  "start-a-lap"
                ]
              }
            }
          ]
        }
      },
      "out-of-range-lands-on": {
        "description": "Where the value lands when a moved limit leaves it outside the new range.",
        "forms": [
          "number",
          "citation"
        ],
        "when": {
          "flag": {
            "when-a-limit-moves": [
              "pull-it-to-a-declared-number"
            ]
          }
        }
      }
    },
    "rows": {
      "reading-notes": {
        "description": "List the part of your game's rules that a reader needs before relying on this adoption. Use at most one row.",
        "when-empty": "No advance reading is required.",
        "record": {
          "read-first": {
            "type": "citation",
            "required": true,
            "description": "Where your game's rules say what a reader needs first, such as which of a soft top and a hard top the game calls full."
          }
        }
      },
      "quantity-unit": {
        "description": "Name the unit that your game uses for absolute numbers. Use at most one row.",
        "when-empty": "This contract gives the quantity no named unit.",
        "record": {
          "unit": {
            "type": "string",
            "required": true,
            "description": "The unit of every absolute number, whether you give the number or point to it, such as kg or XP. This contract neither interprets nor converts the unit."
          }
        }
      },
      "lines": {
        "description": "List the named marks this value reports crossing.",
        "when-empty": "No crossing is reported.",
        "record": {
          "id": {
            "type": "string",
            "required": true,
            "pattern": "kebab-case",
            "unique": true,
            "description": "A short name for this mark in your game's words, such as low-health or full."
          },
          "value-kind": {
            "type": "string",
            "required": true,
            "options": [
              "number",
              "citation"
            ],
            "description": "Whether this mark uses a fixed number, or points to a number in your game's rules. When it points to a number, the mark moves when that number changes."
          },
          "value": {
            "type": "number",
            "when": {
              "row": {
                "value-kind": [
                  "number"
                ]
              }
            },
            "description": "The fixed number where this mark sits."
          },
          "value-key": {
            "type": "citation",
            "when": {
              "row": {
                "value-kind": [
                  "citation"
                ]
              }
            },
            "description": "Where your game's rules state the number of this mark. The number is read again at each comparison."
          },
          "line-unit": {
            "type": "string",
            "options": [
              "number",
              "share-of-the-ceiling"
            ],
            "description": "Use a fixed number or a share of the marked top. A share is a part of the marked top, such as 0.25 for a quarter. The top is read again at each comparison. The resulting position can be between whole points. It is only compared and never stored. When this field is absent, number applies."
          },
          "direction": {
            "type": "string",
            "required": true,
            "options": [
              "rising",
              "falling",
              "both"
            ],
            "description": "The direction of movement in which this mark reports a crossing."
          },
          "refire": {
            "type": "string",
            "required": true,
            "options": [
              "every-crossing",
              "once",
              "a-set-number-of-times"
            ],
            "description": "How often this line reports: at every crossing, once per arm, or a fixed number of times per arm. To arm a line is to make it ready to report."
          },
          "times": {
            "type": "integer",
            "within": [
              1,
              2147483647
            ],
            "when": {
              "row": {
                "refire": [
                  "a-set-number-of-times"
                ]
              }
            },
            "description": "The positive whole number of reports allowed per arm."
          },
          "rearm": {
            "type": "string",
            "when": {
              "row": {
                "refire": [
                  "once",
                  "a-set-number-of-times"
                ]
              }
            },
            "options": [
              "on-reset",
              "on-event",
              "after-a-wait",
              "never"
            ],
            "description": "What re-arms this line. To re-arm a line is to make it ready to report again, with its full number of reports."
          },
          "rearm-declared-in": {
            "type": "citation",
            "when": {
              "row": {
                "rearm": [
                  "on-reset",
                  "on-event",
                  "after-a-wait"
                ]
              }
            },
            "description": "Where your game's rules name the reset, event, or wait that re-arms this line."
          },
          "repeat": {
            "type": "string",
            "when": {
              "row": {
                "rearm": [
                  "after-a-wait"
                ]
              }
            },
            "options": [
              "restart-the-wait",
              "keep-the-first-wait",
              "add-to-the-wait"
            ],
            "description": "Choose what another crossing does while the wait is running."
          },
          "counts-at-line": {
            "type": "string",
            "options": [
              "at-the-line-counts",
              "must-pass-the-line"
            ],
            "description": "The default answer is the answer to exact-landing. Leave this field out to use the default answer, or choose another answer for this line."
          },
          "starts-armed": {
            "type": "string",
            "options": [
              "armed",
              "disarmed"
            ],
            "description": "Whether this line starts able to report. When this field is absent, the line starts armed."
          }
        }
      },
      "sets": {
        "description": "List each refill, direct assignment, or recomputed total. Use a set when lines and bands must be able to react to the movement.",
        "when-empty": "Nothing sets the value directly; laps, resets and limit moves remain their own movements.",
        "record": {
          "id": {
            "type": "string",
            "required": true,
            "pattern": "kebab-case",
            "unique": true,
            "description": "A short name for this set in your game's words, such as surface-refill or inventory-total."
          },
          "mode": {
            "type": "string",
            "required": true,
            "options": [
              "set-to-target",
              "recompute"
            ],
            "description": "Use a named target or a rule's computed result."
          },
          "target-key": {
            "type": "citation",
            "required": true,
            "description": "Where your game's rules state the target number or the rule that computes it."
          },
          "trigger-declared-in": {
            "type": "citation",
            "required": true,
            "description": "Where your game's rules name the event or rule that offers this set."
          },
          "enabled-while": {
            "type": "citation",
            "description": "Where your game's rules state a condition that allows this set. This field is optional."
          }
        }
      },
      "bands": {
        "description": "List named on-or-off states between marks, such as overheated or low-warning.",
        "when-empty": "This value keeps no named state.",
        "record": {
          "id": {
            "type": "string",
            "required": true,
            "pattern": "kebab-case",
            "unique": true,
            "description": "A short name for this state in your game's words, such as overheated or low-warning."
          },
          "enter-on": {
            "type": "string",
            "required": true,
            "options": [
              "line",
              "event"
            ],
            "description": "Whether a line or named event turns the state on."
          },
          "enter-value-kind": {
            "type": "string",
            "when": {
              "row": {
                "enter-on": [
                  "line"
                ]
              }
            },
            "options": [
              "number",
              "citation"
            ],
            "description": "Whether the entering line uses a fixed number, or points to a number in your game's rules. When it points to a number, the line moves when that number changes."
          },
          "enter-value": {
            "type": "number",
            "when": {
              "row": {
                "enter-value-kind": [
                  "number"
                ]
              }
            },
            "description": "The fixed number of the entering line."
          },
          "enter-value-key": {
            "type": "citation",
            "when": {
              "row": {
                "enter-value-kind": [
                  "citation"
                ]
              }
            },
            "description": "Where your game's rules state the number of the entering line. The number is read again at each comparison."
          },
          "enter-unit": {
            "type": "string",
            "when": {
              "row": {
                "enter-on": [
                  "line"
                ]
              }
            },
            "options": [
              "number",
              "share-of-the-ceiling"
            ],
            "description": "Use a fixed number or a share of the marked top. A share is a part of the marked top, such as 0.25 for a quarter. The top is read again at each comparison. The resulting position can be between whole points. It is only compared and never stored."
          },
          "enter-direction": {
            "type": "string",
            "when": {
              "row": {
                "enter-on": [
                  "line"
                ]
              }
            },
            "options": [
              "rising",
              "falling"
            ],
            "description": "The direction that turns the state on."
          },
          "enter-declared-in": {
            "type": "citation",
            "when": {
              "row": {
                "enter-on": [
                  "event"
                ]
              }
            },
            "description": "Where your game's rules name the event that turns the state on."
          },
          "leave-on": {
            "type": "string",
            "required": true,
            "options": [
              "line",
              "event",
              "after-a-wait"
            ],
            "description": "Whether a line, event, or wait turns the state off."
          },
          "leave-value-kind": {
            "type": "string",
            "when": {
              "row": {
                "leave-on": [
                  "line"
                ]
              }
            },
            "options": [
              "number",
              "citation"
            ],
            "description": "Whether the leaving line uses a fixed number, or points to a number in your game's rules. When it points to a number, the line moves when that number changes."
          },
          "leave-value": {
            "type": "number",
            "when": {
              "row": {
                "leave-value-kind": [
                  "number"
                ]
              }
            },
            "description": "The fixed number of the leaving line."
          },
          "leave-value-key": {
            "type": "citation",
            "when": {
              "row": {
                "leave-value-kind": [
                  "citation"
                ]
              }
            },
            "description": "Where your game's rules state the number of the leaving line. The number is read again at each comparison."
          },
          "leave-unit": {
            "type": "string",
            "when": {
              "row": {
                "leave-on": [
                  "line"
                ]
              }
            },
            "options": [
              "number",
              "share-of-the-ceiling"
            ],
            "description": "Use a fixed number or a share of the marked top. A share is a part of the marked top, such as 0.25 for a quarter. The top is read again at each comparison. The resulting position can be between whole points. It is only compared and never stored."
          },
          "leave-direction": {
            "type": "string",
            "when": {
              "row": {
                "leave-on": [
                  "line"
                ]
              }
            },
            "options": [
              "rising",
              "falling"
            ],
            "description": "The direction that turns the state off."
          },
          "leave-declared-in": {
            "type": "citation",
            "when": {
              "row": {
                "leave-on": [
                  "event",
                  "after-a-wait"
                ]
              }
            },
            "description": "Where your game's rules name the event that turns the state off, or state the length of the wait."
          },
          "repeat": {
            "type": "string",
            "when": {
              "row": {
                "leave-on": [
                  "after-a-wait"
                ]
              }
            },
            "options": [
              "restart-the-wait",
              "keep-the-first-wait",
              "add-to-the-wait"
            ],
            "description": "Choose what another entry does while the wait is running."
          },
          "starts": {
            "type": "string",
            "required": true,
            "options": [
              "in",
              "out",
              "read-from-initial"
            ],
            "description": "Whether this state starts on (in), starts off (out), or is read from the starting value (read-from-initial)."
          },
          "blocks-change": {
            "type": "string",
            "options": [
              "none",
              "blocks-rises",
              "blocks-falls",
              "blocks-all"
            ],
            "description": "Which incoming plain changes, costs, or drifts are blocked while this state is on. When this field is absent, none applies."
          },
          "counts-at-line": {
            "type": "string",
            "options": [
              "at-the-line-counts",
              "must-pass-the-line"
            ],
            "description": "The default answer is the answer to exact-landing. Leave this field out to use the default answer, or choose another answer for the line edges of this state."
          }
        }
      },
      "costs": {
        "description": "List each action that checks whether this value can pay before it changes.",
        "when-empty": "No action checks this value before it changes; every change simply happens, and bands and limits still apply.",
        "record": {
          "id": {
            "type": "string",
            "required": true,
            "pattern": "kebab-case",
            "unique": true,
            "description": "A short name for this cost in your game's words, such as dodge or cast-spell."
          },
          "direction": {
            "type": "string",
            "required": true,
            "options": [
              "rising",
              "falling"
            ],
            "description": "Whether paying moves the value toward the top or toward the bottom."
          },
          "amount-key": {
            "type": "citation",
            "required": true,
            "description": "Where your game's rules state the cost. The cost is not negative."
          },
          "action-declared-in": {
            "type": "citation",
            "required": true,
            "description": "Where your game's rules name the action that receives the reports of refusal, shortage, or amount paid."
          },
          "answer": {
            "type": "string",
            "options": [
              "refuse-the-action",
              "take-what-fits"
            ],
            "description": "The default answer is the answer to not-enough-room. Leave this field out to use the default answer, or choose another answer for this action."
          }
        }
      },
      "drifts": {
        "description": "List each drift, such as draining, recovery, or cooling.",
        "when-empty": "This value never changes on its own.",
        "record": {
          "id": {
            "type": "string",
            "required": true,
            "pattern": "kebab-case",
            "unique": true,
            "description": "A short name for this drift in your game's words, such as recovery or cooling."
          },
          "direction": {
            "type": "string",
            "required": true,
            "options": [
              "rises",
              "falls",
              "toward-the-anchor"
            ],
            "description": "Whether the value moves upward, downward, or toward the shared resting point."
          },
          "cadence": {
            "type": "string",
            "required": true,
            "options": [
              "per-game-step",
              "per-real-second",
              "per-turn",
              "at-a-named-moment"
            ],
            "description": "Choose when one contribution is made."
          },
          "moment-declared-in": {
            "type": "citation",
            "when": {
              "row": {
                "cadence": [
                  "at-a-named-moment"
                ]
              }
            },
            "description": "Where your game's rules name the moment."
          },
          "step-amount-key": {
            "type": "citation",
            "required": true,
            "description": "Where your game's rules state the amount for each occurrence. The amount is positive."
          },
          "amount-kind": {
            "type": "string",
            "required": true,
            "options": [
              "fixed",
              "depends-on-state",
              "grows-while-it-runs"
            ],
            "description": "Whether the amount stays fixed, depends on state, or grows while the drift runs."
          },
          "amount-declared-in": {
            "type": "citation",
            "when": {
              "row": {
                "amount-kind": [
                  "depends-on-state",
                  "grows-while-it-runs"
                ]
              }
            },
            "description": "Where your game's rules state how the amount depends on state, or how it grows."
          },
          "stops-at-key": {
            "type": "citation",
            "required": true,
            "description": "Where your game's rules state the number at which this drift stops."
          },
          "at-stop": {
            "type": "string",
            "required": true,
            "options": [
              "lands-exactly",
              "overshoots-then-returns"
            ],
            "description": "Whether the value lands exactly on the stop, or passes the stop for a short time and then returns. The value passes the stop only when this drift runs alone."
          }
        }
      },
      "interrupts": {
        "description": "List the conditions and pauses that control a drift. Every row for one drift must allow it before it runs.",
        "when-empty": "No listed condition or pause controls a drift.",
        "record": {
          "id": {
            "type": "string",
            "required": true,
            "pattern": "kebab-case",
            "unique": true,
            "description": "A short name for this interruption in your game's words, such as after-a-hit or only-underwater."
          },
          "drift": {
            "type": "string",
            "required": true,
            "description": "Name the drift this row controls."
          },
          "kind": {
            "type": "string",
            "required": true,
            "options": [
              "off-while-condition",
              "runs-only-while-condition",
              "paused-after-event"
            ],
            "description": "Whether the drift stays off during a condition, runs only during a condition, or pauses after an event."
          },
          "condition-declared-in": {
            "type": "citation",
            "when": {
              "row": {
                "kind": [
                  "off-while-condition",
                  "runs-only-while-condition"
                ]
              }
            },
            "description": "Where your game's rules state the condition. For a condition that is a named state of this value, such as a band, cite the chapter section that states it."
          },
          "event-declared-in": {
            "type": "citation",
            "when": {
              "row": {
                "kind": [
                  "paused-after-event"
                ]
              }
            },
            "description": "Where your game's rules name the event that starts the pause."
          },
          "pause-length-key": {
            "type": "citation",
            "when": {
              "row": {
                "kind": [
                  "paused-after-event"
                ]
              }
            },
            "description": "Where your game's rules state the length of the pause."
          },
          "pause-length-kind": {
            "type": "string",
            "when": {
              "row": {
                "kind": [
                  "paused-after-event"
                ]
              }
            },
            "options": [
              "fixed",
              "depends-on-state"
            ],
            "description": "Whether the length of the pause stays fixed or depends on state."
          },
          "pause-length-declared-in": {
            "type": "citation",
            "when": {
              "row": {
                "pause-length-kind": [
                  "depends-on-state"
                ]
              }
            },
            "description": "Where your game's rules state how the length of the pause depends on state."
          },
          "repeat-event": {
            "type": "string",
            "when": {
              "row": {
                "kind": [
                  "paused-after-event"
                ]
              }
            },
            "options": [
              "restart-the-wait",
              "keep-the-first-wait",
              "add-to-the-wait"
            ],
            "description": "Choose what another event does while the pause is running."
          }
        }
      },
      "resets": {
        "description": "List named moments that put this value back, such as respawn, a new day, or turn start. Use a reset when this return must never make a report.",
        "when-empty": "Only creation applies the starting value.",
        "record": {
          "id": {
            "type": "string",
            "required": true,
            "pattern": "kebab-case",
            "unique": true,
            "description": "A short name for this reset moment in your game's words, such as respawn or new-day."
          },
          "happens-at": {
            "type": "citation",
            "required": true,
            "description": "Where your game's rules name the moment."
          },
          "sets-value-to-key": {
            "type": "citation",
            "description": "Where your game's rules state the number that this reset stores. When this field is absent, the reset stores the starting value. When the starting value points to a number in your game's rules, the reset stores the number that was read at creation."
          },
          "clears-saved-fraction": {
            "type": "string",
            "when": {
              "flag": {
                "small-change": [
                  "save-the-fraction"
                ]
              }
            },
            "options": [
              "yes",
              "no"
            ],
            "description": "Whether this reset clears the saved fraction."
          },
          "re-arms-lines": {
            "type": "string",
            "required": true,
            "options": [
              "yes",
              "no"
            ],
            "description": "Whether this reset re-arms every line whose rearm-declared-in points to this reset's moment."
          },
          "resets-bands": {
            "type": "string",
            "options": [
              "return-to-starts",
              "recompute-from-target",
              "keep"
            ],
            "description": "Choose what the named states do at this reset. With return-to-starts, every named state goes back to its declared start. With recompute-from-target, every named state that has a line at both ends is worked out again from the reset target, and every other named state keeps its state. With keep, every named state keeps its state. None of the three answers makes a report. When this field is absent, keep applies."
          }
        }
      }
    }
  },
  "rules": {
    "range-nonempty": "max > min",
    "initial-not-below-min": "initial >= min",
    "initial-not-above-max": "initial <= max",
    "fraction-step-positive": "fraction-step > 0",
    "anchor-not-below-min": "anchor >= min",
    "anchor-not-above-max": "anchor <= max",
    "lap-lands-not-below-min": "lap-lands-on >= min",
    "lap-lands-not-above-max": "lap-lands-on <= max",
    "out-of-range-lands-not-below-min": "out-of-range-lands-on >= min",
    "out-of-range-lands-not-above-max": "out-of-range-lands-on <= max",
    "share-needs-two-walls": {
      "forbid": {
        "any": [
          {
            "flag": {
              "when-a-limit-moves": [
                "keep-the-share"
              ],
              "at-the-ceiling": [
                "no-ceiling"
              ]
            }
          },
          {
            "flag": {
              "when-a-limit-moves": [
                "keep-the-share"
              ],
              "at-the-floor": [
                "no-floor"
              ]
            }
          }
        ]
      },
      "message": "The answer keep-the-share needs a bottom and a top that the value cannot pass. Change the answer no-ceiling or no-floor, or choose another answer to when-a-limit-moves."
    },
    "moving-refill-cannot-lap": {
      "forbid": {
        "any": [
          {
            "flag": {
              "when-a-limit-moves": [
                "refill-to-the-new-ceiling"
              ],
              "at-the-ceiling": [
                "start-a-lap"
              ]
            }
          },
          {
            "flag": {
              "when-a-limit-moves": [
                "refill-to-the-new-ceiling"
              ],
              "at-the-floor": [
                "start-a-lap"
              ]
            }
          }
        ]
      },
      "message": "The answer refill-to-the-new-ceiling can put the value exactly on a limit whose answer is start-a-lap. A moving limit never starts a lap. Change one of these two answers."
    },
    "another-lap-needs-extra": {
      "forbid": {
        "flag": {
          "more-than-one-lap": [
            "lap-again-while-it-fits"
          ],
          "the-extra-after-a-lap": [
            "drop-it"
          ]
        }
      },
      "message": "The answer lap-again-while-it-fits needs the extra that remains after the first lap. Choose carry-it-onto-the-new-value, or choose one-lap-per-change."
    }
  },
  "pack": "sha256:7a5cc40f34b4ff0c89b9c577964f8b396f1b04ec8003ca286c73a5f3ffdf5fc1",
  "origin": "https://opengdd.org/contracts/ranged-value-2",
  "mechanism": [
    "This text decides the order of the steps for a plain change, a cost, a set, a drift, a lap movement, a limit move, and a reset. The questions and rows supply choices and cited game rules. They do not change the order.",
    "An **operation** is one change that this contract settles from start to finish: a plain change, cost, set, drift step or composed drift batch, lap movement, limit move, or reset. An **imposed change** is a plain change that happens without checking whether the value can pay it. **Residue** is the signed fraction saved for later. A **mark** is a line or one edge of a band. A **bare line** is a line that is not an edge of a band. A **leg** is one visible stretch of movement with its own start and end. A **consuming limit** is a limit whose answer is `start-a-lap`. An operation **consumes** that limit when it starts a lap there. The **leftover** is the amount of the change that is past the consuming limit. Each operation has an id supplied by its row or cited game rule and settles fully before the next begins. A lap sequence's outward, lap, and leftover legs retain their originating operation id; leg position and lap count distinguish their reports.",
    "A band is **line-geometry** only when both its edges are lines. A band with any event or wait edge is **event-edged**. An event-edged band keeps its state under every silent recompute (a set, a reset, or a limit move). A reset's explicit `return-to-starts` is a restore, not a recompute, and applies to every band.",
    "1. **Block.** If an active band blocks the operation's direction and the operation is an imposed change, cost, or drift, discard it. Report `blocked` to its source; a drift records the block but has no receiver. Stop this operation. Remove a blocked drift before several drifts combine. Blocks never stop sets, resets, limit moves, or lap movements.",
    "2. **Resolve provisionally.** Fractional handling computes a provisional movement and new residue without committing either. A whole change leaves residue untouched. An absolute repositioning computes its destination and applies its own clearing rule; it does not pass through `small-change`.",
    "3. **Check capacity for a cost.** Compare provisional movement with the room toward the named limit. Refusal discards movement and residue together. `take-what-fits` recomputes both from the approved amount and never keeps a fraction from the refused part.",
    "4. **Find the raw candidate.** A change adds provisional movement to the held value. A set uses its accepted or clamped stored target. A lap movement, limit move, or reset uses its own absolute destination.",
    "5. **Apply the limit.** A raw candidate strictly past a limit uses that limit's answer. Exact landing is ordinary unless `start-a-lap` applies, in which case `exact-landing` decides whether it consumes the limit. Refusing the whole change discards provisional movement and residue, leaves the store untouched, reports refusal, and stops. Sets, resets, and limit moves use their own target rules. Drifts use the clipping exception below.",
    "6. **Commit.** Store the final value and new residue together. No partly settled state is visible.",
    "7. **Test marks for each leg.** The outward leg ends at the final value or consuming limit. A lap then has a lap leg, followed by each carried leftover as a new lap operation returning to step 1. Test lines and band edges on every leg. Recompute reached state from each stored endpoint in both directions; only a false-to-true transition in the row's direction reports. A `both` line keeps one reached state per direction. With raw reading, only the outward leg tests the raw candidate; lap and leftover legs test stored values. At a consuming limit, report outward marks, including a line on the limit, then the lap, then lap-leg marks. Apply `jumped-lines` separately to every leg.",
    "8. **Report.** Every report carries before, after, direction, leg position, and operation id. A crossing or band transition also carries its row id. A lap carries its count. `lap-cap-reached` carries committed state. Returned extra carries its amount. A cost or block carries refusal, shortage, approved amount, or `blocked` as applicable. Raw-reading reports use the tested raw candidate as `after`; other reports use the stored endpoint. Direction is `rising`, `falling`, or `unchanged`.",
    "Every comparison reads the stored value at full precision, never a rounded display number. Every cited limit, mark and stop is read again at each comparison. Creation is not a movement or report. At creation, line and band-edge reached state is stored without reporting; lines use their declared arm state, and bands use their declared starting state. A share mark sits at the declared share multiplied by the value at `max`, read again at that comparison; that position may be fractional and is compared, never stored. A line-geometry band that answers `read-from-initial` starts in exactly when the initial value sits on the reached side of its entering edge and not on the reached side of its leaving edge, each side read per that edge's declared direction, with an exact landing counted as this value's `exact-landing` answer or the row's `counts-at-line` override directs. An event-edged band that answers `read-from-initial` starts out. A silent recompute of a line-geometry band's state (a set, a limit move, or `recompute-from-target`) applies the same two-sided test against the value it recomputes from.",
    "**Lap loop.** A single originating operation may settle at most 32 laps. Before lap 33, refuse the continuation, report `lap-cap-reached`, drop the remaining leftover, and keep the state committed by lap 32. A statically provable repeated lap state is a package error, but the runtime cap remains the termination guard. After each lap report, re-read either cited limit and check the lap target against the re-read range. If a limit moved, apply the limit-move ruling below; any `limit-moved` band report occurs before the lap leg. A limit move never repositions the value during this loop. Under `clear-it`, a lap clears the saved fraction after the consuming movement and before carried leftover is applied. Under `one-lap-per-change`, a second reach of the same consuming limit is stored without consuming and the amount past that limit is dropped.",
    "**Drift clipping.** A rising drift is eligible only below its stop; a falling drift only above it; a drift toward the anchor uses the matching test for its current direction. Clip each contribution to its stop before combining drifts. `overshoots-then-returns` is observable only when that row settles alone: its raw path may expose the overshoot, then a silent correction stores the stop, reports no mark, and recomputes reached state from the stored stop. A drift reaching a consuming limit clips there without consuming it.",
    "**Sets.** A set has no raw result. Marks see only a successful stored outcome. Clamping a target stores the nearer limit; raw reading never sees the target outside the range. A successful set clears residue. A refused set does not.",
    "**Resets.** A reset stores its target, recomputes and stores every line's reached state from that target, and applies its arm, band, and fraction choices silently. Band state follows the row's `resets-bands` answer, not an automatic recompute. For a band that answers `read-from-initial`, `return-to-starts` restores the state stored at creation. A reset reports no crossing.",
    "**Limit moves.** A moving limit never fires a bare line. After the limit and any resulting current-value change settle, recompute and store every line's reached state and every line-geometry band's state against the new positions. Event-edged bands keep their state. A line-geometry band that enters or leaves this way reports the state change with cause `limit-moved`; a bare line reports nothing. The saved fraction is not cleared. A limit move that lands the value exactly on a consuming limit does not consume it.",
    "**Continuous drift.** A `per-real-second` drift accrues continuously. Each settlement contributes its rate multiplied by elapsed cited-clock time, and fractional accrual passes through `small-change`.",
    "Sets, resets, and limit moves at one moment settle in the order in which the citing game rules trigger them. A game rule that triggers several at once must state their order.",
    "**Share-preserving limit moves.** `keep-the-share` is one absolute repositioning. A whole-number target rounds to nearest, with ties away from zero. Residue is unchanged.",
    "A band has no address of its own. Where this adoption's own game asks for a condition that is a band, cite the Fixed chapter section that states the band. Another adoption cannot cite a band."
  ],
  "answers": {
    "number-kind": "whole-number",
    "small-change": "save-the-fraction",
    "exact-landing": "at-the-line-counts",
    "at-the-ceiling": "stop-at-the-ceiling",
    "at-the-floor": "stop-at-the-floor",
    "jumped-lines": "all-in-order",
    "when-a-limit-moves": "refill-to-the-new-ceiling",
    "not-enough-room": "refuse-the-action",
    "lines-read": "the-final-value"
  },
  "values": {
    "min": 0,
    "max": "tuning.stamina.max",
    "initial": "tuning.stamina.max",
    "fraction-step": 1
  },
  "rows": {
    "reading-notes": [
      {
        "read-first": "02-mechanics.md#stamina"
      }
    ],
    "quantity-unit": [
      {
        "unit": "stamina points"
      }
    ],
    "lines": [
      {
        "id": "winded",
        "value-kind": "number",
        "value": 0.2,
        "direction": "both",
        "refire": "every-crossing",
        "line-unit": "share-of-the-ceiling"
      },
      {
        "id": "second-wind",
        "value-kind": "number",
        "value": 0,
        "direction": "falling",
        "refire": "once",
        "rearm": "on-reset",
        "rearm-declared-in": "02-mechanics.md#respawn"
      }
    ],
    "sets": [],
    "bands": [
      {
        "id": "exhausted",
        "enter-on": "line",
        "enter-value-kind": "number",
        "enter-value": 0,
        "enter-unit": "number",
        "enter-direction": "falling",
        "leave-on": "line",
        "leave-value-kind": "number",
        "leave-value": 0.3,
        "leave-unit": "share-of-the-ceiling",
        "leave-direction": "rising",
        "starts": "out",
        "blocks-change": "blocks-falls"
      }
    ],
    "costs": [
      {
        "id": "attack",
        "direction": "falling",
        "amount-key": "tuning.stamina.attack-cost",
        "action-declared-in": "02-mechanics.md#attack-action"
      },
      {
        "id": "sprint",
        "direction": "falling",
        "amount-key": "tuning.stamina.sprint-cost",
        "action-declared-in": "02-mechanics.md#sprint-action",
        "answer": "take-what-fits"
      }
    ],
    "drifts": [
      {
        "id": "recovery",
        "direction": "rises",
        "cadence": "per-real-second",
        "step-amount-key": "tuning.stamina.regen-rate",
        "amount-kind": "fixed",
        "stops-at-key": "contracts.stamina.max",
        "at-stop": "lands-exactly"
      }
    ],
    "interrupts": [
      {
        "id": "after-exertion",
        "drift": "recovery",
        "kind": "paused-after-event",
        "event-declared-in": "02-mechanics.md#stamina-spent",
        "pause-length-key": "tuning.stamina.regen-delay",
        "pause-length-kind": "fixed",
        "repeat-event": "restart-the-wait"
      }
    ],
    "resets": [
      {
        "id": "respawn",
        "happens-at": "02-mechanics.md#respawn",
        "sets-value-to-key": "contracts.stamina.max",
        "clears-saved-fraction": "yes",
        "re-arms-lines": "yes",
        "resets-bands": "return-to-starts"
      }
    ]
  }
}