Vigil
← All news

v1.4.1 — cactbot Stage 2.1 polish (multi-cast assignment + comment-block fallback names)

- `timeline_diff_for_fight` previously had each fight_model row pick the closest cast independently — when ability X had N rows and M casts, multiple rows could pick the same cast…

Fixed — multi-cast assignment collision

  • timeline_diff_for_fight previously had each fight_model row pick the closest cast independently — when ability X had N rows and M casts, multiple rows could pick the same cast or distant casts. Now per-ability matching: sort rows by expected time, then for each row pick the nearest unused cast and consume it. Optimal for the common case of monotonic timelines; degrades gracefully when there are more rows than casts (extras marked missing).
  • 2 new regression tests in tests/test_timeline_diff.py: test_multi_cast_no_collision, test_multi_cast_extra_rows_marked_missing.

Fixed — adds-phase / sub-cast abilities now get human labels

  • Cactbot timelines document many abilities in two places the old parser missed:
    • Trailing # <HEX> <Name> comment block at the bottom of the file (sub-cast VFX, etc.). Used by FRU + Heavyweight tier.
    • Commented-out <time> "<label>" #Ability { id: ... } lines scattered through the file body (sub-cast effects with labels). Used heavily by TOP / DSR.
  • Parser now harvests both into ParsedTimeline.fallback_names: dict[ability_id, str]. The annotator uses fallback names when no timeline-body entry matches. Fallback annotations get a label but no cactbot_expected_t_ms (no firm expected timing).
  • New result counter: annotated_fallback. UI / API consumers see cactbot_label populated where it was previously null.
  • Loosened the #Ability regex to accept <time> "<label>" duration N #Ability {...} (TOP-style) by allowing optional intermediate tokens between the closing quote and the #Ability marker.
  • 4 new tests in tests/test_cactbot.py: test_fallback_names_parsed_from_comment_block, test_real_fru_comment_block_picks_up_hiemal_ray, test_commented_ability_lines_become_fallback_names, test_annotate_uses_fallback_name_when_no_body_entry.

Live AC — annotation coverage after the fixes

EncounterBefore (1.3.0)After (1.4.1)Δ
M9S38% (16/42)93% (39/42)+55pp
M10S52% (32/62)94% (58/62)+42pp
M11S63% (52/83)94% (78/83)+31pp
M12S50% (28/56)84% (47/56)+34pp
M12S-P262% (45/72)93% (67/72)+31pp
FRU55% (79/144)94% (135/144)+39pp
TOP48% (63/132)56% (74/132)+8pp (cosmetic-heavy remainder)
DSR46% (75/162)72% (116/162)+26pp

340 tests passing (334 → 340, +6).

Known limitation still standing (not in scope of this fix)

  • Multi-ID slot mapping. When cactbot writes Ability { id: ["X", "Y"] } (random-variant mechanics like Sinsmoke/Sinsmite), our fight_model has separate rows per ability_id. A pull where the "Y" variant fired first leaves the "X" row hunting the second-position cast, producing misleading drift like the FRU Sinsmoke +42.8s case. Fixing requires structural changes (slot-level matching where one cactbot slot maps to N possible fight_model rows). Documented for later.