Skip to content

Use every attack routine a monster has in battle - #192

Merged
mmacy merged 4 commits into
mainfrom
chunk/attack-routines
Oct 30, 2026
Merged

mmacy merged 4 commits into
mainfrom
chunk/attack-routines

Conversation

@mmacy

@mmacy mmacy commented Oct 30, 2026

Copy link
Copy Markdown
Owner

Battle used only the first attack routine in a monster's stat block. A thoul never used a weapon, a mule never bit, an elephant never trampled, and an effect in a later routine never applied. Now battle uses every routine.

What changed

  • The SRD compiler labels each routine with a RoutineUse:

    • PRIMARY: the first routine.
    • ALTERNATIVE: a free choice the OSE SRD prints with "or". Five monsters have one: the spitting cobra, thoul, mule, minotaur, and wererat.
    • SITUATIONAL: tied to something osrlib doesn't model, like a trample, a thrown boulder, or a ship. There are 24.
    • SPECIAL: a breath, a gaze, or a spell. There are 21.

    The compiler stops the build when a later routine has no label, so a new SRD monster can't arrive unlabelled. Each table entry quotes the SRD text behind it.

  • ScriptedPolicy picks a routine with one draw on the monster-action stream, among the primary and alternative routines. A monster with only one candidate makes no draw, so no golden file changed.

  • MonsterAction.routine lets a game's own policy name any routine, including a situational one. Battle never rolls a special routine, or an entry named gaze or breath, and it skips an index the template doesn't have.

  • The adaptations register, the RNG stream reference, and the changelog describe the rule.

What to check

  • The labels in tools/srd_compile/monsters.py. The test file's EXPECTED_USES table lists all 48 monsters that have more than one routine. The white ape's thrown rock is SITUATIONAL, as a missile attack. The SRD gives it no situation either way.
  • A routine in a game's own template has no label, so it's PRIMARY, and the default policy picks it as often as the first. The changelog tells game developers to label later routines.
  • Confused monsters still attack with their first routine.

https://claude.ai/code/session_01V7ErtnsmhaP7aLwcfmzohD

mmacy added 4 commits October 30, 2026 14:11
Battle resolves only a monster's first attack routine. This commit fixes the contract for using the others and the tests the implementation must pass.

RoutineUse labels each routine. The first is primary. A later one is alternative when the OSE SRD offers it as a free choice each round, situational when the SRD ties it to a situation osrlib doesn't model, and special when it is a breath, a gaze, or a spell. AttackRoutine.use carries the label and MonsterAction.routine names a routine by index. Both are stubs with no behavior yet.

The generated data is regenerated through the compiler because the new field changes the dump. Every routine now reads "use": "primary" until the implementation labels the later ones.

The tests pin the label of every routine in the catalog, a compiler that refuses an unlabelled later routine, a default policy that picks only among primary and alternative routines, and a game policy that can name a situational routine. They also pin that a special routine or an out-of-range index rolls nothing and that a replay gives the same events. The tests that fail today are marked as expected failures for the attack-routines chunk. The tests that pass today guard behavior the chunk must keep, such as a monster without an alternative drawing exactly as before.

Claude-Session: https://claude.ai/code/session_01V7ErtnsmhaP7aLwcfmzohD
Battle resolved only a monster's first attack routine, so a thoul never used a weapon, a mule never bit, and an effect in a later routine never applied. Now every routine carries a label, the default policy picks among the free choices, and a game's own policy can name any routine.

The SRD compiler labels every routine after the overrides apply, so the bone golem's override-split routines are labelled too. A later routine whose attacks are a breath, a gaze, or a spell is special by its name. Every other later routine takes its label from the _ROUTINE_USES table in tools/srd_compile/monsters.py, keyed by template id and the routine's attack name, with the OSE SRD text that justifies it. The compiler fails the build on a later routine with no label, naming the monster, and the conjured monsters on spell pages go through the same function. monsters.json was regenerated with the compiler, and only the labels of later routines changed. spells.json didn't change, because the conjured snake has one routine.

ScriptedPolicy picks a melee routine after its target, on the monster-action stream, with equal chance among the primary and alternative routines. A monster with one such routine draws nothing for the pick. Battle resolves the routine a melee action names. It resolves nothing for a special routine or an index the template doesn't have, and it never rolls an attack entry named gaze or breath. A confused monster's attack still uses its first routine.

No golden file moved. None of the goldens contains a monster with an alternative routine, and every other monster draws exactly as before.

Claude-Session: https://claude.ai/code/session_01V7ErtnsmhaP7aLwcfmzohD
@mmacy
mmacy force-pushed the chunk/attack-routines branch from 4f8eff7 to 99124fc Compare October 30, 2026 21:11
@mmacy
mmacy merged commit 0dd6b0c into main Oct 30, 2026
5 checks passed
@mmacy
mmacy deleted the chunk/attack-routines branch October 30, 2026 21:13
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.

1 participant