Repository navigation
Let a member withdraw or retreat alone - #195
Merged
Merged
Conversation
…gle-withdrawal) Issue #171. A fighting withdrawal or a retreat that every declarer makes still moves the formation. One that not every declarer makes becomes the declaring member's own. A front-rank member trades places in the marching order with the back-rank member named in the new BattleDeclaration.replacement_id, and the new MemberWithdrewEvent reports the swap. The adaptations register and the spec state the rule, with the SRD text behind each decision. The event is registered with a message template for each of its two codes. The acceptance tests in tests/test_single_withdrawal.py are marked expected-to-fail. Three tests in test_battle.py pinned formation_split for rounds the contract now refuses with no_replacement and not_in_front_rank. They are rewritten to the new codes and marked the same way. The unmarked guards at the end of the new file pin the formation moves as they are today. The new field defaults to None, so SCHEMA_VERSION stays 4. I regenerated phase5_milestone.json with its generator, because every logged battle declaration now serializes the field. Its only change is 56 added "replacement_id": null lines. Claude-Session: https://claude.ai/code/session_01V7ErtnsmhaP7aLwcfmzohD
…ingle-withdrawal) The retreat penalty now has one rule for a member who retreats alone and for the formation. Combat.md says "This round: ... the opponent gains a +2 bonus to all attacks against the retreating combatant and ignores any AC bonus due to the combatant's shield". A formation retreat is every declarer retreating, so every member is exposed for the whole round. That includes monsters that win initiative and strike before the party moves off. Today the formation is exposed only after its own movement step. A new acceptance test, marked expected-to-fail, pins the monsters-first case: +2 on every attack and the shield ignored. An unmarked guard pins the party-first case, which works today. The register and the BattleDeclaration.move docstring state the one rule. Claude-Session: https://claude.ai/code/session_01V7ErtnsmhaP7aLwcfmzohD
…t all round Issue #171. A fighting withdrawal or a retreat that every declarer makes still moves the whole formation. Any other is the declaring member's own. The validation pre-phase judges each one in marching order: the member has to stand in the front rank, name a replacement in replacement_id, name one who can step up, and both have to cover 5 feet a rank at their own limits. Each gets the first rejection that applies: not_in_front_rank, no_replacement, replacement_unavailable, or replacement_too_far. A melee declaration is judged against the front rank the planned swaps leave, so the replacement may strike the round they step up. The checks read state and take no draw. The swaps run in the party's movement step, before the close, and each reports a MemberWithdrewEvent. A swap whose two members no longer stand where they did, or can't move, doesn't happen. A replacement's melee attack resolves only if their swap happened, and a monster-side melee attack chosen against a member who has since stepped back is skipped. The retreat penalty is now keyed on the declarations at the top of the round, so it holds whichever side acts first. A formation retreat exposes every member, as it did once the party had moved. A lone retreat exposes that member alone. A confused monster's attack on a retreating member now gets the penalty too, which it never did. formation_split and _formation_split_rejections are gone, and the rejection catalog describes the three new codes. The docstrings that called ReorderParty the only way to change the marching order now name the withdrawal too. No golden file moved: every golden test passes unchanged. Claude-Session: https://claude.ai/code/session_01V7ErtnsmhaP7aLwcfmzohD
Brings in #191 (AddMember), #192 (attack routines), and #193 (default weapon, device and turning re-check). The round's validation now applies the default weapon to each declaration first, then plans the withdrawals and the projected front rank from those declarations, and passes front_rank to _validate_declaration. Monster melee passes both the per-member retreat exposure and the action's routine. _party_movement keeps its two-argument signature, because test_weapon_default_and_device_recheck.py wraps it with one. The swaps now run from _swap_ranks just before it in the party block's movement step, so the order of events is unchanged: swaps, then the formation's move or close. The party and session docstrings name both AddMember and the withdrawal swap. The changelog keeps every bullet, and the 2.0.1 replay bullet carries main's cases and the formation retreat case. No golden file moved beyond the contract's replacement_id lines. Claude-Session: https://claude.ai/code/session_01V7ErtnsmhaP7aLwcfmzohD
…ingle-withdrawal) The movement step judges a swap again before it trades two members' places. A replacement who can no longer act on the party's behalf by then, because they are incapacitated, charmed, or confused, doesn't step up. Nothing reports the swap, the marching order stays, and the replacement's melee attack doesn't resolve. New acceptance tests, marked expected-to-fail, cover a charmed and a confused replacement. A wrapper around _swap_ranks sets the condition at the start of the movement step. An unmarked guard covers a paralysed replacement, which the implementation already handles. The register now says this in its movement-step sentence. It also says that any earlier withdrawing member's naming reserves the replacement, even when that member's own claim is refused, which matches the code. Claude-Session: https://claude.ai/code/session_01V7ErtnsmhaP7aLwcfmzohD
A replacement has to be able to act on the party's behalf: not incapacitated, charmed, or confused. One predicate, _acts_for_party, states that. _can_step_up asks it in the validation pre-phase, so a replacement who is already charmed or confused is refused with replacement_unavailable and the refusal takes no draw. _swap_ranks asks it again in the movement step, so a replacement who became charmed or confused after the round was accepted stays where they are. Nothing reports the swap, and their melee attack doesn't resolve. The MemberWithdrewEvent, _swap_ranks, and BattleDeclaration.replacement_id docstrings and the replacement_unavailable description state the rule. No golden file moved. Claude-Session: https://claude.ai/code/session_01V7ErtnsmhaP7aLwcfmzohD
Claude-Session: https://claude.ai/code/session_01V7ErtnsmhaP7aLwcfmzohD # Conflicts: # CHANGELOG.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Players of a game built on osrlib wanted to pull one badly hurt character out of the front rank without the whole party leaving the fight. osrlib refused any round where only some members declared a fighting withdrawal or a retreat. The SRD describes both as one combatant's move, so this makes them per-member.
What changed
BattleDeclaration.replacement_id. The two trade places in the party's movement step, and the new order stays after the battle. A newMemberWithdrewEventreports each swap.battle.declaration.formation_splitis gone. Three new codes replace it:no_replacement,replacement_unavailable, andreplacement_too_far.Fixed along the way
A whole-party retreat now gives the monsters +2 to hit and ignores shields for the whole round, as the SRD says ("This round: ..."). Before, monsters that won initiative and attacked before the party moved off got no bonus. A confused monster attacking a retreating member now gets it too.
What to check
tests/goldens/phase5_milestone.jsonchanges only by"replacement_id": nullon each logged declaration. No outcome changed.Fixes #171.
https://claude.ai/code/session_01V7ErtnsmhaP7aLwcfmzohD