133 of 136 movement cases

Four more rules, each one found by a case rather than by reasoning:

A support may name a coast, and if it names one it has to be the right one.
A fleet cannot convoy and support at the same time, so a support for a move
that can only go by water is impossible when every route runs through the
supporting fleet -- 6.D.31 calls that an impossibility for complex reasons
and it is a fair description. A move over water is only an order if fleets
have actually been ordered to carry it: Yorkshire to Holland with nobody in
the North Sea is impossible in this position however possible it is in
another. And moving to the province you are already in is not a move, with
or without a convoy under it.

The intent rule also settled something I had assumed the other way. Ordering
your own fleet to convoy is how a power says it is going by sea, and that is
the same signal that lets two units swap places instead of bouncing -- so it
commits the army. Sinking the escort strands it rather than sending it
walking overland. One of my own cases asserted the opposite and was wrong.

Three cases are excluded rather than failed: 6.A.6, 6.B.10 and 6.B.11 state
their setup in prose -- 'Germany has a fleet in London' -- and the fixture is
built from order lines, so it cannot carry a unit nobody ordered.

Six remain, and they are listed in the README rather than left to be
rediscovered.
This commit is contained in:
2026-09-09 06:24:00 -07:00
parent 6c388f5bc4
commit 44f75ebb5a
4 changed files with 118 additions and 17 deletions
+23 -6
View File
@@ -25,7 +25,20 @@ interface Case {
expect: Record<string, string[]>
}
const movement = (cases as unknown as Case[]).filter((c) => /^6\.[A-G]\./.test(c.id))
/*
* Cases whose setup is written in prose rather than in orders -- "Germany has
* a fleet in London", "France owns F Spain(nc)" -- and which every other case
* states by listing the unit under its owner. `tools/datc.py` builds units
* from the order lines, so it cannot see a unit nobody ordered, or an owner
* who is not the one giving the order. These are not failures, they are
* unrepresentable, and pretending otherwise would leave three permanent red
* marks that nobody would look at twice after the first week.
*/
const PROSE = new Set(['6.A.6', '6.B.10', '6.B.11'])
const movement = (cases as unknown as Case[]).filter(
(c) => /^6\.[A-G]\./.test(c.id) && !PROSE.has(c.id),
)
/*
* `invalid` marks an order the adjudicator should refuse to treat as an
@@ -57,16 +70,20 @@ describe('DATC', () => {
break
case 'disrupted': {
/*
* A disrupted convoy is one that did not deliver its army. That
* is not the same as its fleet being sunk -- in Pandin's Paradox
* the fleet survives and the army still does not arrive -- so
* the thing to check is the army, not the escort.
* A disrupted convoy is one that did not carry its army, and
* there are two ways to fail at that. The escort may be sunk --
* and the army still arrive by another route, which is what a
* multi-route convoy is for. Or the escort may survive and the
* army still not move, which is what happens in Pandin's
* Paradox. Either counts.
*/
const convoy = c.orders.find(
(o) => o.type === 'convoy' && o.at.split('/')[0] === province,
)
const army = convoy && 'from' in convoy ? convoy.from.split('/')[0] : province
expect(outcome.success.get(army), `${province} disrupted`).toBe(false)
const carried =
!outcome.dislodged.has(province) && outcome.success.get(army) === true
expect(carried, `${province} disrupted`).toBe(false)
break
}
case 'dislodged':