5 Commits

Author SHA1 Message Date
prosolis
41f98b721a N3/P2: give the combat engine an N-player roster
Splits combatState into a fight-scoped half and a per-character half.
Everything that belongs to one PC -- HP, ward/spore/reflect charges,
heal charges, poison ticks, the death save, Lucky/Rage, the
first-attack one-shots, the arcane ward, concentration, and the
debuffs an enemy stacks onto a specific character -- moves to a new
`actor`. What belongs to the fight stays: the enemy pool, the enemy's
stance (evade/block/advantage/retaliate/regen/survive), the round
counter, the event log, and the RNG stream.

combatState embeds *actor, so the promoted fields keep their names and
all ~230 existing reads (st.playerHP, st.wardCharges, ...) compile
untouched. The embedded pointer is a cursor: seat(i) points it at a
roster member. Solo seats one actor and never moves the cursor, so the
draw order off the single RNG stream is unchanged.

That is the whole point. TestCombatCharacterization -- 57 scenarios x
5 seeds, 7468 pinned golden lines -- is byte-identical before and
after. Solo combat provably did not move, so the d8prereq balance
corpus survives the parties work and only party bands need new
baselines in P7.

Hold-person is fight-scoped (holding the enemy holds it for everyone)
while stat_drain/debuff/max_hp_drain are per-character, which is why
they landed on opposite sides of the split.

No multi-actor *semantics* here: nothing yet decides who the enemy
swings at or how initiative interleaves N players. That is P3. This
commit only lands the data model, and the roster tests cover what the
solo golden structurally cannot see -- cursor isolation, shared-state
visibility across seats, and the pointer embed (a value embed would
silently copy on seat() and fail the round-trip assertion).
2026-07-09 20:29:05 -07:00
prosolis
0cd8fd3337 Combat: back the flavor-only monster abilities with real effects
Turn the four placeholder ability effects into working mechanics:
spell_resist halves player spell damage, reveal_action rolls the
player's next swing at disadvantage, fear_immune fizzles control
spells, and ally_buff grants an accumulating enemy attack bonus.
All four are armed by applyAbility, read by the shared resolution
primitives, and round-tripped through CombatStatuses for turn-based
suspend/resume. New branches are guarded by zero-valued state so the
auto-resolve characterization golden is untouched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 08:59:51 -07:00
prosolis
e629f8fd4d Combat: implement stateful monster ability effects
Slice 3 of the bestiary SRD upgrade: the monster abilities that need
per-fight state (evade, block, advantage, retaliate, regenerate,
survive_at_1, stat_drain, debuff, max_hp_drain). applyAbility arms
combatState flags that the shared resolution primitives read, so both
the auto-resolve and turn-based engines honor them; the turn-based
engine round-trips them through CombatStatuses so a suspended fight
resumes from exact mid-state. New branches are guarded by zero-valued
state so the auto-resolve characterization golden is untouched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 08:32:13 -07:00
prosolis
f1aa9981f8 Combat: implement immediate-resolution monster ability effects
Wires up the ability effects that resolve fully within applyAbility with
no new persistent state: damage riders (bonus_damage, aoe/aoe_fire/
death_aoe, execute) via the shared calcDamage formula, self_heal, and
flavor-only placeholders for effects still pending per-fight state.
Works in both auto-resolve and the turn engine since both call
applyAbility.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 08:17:11 -07:00
prosolis
96b291d831 Combat: SRD multiattack profiles + monster abilities in turn engine
Elites/bosses in turn-based fights now swing a full SRD multiattack
profile and fire their special ability — abilities never fired in the
turn-based path before, and every enemy made a single attack.

bestiary_srd.go adds SRDAttack/SRDProfile and a hand-authored registry
for the named bosses and multiattack elites; auto-resolve keeps its
tuned single-Attack blocks untouched. turnAbilityFires remaps the
auto-resolve phase clock onto fight progress for the phase-less duel.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 07:37:29 -07:00