feat(v0.4): 回响编年史 — 跨周目叙事时间轴系统

- 新增 ChronicleEntry / RunStartSnapshot 类型与 GameState 字段
- 新建 chronicle.ts: 5 纪元名生成、模板化叙事、里程碑检测、run delta 计算
- engine.ts performPrestige 构建编年史条目并重置 runStart
- gameStore: init 兼容旧存档 + tickTide 记录星潮 + resolveCurrentNode 计 BOSS 击杀
- chooseConstellationPerk 回填最近一条 entry 的 perksThisAscension
- 新建 ChronicleDialog.tsx: 时间轴 UI + 5 纪元循环色 + 入场动画
- 新增 6 项成就: 首部编年/三纪元回响/五纪元闭环/首杀维度/维度猎手/星潮亲历者
- 新增 chronicle / chronicleOpen 程序化音效
- 修复关键 BUG: PrestigeDialog disabled 逻辑错误导致玩家永远无法飞升
- 修复 UX: newBp=0 时不再永久禁用飞升按钮(v0.3.1 星图+v0.4 编年史已提供动机)
- 统计面板新增 BOSS 击破/星潮亲历/编年史条目 3 行
- 版本号升至 v0.4
This commit is contained in:
2026-06-23 14:45:38 +00:00
parent 98fbbf1634
commit e69c48ba69
11 changed files with 993 additions and 7 deletions
+25 -4
View File
@@ -10,7 +10,7 @@ import {
DialogFooter,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { RotateCcw, Sparkles, Star } from "lucide-react";
import { RotateCcw, Sparkles, Star, BookOpen } from "lucide-react";
import { PRESTIGE } from "@/lib/game/config";
import { computeNewBlueprints, computePrestigeBonus } from "@/lib/game/engine";
import { constellationProgress } from "@/lib/game/constellation";
@@ -29,6 +29,7 @@ export function PrestigeDialog({
const doPrestige = useGameStore((s) => s.doPrestige);
const { toast } = useToast();
const [confirming, setConfirming] = useState(false);
const [canReconfirm, setCanReconfirm] = useState(false);
const newBp = computeNewBlueprints(state);
const bonus = computePrestigeBonus(state);
@@ -111,6 +112,15 @@ export function PrestigeDialog({
</div>
</div>
</div>
{/* 编年史铭刻提示(v0.4 新增) */}
<div className="rounded-lg border border-violet-400/25 bg-violet-950/20 p-2.5 flex items-center gap-2">
<BookOpen className="h-4 w-4 text-violet-300 flex-shrink-0" />
<div className="flex-1 text-[10px] text-violet-100/70 leading-relaxed">
<span className="text-violet-200 font-bold"></span>
</div>
</div>
</div>
<DialogFooter>
@@ -118,26 +128,37 @@ export function PrestigeDialog({
</Button>
<Button
disabled={newBp <= 0 || confirming}
disabled={confirming && !canReconfirm}
onClick={() => {
if (!confirming) {
setConfirming(true);
setCanReconfirm(false);
window.setTimeout(() => setCanReconfirm(true), 900);
return;
}
const res = doPrestige();
onOpenChange(false);
setConfirming(false);
setCanReconfirm(false);
if (res) {
sfx("prestige");
// 编年史铭刻音效(稍延迟,与飞升音分层)
window.setTimeout(() => sfx("chronicle"), 600);
toast({
title: "✦ 飞升成功",
description: `获得 ${res.newBp} 张蓝图,进入第 ${state.ascensions + 2} 周目。`,
description: `获得 ${res.newBp} 张蓝图,进入第 ${state.ascensions + 2} 周目。编年史已铭刻新纪元。`,
});
}
}}
className="bg-gradient-to-r from-fuchsia-600 to-rose-500 hover:from-fuchsia-500 hover:to-rose-400 text-white border-0"
>
{confirming ? "再次确认飞升" : newBp <= 0 ? "无新蓝图" : "飞升"}
{confirming
? canReconfirm
? "再次确认飞升"
: "请确认…"
: newBp <= 0
? "飞升(无新蓝图)"
: "飞升"}
</Button>
</DialogFooter>
</DialogContent>