Skip to content

Stop recording an NMR for a forced full disband - #1315

Open
johnpooch wants to merge 1 commit into
mainfrom
claude/nmr-automatic-disbands-bug-68ai5p
Open

Stop recording an NMR for a forced full disband#1315
johnpooch wants to merge 1 commit into
mainfrom
claude/nmr-automatic-disbands-bug-68ai5p

Conversation

@johnpooch

Copy link
Copy Markdown
Owner

What this PR does

Fixes #1310. A player with units and no supply centres must disband every unit they have, and the engine fills those disbands in for them — but they were still recorded as having NMRed, shown an NMR badge on their way out of the game, and charged an NMR extension that pushed the deadline out for everyone.

_adjustment_options emits a Disband option per unit whenever required_disbands() > 0 (service/adjudicator/options.py:485-505), so has_possible_orders stayed True and _set_orders_outcome marked orders_outcome=nmr on zero submitted orders. EnforceDisbandLimits / ApplyCivilDisorder (service/adjudicator/engine.py:154-168) fill the missing disbands in anyway, so the board resolves identically whether or not the player submitted anything.

required_disbands() is max(0, units - supply_centres) (service/adjudicator/types.py:1121-1124), so it equals the unit count exactly when the nation holds no supply centres. New Phase.nations_with_forced_orders derives that set from the same counts actionable_units already walks, and three call sites now skip those nations:

  • _set_orders_outcome — leaves orders_outcome null rather than nmr
  • members_that_require_nmr_extension — no extension consumed
  • send_deadline_warnings — no "you'll lose an extension" warning, which would no longer be true

A partial disband (3 units, 1 supply centre, where which two units go is a real choice) still counts as an NMR. Movement and Retreat phases are untouched.

Leaving orders_outcome null matches how a phase state with has_possible_orders=False is already treated, so the phase drops out of commitment rating via the orders_outcome__isnull=False filter in get_rated_outcomes (service/user_profile/commitment.py:49-53).

Two things worth a reviewer's opinion

  1. The UI still says orders are required. orderStatus and PhaseGuidance derive from has_possible_orders / the SC-vs-unit delta, so a forced-disband player still sees "Disband 2 units" and an "Orders required" badge while the backend now asks nothing of them. Surfacing "forced" to the client means a serializer field plus codegen across both schemas, which felt like scope beyond the issue — happy to do it here or in a follow-up.
  2. The retreat case looks like the same bug. _retreat_options appends a Disband option for every dislodged unit regardless of whether a legal retreat exists (service/adjudicator/options.py:396-405), so a player whose dislodged units all have nowhere to go is in the identical position. Issue Forced full disband in an Adjustment phase is recorded as an NMR #1310 scopes to Adjustment, so I left it — but it's probably worth its own issue.

Checklist

  • This PR does one thing — no unrelated fixes, refactors, or drive-by cleanups bundled in
  • For PRs of any significant complexity: I ran /review-pr against this PR in Claude Code and addressed (or responded to) its findings
  • Tests cover the change
  • Screenshots embedded in the PR description for any visual changes (see CLAUDE.md) — n/a, backend only

Seven tests added across TestSetOrdersOutcome, TestNMRExtensionsNothingToOrder and TestSendDeadlineWarnings. The four that pin the bug fail on main and pass here; full backend suite is green (2356 passed, 8 skipped).

🤖 Generated with Claude Code

https://claude.ai/code/session_01RjyUirJePgFH9Q2zPWSp5Z


Generated by Claude Code

A nation with units and no supply centres must disband every unit it has.
_adjustment_options emits a Disband option per unit whenever
required_disbands() > 0, so has_possible_orders stayed True and
_set_orders_outcome marked orders_outcome=nmr on zero submitted orders.
The engine's EnforceDisbandLimits fills the disbands in regardless, so the
resolution is identical either way, but the player was shown an NMR badge
on their way out of the game and burned an NMR extension that pushed the
deadline out for everyone.

required_disbands equals the unit count exactly when the nation holds no
supply centres, so derive the forced set from the counts already used for
actionable_units and exclude those nations from the orders outcome, from
NMR extensions, and from the deadline warning that would otherwise promise
an extension they no longer get. A partial disband, where which units go is
a real choice, keeps counting as an NMR.

Leaving orders_outcome null matches how a phase state with no possible
orders is already treated, so the phase drops out of commitment rating via
the orders_outcome__isnull=False filter in get_rated_outcomes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RjyUirJePgFH9Q2zPWSp5Z
@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Warning

WIP limit exceeded. There are now 8 open pull requests — the project target is 5 or fewer.

Please close or merge an existing PR before continuing with this one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Forced full disband in an Adjustment phase is recorded as an NMR

2 participants