Add monster HP tracking, separate from the game-ending logic

Only the player character's HP was tracked before, so a monster's
death was never detected — only the DM's own (unenforced) narration
of it. Add a new update_monster_hp tool the DM calls whenever an
NPC/monster is introduced or takes damage, stored as DM-only
bookkeeping on Game (never exposed via GameRead, since HP/AC of NPCs
is meant to stay secret from players). The tool reports back
"defeated": true once HP drops to 0 so the DM can react to it — but
unlike a character dying, this does NOT auto-end the session; whether
a monster's death should end the game is a separate decision left to
the DM's own end_game call, or to a future rule.

Building an isolated test for this caught a real bug along the way:
the update mutated the matched monster's dict in place before
reassigning the list, which made SQLAlchemy's old-vs-new JSONB
comparison see identical content and silently skip writing the
change. Fixed by building fresh dicts instead of mutating shared ones.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Thorsten
2026-09-01 17:50:39 +02:00
parent 8da834c5c1
commit 2b2af058b8
5 changed files with 91 additions and 2 deletions
@@ -0,0 +1,32 @@
"""add game monsters (DM-only HP tracking)
Revision ID: d4b2c8a09e51
Revises: c9a1d5e7f316
Create Date: 2026-09-01 17:00:00.000000
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision: str = 'd4b2c8a09e51'
down_revision: Union[str, None] = 'c9a1d5e7f316'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
op.add_column(
'games',
sa.Column(
'monsters', postgresql.JSONB(astext_type=sa.Text()), nullable=False,
server_default='[]',
),
)
def downgrade() -> None:
op.drop_column('games', 'monsters')