Combat System
Status: Designed — independent review applied
Independent /design-review: NEEDS REVISION 2026-08-26 — all findings applied same day (C1 F4 example corrected; C2 telegraph hard floor 350→400 = Rule 11 contract; C3 stale tide cross-ref reconciled; H1 pairwise swing-guard ranges; H2 dodge-economy worst case quantified; H3 F5 tiebreak + AC-17b; H4 party-size scaling contract; H5 gear-shareability Pillar 5 constraint; H6 jitter AC-24b; M1–M7; L-pass. Full report:design/reviews/combat-system-review-2026-08-26.md)
Author: Jeroen + agent panel
Last Updated: 2026-08-26
Creative Director Review (CD-GDD-ALIGN): APPROVED 2026-08-26 (3 advisory notes: Edge Case 16 = playtest watch item — fix via dash tuning, never lag compensation; helpless wait owned by Open Question 5; Enemy AI quick-spec inherits a telegraph-density constraint)
Implements Pillar: Pillar 4 — Scrappy Hands, Serious World (primary); serves Challenge aesthetic (#4) and Pillar 5's new-friend floor
Prototype Reference:prototypes/combat-netcode-feel-concept/REPORT.md(hypothesis CONFIRMED 2026-08-25)
Overview
Combat is Driftlands' moment-to-moment action layer: fast, top-down dash-and-strike fighting where you close on an enemy, read its telegraphed attack, dodge through or around it, and answer with a melee swing — positioning and timing beat gear-checks, and friction lives in the world, never in your hands (Pillar 4). Every fight exists in service of the loop: enemies stand between you and the resources on a docked land, and deeper inland means nastier inhabitants — difficulty is spatial, chosen by how far you push, never statistical scaling. Character power is flat by design; the only combat progression is the crafted gear you bring (Pillar 3/5 — a brand-new friend fights with the same verbs as a veteran). Beneath the action sits a prototype-confirmed network model that shapes the design itself: each player's movement and dash are instant because they own them; all damage is decided by the host; and strikes are built with deliberate windup-and-swing duration so network delay disappears into the fiction of a sword taking time to swing. MVP ships one weapon type plus dash against 2–3 telegraphed enemy types; the system must stay readable at a glance under the Tideline silhouette rule.
Player Fantasy
Combat's fantasy is the fight you win with your feet — earned reflexes, not earned power. You feel graceful before you feel strong: the strike is deliberate (windup, swing, recovery — a sword takes time), so every hit you land is one you placed, and every hit you take is one you misread. The anchor moment: deep in the drifting marsh, twenty minutes before it departs, three raiders close in. You don't out-damage them — you read the third one's windup, step through the gap between two swings, and put your strike exactly where the opening was. No health bar dipped below half. The arc of a good fight is focus → flow → clean, almost silent triumph — the reward is the movement working, not a fanfare. This is Competence made visible (concept: "combat is a visible skill you improve at") at its honest MDA rank: Challenge is aesthetic #4 — combat serves the loop, it doesn't headline it.
Its necessary shadow is the fight you didn't want: the bag three-quarters full of tin, violet climbing on the horizon, a boar-camp guarding the last vein. You take the hit rather than lose the run, because ten minutes costs more than ten HP. Combat's stakes are always opportunity cost under the tide's deadline — never loss spirals, never punitive framing. And at the session level, the individual fantasy compounds into a group one: a fight that took five deaths three weeks ago is clean and wordless tonight, and someone types "we're actually good at this now."
**Tone and design tests this fantasy imposes (binding on future combat features):**
- Telegraphs must be legible enough that failure reads as *"I misread that," never "the game cheated"* — no unavoidable damage, no hits from off-screen.
- No stat-check gear: a better sword makes placed hits count for more; it never wins the fight by number (flat stats, Pillar 5).
- No mechanics that remove the dodge decision: hit-stun chains on players, unbreakable crowd control, and channel-rooted attacks are rejected at the design level (Pillar 4 — friction never lives in the hands).
- Death copy follows the project tone rule: world facts, never player failure ("the tide carries you home" register — no "YOU DIED" theatrics).
- No one-shot randomness and no invisible stats — the skill curve must be readable over weeks, because shared improvement is the co-op expression of this fantasy. (Scope honesty: MVP's three verbs and three enemies validate the first sessions of that curve; the weeks-long arc is carried by Tier 1's enemy roster and gear tiers, with co-op choreography carrying it in between.)
Reference anchor: Hades — fast, readable, joyful top-down handling; enemies telegraph, you dodge, positioning beats gear-checks. Two deliberate divergences from the anchor, so Hades-calibrated players arrive braced: there is no attack-cancel out of Active/Recovery (commitment is the point, Rule 3), and hits are host-resolved with no rollback — a problem space Hades, being single-player, offers no precedent for (Edge Case 16).
Detailed Design
Core Rules
- Three verbs. Combat is built from move, dash, and strike — nothing else at MVP. No stamina, no resource pools, no combos: cooldowns are the only gate (Pillar 4).
- The swing is three phases: windup → active → recovery. Swing-masks-latency (binding principle): every attack, player or enemy, must have deliberate windup and active duration so that network delay (~35–70 ms one-way at target RTT) disappears into the fiction of a weapon taking time to swing. No instant-hit attacks exist on either side, ever.
- Commitment rules. Dash may cancel Strike:Windup (read your own mistake and bail). Nothing cancels Strike:Active — once the blade is moving you are committed. Recovery cannot be skipped.
- Dash is a fixed-length displacement (baseline 22 u/s × 0.14 s; cooldown 0.45 s measured from dash start) with **invulnerability for its full duration** — dodging through an attack is the core skill expression. The cooldown is a hard gate: no chain-dashing, no dash resource.
- Flat stats. All players share identical base HP, move speed, dash, and swing values — global constants, never per-character. Crafted gear is the only power source, through exactly two hooks:
DamageModifier(multiplies outgoing strike damage) andDamageReduction(reduces incoming damage). Gear never changes max HP, speed, or cooldowns. Hook values are owned by the Crafting GDD; this GDD owns the hook signatures. - Health and recovery. One flat HP pool. No passive regeneration anywhere. Field healing is consumables-only as a rule — and MVP ships zero consumables (cloth tier crafts none), so at MVP the only recovery is the home island, where HP restores to full while present. Pushing deeper inland is therefore a commitment of health you cannot buy back mid-run.
- **Hit resolution: swing-locked arc test, host-authoritative, no history. The client sends one unified strike intent** at windup start —
(swingId, aimDirection, targetKind ∈ {enemy, node}, nodeId?), the single message shape shared with Harvesting (its Rule 2 owns referent selection at windup start: a live hostile in the arc wins, else the nearest node, else a plain combat swing; ruled 2026-08-27, cross-GDD review). Combat resolves onlytargetKind = enemyintents; anodeintent is Harvesting's to resolve and never becomes a hit even if a hostile steps into the arc afterwards. The host resolves the hit at a fixed resolution instant — the end of the swing's active window — against current host-side positions, never historical ones. Forgiveness is a fixed widening of the test: arc +12° (100° → 124° effective), range +0.2 u (1.6 → 1.8 u effective), constants never scaled by measured RTT. Position history / lag compensation is rejected by design (it is rewind in disguise — Forbidden Patterns) and this rationale must be restated in the netcode ADR. - Feedback authority split (binding table in States and Transitions). Only outcome-independent effects are client-predicted (swing animation, whiff sound, blade trail, recovery lockout). Every effect that *implies damage happened* — impact VFX, damage numbers, stagger, hitstop, death — fires only on host confirmation. A rejected hit looks like an honest whiff: full swing animation, no impact, no error message.
- Death and downed. At 0 HP: if another living player is on the same land, you are Downed — a crawl-speed, no-combat state with a bleed-out timer; any ally can revive you with a short channel. If the timer expires, or no other living player is present (solo / last standing), you are Dead and respawn at the home island (Pillar 2 — there is no other respawn location). You keep everything: all gear and all carried materials — nothing drops, ever (concept: tension is opportunity cost, "not loss of what you had"). Death's cost is time and position — the walk back, the lost push, the tide still moving. (Tone-rule note: Downed-vs-Dead depending on who is present is a tactical-moment state, not a synchronous-presence reward — it pays out no resources or progression for playing together, so it sits inside the phase brief's async-tone rule.)
- Friendly fire is off, absolutely. Player swings ignore allies entirely: no damage, no knockback, no body-blocking — an ally is transparent to your arc test. Enemies cannot be shielded by a friend standing in the way.
- The enemy combat interface (behaviors live in the Enemy AI quick-spec; every enemy must implement this contract):
- Telegraph contract: no enemy attack may go active in under 400 ms of host-simulation time (derivation: at the prototype's confirmed 140 ms RTT ceiling a remote client sees the telegraph ~70 ms late, leaving ≥330 ms against a ~250 ms visual-motor reaction budget — dash i-frames start on input, owner-authoritative, so reaction time is the binding term). A telegraph must communicate both area and timing. Its primary channel is a silhouette/pose change readable in solid black (art bible §1, §5.3); a crimson rim-accent on the attacking limb is reinforcement only; hard-edged ground decals are reserved for AOE/lunge attacks. Combat sprites and telegraph VFX are never tide-tinted — combat/character sprites sit outside §8.A.4's
DriftLayertinting scope (art-bible fix-pass item: state that exemption explicitly). - Stagger/knockback response is mandatory: every strikeable enemy exposes hit-flash (Stagger White), knockback (distance/duration parameters per attack), and a stagger meter with diminishing returns (~1 s window) plus brief post-stagger immunity — enemies can be staggered, never stun-locked. When multiple players hit simultaneously, knockback takes the largest single contribution (no additive launching); stagger accumulates under the diminishing-returns rule.
- Interruptibility is a per-enemy flag: light enemies (Rusher) may have their windup cancelled by a hit; others (Spacer, Bulwark) always complete a telegraphed attack — Recovery is their punish window. The asymmetry with players is deliberate and stated: the player-side CC ban (Pillar 4) does not extend to enemies.
- No terrain kills at MVP: knockback never pushes enemies into lethal terrain — it is a spacing tool, not an environmental kill.
- **MVP archetypes (interface exemplars — identity, art, and behavior → Enemy AI quick-spec): Rusher** (fast melee closer; short weapon-raise telegraph; teaches punish timing; interruptible), Spacer (ranged/thrown; long visible wind-up arc; teaches repositioning; uninterruptible), Bulwark (slow, tanky, hard-hitting; long windup, large decal; high stagger threshold; teaches commit-vs-bail; uninterruptible).
- No unavoidable damage (content rule). Every source of damage to a player must have a telegraph meeting the contract above — no off-screen hits, no untelegraphed contact damage. This binds all future enemy and hazard design, not just MVP.
- Tide soft-close rulings (at
φ+Won a departing land; invariant: departure never converts to a death): - Soft-close is checked first on the host tick: once fired, no combat resolution executes for entities on that land — pending damage (either direction) whose resolution instant is
≥ φ+Wis discarded. - A player swing whose resolution instant lands before
φ+Wresolves normally (identical in kind to the last-tick harvest); intent arriving at or afterφ+Wis rejected as a silent whiff. **Dockedis re-sampled at resolution — initiation legality is never cached** (mirrors tide Edge Case 2 and the tide review's H1 rule). - Enemy attacks mid-windup or mid-active are cancelled outright — an enemy's swing completing a tick late must never kill a player mid-deposit.
- A Downed player is deposited home exactly like a living one (tide Rule 9 fires unconditionally) and **home stabilizes them on arrival**: Downed → Idle/Move at
reviveHPFraction(40%), bleed-out timer cancelled, then Rule 6's home heal takes over. Edge Case 8 ("Downed cannot exist at home") applies to arrival by deposit exactly as to reaching 0 HP at home (ruled 2026-08-27, cross-GDD review — the prior "arrives still Downed, timer running" let a departure end in a Dead transition). "Departure never converts to a death" is thereby literal. - Aggro and all enemy state are per-docking session state: discarded at teardown, never persisted, never carried to the land's next visit.
States and Transitions
Player combat actor (Hurt is a non-blocking overlay — knockback/flash atop the current state, never an input lockout; Pillar 4):
| From | Event | To | Notes |
|---|---|---|---|
| Idle/Move | dash input (off cooldown) | Dashing | i-frames on; cooldown starts now |
| Idle/Move | strike input | Strike:Windup | — |
| Dashing | 0.14 s elapsed | Idle/Move | — |
| Strike:Windup | windup elapsed | Strike:Active | — |
| Strike:Windup | dash input | Dashing | the bail-out; swing intent revoked |
| Strike:Active | active elapsed | Strike:Recovery | committed — nothing cancels this |
| Strike:Recovery | recovery elapsed | Idle/Move | — |
| any live state | damage taken | same state + Hurt overlay | non-blocking flash + nudge |
| any live state | HP ≤ 0, ally alive on land | Downed | crawl, no combat, bleed-out timer |
| any live state | HP ≤ 0, otherwise | Dead | respawn home, full HP, all items kept |
| Downed | ally revive channel done | Idle/Move | partial HP (knob) |
| Downed | arrives home (tide deposit, Rule 13; rejoin after departure, EC2) | Idle/Move | stabilizes at reviveHPFraction, timer cancelled (Edge Case 8; ruled 2026-08-27) |
| Downed | bleed-out expires | Dead | — |
Enemy combat actor (interface): `Idle/Patrol (AI-owned) → Telegraph → AttackActive → Recovery → (Stagger from any strikeable state, per meter) → Death`. Telegraph is interruptible only where the enemy's flag allows; AttackActive is never interruptible; Recovery is the universal punish window.
Feedback timing (authority table):
| Effect | Timing |
|---|---|
| Windup/swing animation, whiff sound, blade trail, recovery lockout | Instant — client-predicted (outcome-independent) |
| Impact VFX, hitstop, damage number, victim stagger/knockback, death | Host-confirmed broadcast only |
Prototype evidence: hit→stagger ~150 ms at 141 ms RTT read as "the swing taking time," not lag.
Same-tick simultaneity: two players striking one enemy → both hits resolve independently, both apply; **kill credit is deliberately not tracked** (no drops, no kill-based progression — stated to close off future scope creep). Enemy dies before a swing's resolution instant → honest whiff. Player and enemy killing blows on the same tick → both apply (enemy damage resolves first, then the player's; you can die and still fell the enemy — neither event cancels the other; locked 2026-08-26).
Interactions with Other Systems
| System | Combat consumes | Combat provides | Interface owner |
|---|---|---|---|
| Player Movement (quick-spec ✅ 2026-08-27) | Owner-authoritative transform; move/dash executed on the movement rail | Dash parameters, state-machine gates (e.g., Downed forces crawl speed) | Movement owns locomotion; Combat owns when verbs are legal |
| Netcode Session (quick-spec ✅ 2026-08-27; ADR pending playtest) | Host tick, RPC delivery, NetworkTransform interpolation | Swing-intent message shape (→ ADR), feedback-authority table | Combat owns rules; ADR owns mechanisms |
| Crafting & Gear (GDD Reviewed 2026-08-26) | DamageModifier, DamageReduction per equipped item | The two hook signatures; flat-stat guarantee (gear never touches HP/speed/cooldowns) | Combat owns hooks; Crafting owns values |
| Enemy AI (quick-spec ✅ 2026-08-27) | Attack initiation calls (EnterTelegraph(attackId)) | The full enemy combat interface (Rule 11): telegraph contract, stagger/knockback, interrupt flag, archetype exemplars | Combat owns everything downstream of the attack decision |
| Tide Scheduler / Land Dock/Undock | Docked boolean (re-sampled at resolution); soft-close event | The soft-close combat rulings (Rule 13) | Tide owns the clock; Combat owns combat-state consequences |
| UI/HUD (quick-spec ✅ 2026-08-27) | — | HP display data, Downed ally indicator (edge-of-screen, reuses art bible §7.2 hat-silhouette system) | UI owns presentation |
Not consumed: Harvesting (parallel peer — shares only the re-sample-at-resolution principle), Save & Persistence (combat state is never saved: HP/downed/aggro are session state; players load at full HP).
Formulas
All combat math is deterministic — no RNG anywhere in the damage path. Rationale: multiplayer consistency (host and clients must agree on displayed outcomes) and the Player Fantasy's ban on invisible stats and one-shot randomness. Every combat outcome is a pure function of positioning, timing, and gear tier. Zero damage variance is a rule, not a tuning default: a damage roll — even a small, UI-visible one — decouples "I placed that hit well" from "that hit did X," which is exactly the fantasy this system sells.
Rounding is pinned: round() throughout this section means round-half-away-from-zero (MidpointRounding.AwayFromZero in C#) — never the C# Math.Round banker's default. An unpinned mode would let designer hand-math and host results silently diverge at future tier values, violating AC-9's bit-identical promise.
Baseline constants (cloth tier, MVP)
| Constant | Value | Rationale |
|---|---|---|
| Player max HP | 100 | Round number; mistake-cost math reads as fractions of it |
| Cloth weapon base damage | 28 | Derived to hit the 2–4-hit TTK band against the archetype HP bands below |
| Unarmed base damage | 14 | Fists exist (cross-GDD review D2, 2026-08-27): half the cloth sword, same arc/range/phase constants, staggerHitContribution 18. A fresh world's first fight — before any workbench or sword — is defined, never zero, never null. Deliberately outside the 2–4-hit band: unarmed is the pre-sword state and the first sword must read as a real upgrade |
| Rusher HP / damage | 45–65 HP / 10–16 dmg per hit | Interface band for Enemy AI — fast, low-HP, punishes greed |
| Spacer HP / damage | 55–75 HP / 14–20 dmg per hit | Mid — ranged pressure, not melee-tanky |
| Bulwark HP / damage | 90–112 HP / 18–26 dmg per hit | Slow, hits hardest; HP capped at 112 so 4 cloth hits (112 dmg) always kill — 120 would need a 5th hit and breach the TTK target |
TTK check (cloth, zero variance): 28 dmg/hit → Rusher 2–3 hits, Spacer 2–3 hits, Bulwark 4 hits across its full 90–112 band (3 × 28 = 84 falls short of 90; 112 is the exact 4-hit boundary — why the cap sits there). All inside the 2–4 placed-hits target. TTD check (mistake budget): a "mistake" = one unmitigated hit. Against Bulwark's top damage (26): ⌈100/26⌉ = 4 → downed on the 4th mistake. Against Rusher (10–16): 7–10 mistakes. Worse enemies costing more mistakes is intended — difficulty is spatial, and Bulwarks live deeper inland. Unarmed TTK (14 dmg/hit, cross-GDD D2): Rusher 4–5 hits, Spacer 4–6, Bulwark 7–8 — fightable on day one, clearly worse than the sword. **Reinforced cloth garb TTD (crafting's per-item DamageReduction 0.10, cross-GDD D1): Bulwark 26 → round(26 × 0.9) = 23 → ⌈100/23⌉ = 5** mistakes (from 4); Spacer 20 → 18; Rusher 16 → 14 → 8 mistakes (from 7). The deep band buys one extra mistake against the enemy that guards it.
F1. damageDealt
damageDealt = round(weaponBaseDamage × damageModifier)
Variables:
| Variable | Symbol | Type | Range | Description |
|---|---|---|---|---|
| Weapon base damage | weaponBaseDamage | int | 14 (unarmed — empty weapon slot) / 28 (cloth); higher tiers TBD by Crafting GDD | Flat per-hit value from the equipped weapon, data-driven; an empty weapon slot reads the unarmedBaseDamage constant (D2) |
| Gear damage modifier | damageModifier | float | 1.0 (cloth) – ~2.2 (iron, provisional) | Multiplicative gear-tier scalar — the ONLY scaling input; no crit, no RNG, no character stat contributes |
| Result | damageDealt | int | ≥ 0 | Final damage value passed to F2 |
Output Range: bounded in practice by the tier ladder (cloth → iron); always deterministic. Higher-tier damageModifier values are **provisional until the Crafting GDD locks them** — Combat owns the hook, Crafting owns the values. Example: cloth weapon, cloth modifier: 28 × 1.0 = 28.
F2. damageTaken
damageTaken = max(0, round(incomingDamage × (1 − damageReduction)))
Variables:
| Variable | Symbol | Type | Range | Description |
|---|---|---|---|---|
| Incoming damage | incomingDamage | int | per-enemy band (10–26 at MVP) | Raw hit value, pre-mitigation |
| Gear damage reduction | damageReduction | float | 0.0 (cloth) – ~0.35 (iron, provisional) | Percentage mitigation from equipped armor tier |
| Result | damageTaken | int | [0, incomingDamage] | Final HP deducted |
Output Range: [0, incomingDamage], rounded, floored at 0. Percentage chosen over flat reduction (rule, with rationale): flat reduction makes weak enemies trend toward zero damage at high armor tiers — "whiff-harmless" trash collapses the spatial-difficulty premise. Percentage keeps every archetype's relative threat stable across gear tiers: a Bulwark is scarier than a Rusher at every tier; a veteran in iron still respects a Rusher swarm, they just survive it longer. Example: Bulwark hit for 22, cloth armor: round(22 × 1.0) = 22.
F3. hitArcTest
`hitConfirmed = (angleTo(target) ≤ baseArc/2 + arcTolerance) AND (distanceTo(target) ≤ baseRange + rangeTolerance) AND targetIsValid(target)`
Variables:
| Variable | Symbol | Type | Range | Description |
|---|---|---|---|---|
| Angle to target | angleTo(target) | float (deg) | [0, 180] | Between attacker's aim direction and vector to target, at the resolution instant |
| Weapon arc | baseArc | float (deg) | 100 (cloth baseline) | Native swing arc |
| Arc tolerance | arcTolerance | float (deg) | +12 (locked, Rule 7) | Fixed forgiveness — never scaled by RTT |
| Distance to target | distanceTo(target) | float (u) | ≥ 0 | At the resolution instant |
| Weapon range | baseRange | float (u) | 1.6 (cloth baseline) | Native reach |
| Range tolerance | rangeTolerance | float (u) | +0.2 (locked, Rule 7) | Fixed forgiveness — never scaled by RTT |
| Target validity | targetIsValid | bool | — | False for dead/despawned enemies and for allied players (friendly fire off — allies are transparent) |
| Result | hitConfirmed | bool | — | Gate before F1/F2 apply |
Output Range: boolean; evaluated by the host at the fixed resolution instant only — no position history (Rule 7). Example: enemy 46° off-center at 1.75 u: half-arc + tolerance = 50° + 12° = 62° ≥ 46° ✓; range + tolerance = 1.8 u ≥ 1.75 u ✓; alive ✓ → hitConfirmed = true.
F4. staggerMeter
staggerMeter_new = min(staggerThreshold, staggerMeter_old − decayRate·Δt + hitContribution') where hitContribution' = hitContribution × 0.5 if the previous hit on this enemy was < diminishWindow ago, else full value; meter floored at 0.
Variables:
| Variable | Symbol | Type | Range | Description |
|---|---|---|---|---|
| Prior meter | staggerMeter_old | float | [0, 100] | Accumulated at last evaluation |
| Hit contribution | hitContribution | float | per-weapon (cloth: 35; unarmed: 18) | Halved inside the diminishing window — prevents stagger-lock from rapid multi-hits |
| Diminish window | diminishWindow | float (s) | 1.0 (knob) | Window for the 50% reduction |
| Passive decay | decayRate | float (/s) | 20 (knob) | Applied while not being hit |
| Threshold | staggerThreshold | float | 100 (constant) | Stagger fires at threshold; meter resets to 0 |
| Immunity | postStaggerImmunity | float (s) | 1.5 (knob) | Enemy cannot be re-staggered during this window |
Output Range: [0, 100], clamped both ends. Example: Rusher at meter 40 hit inside the window (35 → 17.5): min(100, 40 + 17.5) = 57.5, no stagger. A full-value hit (+35) one second later, minus 1 s of decay in the gap (−20): `min(100, 57.5 − 20 + 35) = 72.5`. Another full-value hit one second after that (−20, +35): min(100, 72.5 − 20 + 35) = 87.5; the next hit — even halved (+17.5) — crosses the threshold (87.5 + 17.5 ≥ 100) and fires the stagger; then 1.5 s immunity.
F5. knockbackApplied
knockbackApplied = knockbackVector(argmax over same-tick contributions of |force|)
Variables:
| Variable | Symbol | Type | Range | Description |
|---|---|---|---|---|
| Contributions | contributions | list (vector, magnitude) | ≥ 1 | All knockback-causing hits resolved the same host tick against one target |
| Result | knockbackApplied | vector | bounded by the contributing weapon's knockback cap | Only the single largest contribution applies — never summed |
Output Range: bounded by the largest contributor's own cap. Tie rule (determinism): on an exact magnitude tie, the contribution from the attacker with the lowest OwnerClientId applies — host-stable and deterministic; never summed, never order-of-arrival (zero-variance rule). Example: two players hit the same enemy the same tick, 4 u and 7 u in different directions → only the 7 u vector applies; the 4 u is discarded.
F6. Revive parameters — knobs, not formulas
Revive channel time, revive HP fraction, and bleed-out duration are direct designer levers with no derived math — they are defined in Tuning Knobs. Bleed-out expiry transitions Downed → Dead → home-island respawn per the state table. Ranges, defaults, and failure modes live in the Tuning Knobs table — F6 deliberately carries no variable-table/example block because there is no derived formula to document.
Edge Cases
Tide soft-close edge cases (in-flight swings, enemy attack cancellation, downed deposit, aggro discard, Docked re-sampling) are resolved in Core Rule 13; same-tick simultaneity in States and Transitions. The cases below cover the remaining rule space.
Disconnects
- If a client disconnects with a swing intent in flight: the host still resolves it normally at the resolution instant — a disconnect never retroactively un-happens a committed swing.
- If a client disconnects while Downed (ruled 2026-08-27, cross-GDD review C1 — reconciled with netcode-session Rule 5): the character despawns — no offline bodies exist anywhere in the game — but the host keeps the player's HP, Downed status, and remaining bleed-out timer as session-scoped state. The timer keeps running with no body to revive; at expiry the player is Dead and respawns home per Rule 9 (arriving at full HP on rejoin). A rejoin before expiry flows through netcode's late-join path and resumes Downed at the last position with the remaining timer, revivable again. A live (non-Downed) disconnector rejoins at their last session HP — there is no disconnect-to-heal via rejoin. Live disconnect ≠ save/load: only a full session end reads the load-at-full-HP default (save Rule 7). The theoretical disconnect-to-heal exploit via full session restart is accepted as a non-concern (ruling 2026-08-26) — friends-only game, same posture as the tide GDD's clock-skip stance; noted in Open Questions for playtest review only.
- If the host quits mid-fight: the session ends (host migration is banned) — no additional combat rule needed; stated explicitly so it doesn't read as an omission.
Downed cluster
- If all players on a land are Downed simultaneously: no mercy wipe — each bleeds out independently on their own timer and respawns home individually at expiry (provisional ruling 2026-08-26 — validation owned by Open Question 5). One consistent rule, no special-cased mercy, pending that playtest read of the helpless wait.
- If the reviver is hit mid-channel: the channel cancels outright — reinitiate from zero, no partial progress.
- If the reviver dashes or is knocked back mid-channel: same — anything other than standing-still-channeling cancels. Dashing and channeling are mutually exclusive states (no reviving while invulnerable).
- If two players attempt to revive the same body: first channel-start wins by host timestamp; the second attempt fails to start.
- If a player would reach Downed HP on the home island: Downed cannot exist at home — the home-island heal rule takes precedence and the player stabilizes instead, at
reviveHPFraction, then heals per Rule 6. This includes arriving home Downed — by tide deposit (Rule 13) or by a rejoin after the land departed (Edge Case 2 + netcode Rule 5): the body stabilizes on arrival (ruled 2026-08-27, cross-GDD review). Downed exists to create stakes on docked lands; home is the safe zone by definition.
Boundary values
- If damage would push HP negative: clamp to exactly 0 before evaluating the Downed transition; the overflow is discarded — never carried into stagger, knockback, or anything else.
- If a config sets
damageReduction ≥ 1.0: rejected at load with a loud error (an unkillable armor tier) — same config-validation pattern as tide Edge Cases 17–18. - If
staggerMeterlands exactly onstaggerThreshold: stagger fires (≥, inclusive toward the player-favorable outcome). - **If a weapon's
hitContributionis missing/zero, or an enemy's HP band is ≤ 0**: content-load validation error, loud — never a silent zero-effect weapon or an enemy that spawns and instantly dies.
Spatial
- **If a dash or knockback vector would cross the shore threshold or a water edge: explicitly punted to the Player Movement quick-spec** — combat produces the displacement vector; traversal owns what boundaries do to it. Recorded as a dependency, not left silent.
- If an attacker swings across the shore seam (home ↔ docked land): illegal —
targetIsValid(F3) requires attacker and target in the same land instance; closed as a geometry impossibility, not a runtime check.
Timing
- **If the attacker reaches 0 HP after sending swing intent but before its resolution instant**: the swing is discarded — attacker state is re-sampled at resolution, never cached (same principle as Rule 7's
Dockedre-sample). - **If i-frames are active on the client but the dash had not reached the host when it computed the hit**: the host's computation wins — the hit stands. This is the direct, accepted consequence of no-rollback (the alternative would require the host to wait or rewind); stated as a known tradeoff, not an oversight.
- If a client joins mid-fight: its first evaluation must show enemies in their true current phase, including mid-telegraph — telegraph phase is replicated state, never re-derived locally from a "just spawned" default. ⚑ Flagged for the netcode ADR: confirm telegraph phase is a synced NetworkVariable, not purely local Animator state.
Interrupt flag
- If a Rusher's stagger meter crosses threshold during AttackActive (the never-interruptible window): the stagger **queues and fires at the start of Recovery** — dropping it would make a well-placed hit feel wasted, contradicting the Player Fantasy's feedback promise.
Dependencies
Upstream (Combat depends on):
| System | Hard/Soft | Interface consumed | Status |
|---|---|---|---|
| Player Movement | Hard | Owner-authoritative transform rail; move/dash execution; boundary rules for dash/knockback vectors (Edge Case 13 — answered by movement Rule 4: collision blocks and never damages, water is a hard edge, so a dash/knockback vector simply stops at the boundary) | Quick-spec ✅ 2026-08-27 — contract confirmed (status updated cross-GDD W6) |
| Netcode Session | Hard | Host tick, swing-intent RPC delivery, NetworkTransform interpolation, telegraph-phase replication (Edge Case 17) | Quick-spec ✅ 2026-08-27; ADR gated on friend playtest |
| Tide Scheduler (via Land Dock/Undock) | Hard | Docked boolean re-sampled at resolution; soft-close event ordering (Rule 13) | GDD Reviewed; independent review APPROVED WITH CONDITIONS 2026-08-26 |
| Crafting & Gear | Soft | DamageModifier / DamageReduction values per item — combat runs correctly at cloth defaults (1.0 / 0.0) with no gear system present | GDD Reviewed 2026-08-26 |
| Art bible | Constraint source | Silhouette/telegraph/feedback rules (§1, §4.x, §5.x, §7.2, §8.A.4) — design-time, not runtime | Approved |
Downstream (systems that depend on Combat):
| Dependent | Hard/Soft | Interface provided |
|---|---|---|
| Enemy AI (quick-spec ✅ 2026-08-27 — the mandated density AC exists: its Rule 6 caps simultaneous uninterruptible telegraphs per target at 1, with a dedicated criterion) | Hard | The enemy combat interface (Rule 11): telegraph contract incl. 400 ms floor, stagger/knockback/interrupt flags, archetype bands (Formulas baseline table), EnterTelegraph(attackId) boundary. Inherited constraint (binding, CD advisory 2026-08-26): uninterruptible telegraphs must never combine with enemy density into a de facto player lockout — the CC ban is on the experience, not just the mechanism; the quick-spec must carry an acceptance criterion capping simultaneous uninterruptible telegraphs targeting one player at MVP density. Party-size scaling contract: F1/F2 apply per-hit with no party-size term — simultaneous strikes fully stack by design (zero-variance rule); if playtest shows 5-player mob-shredding, the lever is enemy HP bands / spawn density (Enemy AI + Land Dock/Undock), never per-hit damage reduction |
| Crafting & Gear (GDD) | Hard | The two hook signatures + flat-stat guarantee (gear never touches HP/speed/cooldowns). Durability events (added 2026-08-27, cross-GDD W2 — closes crafting OQ5's combat half): host-side SwingLanded(attacker, weaponInstance) per confirmed hit and DamageAbsorbed(victim, armorInstance) per confirmed damage-taken, consumed by crafting F3 — emitted only on host confirmation (feedback-authority table), never client-predicted; unarmed swings emit nothing. Binding Pillar 5 constraint: gear must be equippable by any party member regardless of who crafted it — a new friend handed the group's spare weapon fights at parity in their first session (no binding, no crafter locks) |
| UI/HUD (quick-spec) | Soft | HP display data, Downed edge-of-screen indicator |
| Base-Defense Events (Tier 1, first-to-slip) | Deferred | None designed — deliberately, per phase brief |
Bidirectional-consistency obligations:
- The Tide Scheduler GDD carries the matching Combat row in its Interactions table (
Dockedgate re-sampled at action resolution, same H1 rule; soft-close combat-state discard; no other coupling) and lists Combat as a Hard downstream dependent — verified consistent 2026-08-26; no outstanding tide-side action. - Player Movement, Netcode Session, Enemy AI, and UI/HUD quick-specs, when authored, must list Combat as a dependent and consume the interfaces exactly as specified here.
- Harvesting is a peer, not a dependency — the only shared element is the re-sample-
Docked-at-resolution principle, which each doc states independently.
Forward references owned elsewhere: swing-intent message shape, resolution-instant scheduling in NGO ticks, tick-order guarantee for soft-close-before-combat, telegraph-phase NetworkVariable, and tide Edge Case 8's position-snapshot interface → netcode ADR. Dash/knockback boundary behavior at shore/water edges → Player Movement quick-spec. Gear tier values → Crafting GDD.
Tuning Knobs
Not tuning knobs (rules, locked by this GDD): zero damage variance, friendly-fire off, keep-everything death, no field healing at MVP, host resolution with no position history, both-apply mutual kills, telegraph pose-change-first, i-frames' existence on dash. Changing any of these is a design change requiring GDD revision + CD review, not a config edit.
| Knob | Default | Safe range | Too low | Too high | Interacts with |
|---|---|---|---|---|---|
moveSpeed | 6 u/s | Player Movement's knob (safe 5–8) — cited, not owned (cross-GDD W4, 2026-08-27); combat's feel band is 5–7 within it | Sluggish, kiting dies | Enemies can't pressure | dash ratio; enemy speeds (AI spec); a retune re-runs a combat feel pass (movement Rule 7) |
dashSpeed | 22 u/s | 18–26 | Dash feels like a walk | Teleport-y, breaks readability | dashDuration (distance = speed × duration) |
dashDuration | 0.14 s | 0.10–0.18 | Blink; i-frame window useless | Long invuln window trivializes telegraphs | i-frame window = full dash duration |
dashCooldown | 0.45 s | 0.35–0.60 | Chain-dash spam | Dash stops being a reliable answer to telegraphs | telegraphFloor — a dodge must be available for most telegraphs |
swingWindup | 0.10 s | 0.08–0.15 (pairwise: 0.08 legal only if swingActive ≥ 0.10) | Latency stops hiding in the swing | Strikes feel mushy | Guard: swingWindup + swingActive ≥ 0.18 s (≥ 140 ms RTT ceiling + margin — the resolution instant must sit ≥ 1 RTT after intent, or Rule 7's arc test loses its forgiveness-for-free property; the same guard also narrows the Edge Case 16 dash-state race — a dash message gets ≥ 0.18 s to reach the host before resolution) |
swingActive | 0.10 s | 0.08–0.15 (pairwise: 0.08 legal only if swingWindup ≥ 0.10) | same guard | Sweeps feel floaty | same guard |
swingRecovery | 0.05 s | 0.03–0.10 | No commitment cost | Whiffs overly punished | dash-cancel only exists in windup |
baseArc | 100° | 80–120 | Precision-demanding, anti-new-friend | Aim stops mattering | arcTolerance |
baseRange | 1.6 u | 1.4–2.0 | Face-hugging | Spear game | rangeTolerance |
arcTolerance | +12° | +8–15 | "Game cheated" rejections return | Sword feels dishonestly long | re-validate feel at 140 ms RTT after any change |
rangeTolerance | +0.2 u | +0.1–0.3 | same | same | same |
playerMaxHP | 100 | 80–150 | Mistake budget < 3 vs Bulwark | Damage numbers lose meaning | TTD check (Formulas) |
weaponBaseDamage (cloth) | 28 | 24–32 | Bulwark needs 5 hits (TTK breach) | Rusher band compresses to a flat 2-hit kill (only the 65 HP top still takes 3) — the 2–3 variance dies | re-run the TTK band check after any change; archetype HP bands |
unarmedBaseDamage | 14 | 10–18 | Fresh-world bootstrap fights become unwinnable (Bulwark > 10 hits) | The first sword stops reading as an upgrade | weaponBaseDamage (cloth) — keep unarmed ≤ 0.6× cloth; re-run the unarmed TTK note after any change (D2) |
telegraphFloor | 400 ms | 400–600, hard floor 400 (= the Rule 11 contract) | Remote clients lose the read (latency budget, Rule 11) | Enemies feel sluggish | dashCooldown, dashDuration |
staggerHitContribution (cloth) | 35 | 25–50 | Stagger never fires | Stagger-locks light enemies | staggerThreshold 100 (constant), diminishWindow |
diminishWindow | 1.0 s | 0.5–2.0 | Multi-player stun-lock returns | Co-op focus fire feels dead | decayRate |
decayRate | 20/s | 10–40 | Meters effectively never reset | Stagger unreachable solo | — |
postStaggerImmunity | 1.5 s | 1.0–3.0 | Re-stagger chains | Stagger feels pointless | — |
reviveChannelTime | 3.0 s | 2–5 | Revives free under fire | Revives impossible mid-fight | enemy pressure (AI spec) |
reviveHPFraction | 40% | 25–50% | Revived = instant re-down | Death loses its bite | playerMaxHP |
maxTargets (proposed by ADR-0015, pending GDD assent) | 1 | 1–3 | Single-target only (the reading every AC and TTK check assumes) | Cleave — re-run TTK bands and the "mob-shredding lever is density, never per-hit damage" position first | weaponBaseDamage, enemy density; F3 is a per-target predicate silent on cardinality |
reviveReach (proposed by ADR-0015) | 1.5 u | 1.0–2.5 | Revives need face-hugging | Revive from safety | reviveChannelTime; reviver leaving reach = a 4th channel-cancel cause beside EC5 (damage) and EC6 (dash), consistent with "anything other than standing-still-channeling cancels" |
bleedOutTimer | 45 s | 30–60 | No time for a rescue run | Helpless too long (see Edge Case 4) | land size / travel times |
hitstopDuration | 0.05 s | 0.03–0.08 | Hits feel weightless | Combat stutters | local presentation only — never pauses host simulation or other players' views |
Knob-interaction warnings: weaponBaseDamage, playerMaxHP, and the archetype HP/damage bands form one balance system — retune together and re-run the Formulas TTK/TTD checks. dashCooldown + dashDuration vs telegraphFloor define the dodge economy: a telegraph must usually be answerable by an off-cooldown dash — quantified: after a dash starts, the next dash returns at dashCooldown (0.45 s default, 0.60 s max), so a telegraph opening within the first dashCooldown − telegraphFloor of a dash (0.05 s at defaults, up to 0.20 s at knob extremes) goes active before the dash returns and cannot be answered by dashing. This is a stated design position, not a bug: dash is the premium dodge, not a guaranteed one — movement and spacing remain the fallback answer, and Open Question 8 probes whether that fallback is actually exercised. The swing-phase guard (≥ 0.18 s to resolution) is a netcode invariant wearing a feel-knob costume — violating it silently breaks Rule 7's no-history design; enforce it at config load. All values are data-driven (per coding standards), validated at load per Edge Cases 10–12; prototype baselines are starting values, not gospel — the friend-group playtest retunes them.
Visual/Audio Requirements
Combat's visual contract is owned by the art bible; this section states what combat imposes and cites rather than restates.
Telegraph language (binding, per Rule 11): the primary channel is a silhouette/pose change readable in solid black (bible §1, §3.1, §5.2) — if windup and idle silhouettes are near-identical, redesign the pose, never lean on color. Crimson rim-accent on the attacking limb is reinforcement only, never full-body (would collide with Stagger White's "impact" meaning); hard-edged flat ground decals (no gradients, §6.2) only for AOE/lunge attacks. **Combat sprites and telegraph VFX are never tide-tinted** — they sit outside §8.A.4's DriftLayer tinting scope (art-bible fix-pass item: state the combat-sprite exemption explicitly) — telegraphs must read identically under amber, neutral, and violet.
Hit feedback stack (ranked): 1) hitstop (local presentation only, capped per Tuning Knobs), 2) Stagger White one-frame silhouette flash (§4.1/§4.2 — luminance-based, survives tint and colorblindness), 3) knockback displacement, 4) small flat-colored particle burst — palette-locked, low count, Nuclear Throne restraint (§9), 5) sound landing the same frame as hitstop start. Rejected: screen shake beyond a small cap (breaks co-op silhouette tracking), any particle that obscures a silhouette edge, color-flicker VFX, gradient flashes.
Silhouette-first test for combat assets (pass/fail): at solid black, 64–96 px (§5.4): enemy identity and threat tier legible; windup pose distinct from idle; with 2–5 players and 2–3 enemies on screen, the telegraphing enemy is not lost among allies (enemy proportions never overlap the player's 1:2.5 ratio, §5.2); ground decals hard-edged; everything readable while the world moves (§1).
Player states: dash = 2-pose stretch-blur (§5.3), shape-only, no color change. Hurt = Stagger White flash on the player's own sprite — one shared impact language for dealing and receiving. **Downed = distinct low prone silhouette + edge-of-screen indicator reusing the §7.2 hat-silhouette ally-tracking system** — a downed friend must be findable at a glance (Fellowship depends on it); reuse, don't invent.
Audio (desirable, not MVP-blocking): swing/whiff sounds predicted client-side; impact/stagger sounds host-confirmed, same frame as hitstop; death/downed presentation tone-safe per the project tone rule (world facts, no punitive stingers).
📌 *Asset Spec note: after this GDD is approved, run /asset-spec system:combat-system to derive per-asset visual descriptions, dimensions, and generation prompts from this section.*
UI Requirements
Combat contributes three elements to the HUD (visual contract: art bible §7): the player HP display (own + party), the **Downed ally edge-of-screen indicator** (§7.2 hat-silhouette variant), and host-confirmed damage numbers (the only numeric combat feedback). Binding constraints on any UI consuming combat data: damage/death copy follows the project tone rule (world facts, never "YOU DIED" theatrics — a downed banner reads "[Name] is down", never a failure message); no UI element may reveal hidden combat state that gameplay doesn't (**no enemy HP bars at MVP** — stagger flash and death are the feedback; revisit at playtest, ruling 2026-08-26); dash cooldown feedback must be glanceable without reading numbers.
📌 UX Flag — Combat: this system has UI requirements. In Pre-Production, run /ux-design for the HUD before writing epics; combat stories referencing UI must cite design/ux/hud.md, not this GDD.
Acceptance Criteria
[UNIT] = EditMode NUnit over pure logic (damage math, arc-test geometry, stagger meter, state-machine transitions, config validation) — the hit pipeline (F1–F5) must be designed pure/DI-testable, no live NGO session required. [INTEGRATION] = NGO runtime, Multiplayer Play Mode + Network Simulator. [MANUAL] = human feel judgment, never automated (coding standards) — but scoped to a concrete, checkable claim. All criteria use locked baseline constants (cloth tier) unless the vector specifies otherwise.
Core Verbs & Commitment (Rules 1–4)
- AC-1 [UNIT] GIVEN Idle/Move, WHEN dash input fires off-cooldown, THEN state → Dashing, i-frames on, cooldown timer starts at dash start.
- AC-2 [UNIT] GIVEN Strike:Windup, WHEN dash input fires, THEN swing intent is revoked and state → Dashing (Rule 3 bail-out).
- AC-3 [UNIT] GIVEN Strike:Active, WHEN any cancel input fires, THEN state does not change — Active is never cancellable.
- AC-4 [UNIT] GIVEN dash duration 0.14 s, WHEN elapsed, THEN state → Idle/Move and i-frames end exactly at that instant (i-frame window == dash duration).
- AC-5 [UNIT] GIVEN dash cooldown 0.45 s measured from dash start, WHEN a second dash input arrives before expiry, THEN it is rejected.
Flat Stats & Gear Hooks (Rule 5, F1, F2)
- AC-6 [UNIT] GIVEN any equipped gear config, WHEN max HP, move speed, dash parameters, or cooldowns are read, THEN they equal the global constants regardless of
damageModifier/damageReduction(flat-stats guarantee). - AC-7 [UNIT] GIVEN
weaponBaseDamage=28, damageModifier=1.0, WHEN F1 evaluates, THENdamageDealt=28. - AC-7b [UNIT] GIVEN no weapon equipped (empty weapon slot), WHEN F1 evaluates, THEN
weaponBaseDamage=14(unarmedBaseDamage) anddamageDealt=14— unarmed is a defined baseline, never zero, never a null/missing-weapon error (cross-GDD review D2, 2026-08-27). - AC-8 [UNIT] GIVEN
incomingDamage=22, damageReduction=0.0, WHEN F2 evaluates, THENdamageTaken=22. - AC-9 [UNIT] GIVEN identical F1/F2 inputs, WHEN run 1,000×, THEN output is bit-identical every call (zero-variance rule).
Hit Resolution (Rule 7, F3)
- AC-10 [UNIT] GIVEN enemy at 46° off-center, 1.75 u, WHEN F3 evaluates against baseline arc/range + tolerances, THEN
hitConfirmed=true(worked example). - AC-11 [UNIT] GIVEN an ally inside the arc/range window, WHEN F3 evaluates, THEN
targetIsValid=falseand no damage applies (friendly-fire transparency). - AC-12 [UNIT] GIVEN a dead/despawned target inside the geometric window, WHEN F3 evaluates, THEN
hitConfirmed=false. - AC-13 [UNIT] GIVEN
arcTolerance/rangeTolerance, WHEN inspected at any simulated RTT, THEN their values never scale with measured RTT (constants only). - AC-14 [INTEGRATION] GIVEN Multiplayer Play Mode with Network Simulator at 140 ms RTT, WHEN a swing intent is sent and the host resolves at the fixed resolution instant, THEN the confirmed/rejected outcome matches an equivalent 0 ms RTT run for the same positions (no rewind/history).
Stagger & Knockback (Rule 11, F4, F5)
- AC-15 [UNIT] GIVEN meter 40 and a hit inside
diminishWindow, WHEN F4 evaluates, THEN contribution halves and the result matches the worked example (57.5). - AC-16 [UNIT] GIVEN the meter reaches exactly
staggerThreshold, WHEN evaluated, THEN stagger fires (inclusive≥, Edge Case 11). - AC-17 [UNIT] GIVEN two same-tick knockback contributions (4 u, 7 u), WHEN F5 evaluates, THEN only the 7 u vector applies — never summed.
- AC-17b [UNIT] GIVEN two same-tick knockback contributions with exactly equal magnitudes, WHEN F5 evaluates, THEN the contribution from the lowest
OwnerClientIdapplies, identically across repeated runs (tie rule, determinism). - AC-18 [UNIT] GIVEN a Rusher whose meter crosses threshold during AttackActive, WHEN evaluated, THEN the stagger queues and fires at Recovery start, never mid-Active (Edge Case 18).
- AC-19 [UNIT] GIVEN post-stagger immunity active, WHEN a new hit lands within
postStaggerImmunity, THEN no second stagger fires.
Telegraph & Latency (Rules 2, 11)
- AC-20 [UNIT] GIVEN any enemy attack config, WHEN loaded, THEN windup-to-active duration
< 400 msis rejected at load (hard floor = the Rule 11 contract; the floor case is the default case, so AC-25 exercises it). - AC-21 [UNIT] GIVEN
swingWindup + swingActive, WHEN loaded, THEN configs summing< 0.18 sare rejected (swing-phase guard, netcode invariant). - AC-22 [INTEGRATION] GIVEN Network Simulator at 140 ms RTT, WHEN a local player sends move/dash input, THEN local motion begins within 0–1 frame of input (owner-authoritative claim).
- AC-23 [INTEGRATION] GIVEN Network Simulator at 140 ms RTT, WHEN a confirmed hit fires, THEN the stagger/impact broadcast is absorbed within the attacker's own swing animation — no visible post-swing pause (prototype's ~150 ms hit→stagger evidence).
- AC-24 [MANUAL] GIVEN Network Simulator fixed at 140 ms RTT, WHEN a tester triggers a local dash/swing, THEN the tester reports motion/swing start within the same frame as input (no perceived delay) — logged with simulator settings.
- AC-24b [MANUAL] GIVEN Network Simulator with a jitter/spike profile (not fixed RTT), WHEN testers play full combat sessions, THEN the frequency of felt-unfair hits — dash i-frames shown locally but the hit stands (Edge Case 16) — is logged separately from clean-RTT feel testing. Fixed-delay runs cannot exercise this case; this is the CD's designated watch item.
- AC-25 [MANUAL] GIVEN Network Simulator fixed at 140 ms RTT and a 400 ms-floor telegraph, WHEN a remote client observes it, THEN the tester confirms ≥330 ms of visible warning remains, sufficient to dodge (Rule 11 derivation).
Death, Downed, Friendly Fire (Rules 6, 9, 10)
- AC-26 [UNIT] GIVEN HP reaches 0 with a living ally on the same land, WHEN evaluated, THEN state → Downed (crawl, no combat, bleed-out timer starts).
- AC-27 [UNIT] GIVEN HP reaches 0 with no living ally present, WHEN evaluated, THEN state → Dead, respawn at home island, full HP.
- AC-28 [UNIT] GIVEN a player dies (any cause), WHEN inventory is inspected post-respawn, THEN all gear and materials are unchanged (keep-everything death).
- AC-29 [UNIT] GIVEN a swing whose arc includes an ally between attacker and enemy, WHEN F3 evaluates, THEN the ally takes zero damage/knockback and the enemy is not shielded.
- AC-30 [UNIT] GIVEN damage that would drive HP negative, WHEN clamped, THEN HP = exactly 0 before the Downed/Dead transition, and the overflow never reaches stagger/knockback (Edge Case 9).
Same-Tick & Mutual Kills
- AC-31 [UNIT] GIVEN two players hit one enemy the same tick, WHEN resolved, THEN both hits apply independently and no kill-credit field is written.
- AC-32 [UNIT] GIVEN a player's and an enemy's killing blows resolve the same tick, WHEN ordered, THEN enemy damage resolves first, then the player's — both apply (locked 2026-08-26).
Tide Soft-Close Ordering (Rule 13)
- AC-33 [INTEGRATION] GIVEN a host tick at
φ+W, WHEN both soft-close and a pending combat resolution are due, THEN soft-close evaluates first and pending damage with resolution instant≥ φ+Wis discarded. - AC-34 [INTEGRATION] GIVEN an enemy attack mid-windup or mid-active at
φ+W, WHEN soft-close fires, THEN the attack is cancelled outright and cannot kill a player mid-deposit. - AC-35 [INTEGRATION] GIVEN a Downed player on a departing land at
φ+W, WHEN soft-close deposits them home, THEN they arrive stabilized — Downed → Idle/Move atreviveHPFraction, bleed-out timer cancelled, no Dead transition reachable from the deposit (Edge Case 8 applied to arrival; departure never converts to a death — ruled 2026-08-27). - AC-36 [UNIT] GIVEN a swing-intent message, WHEN
Dockedis checked at resolution, THEN it is re-sampled fresh — never cached from intent time.
Config Validation (load-time, fail loudly)
- AC-37 [UNIT]
damageReduction ≥ 1.0→ load rejected (unkillable armor tier). - AC-38 [UNIT]
hitContributionmissing/zero, or an enemy HP band≤ 0→ load rejected. - AC-39 [UNIT]
swingWindup + swingActive < 0.18 s→ load rejected (validation-suite entry for AC-21's guard).
Disconnects & Home (Edge Cases 1–2, 8)
- AC-40 [INTEGRATION] GIVEN a client disconnects after sending swing intent, WHEN the resolution instant arrives, THEN the host resolves the swing normally — the outcome is identical to a connected client's.
- AC-41 [INTEGRATION] GIVEN a client disconnects while Downed, WHEN observed host-side, THEN the character despawns while HP, Downed status, and the remaining bleed-out timer persist as session state; a rejoin before expiry resumes Downed at the last position with the remaining timer; a rejoin after expiry arrives Dead → respawned home at full HP; a non-Downed disconnector rejoins at their last session HP (Edge Case 2, ruled 2026-08-27 — mirrors netcode-session AC-3).
- AC-42 [UNIT] GIVEN a player would reach Downed HP while on the home island, WHEN evaluated, THEN they stabilize (no Downed state) per Edge Case 8.
Minimum smoke set (run first)
- AC-9 — zero-variance determinism: if damage isn't identical every run, nothing downstream is trustworthy.
- AC-10 — canonical F3 hit-arc worked example.
- AC-21 — swing-phase config guard: silently breaks the no-history netcode design if it regresses.
- AC-32 — mutual-kill ordering: highest-ambiguity rule, easiest to regress silently.
- AC-35 — soft-close Downed deposit: highest-blast-radius cross-system rule (departure-never-death invariant).
Four are [UNIT]; AC-35 is [INTEGRATION] but runs headless in Multiplayer Play Mode — together they run in under a minute as a pre-QA-handoff gate.
Deliberately not criteria (and why)
- "The dodge feels satisfying" / "the swing has weight" — feel qualities, routed to
[MANUAL]playtest evidence with lead sign-off once art/animation exists; no numeric proxy substitutes for a body playing it. - *Exact swing-intent RPC shape and the NGO tick-order guarantee for soft-close-before-combat* — deferred to the netcode ADR; testing a shape that isn't locked yet tests the wrong thing.
- Interruptibility "feels fair" per archetype — a balance/playtest question, not a pass/fail contract; covered by the friend-group playtest.
- Disconnect-to-heal (Edge Case 2) — explicitly accepted as a non-concern by ruling; no criterion needed for a non-bug.
Coverage note: Core Rules 1–13 and F1–F5 each have ≥1 criterion. F6 (revive knobs) has no dedicated AC — pure tuning values, exercised via AC-26/AC-35/AC-41. Edge Cases 13–14 (shore-seam geometry) are closed as geometry impossibilities and are not independently testable — flagged, not omitted.
Open Questions
| # | Question | Owner | Target |
|---|---|---|---|
| 1 | Swing-intent RPC shape, resolution-instant scheduling in NGO ticks, soft-close-before-combat tick-order guarantee, telegraph phase as synced NetworkVariable (Edge Case 17), tide Edge Case 8 position snapshot | Netcode ADR | After friend-group playtest |
| 2 | ~~Dash/knockback vector behavior at shore/water boundaries (Edge Case 13)~~ RESOLVED 2026-08-27 — player-movement quick-spec Rule 4: collision blocks and never damages; water is a hard edge; the vector stops at the boundary (cross-GDD W6) | ~~Player Movement quick-spec~~ Resolved | Done |
| 3 | Higher-tier damageModifier / damageReduction values (provisional ~2.2 / ~0.35 iron) | Crafting GDD | Systems Design phase |
| 4 | Disconnect-to-heal accepted as non-concern — does real play ever distort around it? | MVP friend-group playtest | Post-MVP review |
| 5 | Independent bleed-outs on group down (no mercy wipe) — does the helpless wait read badly? | MVP friend-group playtest | Post-MVP tuning |
| 6 | No enemy HP bars at MVP — does stagger/death feedback suffice? | MVP friend-group playtest | Post-MVP tuning |
| 7 | Do the TTK/TTD bands (2–4 hits, ~4-mistake floor) hold up in real co-op play? | MVP friend-group playtest | Post-MVP tuning |
| 8 | Does dash-through dominate all single-enemy engagements — is movement-only dodging (spacing, kiting) a viable secondary skill expression, or is dash always the answer? | MVP friend-group playtest | Post-MVP tuning |
| 10 | Home heal rate: Rule 6 says "restores to full while present" with no rate; ADR-0015 implements immediate on arrival and flags it — a homeHealRate ramp is a one-line change + a knob row if the playtest prefers it. Also: maxTargets and reviveReach above need this GDD's assent before the first combat story | Combat GDD (/quick-design) | Before the first combat story |
| 9 | reviveChannelTime / bleedOutTimer defaults are calibrated for 2-player MVP — do they hold for 5-player groups, where 4 potential rescuers trivialize the 45 s window? | Tier 1 design pass | Tier 1 |