v0.8.1: 探险平衡 + 信标系统扩展(周挑战 + 信标链)

P1-a 探险 BOSS 胜率平衡 + 能量恢复(expedition.ts + config.ts + gameStore.ts + ExpeditionPanel.tsx):
- combatWinRate: scale 8→4, floor 0.25→0.35(基础力10对BOSS胜率 25%→35-45%)
- BOSS 难度 5-7→3-5(配合新公式基础胜率达 45-55%)
- computeEnergyRegenInterval(state): 动态恢复间隔
  - exp_2 解锁 -30%, exp_3 解锁 -20%, 探索力属性 -最高30%
  - 下限 12s(原固定 45s)
- computeEnergyRegen 接受 intervalSec 参数
- gameStore tick 传入动态间隔
- config.ts: exp_2/exp_3 描述加'能量恢复 +30%/+20%'
- ExpeditionPanel: 显示实际恢复速度 + '已加速'标记

P1-b 信标系统扩展(beacon.ts + BeaconPanel.tsx + gameStore.ts)[subagent 9-b]:
- 周挑战: getWeekKey ISO 8601 + FNV-1a 种子确定性生成
  - 难度强制 anomaly 60%/singular 40%, goal 日挑战×3-5倍
  - 完整进度/领奖/排行榜推送(isWeekly标记)
- 信标链: 4里程碑(3/7/14/30天) + grace续命机制(每链1次)
  - recordChainCompletion 核心断链/续命逻辑
  - 奖励 50→680洞见递增
- BeaconPanel: 周挑战fuchsia主题 + 信标链amber→rose渐变里程碑节点
- gameStore: trackBeacon升级 + claimWeeklyBeacon/claimChainReward actions
- VLM 8/10, lint零错误, 5场景bun测试全PASS
This commit is contained in:
2026-06-23 22:34:52 +00:00
parent a3aa381656
commit 71ca5b48a9
14 changed files with 2446 additions and 51 deletions
+125 -14
View File
@@ -39,6 +39,7 @@ import {
computeExpeditionPower,
computeExpeditionHp,
computeEnergyRegen,
computeEnergyRegenInterval,
EXPEDITION_CONFIG,
} from "@/lib/game/expedition";
import { ACHIEVEMENTS, type Achievement } from "@/lib/game/achievements";
@@ -61,10 +62,20 @@ import {
} from "@/lib/game/chronicle";
import {
generateDailyChallenge,
generateWeeklyChallenge,
loadDailyProgress,
loadWeeklyProgress,
loadChainState,
addBeaconProgress,
addWeeklyProgress,
recordChainCompletion,
claimWeeklyReward,
claimChainMilestone,
getTodayKey,
type BeaconDailyChallenge,
type BeaconDailyProgress,
type BeaconWeeklyChallenge,
type BeaconWeeklyProgress,
} from "@/lib/game/beacon";
import { setPendingOfflineReport } from "@/lib/game/offlineReport";
import {
@@ -129,6 +140,19 @@ interface GameActions {
// 深空信标奖励发放(v0.5
grantBeaconReward: (insights: number, contact: number) => void;
// 深空信标 · 周挑战领取 + 信标链里程碑领取(v0.8)
claimWeeklyBeacon: () => {
rewardInsight: number;
rewardContact: number;
score: number;
};
claimChainReward: (milestone: number) => {
rewardInsight: number;
rewardContact: number;
label: string;
ok: boolean;
};
// 深空巡航奖励发放(v0.6 — 实时 Canvas 玩法)
grantCruiseReward: (rewards: { crystals?: number; insights?: number; contact?: number }) => void;
@@ -165,25 +189,61 @@ function syncStats(state: Partial<GameState>) {
}
/**
* 深空信标进度追踪(v0.5)。
* 在游戏关键动作(脉冲/解码/探险/BOSS/洞见)后调用,按今日挑战类型增量更新进度。
* 进度独立存储于 localStorageecho-nexus-beacon-prog-v1),不污染 GameState。
* @returns 若刚完成则返回 true(供 UI 触发通知
* 深空信标进度追踪(v0.5 → v0.8 扩展)。
* 在游戏关键动作(脉冲/解码/探险/BOSS/洞见)后调用,同时更新:
* 1. 日挑战进度(按今日挑战类型增量)
* 2. 周挑战进度(按本周挑战类型增量
* 3. 信标链:日挑战刚完成时记录一次完成(含 grace 续命逻辑)
* 进度独立存储于 localStorage,不污染 GameState。
* @returns 三类状态变更供 UI 触发通知
*/
function trackBeacon(
type: "pulse" | "decode" | "expedition" | "boss" | "insight",
delta: number
): boolean {
if (typeof window === "undefined") return false;
): {
dailyJustCompleted: boolean;
weeklyJustCompleted: boolean;
newChainMilestones: number[];
} {
const result = {
dailyJustCompleted: false,
weeklyJustCompleted: false,
newChainMilestones: [] as number[],
};
if (typeof window === "undefined") return result;
try {
// ---- 日挑战 ----
const challenge: BeaconDailyChallenge = generateDailyChallenge();
if (challenge.type !== type) return false;
const current: BeaconDailyProgress = loadDailyProgress();
if (current.completedAt !== null) return false; // 已完成不再累加
const { justCompleted } = addBeaconProgress(current, challenge, delta);
return justCompleted;
if (challenge.type === type) {
const current: BeaconDailyProgress = loadDailyProgress();
if (current.completedAt === null) {
const { justCompleted } = addBeaconProgress(current, challenge, delta);
result.dailyJustCompleted = justCompleted;
// 日挑战刚完成 → 更新信标链
if (justCompleted) {
const { newMilestones } = recordChainCompletion(getTodayKey());
result.newChainMilestones = newMilestones;
}
}
}
// ---- 周挑战 ----
const wChallenge: BeaconWeeklyChallenge = generateWeeklyChallenge();
if (wChallenge.type === type) {
const wCurrent: BeaconWeeklyProgress = loadWeeklyProgress();
if (wCurrent.completedAt === null) {
const { justCompleted } = addWeeklyProgress(
wCurrent,
wChallenge,
delta
);
result.weeklyJustCompleted = justCompleted;
}
}
return result;
} catch {
return false;
return result;
}
}
@@ -403,11 +463,12 @@ export const useGameStore = create<Store>()(
lastSpawn = now;
}
// 能量恢复(探险系统)
// 能量恢复(探险系统v0.8.1 动态间隔
let energy = s.energy;
let lastEnergyTick = s.lastEnergyTick;
if (energy < s.energyMax) {
const regen = computeEnergyRegen(lastEnergyTick, now, energy, s.energyMax);
const intervalSec = computeEnergyRegenInterval(s);
const regen = computeEnergyRegen(lastEnergyTick, now, energy, s.energyMax, intervalSec);
energy = regen.energy;
lastEnergyTick = regen.lastTick;
} else {
@@ -948,6 +1009,56 @@ export const useGameStore = create<Store>()(
});
},
// 深空信标:领取周挑战奖励(v0.8)
claimWeeklyBeacon: () => {
const challenge = generateWeeklyChallenge();
const progress = loadWeeklyProgress();
const res = claimWeeklyReward(challenge, progress);
if (res.rewardInsight > 0 || res.rewardContact > 0) {
const s = get();
set({
insights: s.insights + Math.round(res.rewardInsight),
contact: Math.min(100, s.contact + res.rewardContact),
});
}
return {
rewardInsight: res.rewardInsight,
rewardContact: res.rewardContact,
score: res.score,
};
},
// 深空信标:领取信标链里程碑奖励(v0.8)
claimChainReward: (milestone) => {
// loadChainState 仅用于前置校验,真正的状态修改由 claimChainMilestone 完成
const pre = loadChainState();
if (
pre.currentStreak < milestone ||
pre.milestonesClaimed.includes(milestone)
) {
return {
rewardInsight: 0,
rewardContact: 0,
label: "",
ok: false,
};
}
const res = claimChainMilestone(milestone);
if (res.rewardInsight > 0 || res.rewardContact > 0) {
const s = get();
set({
insights: s.insights + Math.round(res.rewardInsight),
contact: Math.min(100, s.contact + res.rewardContact),
});
}
return {
rewardInsight: res.rewardInsight,
rewardContact: res.rewardContact,
label: res.label,
ok: res.rewardInsight > 0 || res.rewardContact > 0,
};
},
// 深空巡航:发放实时玩法奖励(v0.6)
grantCruiseReward: (rewards) => {
const s = get();