combat-trainer: don't leave casting set after a failed spell prep (fixes #7563) - #7564
combat-trainer: don't leave casting set after a failed spell prep (fixes #7563)#7564MahtraDR wants to merge 7 commits into
Conversation
SpellProcess#prepare_spell discarded DRCA.prepare?'s return value and set game_state.casting = true unconditionally. When prep failed (e.g. an unknown spell replying "You have no idea how to cast that spell"), execute() then bailed on `if game_state.casting`, starving all offensive and training casting until check_timer cleared it 70s later -- and check_buffs re-picked the same dead spell next tick, silently and indefinitely. Fixes elanthia-online#7563. - Honor prepare?'s return: on failure, reset casting/cast_timer and return before setting casting. Covers every failure reason (unknown spell, area interference, exhausted retries). - Add a ct-spell-unknown flag on "You have no idea how to cast that spell" and, when it trips, mark the spell ct_spell_disabled and message the user so it is not retried every tick. Only that permanent message disables; transient failures just skip the tick. - Skip ct_spell_disabled entries in check_buffs, check_training, and check_offensive so the disable covers all three spell paths. Adds regression specs for prepare_spell (success sets casting; failure leaves it unset with nil timer; unknown spell is disabled) and check_offensive skipping a disabled spell. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Tested this locally against the case from #7563: an Empath with Two gaps I noticed while reading it over. 1. It builds a throwaway hash on every call, so # combat-trainer.lic:2255, and again at :2269, :2271, :2276
data = { 'abbrev' => 'vh', 'mana' => @empath_spells['VH'].first, ... }
prepare_spell(data, game_state)If an Empath's The other three paths are fine. 2. The The spec stubs Stubbing Neither of these blocks the fix as far as I am concerned. |
Address tester feedback on elanthia-online#7564: - Empath healing (VH/FOC/HEAL in check_health_empath) rebuilds the spell hash every tick, so a `ct_spell_disabled` flag written onto that hash was discarded and the "Disabling ..." message repeated forever. Track disabled spells in a persistent @disabled_spells set keyed by abbrev instead, and guard the empath paths (with natural FOC->HEAL fallback). This also stops mutating the caller's spell hash. - Strengthen the #check_offensive spec so it exercises the skip directly: stub sort_by_rate_then_rank to return the spell's skill and is_offense_allowed?/ dancing? so removing the guard actually reaches prepare_spell, rather than exiting early at `return unless data`. - Add a #check_health_empath regression spec for the rebuilt-hash case. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks @simtel12 — both spot-on. Pushed 069342f. 1. Empath healing defeating the disable. Root cause was keying the disable off a flag written onto the spell hash, which the 2. Weak Full suite green (3022 examples, 0 failures), rubocop clean. |
…reset Holistic response to the principal-dev review of elanthia-online#7564: - Centralize the disabled-spell guard at the top of prepare_spell so EVERY caller is covered -- previously only the offensive/buff/training selects and empath healing checked it, leaving the necromancer paths (Siphon Vitality, Call from Beyond/Within, Necrotic Reconstruction, Devour/Consume Flesh) and the Trader regalia path to keep re-sending `prep` and re-triggering the game's "no idea how to cast" line every tick. The central guard also short-circuits before the destructive prep side-effects (release_cyclics, drop_moon_weapon?). - Extract reset_casting_state and use it on both non-casting exits (the aborted prep and the 70s check_timer recovery) so stale casting_* sub-flags (casting_cyclic/sorcery/moonblade/regalia/...) can't bleed into the next cast. Previously the abort path and check_timer each cleared only casting/cast_timer. - Anchor the ct-spell-unknown matcher (^...) to avoid a false, session-long disable from an ambient line containing that phrase. - Keep the disable session-scoped (clears on next combat-trainer start) but make it visible: disable_spell announces the spell once with recovery guidance. - spell_disabled? now returns a strict boolean. Remove the now-redundant per-branch empath VH guards (the central guard covers them); keep the FOC->HEAL fallback guards, which still heal when only FOC is disabled. Adversarial specs (mutation-verified to fail when the guard/reset/flag-gating is removed): transient failure must NOT disable a castable spell; unknown spell is disabled and announced once; disabled spell never pings the game or fires release_cyclics; abort path and check_timer reset the casting_* sub-flags; check_timer boundary; the unguarded necromancer check_consume path is protected by the central guard; FOC->HEAL fallback; and nil-abbrev / case-insensitive / fresh-instance edges. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t exits reset_casting_state now nils @should_invoke and clears @should_harness. A cambrinth cast that aborts (failed prep) or times out (check_timer) otherwise left the invoke intent set; the next non-cambrinth cast would then gate check_current on check_charging? and stall. Cambrinth stays charged game-side (charges never leak), so nothing is stranded -- we only drop the stale intent. Extends the abort and check_timer regression specs to assert @should_invoke is cleared (mutation-verified: both fail if the reset is removed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rd skips a spell Two fixes from the round-3 review: 1. The central spell_disabled? guard in prepare_spell returned before reset_casting_state, but the necromancer callers (check_cfb/check_cfw/ heal_corpse and the Devour/Consume branch of check_consume) set casting_cfb/cfw/nr/consume = true BEFORE calling prepare_spell. A disabled necro spell therefore left the sub-flag set with casting = false, so necro_casting? reported true and suppressed looting, arranging, rituals, and zombie/bonebug creation until the next successful cast (indefinitely if the disabled spell was the necro's only cast). The guard now calls reset_casting_state before returning. Regression spec: a disabled Call from Beyond leaves casting_cfb false and necro_casting? false (mutation-verified). 2. Drop the '^' anchor on the ct-spell-unknown matcher. It diverged from the authoritative unanchored matcher in lich-5 common-arcana.rb (which is what makes prepare? return false) and from the sibling unanchored ct-shock-warning flag; a leading character on the line would have made the disable silently never fire while prepare? still failed. Also switches the #check_consume spec to a real GameState (the guard now runs reset_casting_state on that path). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ilters (test-only) The disabled-skip filters in check_buffs and check_training had no regression coverage (deleting either left the suite green). check_buffs' filter is load-bearing: without it a disabled always-due buff is re-selected by `find` every tick and monopolizes the single per-tick buff slot, starving all other due buffs. Adds two specs (both mutation-verified to fail when their filter is removed): - check_buffs picks the next due buff, never the disabled always-due one. - check_training filters out the disabled skill so prepare_spell isn't called. No production change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The check_buffs regression spec set `$weapon_buffs = []`, but reset_data (the before(:each) hook) does not restore $weapon_buffs, and it's initialized once at spec load. So the assignment leaked [] into every subsequent example in the run -- the load-order cross-spec pollution CLAUDE.md forbids. It was also gratuitous: BadBuff/GoodBuff aren't weapon buffs, so check_buff_conditions? already passes against the real list. Removed the assignment; test stays green and still fails if the check_buffs disabled filter is removed. Verified green under random order. No production change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Please try again one more time? |
Summary
Fixes #7563.
SpellProcess#prepare_spelldiscarded the return value ofDRCA.prepare?and setgame_state.casting = trueunconditionally. When preparation failed — most commonly an unknown spell where the game repliesYou have no idea how to cast that spell—execute()then returned early onif game_state.casting, socheck_offensiveandcheck_trainingnever ran. The character silently stopped casting all offensive and training spells untilcheck_timercleared the state 70s later, at which pointcheck_buffsre-picked the same dead spell and the cycle repeated indefinitely. No error or status message appeared.A character reaches this state by copying a spell list, or by circling to 10 (Analogous Patterns spells are forgotten), or — as noted in the issue thread — by dying while a spell was only temporarily memorized from a scroll.
Changes (all in
combat-trainer.lic)prepare?'s return value. On failure, resetcasting/cast_timerandreturnbefore settingcasting. This unblocks casting for every failure reason (unknown spell, area interference, exhausted retries).ct-spell-unknownflag onYou have no idea how to cast that spell; when it trips, the spell is markedct_spell_disabledand the user is messaged. Only that permanent message disables — transient failures just skip the tick and retry.ct_spell_disabledentries incheck_buffs,check_training, andcheck_offensive, so the disable covers all three spell paths (the issue confirms training is affected too), rather than re-attempting and spamming the error every tick.I deliberately did not use the
DRSpells.known_spells/validate.licroute suggested in the issue: it misses magical tattoos and needs aSPELLSsend to populate.Tests
Regression specs added to
spec/combat_trainer_spec.rb:#prepare_spellsetscastingon success; leaves it unset with a nil timer on failure; disables an unknown spell without settingcasting.#check_offensiveskips a spell flaggedct_spell_disabled.Verification:
bundle exec rspec spec/combat_trainer_spec.rb— 395 examples, 0 failuresbundle exec rspec(full suite) — 3021 examples, 0 failuresbundle exec rubocop combat-trainer.lic— no offenses🤖 Generated with Claude Code