Skip to content

feat: complete mega-damage combat exchange#55

Merged
StreamDemon merged 25 commits into
mainfrom
feat/mdc-combat-exchange
Jul 21, 2026
Merged

feat: complete mega-damage combat exchange#55
StreamDemon merged 25 commits into
mainfrom
feat/mdc-combat-exchange

Conversation

@StreamDemon

@StreamDemon StreamDemon commented Jul 21, 2026

Copy link
Copy Markdown
Owner

Summary

Completes the core S.D.C./M.D.C. hostile-combat boundary through the existing persisted exchange protocol.

  • adds page-stamped conversion, armor, depleted-shell, and final-blast rules from rendered RUE pp. 287, 288, and 354-359
  • authorizes owned M.D. weapons without adding a parallel resolver or client-authored tier
  • persists versioned stopped, armor, body, and fatal route evidence atomically through Convex
  • renders legal M.D. selection, explicit units/history, and a readable terminal dossier in SolidJS
  • keeps manual damage S.D.C.-only and leaves optional near-fatal injury/survival out of this slice

Architecture and authority

The rules package remains pure and deterministic. It classifies attack/protection tiers, multiplies critical damage before conversion, applies the complete routing matrix, and returns immutable before/after evidence. Convex continues to own dice, current-state rederivation, synchronization, exactly-once character writes, and exchange history. The client chooses only legal IDs/context/response actions; it never authors tiers, conversions, routes, rolls, or after-values.

Compatibility and fatal boundary

Legacy Issue #44 resolved routes remain accepted and formatted without migration. Old or racing pending exchanges stale before new dice or character writes, and duplicate responses cannot double-apply damage.

Exactly -P.E. remains a survivable coma state. Raw H.P. below -P.E. persists S.D.C. 0, H.P. at the legal floor, and current.lifeState = "dead". Gameplay mutations reject terminal characters while the dossier, inventory, narrative, history, and cancellation cleanup remain available.

Fresh automated verification

Recorded 2026-07-22 (Asia/Singapore), with package task caching disabled:

  • vp run --no-cache @riftforge/rules#check: 60/60 formatted; 46/46 clean
  • vp run --no-cache @riftforge/rules#test: 17/17 files; 358/358 tests
  • vp run --no-cache @riftforge/backend#check: 13/13 formatted; 15/15 clean
  • vp run --no-cache @riftforge/backend#test: 3/3 files; 128/128 tests
  • vp run --no-cache @riftforge/web#check: 37/37 formatted; 33/33 clean
  • vp run --no-cache @riftforge/web#test: 3/3 files; 52/52 tests
  • vp check: 130/130 formatted; 95/95 clean
  • vp test: 23/23 files; 538/538 tests
  • git diff --check: clean

Live acceptance and catalog limit

Live acceptance ran 2026-07-21 (Asia/Singapore) with attacker j5765xjcskpg5jwrj32qx3br9d8azgwe and defender j57b5e6myeyxxhffcqwy09kg8n8ayxw9.

Verified through the production UI: legal M.D. selection, native M.D. armor ablation, final-blast absorption with no body spill, depleted-shell strike totals 7/8, sub-100 S.D.C. imperviousness, M.D.-to-body fatal conversion, legacy and version-2 history together, terminal dossier behavior, cleanup cancellation, and same-document dead-to-living parameter navigation. Desktop 1440px and narrow 390x844 acceptance had no horizontal overflow, and the fresh browser console had zero errors and zero warnings. Ports 5173 and 3210 were closed after cleanup.

The legal catalog has no S.D.C. armor and no S.D.C. attack profile capable of 100+ completed damage. The 100+ S.D.C.-against-M.D.C. conversion rows and M.D.-against-S.D.C.-armor A.R. rows are therefore verified by exact pure-rules and atomic-backend tests, not by browser UI. No temporary or invented rules content was added to manufacture those scenarios.

Closes #51
Follow-up #54
Roadmap #53


Summary by cubic

Completed full S.D.C./M.D.C. combat exchange with deterministic routing, armor rules, and terminal life-state persistence. UI shows tiered outcomes, disables gameplay for dead characters, and the backend enforces readiness with atomic history.

  • New Features

    • @riftforge/rules: unified S.D.C./M.D. attacks with v2 tiered routes (critical-before-conversion, 100 S.D.C. per M.D., ≥8 strike bypass for depleted M.D.C., final-blast absorption) and terminal life-state via applyBodyDamage.
    • @riftforge/backend: validate/persist tiered routes; reject dead combatants; require rolled M.D.C. armor capacity; block updates and rollVitals for dead; atomic Convex writes; legacy and v2 history coexist.
    • @riftforge/web: clear labels/tones for tiered outcomes; disable gameplay with a friendly reason; terminal dossier; responsive nav on narrow viewports; data-URI favicon to avoid a network request; tests fail loudly when source contracts point to missing files.
  • Migration

    • Roll worn M.D.C. armor capacity before declaring combat.
    • Dead characters cannot perform gameplay actions; dossier and history remain accessible.

Written for commit 1d23ada. Summary will update on new commits.

Review in cubic

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change extends persisted combat from S.D.C.-only exchanges to tiered S.D.C./M.D.C. routing, adds deterministic coma/death handling with a persisted dead marker, preserves legacy route history, enforces terminal backend behavior, and updates the web dossier and combat history presentation.

Changes

M.D.C. Combat Exchange

Layer / File(s) Summary
Rules content and terminal life state
packages/rules/src/content/combat/combat-exchange.json, packages/rules/src/engine/character.ts, packages/rules/src/engine/combat.ts, packages/rules/src/schema/*, packages/rules/tests/*
Adds page-stamped M.D.C. constants, fatal-aware body damage with applyBodyDamage, derived lifeState (alive/coma/dead), persisted current.lifeState?: "dead" schema, and validation tests for life-state edge cases.
Tiered routing and exchange contracts
packages/rules/src/engine/combat-exchange.ts, packages/backend/convex/combat_values.ts, packages/rules/tests/combat-exchange.test.ts
Extends AttackProfile.damageType to include "md"; updates combat-state tokens to v2 with lifeState; replaces routeSdcHit with routeCombatHit returning TieredDamageRoute with versioned snapshots, conversion modeling, and stopped/fatal routing; validates both legacy and v2 routes for backward compatibility.
Backend terminal and atomic persistence
packages/backend/convex/character_state.ts, packages/backend/convex/characters.ts, packages/backend/convex/combat.ts, packages/backend/tests/*
Adds requireLiving guard blocking terminal mutations; checks M.D.C. armor readiness; implements structured disabledReason for targets (dead/armor-not-ready/not-ready); persists tiered outcomes via switch-based patching by route kind; ensures stale exchanges finalize before dice or writes.
Terminal dossier and tiered result presentation
apps/web/src/pages/character-sheet.tsx, apps/web/src/components/*, apps/web/src/lib/combat-exchange.ts, apps/web/tests/*
Computes gameplayDisabledReason from vitals.lifeState; disables gameplay controls (rolls, casting, combat declaration) while preserving history, cancellation, narrative, and inventory; adds epoch-based route ownership guards to prevent stale mutation results; formats tier-aware result labels via exchangeResultLabel and route evidence via formatTieredRoute.
Documentation and verification
.codex/superpowers/plans/2026-07-21-mdc-combat-exchange.md, .codex/superpowers/specs/2026-07-21-mdc-combat-exchange-design.md, README.md, apps/web/index.html, apps/web/src/layouts/app.tsx, apps/web/tests/character-sheet.test.ts
Documents cross-package implementation tasks, design rationale, acceptance gates, and live-browser scenarios; updates README architecture section with tiered exchange details; adds responsive layout breakpoint for mobile navigation; includes source-contract assertions for terminal dossier behavior and route-ownership reset.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Attacker
  participant ConvexCombat
  participant RulesEngine
  participant Defender
  participant WebDossier
  Attacker->>ConvexCombat: declare or respond to attack
  ConvexCombat->>ConvexCombat: require both combatants living
  ConvexCombat->>RulesEngine: resolve strike with damageType
  RulesEngine-->>ConvexCombat: tiered route with snapshots and lifeState
  ConvexCombat->>Defender: patch armor/sdc/hp or set lifeState dead
  ConvexCombat-->>WebDossier: return persisted exchange
  WebDossier-->>Attacker: render exchangeResultLabel and route evidence
Loading
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The favicon and responsive layout tweaks are unrelated to #51's combat exchange scope. Split the favicon/layout polish into a separate PR or remove it so this change stays focused on combat exchange work.
Docstring Coverage ⚠️ Warning Docstring coverage is 15.38% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes align with #51 by adding tiered M.D.C. routing, terminal outcomes, Convex persistence, UI presentation, and #44 compatibility.
Title check ✅ Passed It clearly summarizes the main change: completing the mega-damage combat exchange.
Description check ✅ Passed It describes the combat exchange, backend, UI, and compatibility changes in the PR.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

@StreamDemon
StreamDemon marked this pull request as ready for review July 21, 2026 18:50
@StreamDemon

Copy link
Copy Markdown
Owner Author

@CodeRabbit Please re-review this PR.

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

@StreamDemon: I’ll perform a complete re-review of PR #55.

✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
apps/web/tests/character-sheet.test.ts (1)

8-11: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Make source() fail loudly on a missing file.

Returning "" for a missing path means every .not.toContain(...) assertion (e.g. Line 84, Line 138) passes vacuously if a source file is ever renamed or moved, silently weakening these contract tests. Throwing keeps the suite honest.

♻️ Proposed change
 function source(relative: string): string {
   const url = new URL(relative, import.meta.url);
-  return existsSync(url) ? readFileSync(url, "utf8") : "";
+  if (!existsSync(url)) throw new Error(`Missing source under test: ${relative}`);
+  return readFileSync(url, "utf8");
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/tests/character-sheet.test.ts` around lines 8 - 11, Update the
source() helper to throw an error when the resolved URL does not exist instead
of returning an empty string; retain readFileSync for existing files so the
contract assertions continue validating the intended source content.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@apps/web/tests/character-sheet.test.ts`:
- Around line 8-11: Update the source() helper to throw an error when the
resolved URL does not exist instead of returning an empty string; retain
readFileSync for existing files so the contract assertions continue validating
the intended source content.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7cc19d95-52c8-4248-b494-202934139ea8

📥 Commits

Reviewing files that changed from the base of the PR and between 3067c99 and 4f5f0bb.

📒 Files selected for processing (27)
  • .codex/superpowers/plans/2026-07-21-mdc-combat-exchange.md
  • .codex/superpowers/specs/2026-07-21-mdc-combat-exchange-design.md
  • README.md
  • apps/web/index.html
  • apps/web/src/components/combat-exchange-panel.tsx
  • apps/web/src/components/sheet-view.tsx
  • apps/web/src/layouts/app.tsx
  • apps/web/src/lib/combat-exchange.ts
  • apps/web/src/pages/character-sheet.tsx
  • apps/web/tests/character-sheet.test.ts
  • apps/web/tests/combat-exchange.test.ts
  • packages/backend/convex/character_state.ts
  • packages/backend/convex/characters.ts
  • packages/backend/convex/combat.ts
  • packages/backend/convex/combat_values.ts
  • packages/backend/convex/schema.ts
  • packages/backend/tests/characters.test.ts
  • packages/backend/tests/combat.test.ts
  • packages/rules/src/content/combat/combat-exchange.json
  • packages/rules/src/engine/character.ts
  • packages/rules/src/engine/combat-exchange.ts
  • packages/rules/src/engine/combat.ts
  • packages/rules/src/schema/character.ts
  • packages/rules/src/schema/combat-exchange.ts
  • packages/rules/tests/character.test.ts
  • packages/rules/tests/combat-exchange.test.ts
  • packages/rules/tests/combat.test.ts

@StreamDemon

StreamDemon commented Jul 21, 2026

Copy link
Copy Markdown
Owner Author

Resolved the CodeRabbit source-contract nit in commit 1d23ada. Both duplicated helpers now throw with the missing relative path instead of returning an empty string, so negative assertions cannot pass vacuously. TDD evidence: RED 2/48, GREEN 48/48; web package 54/54; root 540/540; vp check clean.

@StreamDemon
StreamDemon merged commit 77bcd8a into main Jul 21, 2026
3 checks passed
@StreamDemon
StreamDemon deleted the feat/mdc-combat-exchange branch July 21, 2026 20:10
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.

Full M.D.C. combat: tier interaction, armor ablation, and unprotected outcomes

1 participant