74 lines
2.1 KiB
TypeScript
74 lines
2.1 KiB
TypeScript
/**
|
|
* Chapter settlement logic (task 8.2, req 14.1-14.5).
|
|
*
|
|
* After the boss dies the chapter-1 cutscene sequence is strictly:
|
|
*
|
|
* princess_taken_cutscene (≤ 3s) — optional, plays mid-fight when HP ≤ 1/2
|
|
* boss_killed (≤ 2s) — freeze-frame
|
|
* settlement_screen — score + stats UI
|
|
*
|
|
* There is **no** rope-severing rescue event in the MVP (req 14.5), so we
|
|
* expose a single `BANNED_RESCUE_SEQUENCE` constant the QA test asserts
|
|
* against; any future code that tries to play it will fail the guardrail.
|
|
*/
|
|
|
|
export type CutsceneId = 'princess_taken' | 'boss_killed_freeze' | 'settlement_screen';
|
|
|
|
/** Any rescue-style cutscene ID in this set will fail CI (req 14.5). */
|
|
export const BANNED_RESCUE_SEQUENCE: ReadonlyArray<string> = Object.freeze([
|
|
'rope_cut_rescue',
|
|
'princess_rescued',
|
|
'chapter_end_rescue',
|
|
]);
|
|
|
|
export interface ISettlementStats {
|
|
totalScore: number;
|
|
stageScore: number;
|
|
comboCount: number;
|
|
flawless: boolean;
|
|
remainingTimeSec: number;
|
|
}
|
|
|
|
export interface ISettlementResult {
|
|
stats: ISettlementStats;
|
|
closingLine: string;
|
|
}
|
|
|
|
export class ChapterSettlement {
|
|
private stats: ISettlementStats;
|
|
|
|
constructor(initialStats: ISettlementStats) {
|
|
this.stats = { ...initialStats };
|
|
}
|
|
|
|
public addScore(pts: number): void {
|
|
this.stats.stageScore += pts;
|
|
this.stats.totalScore += pts;
|
|
}
|
|
|
|
public registerCombo(): void {
|
|
this.stats.comboCount++;
|
|
}
|
|
|
|
public markTaken(): void {
|
|
this.stats.flawless = false;
|
|
}
|
|
|
|
public setRemainingTime(sec: number): void {
|
|
this.stats.remainingTimeSec = sec;
|
|
}
|
|
|
|
public assertCutsceneAllowed(id: CutsceneId | string): void {
|
|
if (BANNED_RESCUE_SEQUENCE.includes(id)) {
|
|
throw new Error(
|
|
`ChapterSettlement: cutscene "${id}" is explicitly banned — chapter 1 must end with the princess taken (req 14.5)`
|
|
);
|
|
}
|
|
}
|
|
|
|
public build(): ISettlementResult {
|
|
const closing = '公主被带走,续章待续…';
|
|
return { stats: { ...this.stats }, closingLine: closing };
|
|
}
|
|
}
|