v0.9 工单修复: 4 bug + 4 玩法改进

Gitea Issue #2 修复:
Bug:
1. 探险只能一次 → 能量恢复 45→20s, 初始能量 3→5, 上限 5→8
   新增 lastExpeditionSummary 显示上次结果
   新增 dismissExpedition action
2. 元素偏移/遮挡 → 标签页 grid-cols-8 → 响应式 grid-cols-4/sm:8
3. 成就UI遮挡 → max-h 440→520, min-h 提高
4. 提示框遮挡 → toast top-0→top-14 避开 header, TOAST_LIMIT 1→3

玩法改进:
1. 中央晶体球 → 下方新增 4 指标收益面板(产能/脉冲/连击/仓库)
2. 技术树 12→20 节点 (每分支 3→5 级)
3. 成就解锁 → 全屏居中特效通知 (framer-motion 弹窗)
4. 叙事融入 → 新建 narrative.ts, 脉冲/技术树注入世界观文本
This commit is contained in:
2026-06-24 00:41:16 +00:00
parent eb9836b8af
commit 44b94f9325
17 changed files with 2236 additions and 43 deletions
+2 -2
View File
@@ -90,10 +90,10 @@ export const ACHIEVEMENTS: Achievement[] = [
{
id: "ach_tech_all",
name: "全谱精通",
desc: "解锁全部 12 项技术",
desc: "解锁全部 20 项技术",
icon: "⬡",
color: "#e879f9",
check: (s) => Object.values(s.tech).filter((v) => v > 0).length >= 12,
check: (s) => Object.values(s.tech).filter((v) => v > 0).length >= 20,
reward: { crystalsPerSecPct: 15, insightPct: 15 },
rewardText: "产能 +15% · 洞见 +15%",
},
+77 -4
View File
@@ -10,7 +10,7 @@ import type {
export const INITIAL_STATE = {
crystals: 0,
insights: 0,
energy: 3,
energy: 5,
contact: 0,
crystalsPerSec: 0.4,
crystalCap: 50,
@@ -26,10 +26,11 @@ export const INITIAL_STATE = {
activePuzzle: null,
activeExpedition: null,
expeditionLog: [],
energyMax: 5,
energyMax: 8,
lastEnergyTick: Date.now(),
totalExpeditions: 0,
bossKills: 0,
lastExpeditionSummary: null as import("./types").GameState["lastExpeditionSummary"],
achievements: {},
activeTide: null,
lastTideEnd: 0,
@@ -124,7 +125,7 @@ export const COLOR_VISUAL: Record<
fuchsia: { hex: "#e879f9", glow: "rgba(232,121,249,0.55)", label: "紫" },
};
/** 技术树:4 分支 × 3 级 = 12 节点 */
/** 技术树:4 分支 × 5 级 = 20 节点v0.9 工单 #2 扩充:12→20 */
export const TECH_TREE: TechNode[] = [
// 采矿分支
{
@@ -154,6 +155,24 @@ export const TECH_TREE: TechNode[] = [
cost: 120,
effect: { kind: "crystalsPerSec", value: 5 },
},
{
id: "min_4",
branch: "mining",
level: 4,
name: "量子采掘矩阵",
desc: "晶体/秒 +12,仓库上限 +800",
cost: 400,
effect: { kind: "crystalsPerSec", value: 12 },
},
{
id: "min_5",
branch: "mining",
level: 5,
name: "戴森星核",
desc: "晶体/秒 +30,仓库上限 +2000",
cost: 1200,
effect: { kind: "crystalsPerSec", value: 30 },
},
// 解码分支
{
id: "dec_1",
@@ -182,7 +201,25 @@ export const TECH_TREE: TechNode[] = [
cost: 200,
effect: { kind: "autoDecode", value: 1 },
},
// 探险分支(v0.2:探险力/生命/能量上限/产能)
{
id: "dec_4",
branch: "decoding",
level: 4,
name: "谐振放大器",
desc: "主动脉冲产出 +8",
cost: 500,
effect: { kind: "pulsePower", value: 8 },
},
{
id: "dec_5",
branch: "decoding",
level: 5,
name: "全息解码核心",
desc: "自动解码扩展至 T2 晶体,解码步数 +3",
cost: 1500,
effect: { kind: "decodeSteps", value: 3 },
},
// 探险分支(v0.2:探险力/生命/能量上限/产能;v0.9:level 4/5 高级探险力+能量恢复)
{
id: "exp_1",
branch: "expedition",
@@ -210,6 +247,24 @@ export const TECH_TREE: TechNode[] = [
cost: 180,
effect: { kind: "contactRate", value: 0.5 },
},
{
id: "exp_4",
branch: "expedition",
level: 4,
name: "虚空航标",
desc: "探险力 +12,探险生命 +30,能量恢复 +25%",
cost: 450,
effect: { kind: "crystalCap", value: 400 },
},
{
id: "exp_5",
branch: "expedition",
level: 5,
name: "维度跃迁引擎",
desc: "探险力 +25,探险生命 +50,能量恢复 +30%",
cost: 1400,
effect: { kind: "crystalCap", value: 600 },
},
// 叙事分支
{
id: "nar_1",
@@ -238,6 +293,24 @@ export const TECH_TREE: TechNode[] = [
cost: 160,
effect: { kind: "insightMult", value: 0.6 },
},
{
id: "nar_4",
branch: "narrative",
level: 4,
name: "以太低语",
desc: "洞见获取 +100%",
cost: 500,
effect: { kind: "insightMult", value: 1.0 },
},
{
id: "nar_5",
branch: "narrative",
level: 5,
name: "文明回响",
desc: "接触进度转化率 +80%,洞见获取 +150%",
cost: 1600,
effect: { kind: "insightMult", value: 1.5 },
},
];
/** 技术分支元信息 */
+9
View File
@@ -79,6 +79,15 @@ export function recomputeStats(state: Partial<GameState>): {
}
}
// v0.9 工单 #2:多效果节点的次效果(desc 中描述但 effect.kind 只能承载一个)
// 采矿分支 desc 承诺的仓库上限加成(同时补齐 min_2/min_3 旧 desc 文案)
if (tech.min_2) crystalCap += 100;
if (tech.min_3) crystalCap += 300;
if (tech.min_4) crystalCap += 800;
if (tech.min_5) crystalCap += 2000;
// 叙事分支 nar_5 文明回响:接触进度转化率 +80%(与 insightMult +150% 并存)
if (tech.nar_5) contactRateMult += 0.8;
// 飞升蓝图加成(+ 星图「蓝图回响」额外加成)
const bpAllPct = cm.perBlueprintAllStatsPct / 100;
crystalsPerSec *= 1 + (bp * (PRESTIGE.perBlueprint.crystalsPerSecMult + bpAllPct));
+16 -7
View File
@@ -39,10 +39,11 @@ export const EXPEDITION_CONFIG = {
* v0.8.1 scale 84floor 0.250.35 */
combatDifficultyScale: 4,
combatWinRateFloor: 0.35,
/** 能量恢复基础间隔(秒),可被技术/属性缩短 */
energyRegenSec: 45,
/** /
* v0.9 4520 */
energyRegenSec: 20,
/** 能量恢复最短间隔(秒,技术+属性全满时) */
energyRegenMinSec: 12,
energyRegenMinSec: 8,
};
/** 节点类型权重(boss 固定末位,其余按权重随机) */
@@ -181,11 +182,13 @@ export function generateExpedition(
/** 计算探险力(由技术树 + 飞升蓝图 + 星图天赋) */
export function computeExpeditionPower(state: GameState): number {
let power = EXPEDITION_CONFIG.basePower;
// 探险分支技术加成
// 探险分支技术加成v0.9 扩充:exp_4 +12exp_5 +25
const exp1 = state.tech?.exp_1 ?? 0;
const exp2 = state.tech?.exp_2 ?? 0;
const exp3 = state.tech?.exp_3 ?? 0;
power += exp1 * 3 + exp2 * 5 + exp3 * 8;
const exp4 = state.tech?.exp_4 ?? 0;
const exp5 = state.tech?.exp_5 ?? 0;
power += exp1 * 3 + exp2 * 5 + exp3 * 8 + exp4 * 12 + exp5 * 25;
// 飞升蓝图加成
power *= 1 + (state.blueprints?.length ?? 0) * 0.08;
// 飞升周目加成
@@ -200,6 +203,9 @@ export function computeExpeditionPower(state: GameState): number {
export function computeExpeditionHp(state: GameState): number {
let hp = EXPEDITION_CONFIG.baseHp;
hp += (state.tech?.exp_2 ?? 0) * 20;
// v0.9 扩充:exp_4 +30exp_5 +50
hp += (state.tech?.exp_4 ?? 0) * 30;
hp += (state.tech?.exp_5 ?? 0) * 50;
hp += (state.ascensions ?? 0) * 10;
// 星图「维生护盾」加成
const cm = constellationBonuses(state.constellation ?? []);
@@ -366,13 +372,16 @@ export function advanceExpedition(expedition: Expedition): ExpeditionResult {
}
/** +
* v0.8.1exp_2 -30%exp_3 -20% -30% 12s */
* v0.8.1exp_2 -30%exp_3 -20% -30% 12s
* v0.9 exp_4 25%×0.75exp_5 30%×0.70 8s */
export function computeEnergyRegenInterval(state: GameState): number {
const base = EXPEDITION_CONFIG.energyRegenSec;
let mult = 1;
// 技术:遗迹图谱(exp_2)+ 维度信标(exp_3
// 技术:遗迹图谱(exp_2)+ 维度信标(exp_3+ 虚空航标(exp_4)+ 维度跃迁引擎(exp_5
if (state.tech?.exp_2) mult *= 0.7;
if (state.tech?.exp_3) mult *= 0.8;
if (state.tech?.exp_4) mult *= 0.75;
if (state.tech?.exp_5) mult *= 0.7;
// 角色属性:探索力(exploration)每点缩短少量,高探索力最高 -30%
const exploration = state.attributes?.exploration ?? 0;
const explorationBonus = exploration >= 50
+82
View File
@@ -0,0 +1,82 @@
// 回响星核 / Echo Nexus — 叙事氛围文本池(v0.9 工单修复)
// 工单 Issue #2 反馈"很多东西都没靠着世界观去写,像是为了做游戏而做的一样"
// 本模块为各系统交互注入叙事碎片,让世界观渗透到每个角落
/** 脉冲扫描时随机触发的氛围低语(连击越高出现概率越大) */
export const PULSE_WHISPERS: string[] = [
"晶核深处传来一阵微弱的震颤…",
"以太族的余响在谐振中回荡。",
"你听见了一句无法辨认的低语。",
"星海的寂静被你的脉冲短暂打破。",
"晶体表面的纹路闪烁,像是某种回应。",
"回响核心记录着每一次触碰。",
"虚空深处,有什么在倾听。",
"谐振频率与你的脉冲产生了共鸣。",
"这是以太族留下的第一道印记。",
"晶体内部的光,比星辰更古老。",
"你的脉冲唤醒了一段沉睡的频率。",
"维度薄膜因你的触碰而微微颤动。",
"采矿不是掠夺,是聆听。",
"每一颗晶体,都是一封跨越时间的信。",
"以太族信任你,所以留下了这些。",
];
/** 解码成功时的叙事碎片(按解码次数递进解锁更深层的叙事) */
export const DECODE_ECHOES: string[] = [
"一段记忆在你的解码中苏醒…",
"谐振序列被还原,低语化为可读的符文。",
"你触碰到了以太族思维的一角。",
"解码完成的瞬间,你看见了他们的星海。",
"记忆晶体释放出封存了万年的画面。",
"这不仅是数据,是一段被编码的经历。",
"以太族的语言不是文字,是共振本身。",
"你读懂了这句话时,接触便已开始。",
"晶体褪去外壳,露出内核中的回响。",
"解码,是以太族留给后来者的资格证明。",
"你的思维与晶体中的残响短暂重合。",
"一段文明的碎片在你手中重组。",
];
/** 技术解锁时的叙事(每分支一段通用叙事) */
export const TECH_LORE: Record<string, string> = {
mining: "以太族从不掠夺星海,他们与矿脉共振,让晶体自愿献出光芒。",
decoding: "谐振法则是以太族的核心遗产——思维可被编码为共振序列。",
expedition: "他们留下的遗迹不是废墟,是故意设置的试炼场。",
narrative: "记忆校音,是以太族赋予后来者的第一项礼物。",
};
/** 探险节点事件额外的叙事前缀 */
export const EXPEDITION_NARRATIVE: Record<string, string> = {
combat: "以太族的守卫残影仍在执行万年前的指令。",
treasure: "这些宝藏是以太族特意留下的补给。",
choice: "分岔甬道是他们对后来者的考验。",
puzzle: "谐振锁的答案,藏在以太族的思维模式里。",
rest: "这处休憩节点,是以太族留给疲惫旅人的礼物。",
boss: "终焉之核是以太族离去前留下的最后一道试炼。",
};
/** 飞升时的叙事 */
export const PRESTIGE_ECHOES: string[] = [
"你的意识与以太族的残响交汇,维度薄膜被穿透…",
"飞升不是结束,是与更高处的他们相会。",
"你褪去了这一周目的形态,回响却永存。",
"以太族在更高处等你,已等候了万年。",
"接触完成。你成为了他们等待的回应。",
];
/** 随机取一条叙事文本 */
export function pickWhisper(pool: string[], seed?: number): string {
if (pool.length === 0) return "";
const idx = seed !== undefined
? Math.abs(Math.floor(seed)) % pool.length
: Math.floor(Math.random() * pool.length);
return pool[idx];
}
/** 按概率返回一条叙事(probability 0-1 */
export function maybeWhisper(pool: string[], probability: number): string | null {
if (Math.random() < probability) {
return pickWhisper(pool);
}
return null;
}
+7
View File
@@ -125,6 +125,13 @@ export interface GameState {
lastEnergyTick: number;
totalExpeditions: number;
bossKills: number; // 累计击败 BOSS 数(跨周目)
/** v0.9 工单修复:上次探险结果摘要,用于入口界面展示 */
lastExpeditionSummary: {
result: "victory" | "defeat" | "abort";
rewards: { crystals: number; insights: number; contact: number };
nodeCount: number;
finishedAt: number;
} | null;
// 成就
achievements: Record<string, boolean>; // achievementId -> unlocked