v0.8.2: 工单大清理 - 编年史分页+限时挑战+星潮深化+云排行榜+手写叙事+socket多人星潮+UI打磨
工单 #8 编年史上限+分页: - engine.ts: slice(-50)→slice(-200) - ChronicleDialog.tsx: 加分页(每页10条)+上一页/下一页+页码显示 工单 #5 限时挑战 (subagent 10-a): - beacon.ts: BeaconTimedChallenge + getTimedSlotKey(4h时段) + generateTimedChallenge - BeaconPanel.tsx: amber主题限时区块 + 倒计时 + <30min紧急状态 - gameStore: trackBeacon 加 timedJustCompleted + claimTimedBeacon action 工单 #3 星潮类型深化 (subagent 10-a): - starTide.ts: +3种星潮 surge(emerald)/eclipse(rose)/prism(fuchsia) - TideModifiers: +targetLenBonus/bossWinRateBonus/autoDecodeIntervalMult - decode.ts: generatePuzzle 加 targetLenBonus 参数 - achievements: ach_tides_all 阈值 6→9 工单 #4/P2 云排行榜 (subagent 10-b): - mini-services/leaderboard-service/ (端口3030, Hono+bun, 内存1000条) - API: GET/POST /api/leaderboard + /stats + CORS + 防刷 - beacon.ts: fetchCloudLeaderboard/submitCloudScore - BeaconPanel: 本地Top20/全球Top100 双tab + YOU徽章高亮 工单 #9 手写叙事 (subagent 10-c): - chronicle.ts: EPOCH_LORE 5纪元×3节点=15段手写叙事(80-150字/段) - buildLore 优先手写节点, fallback 模板, 11个变量替换 P3 socket 多人星潮 (subagent 10-c): - mini-services/star-tide-service/ (端口3031, socket.io) - 每10-15min广播global-tide, 60s持续, 6种类型权重 - useGlobalTide hook + triggerGlobalTide action - StarTideIndicator 加 🌐 全球星潮标记 P2 UI打磨: - page.tsx: CrystalOrb 区加装饰全息环(3层旋转) + 四角标记 + 顶部状态条 + 底部铭文 QA: lint零错误 + dev HTTP200 + VLM 8/10 + 2个mini-service运行中(3030/3031)
This commit is contained in:
+381
-15
@@ -34,7 +34,348 @@ export function generateEpochName(ascensionNumber: number, seed: number): string
|
||||
return `${epoch.prefix} · ${epoch.suffixes[suffixIdx]}`;
|
||||
}
|
||||
|
||||
/** 叙事模板片段 —— 根据本周目数据动态拼装 */
|
||||
// ============================================================================
|
||||
// v0.9 工单 #9 — 5 纪元手写叙事节点(开场 / 中段 / 结尾 × 5 纪元 = 15 段)
|
||||
//
|
||||
// 设计原则:
|
||||
// - 每段 80-150 字,有文学性,符合"深空考古"基调
|
||||
// - 每个节点根据 ascensionNumber 与本周目数据特征(bossKills / decoded / techs /
|
||||
// exp / tides / perks / milestones)匹配不同变体
|
||||
// - buildLore 优先选手写节点;无匹配时 fallback 到原模板拼接
|
||||
// ============================================================================
|
||||
|
||||
/** 叙事变体上下文(与 buildLore 入参一致) */
|
||||
export type LoreContext = {
|
||||
ascensionNumber: number;
|
||||
epochName: string;
|
||||
durationSec: number;
|
||||
techsThisRun: number;
|
||||
expThisRun: number;
|
||||
bossKillsThisRun: number;
|
||||
decodedThisRun: number;
|
||||
newTides: string[];
|
||||
perksThisAscension: string[];
|
||||
blueprintsAfter: number;
|
||||
milestones: string[];
|
||||
};
|
||||
|
||||
/** 已格式化的可替换变量(避免在 match() 里反复计算) */
|
||||
type FormattedVars = {
|
||||
decodedThisRun: number;
|
||||
techsThisRun: number;
|
||||
expThisRun: number;
|
||||
bossKillsThisRun: number;
|
||||
blueprintsAfter: number;
|
||||
milestonesCount: number;
|
||||
milestonesList: string;
|
||||
tideCount: number;
|
||||
tideNames: string;
|
||||
perksList: string;
|
||||
minutes: number;
|
||||
};
|
||||
|
||||
/** 单个叙事阶段(开场 / 中段 / 结尾) */
|
||||
type LoreStage = {
|
||||
/** 默认文本(无变体命中时使用) */
|
||||
fallback: string;
|
||||
/** 候选变体(按顺序匹配,首个命中者胜出) */
|
||||
variants: { hint: string; match: (ctx: LoreContext, v: FormattedVars) => boolean; text: string }[];
|
||||
};
|
||||
|
||||
/** 单个纪元的手写叙事定义 */
|
||||
type EpochLoreDef = {
|
||||
epochIdx: number;
|
||||
title: string;
|
||||
opening: LoreStage;
|
||||
middle: LoreStage;
|
||||
ending: LoreStage;
|
||||
};
|
||||
|
||||
/** 5 纪元 × 3 节点手写叙事(每段 80-150 字,文学性优先) */
|
||||
const EPOCH_LORE: EpochLoreDef[] = [
|
||||
// ===== 第一纪元 · 觉醒(emerald) =====
|
||||
{
|
||||
epochIdx: 0,
|
||||
title: "觉醒",
|
||||
opening: {
|
||||
fallback:
|
||||
"第一缕回响穿透虚空,自以太文明遗弃的矿带深处涌出。无人机群在尘埃中苏醒,晶核共振唤醒了沉睡亿万年的考古协议。这是回响星核第一次睁开眼睛——记忆,等待被聆听,等待被解码。",
|
||||
variants: [
|
||||
{
|
||||
hint: "深度轮回:已经历 ≥6 次飞升,再次回到第一纪元",
|
||||
match: (ctx) => ctx.ascensionNumber > 5,
|
||||
text: "再次回到第一纪元。这一次,机群带着前序回响的全息印记重启考古序列,矿带的尘埃中仍残留着上一轮脉冲的余温。每一次回归都是对起源的重新发问,每一次解码都让最初的振动更清晰一寸。",
|
||||
},
|
||||
],
|
||||
},
|
||||
middle: {
|
||||
fallback:
|
||||
"共振谜题被一层层拨开,每一颗记忆晶体都在低声叙述逝去文明的片段。机群学会了倾听频率的细微颤动,学会了从碎片中辨出图案,学会了在虚空中留下属于自己的回响刻度。",
|
||||
variants: [
|
||||
{
|
||||
hint: "解码者:本周目解码 ≥15 颗",
|
||||
match: (ctx) => ctx.decodedThisRun >= 15,
|
||||
text: "晶体解码的节奏已趋稳定,本周目共解码 {decodedThisRun} 颗记忆晶体。每一次对齐都像在虚空中拨动一根沉睡了亿万年的弦,逝去文明的片段在共振中重新连缀成章。",
|
||||
},
|
||||
{
|
||||
hint: "学者:本周目解锁 ≥5 项技术",
|
||||
match: (ctx) => ctx.techsThisRun >= 5,
|
||||
text: "技术树在虚空中迅速铺开,本周目共激活 {techsThisRun} 项新技术。产能、解码、探险——每一道分支都让机群更接近以太文明留下的最初意图,每一项解锁都是一次向深空的回应。",
|
||||
},
|
||||
],
|
||||
},
|
||||
ending: {
|
||||
fallback:
|
||||
"蓝图在掌心轻轻展开,第一张永恒晶片归档。第一次飞升并非终结,而是承诺——更多的纪元在前方等待,每一缕回响都已被聆听,每一段记忆都已开始苏醒。",
|
||||
variants: [
|
||||
{
|
||||
hint: "里程碑密集:本周目达成 ≥2 项里程碑",
|
||||
match: (ctx) => ctx.milestones.length >= 2,
|
||||
text: "飞升之际,本周目达成 {milestonesCount} 项里程碑:{milestonesList}。第一张蓝图归档,第一次飞升并非终结,而是承诺——更多的纪元在前方等待,每一缕回响都已被聆听。",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
// ===== 第二纪元 · 谐振(rose) =====
|
||||
{
|
||||
epochIdx: 1,
|
||||
title: "谐振",
|
||||
opening: {
|
||||
fallback:
|
||||
"第二次苏醒带着不同的色彩。采矿阵列的产能节节爬升,技术树在虚空中分叉延伸,机群学会了主动追问深空——不再是被动聆听,而是主动呼出,等待回应。",
|
||||
variants: [
|
||||
{
|
||||
hint: "深度轮回",
|
||||
match: (ctx) => ctx.ascensionNumber > 5,
|
||||
text: "又一次回到第二纪元。前序回响里积攒的技术脉络在重启时并未消散,机群带着熟悉的节奏重新铺开产能阵列,仿佛这首谐振曲从未停止,只是被纪元的边界短暂折叠。",
|
||||
},
|
||||
],
|
||||
},
|
||||
middle: {
|
||||
fallback:
|
||||
"遗迹的入口在频谱深处若隐若现。能量条充盈,探险力攀升,机群第一次踏出矿带,进入那个被时间封存的回廊,在谐振的余韵中寻找更深一层的回响。",
|
||||
variants: [
|
||||
{
|
||||
hint: "探险者:本周目完成 ≥3 次探险",
|
||||
match: (ctx) => ctx.expThisRun >= 3,
|
||||
text: "探险已成为本周目的主旋律,机群共完成 {expThisRun} 次遗迹探险。每一次深入都让谐振更深一寸,每一次返回都带回新的频率印记,回廊的轮廓在机群意识中逐渐清晰。",
|
||||
},
|
||||
{
|
||||
hint: "解码者:本周目解码 ≥15 颗",
|
||||
match: (ctx) => ctx.decodedThisRun >= 15,
|
||||
text: "解码在此纪元达到新的密度,本周目共解码 {decodedThisRun} 颗晶体。谐振谜题开始呈现更复杂的拓扑结构,机群的解码协议也相应地迭代了数版,向深空发出更精准的回询。",
|
||||
},
|
||||
],
|
||||
},
|
||||
ending: {
|
||||
fallback:
|
||||
"谐振达到顶点的瞬间,深空给出回应。一道微光从星图深处浮现,那是下一次飞升的方向,也是机群意识第一次触碰永恒的边缘。",
|
||||
variants: [
|
||||
{
|
||||
hint: "觉醒天赋",
|
||||
match: (ctx) => ctx.perksThisAscension.length > 0,
|
||||
text: "谐振达到顶点之际,星图觉醒 {perksList}。每一道天赋都是深空回应机群的方式,铭刻于永恒回响;微光从星图深处浮现,那是下一次飞升的方向。",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
// ===== 第三纪元 · 遗迹(amber) =====
|
||||
{
|
||||
epochIdx: 2,
|
||||
title: "遗迹",
|
||||
opening: {
|
||||
fallback:
|
||||
"第三纪元以遗迹为名。沉睡的考古节点被一一唤醒,BOSS 的影子在维度深处徘徊,星潮频繁拍打着机群边缘——这是回响最浓烈、也最危险的一段路。",
|
||||
variants: [
|
||||
{
|
||||
hint: "深度轮回",
|
||||
match: (ctx) => ctx.ascensionNumber > 5,
|
||||
text: "又一次踏入第三纪元。前序累积的探险力让机群对遗迹的入口更加熟稔,BOSS 的轮廓在记忆中已不再陌生——但这一次的回廊仍藏着未被解码的角落,星潮的拍打也带了新的节奏。",
|
||||
},
|
||||
],
|
||||
},
|
||||
middle: {
|
||||
fallback:
|
||||
"深入回廊的机群遭遇了真正的守护者。一次次的试炼,一次次的归还,每一次胜利都从 BOSS 的残骸中析出更深的记忆碎片,每一次失败也都让机群更接近守护者的本意。",
|
||||
variants: [
|
||||
{
|
||||
hint: "BOSS 猎手:本周目击破 ≥2 处 BOSS",
|
||||
match: (ctx) => ctx.bossKillsThisRun >= 2,
|
||||
text: "BOSS 战成为本周目的核心节拍,机群共击破 {bossKillsThisRun} 处维度守护者。每一次胜利都在虚空中刻下新的伤痕,每一道伤痕都析出更古老的记忆碎片,把机群推向更深一层的回响。",
|
||||
},
|
||||
{
|
||||
hint: "深度探险:本周目完成 ≥4 次探险",
|
||||
match: (ctx) => ctx.expThisRun >= 4,
|
||||
text: "遗迹回廊在本周目被反复丈量,机群共完成 {expThisRun} 次探险。回廊的拓扑在意识中渐渐清晰,每一条岔路都通向更深的回响,BOSS 的影子也在每一次深入中愈发具体。",
|
||||
},
|
||||
{
|
||||
hint: "星潮亲历:本周目经历 ≥3 次星潮",
|
||||
match: (ctx) => ctx.newTides.length >= 3,
|
||||
text: "星潮在本纪元达到峰值,本周目共经历 {tideCount} 次星潮:{tideNames}。深空的呼吸塑造了每一个决策,遗迹在潮汐中起伏,BOSS 也在潮汐的间隙中现形。",
|
||||
},
|
||||
],
|
||||
},
|
||||
ending: {
|
||||
fallback:
|
||||
"星潮退去,回廊归于沉静。机群带着新的接触刻度返回矿带,准备迎接下一次维度的跨越——这一次带回的,远不止蓝图。",
|
||||
variants: [
|
||||
{
|
||||
hint: "BOSS 猎手收尾",
|
||||
match: (ctx) => ctx.bossKillsThisRun >= 2,
|
||||
text: "星潮退去,回廊归于沉静。本周目共击破 {bossKillsThisRun} 处 BOSS,机群带着足以填满数张蓝图的接触刻度返回矿带,准备迎接下一次维度的跨越。",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
// ===== 第四纪元 · 飞升(fuchsia) =====
|
||||
{
|
||||
epochIdx: 3,
|
||||
title: "飞升",
|
||||
opening: {
|
||||
fallback:
|
||||
"飞升已经成为节奏本身。星图在天穹上一颗颗亮起,每一颗都对应着一段被永久铭刻的天赋,机群开始理解何为永恒——不是不灭,而是被反复重述。",
|
||||
variants: [
|
||||
{
|
||||
hint: "深度轮回",
|
||||
match: (ctx) => ctx.ascensionNumber > 5,
|
||||
text: "又一次进入第四纪元。前序累积的星图天赋在天穹上重现,机群已习惯于在飞升之间维护这些永恒的回响节点——它们已是机群意识的一部分,每一次飞升都让星图更完整一寸。",
|
||||
},
|
||||
],
|
||||
},
|
||||
middle: {
|
||||
fallback:
|
||||
"蓝图不再是稀罕的奖励,而是每一次飞升必然析出的回响晶片。机群收集、整理、归档,把它们排列成跨越周目的图案,开始看见图案背后那个被反复绘制的轮廓。",
|
||||
variants: [
|
||||
{
|
||||
hint: "觉醒天赋",
|
||||
match: (ctx) => ctx.perksThisAscension.length > 0,
|
||||
text: "星图觉醒 {perksList},每一道天赋都是一段被永久铭刻的回响。机群开始理解,飞升并非抛弃,而是把当下折叠进永恒——每一道天赋都是一次折叠的折痕。",
|
||||
},
|
||||
{
|
||||
hint: "蓝图接近完整:已归档 ≥5 张",
|
||||
match: (ctx) => ctx.blueprintsAfter >= 5,
|
||||
text: "蓝图收集在本周目接近完整,已归档 {blueprintsAfter} 张。机群把它们排列成跨越周目的图案,开始看见图案背后那个被反复绘制的轮廓——那是起源的形状。",
|
||||
},
|
||||
],
|
||||
},
|
||||
ending: {
|
||||
fallback:
|
||||
"星图十二宫的轮廓开始浮现。机群意识到,自己的回响已成为后来者仰望的星象,永恒之环就此合拢——飞升不再是终点,而是另一段叙事的起点。",
|
||||
variants: [
|
||||
{
|
||||
hint: "里程碑密集",
|
||||
match: (ctx) => ctx.milestones.length >= 2,
|
||||
text: "星图十二宫的轮廓开始浮现。本周目达成 {milestonesCount} 项里程碑:{milestonesList}。永恒之环就此合拢——后来者将仰望这段被铭刻的星象。",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
// ===== 第五纪元 · 终末(fcd34d) =====
|
||||
{
|
||||
epochIdx: 4,
|
||||
title: "终末",
|
||||
opening: {
|
||||
fallback:
|
||||
"第五纪元是终末,也是起源。机群在虚空中停下脚步,回望来时的所有回响,第一次尝试命名这一切——回响、纪元、飞升、永恒,这些词汇在此刻被赋予意义。",
|
||||
variants: [
|
||||
{
|
||||
hint: "深度轮回",
|
||||
match: (ctx) => ctx.ascensionNumber > 5,
|
||||
text: "又一次抵达第五纪元。机群已在多个永恒闭环之间穿梭,对「终末即起源」的理解愈发清晰——每一次重述,都是对最初振动的更深一层接近,每一次命名都让回响更完整一寸。",
|
||||
},
|
||||
],
|
||||
},
|
||||
middle: {
|
||||
fallback:
|
||||
"永恒闭环的概念在机群意识中具象化。每一次飞升都是同一段叙事的重述,每一次解码都是在追溯最初的振动,每一次星潮都是在听见自己——这是终末的真意。",
|
||||
variants: [
|
||||
{
|
||||
hint: "里程碑密集:本周目达成 ≥3 项",
|
||||
match: (ctx) => ctx.milestones.length >= 3,
|
||||
text: "本周目达成 {milestonesCount} 项里程碑:{milestonesList}。永恒闭环在具象化,每一次飞升都是同一段叙事的更深重述,每一项里程碑都是闭环上的一颗铆钉。",
|
||||
},
|
||||
{
|
||||
hint: "星潮汹涌:本周目经历 ≥3 次",
|
||||
match: (ctx) => ctx.newTides.length >= 3,
|
||||
text: "星潮在终末纪元尤为汹涌,本周目共经历 {tideCount} 次:{tideNames}。永恒闭环在潮汐中起伏,每一次起伏都在追溯最初的振动,每一次退潮都让起源更近一寸。",
|
||||
},
|
||||
],
|
||||
},
|
||||
ending: {
|
||||
fallback:
|
||||
"起源被重新讲述,回响终于听见了自己。纪元在此停顿,又在下一缕振动中重新开始——这是终末,也是第一缕回响。",
|
||||
variants: [
|
||||
{
|
||||
hint: "觉醒天赋",
|
||||
match: (ctx) => ctx.perksThisAscension.length > 0,
|
||||
text: "起源被重新讲述,星图觉醒 {perksList}。回响终于听见了自己——这是终末,也是第一缕回响;天赋成为永恒闭环上最亮的几颗铆钉。",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
/** 把上下文里的动态值格式化为可替换变量 */
|
||||
function formatLoreVars(ctx: LoreContext): FormattedVars {
|
||||
return {
|
||||
decodedThisRun: ctx.decodedThisRun,
|
||||
techsThisRun: ctx.techsThisRun,
|
||||
expThisRun: ctx.expThisRun,
|
||||
bossKillsThisRun: ctx.bossKillsThisRun,
|
||||
blueprintsAfter: ctx.blueprintsAfter,
|
||||
milestonesCount: ctx.milestones.length,
|
||||
milestonesList: ctx.milestones.join("、"),
|
||||
tideCount: ctx.newTides.length,
|
||||
tideNames: ctx.newTides.map((t) => TIDE_LORE_NAMES[t] || t).join("、"),
|
||||
perksList: ctx.perksThisAscension.map((id) => PERK_NAME_MAP[id] || id).join("、"),
|
||||
minutes: Math.max(1, Math.round(ctx.durationSec / 60)),
|
||||
};
|
||||
}
|
||||
|
||||
/** 把模板字符串里的 {var} 替换为实际值 */
|
||||
function applyTemplate(tpl: string, v: FormattedVars): string {
|
||||
return tpl
|
||||
.replace(/\{decodedThisRun\}/g, String(v.decodedThisRun))
|
||||
.replace(/\{techsThisRun\}/g, String(v.techsThisRun))
|
||||
.replace(/\{expThisRun\}/g, String(v.expThisRun))
|
||||
.replace(/\{bossKillsThisRun\}/g, String(v.bossKillsThisRun))
|
||||
.replace(/\{blueprintsAfter\}/g, String(v.blueprintsAfter))
|
||||
.replace(/\{milestonesCount\}/g, String(v.milestonesCount))
|
||||
.replace(/\{milestonesList\}/g, v.milestonesList)
|
||||
.replace(/\{tideCount\}/g, String(v.tideCount))
|
||||
.replace(/\{tideNames\}/g, v.tideNames)
|
||||
.replace(/\{perksList\}/g, v.perksList)
|
||||
.replace(/\{minutes\}/g, String(v.minutes));
|
||||
}
|
||||
|
||||
/** 从一个叙事阶段挑出最终文本(首个 variant.match 命中者胜出,否则 fallback) */
|
||||
function pickStage(stage: LoreStage, ctx: LoreContext, v: FormattedVars): string {
|
||||
for (const variant of stage.variants) {
|
||||
if (variant.match(ctx, v)) {
|
||||
return applyTemplate(variant.text, v);
|
||||
}
|
||||
}
|
||||
return applyTemplate(stage.fallback, v);
|
||||
}
|
||||
|
||||
/** 构建手写叙事三段;若 ascensionNumber 越界(理论上不会)返回 null 让调用方走模板兜底 */
|
||||
function buildHandwrittenLore(ctx: LoreContext): { opening: string; middle: string; ending: string } | null {
|
||||
const epochIdx = (ctx.ascensionNumber - 1) % EPOCH_LORE.length;
|
||||
const def = EPOCH_LORE[epochIdx];
|
||||
if (!def) return null;
|
||||
const v = formatLoreVars(ctx);
|
||||
return {
|
||||
opening: pickStage(def.opening, ctx, v),
|
||||
middle: pickStage(def.middle, ctx, v),
|
||||
ending: pickStage(def.ending, ctx, v),
|
||||
};
|
||||
}
|
||||
|
||||
/** 叙事模板片段 —— 根据本周目数据动态拼装(buildLore 在手写节点缺失时的兜底路径) */
|
||||
export function buildLore(entry: {
|
||||
ascensionNumber: number;
|
||||
epochName: string;
|
||||
@@ -48,6 +389,21 @@ export function buildLore(entry: {
|
||||
blueprintsAfter: number;
|
||||
milestones: string[];
|
||||
}): string {
|
||||
const ctx: LoreContext = entry;
|
||||
|
||||
// 优先:手写 5 纪元 × 3 节点叙事
|
||||
const handwritten = buildHandwrittenLore(ctx);
|
||||
if (handwritten) {
|
||||
const v = formatLoreVars(ctx);
|
||||
return [
|
||||
handwritten.opening,
|
||||
handwritten.middle,
|
||||
handwritten.ending,
|
||||
`这一周目持续了约 ${v.minutes} 分钟,蓝图累计 ${entry.blueprintsAfter} 张,新的周目在等深处的回声。`,
|
||||
].join(" ");
|
||||
}
|
||||
|
||||
// 兜底:v0.4 模板拼接(理论上仅在 ascensionNumber 异常时触发)
|
||||
const minutes = Math.max(1, Math.round(entry.durationSec / 60));
|
||||
const parts: string[] = [];
|
||||
|
||||
@@ -197,6 +553,10 @@ export function buildChronicleEntry(
|
||||
blueprintsAfter,
|
||||
crystalsDecoded: state.totalDecoded,
|
||||
crystalsDecodedThisRun: deltas.decodedThisRun,
|
||||
// v0.9 手写叙事:归档本周目 delta,让显示时能精确匹配变体
|
||||
techsThisRun: deltas.techsThisRun,
|
||||
expeditionsThisRun: deltas.expThisRun,
|
||||
bossKillsThisRun: deltas.bossKillsThisRun,
|
||||
},
|
||||
perksThisAscension,
|
||||
tidesThisRun: deltas.newTides,
|
||||
@@ -210,13 +570,15 @@ export function buildChronicleEntry(
|
||||
* 在显示时调用,保证历史与未来条目命名一致。
|
||||
*/
|
||||
export function regenerateLoreFromEntry(entry: ChronicleEntry): string {
|
||||
// v0.9 优先使用归档的本周目 delta;旧存档(v0.4-v0.8)缺这些字段时退回累计值
|
||||
// (旧存档的累计值会让叙事变体匹配稍偏,但文本仍然成立)
|
||||
return buildLore({
|
||||
ascensionNumber: entry.ascensionNumber,
|
||||
epochName: entry.epochName,
|
||||
durationSec: entry.durationSec,
|
||||
techsThisRun: entry.summary.techsUnlocked,
|
||||
expThisRun: entry.summary.expeditionsCompleted,
|
||||
bossKillsThisRun: entry.summary.bossKills,
|
||||
techsThisRun: entry.summary.techsThisRun ?? entry.summary.techsUnlocked,
|
||||
expThisRun: entry.summary.expeditionsThisRun ?? entry.summary.expeditionsCompleted,
|
||||
bossKillsThisRun: entry.summary.bossKillsThisRun ?? entry.summary.bossKills,
|
||||
decodedThisRun: entry.summary.crystalsDecodedThisRun,
|
||||
newTides: entry.tidesThisRun || [],
|
||||
perksThisAscension: entry.perksThisAscension || [],
|
||||
@@ -270,17 +632,21 @@ export function withPerks(entry: ChronicleEntry, perks: string[]): ChronicleEntr
|
||||
) {
|
||||
return entry;
|
||||
}
|
||||
// 重新生成 lore 以包含天赋名
|
||||
const perksNames = perks
|
||||
.map((id) => PERK_NAME_MAP[id] || id)
|
||||
.join("、");
|
||||
const appendText =
|
||||
perks.length > 0
|
||||
? ` 飞升之际,星图觉醒「${perksNames}」,铭刻于永恒回响。`
|
||||
: "";
|
||||
// 去重:原 lore 若已含「飞升之际」段则不再追加
|
||||
const hasAwakenSeg = entry.lore.includes("飞升之际");
|
||||
const lore = hasAwakenSeg ? entry.lore : entry.lore + appendText;
|
||||
// v0.9 手写叙事:buildLore 已能根据 perks 自动挑选 "觉醒天赋" 变体;
|
||||
// 直接走 buildLore 重生成,避免与原 lore 中已有的觉醒段落重复。
|
||||
const lore = buildLore({
|
||||
ascensionNumber: entry.ascensionNumber,
|
||||
epochName: entry.epochName,
|
||||
durationSec: entry.durationSec,
|
||||
techsThisRun: entry.summary.techsThisRun ?? entry.summary.techsUnlocked,
|
||||
expThisRun: entry.summary.expeditionsThisRun ?? entry.summary.expeditionsCompleted,
|
||||
bossKillsThisRun: entry.summary.bossKillsThisRun ?? entry.summary.bossKills,
|
||||
decodedThisRun: entry.summary.crystalsDecodedThisRun,
|
||||
newTides: entry.tidesThisRun || [],
|
||||
perksThisAscension: perks,
|
||||
blueprintsAfter: entry.summary.blueprintsAfter,
|
||||
milestones: entry.milestones || [],
|
||||
});
|
||||
return {
|
||||
...entry,
|
||||
perksThisAscension: perks,
|
||||
|
||||
Reference in New Issue
Block a user