v1.5.5 — Hungarian multi-cast assignment
- New helper module [analysis/_assignment.py](analysis/_assignment.py): `min_cost_assignment(cost, *, skip_penalty)` — pure-Python O(n³) Jonker-Volgenant solver for rectangular bi…
Changed — timeline-diff slot-to-cast matching is now globally optimal
- New helper module analysis/_assignment.py:
min_cost_assignment(cost, *, skip_penalty)— pure-Python O(n³) Jonker-Volgenant solver for rectangular bipartite assignment with forbidden-pair support via a per-rowskip_penalty. No new dependencies (scipy would be a 50 MB add for one algorithm). - analysis/timeline_diff.py now builds a
slots × castscost matrix per phase (cost = absolute drift in ms, orSKIP_PENALTY = 10× phase durationfor forbidden ability-set pairings) and calls the new solver. Replaces the v1.4.1 nearest-unused per-slot greedy. - Greedy was optimal for monotonic same-ability sequences (typical case) but mis-assigned when several slots had overlapping multi-ID candidate pools and a closer-in-time slot ate a cast that a later slot needed. Hungarian finds the globally-min-drift assignment that also maximizes matched slots.
- Live AC on FRU fight 1500: cascade-drift signal preserved exactly vs v1.5.0 baseline (P1 +0.9s / P2 +10.9s / Adds -13.0s / P3 -14.0s / P4 +7.8s / P5 +11.1s). Fired-counts per phase unchanged or slightly higher.
- 10 new tests in tests/test_assignment.py covering empty matrix, square, rectangular both ways, forbidden-pair, all-forbidden, single-row, and the motivating multi-cast pathological case (slot A accepts {X,Y}, slot B accepts {X} only — greedy strands B, Hungarian swaps). 360 tests passing (350 → 360).