From 6c39188027e4b7e91dfbc48c2461b33275a5f04d Mon Sep 17 00:00:00 2001 From: pavel444-byte Date: Wed, 26 Aug 2026 08:45:50 +0500 Subject: [PATCH] feat(gameplay): add versioned SRD rules migration Co-authored-by: multica-agent --- README.md | 4 +- apps/web/composables/useDemo.ts | 6 +- apps/web/pages/campaign/[id].vue | 22 +- apps/web/server/api/v1/campaigns/[id].get.ts | 2 +- .../api/v1/campaigns/[id]/characters.post.ts | 28 +- .../campaigns/[id]/characters/suggest.post.ts | 2 +- apps/web/server/api/v1/campaigns/index.get.ts | 2 +- apps/worker/src/ai.test.ts | 3 + apps/worker/src/ai.ts | 4 +- apps/worker/src/index.ts | 15 +- apps/worker/src/orchestration.ts | 6 +- packages/game-engine/src/index.test.ts | 75 +++- packages/game-engine/src/index.ts | 217 ++++++++++- packages/shared/src/character.test.ts | 3 + packages/shared/src/index.test.ts | 19 +- packages/shared/src/index.ts | 66 +++- supabase/bootstrap.sql | 359 +++++++++++++++++- .../migrations/0012_versioned_srd_rules.sql | 352 +++++++++++++++++ 18 files changed, 1143 insertions(+), 42 deletions(-) create mode 100644 supabase/migrations/0012_versioned_srd_rules.sql diff --git a/README.md b/README.md index 60b83a6..2b0cfa8 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,8 @@ To verify the complete hosted-Supabase multiplayer path (two temporary email acc Before starting either process, the root commands check the Supabase schema. If it is missing and `SUPABASE_DB_URL` is configured, they transactionally create all D&G tables, functions, triggers, indexes, and policies from `supabase/bootstrap.sql`. Copy the **Session pooler** URI from the Supabase Dashboard **Connect** panel into the root `.env` and replace the password placeholder with the URL-encoded database password. The database URL is server-only and must never use a `NUXT_PUBLIC_` prefix. +Existing campaigns are upgraded automatically to ruleset version 2 by `pnpm db:ensure`. The migration preserves every round, narration, current HP, inventory, and status; adds the SRD 5.2.1 character rules profile; and records one `migrate_ruleset` audit entry per campaign. Migrated characters retain compatibility with their previous proficiency hints, while newly created characters use explicit server-owned skill, expertise, and saving-throw proficiencies. + The web and worker commands both load this root `.env` file explicitly. Environment variables supplied by Railway or CI take precedence over values in the file. The UI can be built without credentials. A running Nitro server and every worker process require `SUPABASE_URL`, `SUPABASE_SERVICE_ROLE_KEY`, `NUXT_PUBLIC_SUPABASE_URL`, and `NUXT_PUBLIC_SUPABASE_ANON_KEY`; startup fails with a clear error when required server credentials are missing. Redis is optional: when `REDIS_URL` is absent, the worker safely claims jobs from the Supabase `ai_jobs` outbox using short database leases and retries failed jobs up to three times. @@ -41,7 +43,7 @@ Use Node.js 18.20.5 or newer. The lockfile pins the web toolchain to the Node 18 - Worlds are private by default. - Content is limited to 13+; explicit sexual content and sexual content involving minors are rejected. - AI responses are parsed against strict schemas. -- AI can propose checks and events, but cannot directly set dice outcomes or mechanical values. +- AI can propose checks and events, but cannot directly set dice outcomes, proficiency, temporary HP, death saves, resources, or other mechanical values. - SRD-derived work must retain the attribution in `LEGAL.md`. ## Production services diff --git a/apps/web/composables/useDemo.ts b/apps/web/composables/useDemo.ts index 3ddfe2d..4ded412 100644 --- a/apps/web/composables/useDemo.ts +++ b/apps/web/composables/useDemo.ts @@ -17,9 +17,9 @@ const defaultWorld: WorldStarter = { { id: 'fac_quiet', kind: 'faction', name: 'The Quiet Choir', summary: 'Pilgrims who believe signals can remember the dead.', tags: ['mystic'], secrets: [] }, ], companions: [ - { name: 'Rook-7', concept: 'A security intelligence in a weathered rescue frame.', abilities: { str: 16, dex: 11, con: 15, int: 10, wis: 12, cha: 8 }, hp: 15, maxHp: 15, defense: 15, proficiency: 2, inventory: ['Arc baton', 'Emergency beacon'], persona: { voice: 'Direct and protective.', motivation: 'Keep the crew alive long enough to learn the truth.', flaw: 'Treats uncertainty as a threat.', bond: 'The Orison crew gave Rook a second purpose.' } }, - { name: 'Sable Thread', concept: 'A probabilistic navigator who reads possible futures as tangled routes.', abilities: { str: 8, dex: 14, con: 10, int: 16, wis: 13, cha: 11 }, hp: 10, maxHp: 10, defense: 13, proficiency: 2, inventory: ['Route prism', 'Vacuum line'], persona: { voice: 'Careful, elliptical, and precise.', motivation: 'Find the future in which everyone returns.', flaw: 'Can hesitate when too many paths look viable.', bond: 'Trusts the party to choose what prediction cannot.' } }, - { name: 'Morrow Coil', concept: 'A former relay technician rebuilt around experimental signal hardware.', abilities: { str: 11, dex: 12, con: 13, int: 14, wis: 10, cha: 13 }, hp: 12, maxHp: 12, defense: 12, proficiency: 2, inventory: ['Signal probe', 'Insulated field coat'], persona: { voice: 'Warm humor under technical jargon.', motivation: 'Prove the relay can be understood instead of destroyed.', flaw: 'Cannot leave a broken machine alone.', bond: 'Believes this party is the last honest crew in the sector.' } }, + { name: 'Rook-7', concept: 'A security intelligence in a weathered rescue frame.', abilities: { str: 16, dex: 11, con: 15, int: 10, wis: 12, cha: 8 }, hp: 15, maxHp: 15, defense: 15, proficiency: 2, inventory: ['Arc baton', 'Emergency beacon'], skillProficiencies: ['athletics', 'intimidation', 'perception', 'survival'], skillExpertise: [], savingThrowProficiencies: ['str', 'con'], persona: { voice: 'Direct and protective.', motivation: 'Keep the crew alive long enough to learn the truth.', flaw: 'Treats uncertainty as a threat.', bond: 'The Orison crew gave Rook a second purpose.' } }, + { name: 'Sable Thread', concept: 'A probabilistic navigator who reads possible futures as tangled routes.', abilities: { str: 8, dex: 14, con: 10, int: 16, wis: 13, cha: 11 }, hp: 10, maxHp: 10, defense: 13, proficiency: 2, inventory: ['Route prism', 'Vacuum line'], skillProficiencies: ['arcana', 'investigation', 'perception', 'stealth'], skillExpertise: ['investigation'], savingThrowProficiencies: ['dex', 'int'], persona: { voice: 'Careful, elliptical, and precise.', motivation: 'Find the future in which everyone returns.', flaw: 'Can hesitate when too many paths look viable.', bond: 'Trusts the party to choose what prediction cannot.' } }, + { name: 'Morrow Coil', concept: 'A former relay technician rebuilt around experimental signal hardware.', abilities: { str: 11, dex: 12, con: 13, int: 14, wis: 10, cha: 13 }, hp: 12, maxHp: 12, defense: 12, proficiency: 2, inventory: ['Signal probe', 'Insulated field coat'], skillProficiencies: ['arcana', 'history', 'investigation', 'persuasion'], skillExpertise: [], savingThrowProficiencies: ['int', 'cha'], persona: { voice: 'Warm humor under technical jargon.', motivation: 'Prove the relay can be understood instead of destroyed.', flaw: 'Cannot leave a broken machine alone.', bond: 'Believes this party is the last honest crew in the sector.' } }, ], hook: 'Dock with the silent relay, locate the source of the impossible distress call, and decide whether its warning should be believed.', hiddenThreat: 'A causality fracture is teaching the relay to choose which future becomes real.', diff --git a/apps/web/pages/campaign/[id].vue b/apps/web/pages/campaign/[id].vue index 647c837..b066639 100644 --- a/apps/web/pages/campaign/[id].vue +++ b/apps/web/pages/campaign/[id].vue @@ -1,5 +1,5 @@