feat(api): add hp_regen_since column for persistent HP regeneration

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Bastian Wagner
2026-08-21 14:26:44 +02:00
parent b55518b451
commit 19e08f0b16
3 changed files with 48 additions and 0 deletions

View File

@@ -35,6 +35,12 @@ export class Character {
@Column({ name: 'current_hp', type: 'integer' }) @Column({ name: 'current_hp', type: 'integer' })
currentHp!: number; currentHp!: number;
// `current_hp` is only exact while this is null (regeneration paused, e.g.
// mid-combat). Otherwise it's the HP as of this timestamp -- read it
// through CharacterVitalsService.effectiveHp(), never directly.
@Column({ name: 'hp_regen_since', type: 'timestamptz', nullable: true })
hpRegenSince!: Date | null;
@Column({ name: 'current_location_id', type: 'uuid' }) @Column({ name: 'current_location_id', type: 'uuid' })
currentLocationId!: string; currentLocationId!: string;

View File

@@ -0,0 +1,25 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class AddHpRegeneration1792000000000 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
'ALTER TABLE "characters" ADD COLUMN "hp_regen_since" TIMESTAMP WITH TIME ZONE',
);
// Existing characters start regenerating immediately from their current
// HP. A character whose fight is still ACTIVE keeps regeneration paused
// until that fight resolves, matching the "no combat-time regen" rule
// (persistent-hp-and-regeneration design, R4) -- this migration must not
// gift them free healing mid-fight.
await queryRunner.query('UPDATE "characters" SET "hp_regen_since" = now()');
await queryRunner.query(`UPDATE "characters" AS "character"
SET "hp_regen_since" = NULL
FROM "combats" AS "combat"
WHERE "combat"."character_id" = "character"."id"
AND "combat"."status" = 'ACTIVE'`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('ALTER TABLE "characters" DROP COLUMN "hp_regen_since"');
}
}

View File

@@ -0,0 +1,17 @@
import 'reflect-metadata';
import { getMetadataArgsStorage } from 'typeorm';
import { Character } from '../../characters/entities/character.entity';
describe('characters.hp_regen_since schema', () => {
it('stores the regeneration anchor as a nullable timestamptz', () => {
const metadata = getMetadataArgsStorage();
const column = metadata.columns.find(
(candidate) =>
candidate.target === Character && candidate.propertyName === 'hpRegenSince',
);
expect(column).toBeDefined();
expect(column?.options.type).toBe('timestamptz');
expect(column?.options.nullable).toBe(true);
});
});