Skip to content

fix(combat): honor participant side field as alias for isEnemy#56

Merged
Mnehmos merged 1 commit intomainfrom
fix/46-participant-side-field
Apr 20, 2026
Merged

fix(combat): honor participant side field as alias for isEnemy#56
Mnehmos merged 1 commit intomainfrom
fix/46-participant-side-field

Conversation

@Mnehmos
Copy link
Copy Markdown
Owner

@Mnehmos Mnehmos commented Apr 18, 2026

Summary

The combat_manage create schema only accepted isEnemy: boolean. Callers sending side: "enemy" (the natural shape based on the runtime hint) were silently ignored — Zod dropped the unknown field — and enemies came back with isEnemy: false, rendering as PCs with player turn prompts.

Fix

Add a side enum (party | enemy | hostile | ally | friendly | neutral) to the participant schema. A deriveIsEnemy helper coerces side to isEnemy at the consolidated-tool boundary; explicit isEnemy still wins when both are provided. The underlying handleCreateEncounter contract is unchanged.

Test plan

  • Two new regression tests:
    • side values (party/ally/enemy/hostile) map to correct isEnemy flags
    • explicit isEnemy wins over conflicting side
  • combat-manage suite: 23/23 pass
  • Full suite: 1891 passed, 6 skipped, 0 failed

Closes #46

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Combat participants can now be designated with an allegiance type (enemy, ally, hostile, friendly, neutral) for improved encounter setup flexibility.
  • Tests

    • Added regression tests to ensure participant allegiance mappings are correctly applied.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 18, 2026

📝 Walkthrough

Walkthrough

The ParticipantSchema now includes an optional side enum field as a convenience alias for isEnemy. A new deriveIsEnemy() helper normalizes participant data by mapping enum values (enemy/hostiletrue, others → false) while preserving explicit isEnemy when provided. The create action preprocesses participants to inject the derived isEnemy before forwarding to handleCreateEncounter.

Changes

Cohort / File(s) Summary
Core Combat Participant Normalization
src/server/consolidated/combat-manage.ts
Extended ParticipantSchema with optional side enum; added deriveIsEnemy() helper to map side values to isEnemy boolean with explicit isEnemy taking precedence; modified create action to preprocess and inject derived isEnemy into participants payload.
Combat Participant Tests
tests/server/consolidated/combat-manage.test.ts
Added two regression tests validating side field mapping: (1) side: "enemy"/"hostile" correctly maps to isEnemy: true; (2) explicit isEnemy overrides side enum value.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related issues

Poem

🐰 A rabbit hops through fields of strife,
Where sides were muddled, wrong was right—
But now the enemy stands tall and true,
While allies glow in friendlier hue,
No more shall foes wear friendly light! ⚔️

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the primary change: adding support for the participant side field as an alias for isEnemy in the combat-manage module.
Linked Issues check ✅ Passed The PR implementation fully addresses all three acceptance criteria from #46: schema enforces the side enum, engine respects the field via deriveIsEnemy helper, and tests validate side-to-isEnemy mapping with precedence logic.
Out of Scope Changes check ✅ Passed All changes are scoped to implementing the participant side field support and its coercion to isEnemy; no unrelated modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/46-participant-side-field

Comment @coderabbitai help to get the list of available commands and usage tips.

The combat_manage create schema only accepted isEnemy:boolean. Callers
following the consolidated tool's runtime hint sent side:"enemy" instead,
which Zod silently dropped. Enemies came back with isEnemy=undefined →
false, so they rendered with the PC glyph and the engine prompted
"PLAYER TURN" on enemy turns.

Adds a `side` enum (party/enemy/hostile/ally/friendly/neutral) on the
participant schema and a deriveIsEnemy helper. Convention:
  - explicit isEnemy wins
  - otherwise side="enemy"|"hostile" → isEnemy=true
  - otherwise → false

The transform happens at the consolidated-tool boundary; the underlying
handleCreateEncounter contract is unchanged.

Closes #46

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/server/consolidated/combat-manage.ts (1)

65-69: Consider centralizing side literals to prevent drift.

side values are currently repeated in schema and mapping logic; extracting shared constants improves maintainability.

Refactor sketch
+const PARTICIPANT_SIDES = ['party', 'enemy', 'hostile', 'ally', 'friendly', 'neutral'] as const;
+type ParticipantSide = (typeof PARTICIPANT_SIDES)[number];
+const ENEMY_SIDES: ReadonlySet<ParticipantSide> = new Set(['enemy', 'hostile']);

 const ParticipantSchema = z.object({
@@
-    side: z.enum(['party', 'enemy', 'hostile', 'ally', 'friendly', 'neutral']).optional(),
+    side: z.enum(PARTICIPANT_SIDES).optional(),
@@
-function deriveIsEnemy(p: { isEnemy?: boolean; side?: string }): boolean | undefined {
+function deriveIsEnemy(p: { isEnemy?: boolean; side?: ParticipantSide }): boolean | undefined {
     if (typeof p.isEnemy === 'boolean') return p.isEnemy;
     if (!p.side) return undefined;
-    return p.side === 'enemy' || p.side === 'hostile';
+    return ENEMY_SIDES.has(p.side);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server/consolidated/combat-manage.ts` around lines 65 - 69, The
deriveIsEnemy function duplicates literal side strings; centralize these values
by introducing a shared enum or constant (e.g., SIDES or SideEnum) and replace
the string checks in deriveIsEnemy with references to those constants, and
update any related schema/mapping code that uses 'enemy' or 'hostile' so all
code (including deriveIsEnemy) reads from the same symbol(s) to avoid drift.
tests/server/consolidated/combat-manage.test.ts (1)

124-140: Optionally extend matrix to include friendly and neutral.

This would fully exercise all enum values introduced in the schema.

Suggested test extension
                 participants: [
                     { id: 'pc-vela', name: 'Vela', initiativeBonus: 0, hp: 38, maxHp: 38, side: 'party', position: { x: 1, y: 1 } },
                     { id: 'pc-tobin', name: 'Tobin', initiativeBonus: 4, hp: 28, maxHp: 28, side: 'ally', position: { x: 1, y: 2 } },
+                    { id: 'pc-lyra', name: 'Lyra', initiativeBonus: 1, hp: 24, maxHp: 24, side: 'friendly', position: { x: 2, y: 2 } },
+                    { id: 'npc-warden', name: 'Warden', initiativeBonus: 0, hp: 30, maxHp: 30, side: 'neutral', position: { x: 3, y: 2 } },
                     { id: 'enemy-rurk', name: 'Rurk', initiativeBonus: 1, hp: 22, maxHp: 22, side: 'enemy', position: { x: 5, y: 5 } },
                     { id: 'enemy-mira', name: 'Mira', initiativeBonus: 2, hp: 16, maxHp: 16, side: 'hostile', position: { x: 6, y: 5 } }
                 ]
             }, ctx);

             const data = parseResult(result);
             expect(data.success).toBe(true);

             const byId = Object.fromEntries(
                 (data.participants as Array<{ id: string; isEnemy: boolean }>).map((p) => [p.id, p.isEnemy])
             );
             expect(byId['pc-vela']).toBe(false);
             expect(byId['pc-tobin']).toBe(false);
+            expect(byId['pc-lyra']).toBe(false);
+            expect(byId['npc-warden']).toBe(false);
             expect(byId['enemy-rurk']).toBe(true);
             expect(byId['enemy-mira']).toBe(true);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/server/consolidated/combat-manage.test.ts` around lines 124 - 140,
Extend the test participant matrix to include entries using the new enum sides
(e.g., side: 'friendly' and side: 'neutral') so the participants parsing logic
is exercised for all enum values; update the array passed into the tested call
(the list containing pc-vela, pc-tobin, enemy-rurk, enemy-mira) to add two new
objects with unique ids (e.g., 'pc-ally-friendly' and 'npc-neutral'),
appropriate names/hp/position, and verify their resulting flags in the byId map
(checking (data.participants).map(...).map key lookup)—ensuring assertions
validate the expected isEnemy values for these new sides as well.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/server/consolidated/combat-manage.ts`:
- Around line 65-69: The deriveIsEnemy function duplicates literal side strings;
centralize these values by introducing a shared enum or constant (e.g., SIDES or
SideEnum) and replace the string checks in deriveIsEnemy with references to
those constants, and update any related schema/mapping code that uses 'enemy' or
'hostile' so all code (including deriveIsEnemy) reads from the same symbol(s) to
avoid drift.

In `@tests/server/consolidated/combat-manage.test.ts`:
- Around line 124-140: Extend the test participant matrix to include entries
using the new enum sides (e.g., side: 'friendly' and side: 'neutral') so the
participants parsing logic is exercised for all enum values; update the array
passed into the tested call (the list containing pc-vela, pc-tobin, enemy-rurk,
enemy-mira) to add two new objects with unique ids (e.g., 'pc-ally-friendly' and
'npc-neutral'), appropriate names/hp/position, and verify their resulting flags
in the byId map (checking (data.participants).map(...).map key lookup)—ensuring
assertions validate the expected isEnemy values for these new sides as well.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a2613275-7223-48be-a259-c789bda248eb

📥 Commits

Reviewing files that changed from the base of the PR and between 2d9ddb9 and b56482f.

📒 Files selected for processing (2)
  • src/server/consolidated/combat-manage.ts
  • tests/server/consolidated/combat-manage.test.ts

@Mnehmos Mnehmos merged commit bee1566 into main Apr 20, 2026
4 checks passed
@Mnehmos Mnehmos deleted the fix/46-participant-side-field branch April 20, 2026 14:19
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.

combat_manage create: participant "side" field ignored, all combatants flagged isEnemy=false

1 participant