fix(monsters): finish deleting experienceReward, column included

Design ruling R7 abolishes XP as a concept and says the column goes with
it, but no task in the plan actually dropped it -- the plan only dropped
characters.experience and combat_rewards.experience_granted. Task 9
removed experienceReward from the seed literals, leaving
monster_definitions.experience_reward as a NOT NULL column with no
default that nothing supplies. The first monster insert against a real
database would have failed on a constraint violation.

No suite here could have caught it: none of them connect to Postgres.

Drops the column in the slice migration (which has never been run, so
amending it in place is correct rather than stacking a second one),
removes the entity field, and clears the three test fixtures that still
set it. silver_min/silver_max deliberately stay -- spec 15 keeps a
direct currency drop available as a lore-valid exception, and XP has no
such carve-out.

Also retargets the seed idempotency test off renown: 1, which is the
seed's own default and so could not distinguish "preserved" from
"reset to default".

NOTE ON SCOPE: this commit also absorbs a Prettier reformatting pass
that was already sitting uncommitted in the working tree, which is why
it touches ~59 files. That churn is purely cosmetic line-rewrapping --
verified by inspection, and the suite is green at 267/267 with the build
at exactly the 3 expected errors owned by Tasks 10 and 11. The repo is
not Prettier-clean at baseline (119 files still flagged), so this was a
partial run by an earlier step, not a deliberate repo-wide format.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Bastian Wagner
2026-08-21 14:07:47 +02:00
parent dce26e00ad
commit 8835671657
59 changed files with 1368 additions and 399 deletions

View File

@@ -35,8 +35,12 @@ describe('CreateRenownAndReputation1791000000000', () => {
it('backfills renown from level BEFORE dropping the level column', async () => {
const up = await runUp();
const backfillIndex = up.findIndex((sql) => sql.includes('LEAST(GREATEST("level", 1), 15)'));
const dropLevelIndex = up.findIndex((sql) => sql.includes('DROP COLUMN "level"'));
const backfillIndex = up.findIndex((sql) =>
sql.includes('LEAST(GREATEST("level", 1), 15)'),
);
const dropLevelIndex = up.findIndex((sql) =>
sql.includes('DROP COLUMN "level"'),
);
expect(backfillIndex).toBeGreaterThanOrEqual(0);
expect(dropLevelIndex).toBeGreaterThanOrEqual(0);
@@ -47,8 +51,12 @@ describe('CreateRenownAndReputation1791000000000', () => {
it('adds the renown range constraint only AFTER the backfill has populated valid values', async () => {
const up = await runUp();
const backfillIndex = up.findIndex((sql) => sql.includes('LEAST(GREATEST("level", 1), 15)'));
const constraintIndex = up.findIndex((sql) => sql.includes('CHK_characters_renown'));
const backfillIndex = up.findIndex((sql) =>
sql.includes('LEAST(GREATEST("level", 1), 15)'),
);
const constraintIndex = up.findIndex((sql) =>
sql.includes('CHK_characters_renown'),
);
expect(backfillIndex).toBeGreaterThanOrEqual(0);
expect(constraintIndex).toBeGreaterThanOrEqual(0);
@@ -62,7 +70,9 @@ describe('CreateRenownAndReputation1791000000000', () => {
expect(up).toEqual(
expect.arrayContaining([
expect.stringContaining('ALTER TABLE "item_definitions" DROP COLUMN "required_level"'),
expect.stringContaining(
'ALTER TABLE "item_definitions" DROP COLUMN "required_level"',
),
]),
);
});
@@ -72,8 +82,12 @@ describe('CreateRenownAndReputation1791000000000', () => {
expect(up).toEqual(
expect.arrayContaining([
expect.stringContaining(`SET "type" = 'EQUIPMENT' WHERE "type" IN ('WEAPON', 'ARMOR')`),
expect.stringContaining(`SET "type" = 'TRADE_GOOD' WHERE "type" = 'MATERIAL'`),
expect.stringContaining(
`SET "type" = 'EQUIPMENT' WHERE "type" IN ('WEAPON', 'ARMOR')`,
),
expect.stringContaining(
`SET "type" = 'TRADE_GOOD' WHERE "type" = 'MATERIAL'`,
),
expect.stringContaining('DROP TYPE "item_type_enum"'),
expect.stringContaining(
`CREATE TYPE "item_type_enum" AS ENUM ('EQUIPMENT', 'TRADE_GOOD', 'TROPHY', 'QUEST_ITEM', 'CONSUMABLE')`,
@@ -87,30 +101,63 @@ describe('CreateRenownAndReputation1791000000000', () => {
expect(up).toEqual(
expect.arrayContaining([
expect.stringContaining('ALTER TABLE "combat_rewards" DROP COLUMN "experience_granted"'),
expect.stringContaining(
'ALTER TABLE "combat_rewards" DROP COLUMN "experience_granted"',
),
]),
);
});
/**
* XP is abolished outright (design R7), so the column goes with the concept.
* It is NOT NULL with no default, so leaving it behind while the seed stops
* supplying a value would fail every monster insert against a real database
* -- a break no suite here can catch, since none of them connect to Postgres.
*/
it('drops experience_reward from monster_definitions', async () => {
const up = await runUp();
expect(up).toEqual(
expect.arrayContaining([
expect.stringContaining(
'ALTER TABLE "monster_definitions" DROP COLUMN "experience_reward"',
),
]),
);
});
it('keeps silver_min and silver_max on monster_definitions', async () => {
const up = await runUp();
expect(up.join(' ')).not.toContain('"silver_min"');
expect(up.join(' ')).not.toContain('"silver_max"');
});
it('creates all five new tables with their unique constraints', async () => {
const up = await runUp();
expect(up).toEqual(
expect.arrayContaining([
expect.stringContaining('CREATE TABLE "reputation_factions"'),
expect.stringContaining('CREATE UNIQUE INDEX "IDX_reputation_factions_key"'),
expect.stringContaining(
'CREATE UNIQUE INDEX "IDX_reputation_factions_key"',
),
expect.stringContaining('CREATE TABLE "character_reputation"'),
expect.stringContaining(
'CREATE UNIQUE INDEX "IDX_character_reputation_character_faction" ON "character_reputation" ("character_id", "faction_id")',
),
expect.stringContaining('CREATE TABLE "renown_milestone_definitions"'),
expect.stringContaining('CREATE UNIQUE INDEX "IDX_renown_milestone_definitions_key"'),
expect.stringContaining(
'CREATE UNIQUE INDEX "IDX_renown_milestone_definitions_key"',
),
expect.stringContaining('CREATE TABLE "character_renown_milestones"'),
expect.stringContaining(
'CREATE UNIQUE INDEX "IDX_character_renown_milestones_character_milestone" ON "character_renown_milestones" ("character_id", "milestone_id")',
),
expect.stringContaining('CREATE TABLE "turn_in_definitions"'),
expect.stringContaining('CREATE UNIQUE INDEX "IDX_turn_in_definitions_key"'),
expect.stringContaining(
'CREATE UNIQUE INDEX "IDX_turn_in_definitions_key"',
),
]),
);
});
@@ -125,14 +172,27 @@ describe('CreateRenownAndReputation1791000000000', () => {
expect.stringContaining('DROP TABLE "renown_milestone_definitions"'),
expect.stringContaining('DROP TABLE "character_reputation"'),
expect.stringContaining('DROP TABLE "reputation_factions"'),
expect.stringContaining('ALTER TABLE "combat_rewards" ADD COLUMN "experience_granted"'),
expect.stringContaining('ALTER TABLE "item_definitions" ADD COLUMN "required_level"'),
expect.stringContaining(
'ALTER TABLE "monster_definitions" ADD COLUMN "experience_reward"',
),
expect.stringContaining(
'ALTER TABLE "combat_rewards" ADD COLUMN "experience_granted"',
),
expect.stringContaining(
'ALTER TABLE "item_definitions" ADD COLUMN "required_level"',
),
expect.stringContaining('ALTER TABLE "characters" ADD COLUMN "level"'),
expect.stringContaining('ALTER TABLE "characters" ADD COLUMN "experience"'),
expect.stringContaining('ALTER TABLE "characters" DROP COLUMN "renown"'),
expect.stringContaining(
'ALTER TABLE "characters" ADD COLUMN "experience"',
),
expect.stringContaining(
'ALTER TABLE "characters" DROP COLUMN "renown"',
),
]),
);
const turnInDropIndex = down.findIndex((sql) => sql.includes('DROP TABLE "turn_in_definitions"'));
const turnInDropIndex = down.findIndex((sql) =>
sql.includes('DROP TABLE "turn_in_definitions"'),
);
const reputationFactionsDropIndex = down.findIndex((sql) =>
sql.includes('DROP TABLE "reputation_factions"'),
);