Playable resilience and short conflicts

These optional modules turn injury, fatigue and opposition into authored play. They use bounded adventure policies rather than a per-parser-turn medical or tactical simulation. PCs and NPCs use the same profile and body types.

#include <vhyl-rpg.t>
#include <vhyl-rpg-resilience.t>
#include <vhyl-rpg-conflict.t>

The reporting module remains independently optional. See the complete gatehouse scene and play instructions.

Body state and condition effects

Bind an authored RpgBody to exactly one RpgProfile in both directions:

traveller: RpgProfile actor = player resilience = travellerBody;
travellerBody: RpgBody profile = traveller injury = 3 aidRemaining = 3;

body.hp() is profile.maxHP - body.injury. Injury is a stored deficit, so a capacity increase preserves existing wounds. The stored fields also include shock, stun, unconsciousness, death, untreated injury and recovery clocks. All are journalled and included in logical saves.

Condition Mechanical consequence
Injury The next committed check using coordination or intelligence, including associated skills, takes accumulated shock, capped at −4
Major wound: one injury greater than half capacity One health check; failure stuns, and failure by five or critical failure causes unconsciousness
HP strictly below one third of capacity mobilityDivisor() returns two; the short conflict halves defense, rounding up
Energy strictly below one third of capacity Strength checks use half strength, rounding up; mobility/defense are also reduced
Zero energy Approaches requiring exertion are blocked; an authored non-exerting approach can still permit talking
Zero HP or lower Deterministic incapacitation; recovery or an authored rescue is required
Lethal body at −5 times capacity Death; ordinary rest and treatment cannot revive it

The one-third boundary is consistently strictly below, checked with integer arithmetic. Injury and exhaustion share one mobility divisor rather than stacking multiple halvings. Maximum HP and authored weapon damage are not recalculated from temporarily reduced strength. A zero-capacity energy pool opts out of effort exhaustion. The default lethal = nil makes defeat survivable; death requires an explicit author setting. There are no repeated consciousness/death rolls on ordinary parser commands, automatic bleeding timers or mortal-wound subsystem. Health below three cannot pass recovery checks; a major wound still uses the rolled failure margin to determine whether the character loses consciousness. The library also does not drop inventory or force posture changes on a failed major-wound check; add those consequences explicitly if the scene supports them.

condition() gives a summary: well, wounded, badly-hurt, exhausted, stunned, unconscious or dead. It prioritises more serious states; it does not replace the individual flags or numerical values. rpgPublishBody(profile) explicitly emits the player's condition and capacities. Publishing NPC health is a separate author decision.

The default RpgApproach.requiresExertion is true when this module is included. Set it to nil for an appropriate low-effort or social method. Condition checks and modifiers feed the same preparation/calculation used by rpgAssess and rpgResolve; previews never draw recovery dice. afterAttempt clears shock only after a fresh check has used it. A blocked or cached check does not clear it.

For non-RPG movement, inventory actions and bespoke adapters, enforce applicable body flags in their execution checks and call body.endAction() after committed activity. It expires shock and interrupts quiet rest. The core does not globally prevent all standard adventure verbs while unconscious. The gatehouse adapter demonstrates an explicit help route and conditions on its gameplay actions.

Applying injury

rpgInjuryFor(damage, armor, numerator, denominator) is pure arithmetic: subtract armor, multiply positive penetration by the explicit ratio, round down, and retain at least one HP when penetration is positive. Armor can stop an attack completely. Damage and armor are nonnegative integers up to 10000; ratio parts are 1–10. Weapon ranges, armor divisors and damage categories are authored outside this helper.

rpgWound(profile, damage, armor, numerator, denominator) commits that injury, updates shock, adds only fresh injury to the treatment budget, and makes a major wound check when needed. Injury saturates at six times maximum HP. Its temporary RpgCareResult reports actual injury and the private health check. It emits no player prose or automatic medical report.

Invoke mutation helpers only during committed actions or consequences. Exceptions roll back the enclosing gameplay transaction, including dice and condition state.

Treatment, rest and rescue

An authored RpgTreatment declares its skill, modifier, supply charges, duration, healing bonus and observational available(healer, patient) gate. The default duration is 660 seconds. rpgTreat(healer, patient, treatment) checks the healer, skill, patient and supplies before charging anything. Self-treatment is allowed when the healer can act. Healer condition modifiers apply to the skill check.

One fresh attempt consumes one supply charge and its declared time:

  • Success restores one die plus the declared bonus, with a minimum of one HP.
  • Critical success uses six instead of drawing the healing die.
  • Ordinary failure provides one HP of bandaging.
  • Critical failure instead inflicts two HP of injury.

The total healing already includes bandaging. It is capped by untreated injury and actual injury. An attempt consumes the entire current treatment budget even when it fails. A later wound adds only its own damage to that budget, so a minor scratch cannot reopen old failed treatments. Starting injuries need an explicit aidRemaining budget if they are eligible for first aid. Successfully restoring positive HP and usable energy can wake a patient; it does not automatically clear stun. This is an explicit adventure recovery policy.

rpgRest(profile, seconds) models uninterrupted, safe quiet rest supplied by the game, accepting 1–86400 seconds per call. It charges elapsed RPG time, restores one energy per full ten minutes, and makes one health check per full day for one HP. Fractions carry only within uninterrupted rest; exertion, injury and explicit endAction() interrupt the clocks. Rest at full energy cannot bank future energy. A quarter-hour of rest clears temporary stun/unconsciousness when HP and energy are positive at the end. Dead bodies do not recover.

The game must author safety, shelter and eligibility before calling rest. There is no automatic recovery attached to WAIT, movement or dialogue refresh, and the RPG ledger does not run the vhyl scheduler. rpgRecoverStun(profile) is a separate one-second committed recovery maneuver with one health roll.

Author a rescue or alternate route when the story should continue after defeat. The gatehouse's accept help is a specific clinic transition costing a day, returning an incapacitated protagonist to one HP. It is not a general free-healing API, does not revive death, and is unavailable during the confrontation.

Social opposition

RpgDisposition binds an NPC profile to an authored attitude from −3 to +3. Its observational permits(actor, approach) hook can refuse requests regardless of the numeric score. RpgSocialApproach uses a skill-versus-Will contest, adjusts resistance by disposition, and changes attitude after a fresh resolution. Defaults are +1 for achievement and −1 for any other resolved outcome, clamped to the range. Authors can set those shifts.

Social attempts retain the core's slots, prerequisite checks, costs and retry rules. A repeated request neither rolls again nor changes attitude again. New facts, concessions or an explicitly related development offer can advance the goal revision. Success changes only the authored consequences; it grants no general permission to control an NPC or override a forbidden request. Keep opposition scores and reactions undisclosed unless the story permits them.

Short physical conflicts

An RpgConflict has exactly two authored RpgCombatant objects, a stable ID, availability hook, time per exchange and maximum exchange count. It is a bounded scene controller. It does not implement a tactical map, initiative system, ammunition, equipment catalogue or one-second simulation for every world actor.

rpgBeginConflict(conflict) begins it once. rpgConflictStep(conflict, maneuver) commits one protagonist maneuver and one eligible opposition reply:

Maneuver Effect
attack Skill/attribute check, eligible defense, authored damage dice and adds, armor and injury
defend +2 defense against the reply
recover A health check to remove stun before the reply
escape A coordination contest; success ends the scene before a reply
surrender Ends immediately without retaliation or another roll

Attacks default to one energy; unaffordable or blocked attacks consume no dice, time or exchange. The default defense is (coordination + health) / 4 + 3, rounded down, then adjusted for condition. Stun adds −4. Critical attack success bypasses defense; ordinary attack failure misses. Damage dice, bonus, armor and wound ratio are explicit combatant settings. The default unarmed attack is one die minus two, with a minimum of zero damage. A stunned opponent recovers instead of attacking. Unconscious or dead participants do not make attacks.

Resolved exchanges cost the authored duration (two seconds by default). The default six-exchange limit ends in stalemate. Outcomes are won, defeated, escaped, surrendered, stalemate, or another explicit authored end such as truce. A finished conflict cannot restart; use a new authored scene for a later encounter. ended(result) runs after the persistent outcome is set, exactly once. It can award a milestone, open a route or arrange rescue. Call rpgFinishConflict from committed story code to end a scene when its fiction changes.

rpgPublishConflict(result) is opt-in. It emits the encounter outcome and count; individual strike details appear only when discloseRolls is authored true. Trusted strike results contain attack/defense checks, individual faces, damage and injury. The gatehouse's --details view shows permitted dice. Encounter state, randomness and consequences survive undo and cross-process restore; the library does not retain a complete combat log or retrospectively reroll a past strike.

Event envelopes

All envelopes below begin with version 1 and are emitted only by explicit publication functions:

rpg.body: actor, condition, currentHP, maxHP, energy, maxEnergy, mobilityDivisor
rpg.conflict: conflictID, status, reason, maneuver, outcome, exchanges, active
rpg.strike: conflictID, side, outcome, score, attackTotal, defenseTotal,
            damage, injury, attackFaceCount, defenseFaceCount, damageFaceCount,
            attackFaces..., defenseFaces..., damageFaces...

Strike side is acting or reply. No hidden NPC HP is included in the encounter envelope. Roll publication is an author ceiling; hosts should additionally honour the player's display preference. Core rpg.result, rpg.assessment, rpg.roll and rpg.calculation schemas are unchanged.