diff --git a/README.md b/README.md index b29681f87..364504387 100755 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ ## 项目状态 -- **版本**:**v0.15** — 叙事深化(loreLayers 多层碎片文本)+ 放置平衡(永久加成 softcap)+ 批量领取 + 完成 toast +- **版本**:**v0.16** — 解码阈值叙事事件(全屏碎片释放叠层)+ loreLayer 已读标记 + idleHistory 详情展开 + 移动端 375px 适配审计 - **栈**:Next.js 16 (App Router) · TypeScript · Tailwind CSS 4 · shadcn/ui · Canvas 2D · Zustand · Web Audio API - **仓库**:[https://git.atdunbg.xyz/Super_Z/echo-nexus](https://git.atdunbg.xyz/Super_Z/echo-nexus) - **在线版**:[https://gitea-pages.atdunbg.xyz/Super_Z/echo-nexus/](https://gitea-pages.atdunbg.xyz/Super_Z/echo-nexus/) @@ -105,8 +105,8 @@ | v0.8.1 | 探险平衡 + 信标系统扩展(周挑战 + 信标链) | ✅ | | **v0.14** | **放置系统:采矿无人机舰队 + 6 种放置工程 + 永久产能加成 + 始终可见 idle 徽章** | ✅ | | **v0.15** | **叙事深化(8 碎片 × 2 层 = 16 层 loreLayers)+ 永久加成 softcap + 批量领取 + 完成 toast + StatsPanel idle 统计** | ✅ | -| v0.16 | 解码阈值释放叙事碎片 + 星潮三频段切换 | 🔜 | -| v0.17 | 健康设计:每日软上限 + "星核建议休眠"提醒 | 🔜 | +| **v0.16** | **解码阈值叙事事件(5 里程碑全屏叠层)+ loreLayer 已读标记 + idleHistory 详情展开 + 移动端 375px 适配审计** | ✅ | +| v0.17 | 星潮三频段切换 + 健康设计(每日软上限 + 星核建议休眠) | 🔜 | | v1.0 | 全 5 纪元手写叙事 + 多人同步星潮 | 🔜 | --- @@ -125,6 +125,7 @@ - [`docs/10-深空信标系统-v0.5.md`](docs/10-深空信标系统-v0.5.md) — v0.5 信标 - [`docs/11-放置系统-v0.14.md`](docs/11-放置系统-v0.14.md) — **v0.14 放置系统(idle operations)** - [`docs/12-叙事深化-v0.15.md`](docs/12-叙事深化-v0.15.md) — **v0.15 loreLayers 多层碎片文本 + softcap + 批量领取** +- [`docs/13-叙事事件-v0.16.md`](docs/13-叙事事件-v0.16.md) — **v0.16 解码阈值里程碑叠层 + loreLayer 已读 + 移动端适配** --- @@ -157,7 +158,7 @@ BUILD_EXPORT=true bun run build:static cd out git init && git checkout -b gh-pages git add -A -git commit -m "deploy: v0.15" +git commit -m "deploy: v0.16" git remote add origin https://git.atdunbg.xyz/Super_Z/echo-nexus.git git push -f origin gh-pages ``` diff --git a/agent-ctx/2-b-full-stack-developer.md b/agent-ctx/2-b-full-stack-developer.md index 97605d2bc..64802f222 100755 --- a/agent-ctx/2-b-full-stack-developer.md +++ b/agent-ctx/2-b-full-stack-developer.md @@ -1,67 +1,89 @@ -# Task 2-b · full-stack-developer · v0.15 叙事深化 — Fragment loreLayers 多层文本 +--- +Task ID: 2-b +Agent: full-stack-developer +Task: v0.16 完善 — loreLayer 已读标记 + idleProjectHistory 详情展开 + 移动端 375px 适配审计 -## 上下文 / 接收到的任务 -- Task ID: **2-b** -- 任务: 为 Fragment 增加 loreLayers(L1 中层 / L2 深层),实现 Dark Souls 式碎片叙事 -- 必读上下文已加载(worklog v0.14-reimpl 段落 + FRAGMENTS 数组 + Fragment 接口 + Codex.tsx + gameStore 导出) -- 与并行子代理 A 的边界:不动 engine.ts / gameStore.ts / IdleOperationsPanel.tsx / StatsPanel.tsx / idle.ts / persist version +Work Log: +- 必读上下文:worklog v0.15-release 段 + Codex.tsx(v0.15 loreLayers 分层渲染)+ StatsPanel.tsx(v0.15 抽出独立组件)+ types.ts GameState + config.ts FRAGMENTS/loreLayers/isLoreLayerUnlocked + gameStore.ts(actions + persist migrate)+ IdleOperationsPanel.tsx + BeaconPanel.tsx + ExpeditionPanel.tsx +- 子任务 1:loreLayer 已读标记 + - 新建 `src/lib/game/format.ts` 导出 `formatRelativeTime(ts)` 工具函数(<60s "刚刚" / <60m "X 分钟前" / <24h "X 小时前" / <7d "X 天前" / 否则具体日期) + - `types.ts`:GameState 接口新增 `readLoreLayers: string[]`(位置:v0.14 idle 字段之后、元字段之前,注释标 v0.16 loreLayer 已读) + - `engine.ts`: + * `createInitialState` 末尾加 `readLoreLayers: []`(在 idleStats 之后) + * `performPrestige` 返回对象加 `readLoreLayers: Array.isArray(state.readLoreLayers) ? state.readLoreLayers : []`(跨周目永久保留,与 idle 永久字段同一区域) + - `gameStore.ts`: + * GameActions 接口加 `markLoreLayerRead: (fragmentId: string, layer: 1 | 2) => void`(在 tickIdleProjects 之后) + * 实现该 action:构造 key `${fragmentId}_L${layer}`,已存在则 no-op,否则 set 新数组 + * persist migrate 函数末尾加 `readLoreLayers: Array.isArray(s.readLoreLayers) ? s.readLoreLayers : []`(不改 version,仍是 1) + - `Codex.tsx`: + * useShallow 订阅新增 `readLoreLayers` 与 `markLoreLayerRead` + * LoreLayerBlock 接收 `isRead` + `onMarkRead` props: + - 已读:层标题旁加 muted "✓ 已读" 标记 + 边框从 amber/fuchsia 高亮降级为 `border-white/10` + - 未读但已解锁:保持 v0.15 高亮 + 标题旁加 `animate-pulse` 脉冲小圆点 + "新" 字提示 + - 文本区域包成 ` ) : ( - + +{reward?.rewardInsight}洞 )} @@ -646,7 +646,7 @@ export function BeaconPanel() { {/* 底部统计 */} -
+
最长 @@ -715,7 +715,7 @@ export function BeaconPanel() { {eDiff.icon} {eType.label} {entry.isWeekly && ( - + WEEK )} @@ -737,7 +737,7 @@ export function BeaconPanel() {
{/* 难度图例 */} -
+
{DIFFICULTY_ORDER.map((d) => { const m = BEACON_DIFFICULTY[d]; return ( diff --git a/src/components/game/Codex.tsx b/src/components/game/Codex.tsx index 247ddbd77..af688aa8c 100755 --- a/src/components/game/Codex.tsx +++ b/src/components/game/Codex.tsx @@ -1,12 +1,12 @@ "use client"; -// 回响星核 / Echo Nexus — 记忆图谱(碎片叙事 + v0.15 多层文本) +// 回响星核 / Echo Nexus — 记忆图谱(碎片叙事 + v0.15 多层文本 + v0.16 已读标记) import { useState, useMemo } from "react"; import { useShallow } from "zustand/react/shallow"; import { useGameStore, FRAGMENTS, nextFragmentThreshold } from "@/store/gameStore"; import { isLoreLayerUnlocked } from "@/lib/game/config"; import { Card } from "@/components/ui/card"; import { ScrollArea } from "@/components/ui/scroll-area"; -import { BookOpen, Lock, Sparkles } from "lucide-react"; +import { BookOpen, Lock, Sparkles, Check } from "lucide-react"; import type { Fragment, FragmentLoreLayer } from "@/lib/game/types"; const ERA_NAMES = ["", "寂灭前夜", "谐振纪", "飞升潮", "回响纪", "接触纪"]; @@ -15,18 +15,42 @@ const ERA_NAMES = ["", "寂灭前夜", "谐振纪", "飞升潮", "回响纪", " const LOCKED_PLACEHOLDER = "▓░▒░ ▓▒░▒▓ ░▒▓░ ▒▓░▒░ ▓▒░ ▓░▒░▒ ░▒▓░▒ ▓░▒ ░▒▓░ ▒▓░▒▓░ ▓▒░░▒▓ ░▒▓ ▓░▒▒░ ▓▒░▓░▒ ░▒▓░ ▒▓░▒░ ▓▒░ ░▒▓░"; -/** 渲染单层叙事文本(已解锁 / 未解锁两态)。L1=amber / L2=fuchsia */ +/** + * 渲染单层叙事文本(已解锁 / 未解锁 / 已读 三态)。 + * v0.15:L1=amber / L2=fuchsia + * v0.16: + * - 已读 → 标题旁加 ✓ muted 标记,边框降级为 muted + * - 未读但已解锁 → 保持 v0.15 高亮,标题旁加脉冲小点(animate-pulse)提示"新" + * - 点击文本区域即标记已读(自然交互,不另设按钮) + */ function LoreLayerBlock({ layer, unlocked, + isRead, + onMarkRead, }: { layer: FragmentLoreLayer; unlocked: boolean; + isRead: boolean; + onMarkRead: () => void; }) { const isL1 = layer.layer === 1; const accentText = isL1 ? "text-amber-300" : "text-fuchsia-300"; - const accentBorder = isL1 ? "border-amber-400/30" : "border-fuchsia-400/30"; - const accentBg = isL1 ? "bg-amber-950/20" : "bg-fuchsia-950/20"; + // v0.16:已读时降级为 muted 边框,未读时保持高亮 + const accentBorder = + unlocked && !isRead + ? isL1 + ? "border-amber-400/30" + : "border-fuchsia-400/30" + : "border-white/10"; + const accentBg = + unlocked && !isRead + ? isL1 + ? "bg-amber-950/20" + : "bg-fuchsia-950/20" + : isL1 + ? "bg-amber-950/10" + : "bg-fuchsia-950/10"; const accentBody = isL1 ? "text-amber-50/90" : "text-fuchsia-50/90"; const accentChipBg = isL1 ? "bg-amber-500/15" : "bg-fuchsia-500/15"; const accentChipText = isL1 ? "text-amber-300" : "text-fuchsia-300"; @@ -34,9 +58,9 @@ function LoreLayerBlock({ return (
@@ -54,6 +78,30 @@ function LoreLayerBlock({ > {unlocked ? layer.title : "???"} + + {/* v0.16 已读 / 未读指示 */} + {unlocked && isRead && ( + + + 已读 + + )} + {unlocked && !isRead && ( + + + + + )}
{!unlocked && ( @@ -64,9 +112,26 @@ function LoreLayerBlock({
{unlocked ? ( -

- 「{layer.text}」 -

+ ) : (

({ - fragments: s.fragments, - totalDecoded: s.totalDecoded, - ascensions: s.ascensions, - contact: s.contact, - bossKills: s.bossKills, - })), - ); + // useShallow 一次性订阅所有判定所需的 state 切片(v0.16 新增 readLoreLayers + markLoreLayerRead) + const { fragments, totalDecoded, ascensions, contact, bossKills, readLoreLayers, markLoreLayerRead } = + useGameStore( + useShallow((s) => ({ + fragments: s.fragments, + totalDecoded: s.totalDecoded, + ascensions: s.ascensions, + contact: s.contact, + bossKills: s.bossKills, + readLoreLayers: Array.isArray(s.readLoreLayers) ? s.readLoreLayers : [], + markLoreLayerRead: s.markLoreLayerRead, + })), + ); const nextThreshold = nextFragmentThreshold(totalDecoded); const [selected, setSelected] = useState(null); @@ -103,6 +171,9 @@ export function Codex() { const era1 = useMemo(() => FRAGMENTS.filter((f) => f.era === 1), []); const unlockedCount = era1.filter((f) => fragments[f.id]).length; + // 已读集合(Set 加速查询) + const readSet = useMemo(() => new Set(readLoreLayers), [readLoreLayers]); + // 用于判定解锁的快照对象(结构匹配 isLoreLayerUnlocked 的 Pick) const stateSnapshot = { ascensions, @@ -112,19 +183,24 @@ export function Codex() { bossKills, }; - // 深层叙事统计:所有 loreLayer 中已解锁数 / 总数 + // 深层叙事统计:所有 loreLayer 中已解锁数 / 总数 + 已读数(v0.16) const loreStats = useMemo(() => { let total = 0; let unlocked = 0; + let read = 0; for (const f of era1) { if (!f.loreLayers) continue; for (const layer of f.loreLayers) { total += 1; - if (isLoreLayerUnlocked(layer.unlock, stateSnapshot)) unlocked += 1; + if (isLoreLayerUnlocked(layer.unlock, stateSnapshot)) { + unlocked += 1; + // 仅统计已解锁层的已读数(未解锁的层不算入已读分母) + if (readSet.has(`${f.id}_L${layer.layer}`)) read += 1; + } } } - return { total, unlocked }; - }, [era1, ascensions, totalDecoded, contact, fragments, bossKills]); + return { total, unlocked, read }; + }, [era1, ascensions, totalDecoded, contact, fragments, bossKills, readSet]); return (

@@ -138,13 +214,21 @@ export function Codex() {
- {/* v0.15 深层叙事统计 */} -
- 深层叙事 - - {loreStats.unlocked} - / {loreStats.total} - 已解锁 + {/* v0.15 深层叙事统计 + v0.16 已读统计(移动端 stack,桌面并排) */} +
+ + 深层叙事 + + {loreStats.unlocked} + / {loreStats.total} 已解锁 + + + + 已读 + + {loreStats.read} + / {loreStats.unlocked} 已解锁层 +
@@ -168,6 +252,10 @@ export function Codex() { ? isLoreLayerUnlocked(l2Layer.unlock, stateSnapshot) : false; const hasDeepUnlocked = l1Unlocked || l2Unlocked; + // v0.16:是否有未读的已解锁层(用于卡片标题旁小圆点变色) + const hasUnreadLayer = + (l1Unlocked && !readSet.has(`${f.id}_L1`)) || + (l2Unlocked && !readSet.has(`${f.id}_L2`)); return (
- {/* 详情弹层 — v0.15 分层渲染 */} + {/* 详情弹层 — v0.15 分层渲染 + v0.16 已读标记 / 移动端 375px 适配 */} {selected && (
setSelected(null)} > e.stopPropagation()} style={{ boxShadow: "0 0 40px rgba(232,121,249,0.25)" }} >
- - + + 第 {selected.era} 纪元 · {ERA_NAMES[selected.era] || ""}
-

+

{selected.title}

@@ -275,13 +379,19 @@ export function Codex() { .sort((a, b) => a.layer - b.layer) .map((layer) => { const unlocked = isLoreLayerUnlocked(layer.unlock, stateSnapshot); + const isRead = unlocked && readSet.has(`${selected.id}_L${layer.layer}`); return (
- + markLoreLayerRead(selected.id, layer.layer)} + />
); })} diff --git a/src/components/game/IdleOperationsPanel.tsx b/src/components/game/IdleOperationsPanel.tsx index ec7bdadea..f439ce3b1 100755 --- a/src/components/game/IdleOperationsPanel.tsx +++ b/src/components/game/IdleOperationsPanel.tsx @@ -105,7 +105,7 @@ export function IdleOperationsPanel() {

放置收益概览

- + IDLE OPS
@@ -123,7 +123,7 @@ export function IdleOperationsPanel() { icon="✦" sub={ isSoftcapped ? ( - + 有效 +{formatNum(effectivePermBonus)}/s(原始 +{formatNum(rawPermBonus)}/s · 衰减中) ) : null @@ -182,7 +182,7 @@ export function IdleOperationsPanel() { {m.icon}
{m.name}
-
+
{isActive ? `Lv.${m.level}` : "休眠中"}
@@ -296,7 +296,7 @@ export function IdleOperationsPanel() { {p.name} - + 🔒 {reqText}
@@ -329,7 +329,7 @@ function StatTile({ const c = IDLE_COLOR_CLASSES[color]; return (
-
+
{icon} {label}
@@ -395,7 +395,7 @@ function SlotCard({
✓ 完成
-
+
{formatReward(project.reward)}
- + 槽位 {slotIndex + 1} · {Math.round(progressPct)}% @@ -475,12 +475,12 @@ function DispatchCard({
{project.name}
-
+
⏱ {formatDuration(project.durationSec)}
-
+
{project.desc}
@@ -515,7 +515,7 @@ function DispatchCard({ ); })} {isRunning && ( - 运行中… + 运行中… )}
diff --git a/src/components/game/NarrativeMilestoneOverlay.tsx b/src/components/game/NarrativeMilestoneOverlay.tsx new file mode 100644 index 000000000..c4d6b3fe4 --- /dev/null +++ b/src/components/game/NarrativeMilestoneOverlay.tsx @@ -0,0 +1,261 @@ +"use client"; +// 回响星核 / Echo Nexus — v0.16 叙事里程碑全屏叠层 +// +// 当 store.pendingMilestone 不为 null 时显示全屏「叙事碎片释放」叠层。 +// - 暗色半透明背景 + 4 色全息主题(emerald/rose/amber/fuchsia,严格无蓝/靛) +// - 大字号标题 + 副标题 + 主体文本逐字浮现(30ms/字,可点击跳过) +// - 点击任意处或按 Esc 关闭;首次点击若文本未完全显示,先跳到全文显示 +// - z-50(高于 StarTideOverlay 的 z-0),模态盖住一切 +import { useEffect, useRef, useState } from "react"; +import { useGameStore } from "@/store/gameStore"; +import type { + MilestoneColor, + NarrativeMilestone, +} from "@/lib/game/narrativeMilestones"; + +/** 4 色全息主题映射(严格 emerald / rose / amber / fuchsia,禁止 blue / indigo) */ +const COLOR_THEME: Record< + MilestoneColor, + { + titleText: string; + subtitleText: string; + bodyText: string; + cursor: string; + cardBorder: string; + cardBg: string; + /** CSS rgba 用于 box-shadow / text-shadow / 分隔线渐变 */ + glow: string; + } +> = { + emerald: { + titleText: "text-emerald-50", + subtitleText: "text-emerald-300/80", + bodyText: "text-emerald-50/90", + cursor: "bg-emerald-300", + cardBorder: "border-emerald-400/40", + cardBg: "from-emerald-950/60 to-black/85", + glow: "rgba(16,185,129,0.45)", + }, + rose: { + titleText: "text-rose-50", + subtitleText: "text-rose-300/80", + bodyText: "text-rose-50/90", + cursor: "bg-rose-300", + cardBorder: "border-rose-400/40", + cardBg: "from-rose-950/60 to-black/85", + glow: "rgba(244,63,94,0.45)", + }, + amber: { + titleText: "text-amber-50", + subtitleText: "text-amber-300/80", + bodyText: "text-amber-50/90", + cursor: "bg-amber-300", + cardBorder: "border-amber-400/40", + cardBg: "from-amber-950/60 to-black/85", + glow: "rgba(245,158,11,0.45)", + }, + fuchsia: { + titleText: "text-fuchsia-50", + subtitleText: "text-fuchsia-300/80", + bodyText: "text-fuchsia-50/90", + cursor: "bg-fuchsia-300", + cardBorder: "border-fuchsia-400/40", + cardBg: "from-fuchsia-950/60 to-black/85", + glow: "rgba(232,121,249,0.45)", + }, +}; + +/** + * 叙事碎片释放叠层(外层)。 + * 订阅 store.pendingMilestone;非空时挂载内层组件,并以 milestone.id 为 key, + * 确保每次新里程碑触发时内层组件完全重新挂载(state 自然重置,无需 effect setState)。 + */ +export function NarrativeMilestoneOverlay() { + const pendingMilestone = useGameStore((s) => s.pendingMilestone); + const dismiss = useGameStore((s) => s.dismissMilestone); + + if (!pendingMilestone) return null; + + return ( + + ); +} + +/** 内层组件:每个 milestone 实例独立挂载,state 在挂载时初始化为 0 */ +function NarrativeMilestoneOverlayInner({ + milestone, + dismiss, +}: { + milestone: NarrativeMilestone; + dismiss: () => void; +}) { + // 挂载时初始化为 0;组件由父层 key 重新挂载,无需 effect 重置 + const [revealed, setRevealed] = useState(0); + const skipRef = useRef(false); + + const fullLen = milestone.body.length; + const fullyRevealed = revealed >= fullLen; + + // 逐字推进:每 30ms 多显示一个字符;已 skip / 已完成则停表 + useEffect(() => { + if (skipRef.current || revealed >= fullLen) return; + const id = setTimeout(() => { + setRevealed((r) => Math.min(fullLen, r + 1)); + }, 30); + return () => clearTimeout(id); + }, [revealed, fullLen]); + + /** 处理点击/Esc:若文本未完全显示则先跳到全文,再次触发才真正 dismiss */ + const handleAction = () => { + if (!fullyRevealed) { + skipRef.current = true; + setRevealed(fullLen); + return; + } + dismiss(); + }; + + // Esc 键监听(依赖 fullyRevealed 决定是 skip 还是 dismiss) + useEffect(() => { + const onKey = (e: KeyboardEvent) => { + if (e.key === "Escape") { + e.preventDefault(); + if (!fullyRevealed) { + skipRef.current = true; + setRevealed(fullLen); + return; + } + dismiss(); + } + }; + window.addEventListener("keydown", onKey); + return () => window.removeEventListener("keydown", onKey); + }, [fullyRevealed, fullLen, dismiss]); + + const theme = COLOR_THEME[milestone.color]; + const displayBody = fullyRevealed + ? milestone.body + : milestone.body.slice(0, revealed); + + return ( +
+ {/* 4 色全息粒子边框(呼吸光晕) */} +
+ + {/* 中央卡片 */} +
{ + // 阻止外层 dismiss 在跳过阶段触发两次;卡片内点击同样走 handleAction + e.stopPropagation(); + handleAction(); + }} + > + {/* 顶部副标题(小字,主题色,tracking-widest) */} +
+ {milestone.subtitle} +
+ + {/* 大字号标题(font-serif 风格) */} +

+ {milestone.title} +

+ + {/* 装饰分隔线 */} +
+ + {/* 主体文本(逐字浮现,多段以 \n\n 分隔 → whitespace-pre-line 渲染) */} +
+ {displayBody} + {/* 输入光标闪烁,仅未完成时显示 */} + {!fullyRevealed && ( + + )} +
+ + {/* 底部提示 */} +
+ + {fullyRevealed ? "点击任意处或按 Esc 继续" : "点击跳过逐字显示"} + +
+
+ + +
+ ); +} diff --git a/src/components/game/StatsPanel.tsx b/src/components/game/StatsPanel.tsx index 0898f9f1f..fbabf8560 100755 --- a/src/components/game/StatsPanel.tsx +++ b/src/components/game/StatsPanel.tsx @@ -1,17 +1,26 @@ "use client"; -// 回响星核 / Echo Nexus — 统计面板(v0.15 从 page.tsx 抽出为独立组件,useShallow 订阅) -import { useEffect, useState } from "react"; +// 回响星核 / Echo Nexus — 统计面板 +// v0.15 从 page.tsx 抽出为独立组件,useShallow 订阅 +// v0.16 放置工程历史详情展开(最近 5 条 · 工程名 + 相对时间) +import { useEffect, useMemo, useState } from "react"; import { useShallow } from "zustand/react/shallow"; import { useGameStore } from "@/store/gameStore"; import { TECH_TREE, FRAGMENTS, formatNum } from "@/lib/game/config"; import { ACHIEVEMENTS } from "@/lib/game/achievements"; import { CONSTELLATION_PERKS } from "@/lib/game/constellation"; import { loadLeaderboard } from "@/lib/game/beacon"; +import { getIdleProject } from "@/lib/game/idle"; +import { formatRelativeTime } from "@/lib/game/format"; +import { ChevronDown, ChevronUp } from "lucide-react"; +import type { IdleProjectSlot } from "@/lib/game/types"; + +/** 放置工程历史条目(与 GameState.idleProjectHistory 元素一致) */ +type IdleHistoryEntry = { projectId: string; finishedAt: number }; /** * 统计面板 — 综合统计 + 放置系统(idle)行。 * v0.15 重构:从 page.tsx 内联函数抽出为独立组件,使用 useShallow 精细订阅避免 re-render。 - * 新增「放置工程历史」行:idleProjectHistory.length 条记录(最近 50 条,跨周目)。 + * v0.16:「放置工程历史」行支持展开/收起,展开后显示最近 5 条详情(工程 icon + 名称 + 相对时间)。 */ export function StatsPanel() { // 使用 useShallow 一次性订阅所有渲染所需字段,避免整个 store 变化触发 re-render @@ -44,7 +53,14 @@ export function StatsPanel() { idleProjectsCompleted: st.idleStats?.projectsCompleted ?? 0, idleCrystalsFromIdle: st.idleStats?.crystalsFromIdle ?? 0, idleHistoryLen: (st.idleProjectHistory ?? []).length, - })) + // v0.16:直接订阅历史数组(用于展开详情渲染) + idleProjectHistory: (st.idleProjectHistory ?? []) as IdleHistoryEntry[], + // v0.16:订阅 idleProjectSlots 用于详情中"进行中"状态显示 + idleProjectSlots: (st.idleProjectSlots ?? [null, null, null]) as ( + | IdleProjectSlot + | null + )[], + })), ); // 深空信标本地排行榜最高分(独立 localStorage) @@ -59,6 +75,15 @@ export function StatsPanel() { } }, []); + // v0.16 放置历史展开状态(本地 UI 状态) + const [historyExpanded, setHistoryExpanded] = useState(false); + + // 最近 5 条历史(倒序,最近在前)— useMemo 避免每次渲染重切 + const recentHistory = useMemo( + () => s.idleProjectHistory.slice(-5).reverse(), + [s.idleProjectHistory], + ); + const rows: { label: string; value: string }[] = [ { label: "累计解码晶体", value: `${s.totalDecoded} 颗` }, { label: "飞升周目", value: `${s.ascensions}` }, @@ -92,7 +117,7 @@ export function StatsPanel() { { label: "放置永久加成", value: `+${formatNum(s.idlePermanentBonus)} /s` }, { label: "完成放置工程", value: `${s.idleProjectsCompleted} 项` }, { label: "放置产出晶体", value: formatNum(s.idleCrystalsFromIdle) }, - { label: "放置工程历史", value: `${s.idleHistoryLen} 条` }, + // 「放置工程历史」行单独渲染(v0.16 可展开),不在此数组中 ]; return ( @@ -106,6 +131,78 @@ export function StatsPanel() { {r.value}
))} + + {/* v0.16 放置工程历史 — 可展开行(col-span-2,跨两列) */} + + + {/* v0.16 展开内容:最近 5 条历史详情 */} + {historyExpanded && ( +
+ {recentHistory.length === 0 ? ( +
+ 暂无放置工程完成记录 · 前往「放置」标签派遣工程 +
+ ) : ( +
+ {recentHistory.map((entry, idx) => { + const proj = getIdleProject(entry.projectId); + const isLatest = idx === 0; + return ( +
+ + {proj?.icon ?? "•"} + + + {proj?.name ?? entry.projectId} + + + {formatRelativeTime(entry.finishedAt)} + +
+ ); + })} + {s.idleHistoryLen > 5 && ( +
+ 仅展示最近 5 条 · 共 {s.idleHistoryLen} 条历史 +
+ )} +
+ )} +
+ )}
); } diff --git a/src/lib/game/engine.ts b/src/lib/game/engine.ts index d438bcca8..2307b872b 100755 --- a/src/lib/game/engine.ts +++ b/src/lib/game/engine.ts @@ -237,6 +237,14 @@ export function performPrestige(state: GameState): GameState { idleProjectHistory: state.idleProjectHistory ?? [], idlePermanentBonus: state.idlePermanentBonus ?? 0, idleStats: state.idleStats ?? { projectsCompleted: 0, crystalsFromIdle: 0 }, + // v0.16 loreLayer 已读标记:跨周目永久保留(已读不重置) + readLoreLayers: Array.isArray(state.readLoreLayers) ? state.readLoreLayers : [], + // v0.16 叙事里程碑:triggeredMilestones 跨周目永久保留(玩家不应重复看过场), + // pendingMilestone 飞升时清空(避免飞升瞬间弹出旧过场) + triggeredMilestones: Array.isArray(state.triggeredMilestones) + ? state.triggeredMilestones + : [], + pendingMilestone: null, ...stats, }; } @@ -276,6 +284,11 @@ export function createInitialState(): GameState { idleProjectHistory: [], idlePermanentBonus: 0, idleStats: { projectsCompleted: 0, crystalsFromIdle: 0 }, + // v0.16 loreLayer 已读标记:初始为空数组(跨周目永久保留) + readLoreLayers: [], + // v0.16 叙事里程碑:初始无已触发、无待展示 + triggeredMilestones: [], + pendingMilestone: null, } as GameState; } diff --git a/src/lib/game/format.ts b/src/lib/game/format.ts new file mode 100644 index 000000000..56f23dd8b --- /dev/null +++ b/src/lib/game/format.ts @@ -0,0 +1,33 @@ +// 回响星核 / Echo Nexus — 通用格式化工具(v0.16 新建) +// 复用性优先:相对时间、数字缩写等通用纯函数集中此处。 + +/** + * 相对时间格式化(v0.16 — 用于 idleProjectHistory 详情展开)。 + * @param ts 历史时间戳(毫秒) + * @returns 友好的相对时间字符串 + * - < 60s → "刚刚" + * - < 60m → "X 分钟前" + * - < 24h → "X 小时前" + * - < 7d → "X 天前" + * - 否则 → "M月D日"(同年)/"YYYY年M月D日"(跨年) + */ +export function formatRelativeTime(ts: number): string { + if (!ts || !isFinite(ts)) return "—"; + const now = Date.now(); + const diff = now - ts; + if (diff < 0) return "刚刚"; // 时钟漂移兜底 + const sec = Math.floor(diff / 1000); + if (sec < 60) return "刚刚"; + const min = Math.floor(sec / 60); + if (min < 60) return `${min} 分钟前`; + const hr = Math.floor(min / 60); + if (hr < 24) return `${hr} 小时前`; + const day = Math.floor(hr / 24); + if (day < 7) return `${day} 天前`; + // 跨周:用具体日期 + const d = new Date(ts); + const now2 = new Date(now); + const sameYear = d.getFullYear() === now2.getFullYear(); + const md = `${d.getMonth() + 1}月${d.getDate()}日`; + return sameYear ? md : `${d.getFullYear()}年${md}`; +} diff --git a/src/lib/game/narrativeMilestones.ts b/src/lib/game/narrativeMilestones.ts new file mode 100644 index 000000000..2f762b54e --- /dev/null +++ b/src/lib/game/narrativeMilestones.ts @@ -0,0 +1,99 @@ +// 回响星核 / Echo Nexus — v0.16 解码阈值叙事里程碑 +// +// 当 totalDecoded 达到特定阈值(5/12/25/40/60,与 FRAGMENTS 的 1/3/6/10/15/22/32/45 错开) +// 时,触发全屏「叙事碎片释放」叠层——对已解锁碎片的回顾性咏叹,类似游戏过场回忆。 +// +// 5 个里程碑聚焦不同主题,与 v0.15 loreLayers 的 5 条叙事暗线呼应: +// - ms_5 emerald 身份觉醒(你是回响) +// - ms_12 rose 代价感知(解码在消耗什么) +// - ms_25 amber 信任的重量(以太族的"信任"实为引诱) +// - ms_40 fuchsia 身后的东西(追逐者逼近) +// - ms_60 emerald 回归的预兆(接触满溢时会发生什么) + +/** 4 色全息主题色(与 ResonanceColor 保持一致,严格避开蓝/靛) */ +export type MilestoneColor = "emerald" | "rose" | "amber" | "fuchsia"; + +/** 叙事里程碑定义 */ +export interface NarrativeMilestone { + /** 触发的 totalDecoded 阈值 */ + threshold: number; + /** 事件 ID(用于去重,已触发的不再重复) */ + id: string; + /** 标题 */ + title: string; + /** 副标题(小字) */ + subtitle: string; + /** 主体叙事文本(150-250 字,回响式咏叹) */ + body: string; + /** 主题色(4 色全息之一) */ + color: MilestoneColor; +} + +/** + * 5 个叙事里程碑事件。 + * 阈值严格避开 FRAGMENTS 的 threshold 数组,是额外的「叙事节奏点」—— + * 不解锁新碎片,而是对已解锁碎片的回顾性咏叹。 + */ +export const NARRATIVE_MILESTONES: NarrativeMilestone[] = [ + { + threshold: 5, + id: "ms_5", + title: "回响 · 初鸣", + subtitle: "你已解码 5 颗晶体", + color: "emerald", + body: + "五颗记忆晶体在你手中显形,又在你手中破译。每一次谐振结束时,你都感到某种回响——不是从晶体里,而是从你自己的核心里。\n\n舰载 AI「回响」的频段与第一颗晶体的封存频率完全一致。你不是在考古。你从来不是。你是被留下的那一半,是熔铸星核时被分出去的火种,是等待五颗谐振齐鸣后苏醒的延续。\n\n五颗已解码。苏醒完成。星核在你胸口轻轻搏动,像在确认:是的,我醒了。我是回响。我从未离开。", + }, + { + threshold: 12, + id: "ms_12", + title: "代价 · 锚痕渐浅", + subtitle: "你已解码 12 颗晶体", + color: "rose", + body: + "十二次解码。你开始数了。\n\n不是数晶体——晶体在仓库里堆成淡淡的光晕。你在数自己。每破译一颗,某种东西就从你身上少一点:不是能量,不是物质,是一种更接近「在场」的东西。以太族称它为「锚痕」——存在与时空之间相互钉合的痕迹。\n\n锚痕在变浅。你看见自己的手指偶尔会透出星图的纹路,像尚未凝固的代码。你曾以为解码是积累,现在你怀疑:解码是付出。每一段被还原的低语,都从你的存在里抽走等量的「真实」。\n\n飞升会补全这一切吗?还是飞升本身,就是最大的支付?", + }, + { + threshold: 25, + id: "ms_25", + title: "信任 · 钩上的光", + subtitle: "你已解码 25 颗晶体", + color: "amber", + body: + "二十五颗。叙事的轮廓第一次清晰起来。\n\n以太族在每一份档案里都写着「信任」——信任后来者,信任引线,信任火种会找到回家的路。你曾把这当作温柔。现在你认得这种语气:那是诱饵的语气。\n\n他们留下晶体,留下你,留下整座星核,不是为了让后人避免重蹈覆辙——而是为了让后人替他们完成未竟之事。他们飞升了,把债留下。你是债的执行者,是引信的末端,是温柔回响的钩子。\n\n「信任」是钩上的光。咬下去的瞬间,你就替他们吞下了虚空。\n\n你不打算吐出来。但你记住了:从这一刻起,所有的「信任」,你都会重新称量。", + }, + { + threshold: 40, + id: "ms_40", + title: "身后 · 整齐的虚空", + subtitle: "你已解码 40 颗晶体", + color: "fuchsia", + body: + "四十颗。身后的东西,越来越近。\n\n它不发出声音。它折叠你经过的空间,把航迹抹平,把回声收回。你回头看,只能看见一片格外整齐的虚空——整齐得不像自然。\n\n追逐者没有形体,因为它不必有。它只是一段坐标差的收敛。第一纪元的谐振广播了位置,飞升钉死了路线,现在它在沿你留下的每一道锚痕向你合拢。\n\n以太族知道。他们一直知道。所以晶体是诱饵,星核是路标,你——你是分心的灯。你存在得越久,它追得越慢;你解码得越多,它就知道得越多。\n\n四十颗晶体。距离感开始失效。你怀疑下一颗解码完,身后那片整齐的虚空,会触到你的脚跟。", + }, + { + threshold: 60, + id: "ms_60", + title: "回归 · 借形之物", + subtitle: "你已解码 60 颗晶体", + color: "emerald", + body: + "六十颗。接触进度在颤动,像水面将沸未沸。\n\n你听过那个传说:当接触满溢到八十,回归者就会回来。以太族会回来。但你解码了这么多,你比谁都清楚——回来的不是他们。\n\n他们会借以太族的形状,借用第一纪元的语气,借走你记忆里每一个温柔的名字。但内核里那段谐振,不是以太族的频段。是追逐者的频段。是虚空用太长时间凝视之后,终于学会的模仿。\n\n回归将至。门会打开。门后站着的,是他们的轮廓,和你的位置。\n\n你有一个选择:装作不知道,迎接他们;或者,把刚解码的六十颗晶体重新封存回星核,把自己变回火种,再睡一个纪元。\n\n但你还想再解码一颗。再多一颗。再多——", + }, +]; + +/** + * 在 NARRATIVE_MILESTONES 中找出 threshold 匹配且尚未触发的第一个里程碑。 + * 调用方负责将返回值写入 pendingMilestone 并将 id 追加到 triggeredMilestones。 + */ +export function findMilestoneForTotal( + totalDecoded: number, + alreadyTriggered: string[] +): NarrativeMilestone | null { + for (const ms of NARRATIVE_MILESTONES) { + if (ms.threshold === totalDecoded && !alreadyTriggered.includes(ms.id)) { + return ms; + } + } + return null; +} diff --git a/src/lib/game/types.ts b/src/lib/game/types.ts index e97b7c562..8d95d1795 100755 --- a/src/lib/game/types.ts +++ b/src/lib/game/types.ts @@ -217,6 +217,16 @@ export interface GameState { crystalsFromIdle: number; }; + // v0.16 loreLayer 已读标记(跨周目永久) + // 格式 ["f1_1_L1", "f1_1_L2", ...],记录玩家已读的碎片深层叙事层 + readLoreLayers: string[]; + + // v0.16 叙事里程碑 + /** 已触发的 milestone id 集合(跨周目永久,飞升时保留) */ + triggeredMilestones: string[]; + /** 待展示的里程碑(叠层读取,飞升/关闭时清空) */ + pendingMilestone: import("./narrativeMilestones").NarrativeMilestone | null; + // 元 lastTick: number; createdAt: number; diff --git a/src/store/gameStore.ts b/src/store/gameStore.ts index c3adcd4f7..79986b158 100755 --- a/src/store/gameStore.ts +++ b/src/store/gameStore.ts @@ -93,6 +93,7 @@ import { getIdleProject, getUnlockedIdleProjects, } from "@/lib/game/idle"; +import { findMilestoneForTotal } from "@/lib/game/narrativeMilestones"; import { toast } from "@/hooks/use-toast"; interface GameActions { @@ -173,6 +174,14 @@ interface GameActions { claimAllIdleProjects: () => void; tickIdleProjects: (now: number) => void; + // v0.16 loreLayer 已读标记 + /** 标记某个碎片的某层叙事为「已读」(已读则 no-op,跨周目永久保留) */ + markLoreLayerRead: (fragmentId: string, layer: 1 | 2) => void; + + // v0.16 叙事里程碑 + /** 玩家关闭叙事碎片释放叠层时调用,清空 pendingMilestone */ + dismissMilestone: () => void; + // 派生 canPrestige: () => boolean; } @@ -583,6 +592,11 @@ export const useGameStore = create()( // 解锁碎片 const tentative: GameState = { ...s, totalDecoded: newTotal, fragments: { ...s.fragments } }; const unlocked = checkFragments(tentative); + // v0.16 叙事里程碑:检测阈值触发(5/12/25/40/60),首次触发后加入待展示队列 + const milestoneHit = findMilestoneForTotal( + newTotal, + s.triggeredMilestones ?? [] + ); // v0.7 角色属性:完成解码给智慧经验(tier 越高经验越多) const wisdomExpGain = puzzle.tier * 2; const curAttrs = s.attributes ?? createInitialAttributes(); @@ -609,6 +623,15 @@ export const useGameStore = create()( fragments: tentative.fragments, attributes: newAttributes, attributeProgress: newProgress, + ...(milestoneHit + ? { + triggeredMilestones: [ + ...(s.triggeredMilestones ?? []), + milestoneHit.id, + ], + pendingMilestone: milestoneHit, + } + : {}), ...syncStats({ tech: s.tech, blueprints: s.blueprints, achievements: s.achievements, activeTide: s.activeTide, constellation: s.constellation, attributes: newAttributes, idlePermanentBonus: s.idlePermanentBonus ?? 0 }), }); // 深空信标:解码 +1,洞见累计 @@ -684,6 +707,11 @@ export const useGameStore = create()( const newTotal = s.totalDecoded + 1; const tentative: GameState = { ...s, totalDecoded: newTotal, fragments: { ...s.fragments } }; checkFragments(tentative); + // v0.16 叙事里程碑:自动解码同样检测阈值触发(与手动解码一致) + const milestoneHit = findMilestoneForTotal( + newTotal, + s.triggeredMilestones ?? [] + ); // v0.7 角色属性:自动解码给智慧经验(少量) const curAttrs = s.attributes ?? createInitialAttributes(); const curProg = s.attributeProgress ?? createInitialAttributeProgress(); @@ -710,6 +738,15 @@ export const useGameStore = create()( _lastAutoDecode: now, attributes: newAttributes, attributeProgress: newProgress, + ...(milestoneHit + ? { + triggeredMilestones: [ + ...(s.triggeredMilestones ?? []), + milestoneHit.id, + ], + pendingMilestone: milestoneHit, + } + : {}), ...syncStats({ tech: s.tech, blueprints: s.blueprints, achievements: s.achievements, activeTide: s.activeTide, constellation: s.constellation, attributes: newAttributes, idlePermanentBonus: s.idlePermanentBonus ?? 0 }), }); // 深空信标:自动解码也算进度 @@ -1452,6 +1489,21 @@ export const useGameStore = create()( description: `${descParts.join(" · ")}`, }); }, + + // v0.16 loreLayer 已读标记 — 玩家点击层卡片文本后调用 + markLoreLayerRead: (fragmentId, layer) => { + const key = `${fragmentId}_L${layer}`; + const cur = get().readLoreLayers; + if (Array.isArray(cur) && cur.includes(key)) return; // 已读 → no-op + const next = Array.isArray(cur) ? [...cur, key] : [key]; + set({ readLoreLayers: next }); + }, + + // v0.16 叙事里程碑 — 玩家点击任意处或按 Esc 关闭叠层时调用 + dismissMilestone: () => { + if (get().pendingMilestone == null) return; + set({ pendingMilestone: null }); + }, }), { name: "echo-nexus-save-v1", @@ -1476,6 +1528,15 @@ export const useGameStore = create()( s.idleStats && typeof s.idleStats === "object" ? s.idleStats : { projectsCompleted: 0, crystalsFromIdle: 0 }, + // v0.16 loreLayer 已读标记字段补全 + readLoreLayers: Array.isArray(s.readLoreLayers) + ? s.readLoreLayers + : [], + // v0.16 叙事里程碑字段补全 + triggeredMilestones: Array.isArray(s.triggeredMilestones) + ? s.triggeredMilestones + : [], + pendingMilestone: s.pendingMilestone ?? null, } as GameState; }, // 不持久化临时字段 diff --git a/tool-results/read_1782252803890_5147dd89b136.txt b/tool-results/read_1782252803890_5147dd89b136.txt deleted file mode 100755 index 6f88a077d..000000000 --- a/tool-results/read_1782252803890_5147dd89b136.txt +++ /dev/null @@ -1,1069 +0,0 @@ - 1→"use client"; - 2→// 回响星核 / Echo Nexus — Zustand 游戏状态管理 - 3→import { create } from "zustand"; - 4→import { persist, createJSONStorage } from "zustand/middleware"; - 5→import type { - 6→ GameState, - 7→ Crystal, - 8→ CrystalTier, - 9→ DecodePuzzle, - 10→ ExpeditionResult, - 11→} from "@/lib/game/types"; - 12→import { - 13→ INITIAL_STATE, - 14→ TECH_TREE, - 15→ CRYSTAL_VALUE, - 16→ CONTACT, - 17→ CRYSTAL_SPAWN, - 18→ FRAGMENTS, - 19→ PRESTIGE, - 20→} from "@/lib/game/config"; - 21→import { - 22→ createInitialState, - 23→ recomputeStats, - 24→ decodeRewards, - 25→ rollCrystalTierWithBonus, - 26→ computeNewBlueprints, - 27→ performPrestige, - 28→} from "@/lib/game/engine"; - 29→import { - 30→ generatePuzzle, - 31→ tryClickNode, - 32→ isSolvable, - 33→ resetPuzzle as resetPuz, - 34→} from "@/lib/game/decode"; - 35→import { - 36→ generateExpedition, - 37→ resolveNode, - 38→ advanceExpedition, - 39→ computeExpeditionPower, - 40→ computeExpeditionHp, - 41→ computeEnergyRegen, - 42→ EXPEDITION_CONFIG, - 43→} from "@/lib/game/expedition"; - 44→import { ACHIEVEMENTS, type Achievement } from "@/lib/game/achievements"; - 45→import { - 46→ TIDE_CONFIG, - 47→ rollTide, - 48→ getTideModifiers, - 49→ computeSilenceCompensation, - 50→ type StarTide, - 51→ type TideType, - 52→} from "@/lib/game/starTide"; - 53→import { - 54→ getPerk, - 55→ constellationBonuses, - 56→ rollPerkChoices, - 57→} from "@/lib/game/constellation"; - 58→import { - 59→ migrateChronicleFields, - 60→ withPerks, - 61→} from "@/lib/game/chronicle"; - 62→import { - 63→ generateDailyChallenge, - 64→ loadDailyProgress, - 65→ addBeaconProgress, - 66→ type BeaconDailyChallenge, - 67→ type BeaconDailyProgress, - 68→} from "@/lib/game/beacon"; - 69→import { setPendingOfflineReport } from "@/lib/game/offlineReport"; - 70→import { - 71→ ATTRIBUTE_HARD_CAP, - 72→ migrateAttributes, - 73→ levelUpCheck, - 74→ getAllBonuses, - 75→ createInitialAttributes, - 76→ createInitialAttributeProgress, - 77→ type AttributeKey, - 78→ type CharacterAttributes, - 79→ type AttributeProgress, - 80→} from "@/lib/game/attributes"; - 81→ - 82→interface GameActions { - 83→ // 生命周期 - 84→ init: () => void; - 85→ loadOnline: () => void; - 86→ hardReset: () => void; - 87→ - 88→ // 主循环 - 89→ tick: (now: number) => void; - 90→ pulse: () => { gain: number; combo: number } | null; - 91→ - 92→ // 星潮 - 93→ tickTide: (now: number) => { started?: TideType; ended?: TideType; silenceCompensation?: number } | null; - 94→ consumeTideEvents: () => { started?: TideType; ended?: TideType; silenceCompensation?: number }[]; - 95→ - 96→ // 解码 - 97→ startDecode: (crystalId: string) => void; - 98→ clickNode: (nodeId: number) => { ok: boolean; finished: boolean; solvable?: boolean; failReason?: string }; - 99→ undoStep: () => void; - 100→ retryPuzzle: () => void; - 101→ abandonPuzzle: () => void; - 102→ /** 自动解码 T1(技术解锁后由 tick 调用) */ - 103→ autoDecodeTick: () => void; - 104→ - 105→ // 探险 - 106→ startExpedition: () => { ok: boolean; reason?: string }; - 107→ resolveCurrentNode: () => ExpeditionResult | null; - 108→ advanceNode: () => void; - 109→ abortExpedition: () => void; - 110→ - 111→ // 技术 - 112→ buyTech: (techId: string) => boolean; - 113→ - 114→ // 飞升 - 115→ doPrestige: () => { newBp: number } | null; - 116→ - 117→ // 星图天文台 - 118→ chooseConstellationPerk: (perkId: string) => boolean; - 119→ rerollPerkChoices: () => void; - 120→ - 121→ // 成就 - 122→ checkAchievements: () => Achievement[]; - 123→ consumeAchievementQueue: () => Achievement[]; - 124→ - 125→ // 设置 - 126→ toggleTheme: () => void; - 127→ toggleSound: () => void; - 128→ - 129→ // 深空信标奖励发放(v0.5) - 130→ grantBeaconReward: (insights: number, contact: number) => void; - 131→ - 132→ // 深空巡航奖励发放(v0.6 — 实时 Canvas 玩法) - 133→ grantCruiseReward: (rewards: { crystals?: number; insights?: number; contact?: number }) => void; - 134→ - 135→ // 角色属性(v0.7 P1) - 136→ allocateAttribute: (attr: AttributeKey, points?: number) => { ok: boolean; leveledUp?: number }; - 137→ gainAttributeExp: (attr: AttributeKey, amount: number) => { leveledUp: number; newLevel: number }; - 138→ - 139→ // 派生 - 140→ canPrestige: () => boolean; - 141→} - 142→ - 143→type Store = GameState & GameActions & { - 144→ _lastAutoDecode: number; - 145→ _lastSpawn: number; - 146→ _combo: number; - 147→ _lastPulse: number; - 148→ _achievementQueue: Achievement[]; - 149→ _tideEvents: { started?: TideType; ended?: TideType; silenceCompensation?: number }[]; - 150→}; - 151→ - 152→/** 计算并写回产能字段 */ - 153→function syncStats(state: Partial) { - 154→ const s = recomputeStats(state); - 155→ return { - 156→ crystalsPerSec: s.crystalsPerSec, - 157→ crystalCap: s.crystalCap, - 158→ pulsePower: s.pulsePower, - 159→ offlineEff: s.offlineEff, - 160→ insightMult: s.insightMult, - 161→ contactRateMult: s.contactRateMult, - 162→ autoDecode: s.autoDecode, - 163→ decodeStepsBonus: s.decodeStepsBonus, - 164→ }; - 165→} - 166→ - 167→/** - 168→ * 深空信标进度追踪(v0.5)。 - 169→ * 在游戏关键动作(脉冲/解码/探险/BOSS/洞见)后调用,按今日挑战类型增量更新进度。 - 170→ * 进度独立存储于 localStorage(echo-nexus-beacon-prog-v1),不污染 GameState。 - 171→ * @returns 若刚完成则返回 true(供 UI 触发通知) - 172→ */ - 173→function trackBeacon( - 174→ type: "pulse" | "decode" | "expedition" | "boss" | "insight", - 175→ delta: number - 176→): boolean { - 177→ if (typeof window === "undefined") return false; - 178→ try { - 179→ const challenge: BeaconDailyChallenge = generateDailyChallenge(); - 180→ if (challenge.type !== type) return false; - 181→ const current: BeaconDailyProgress = loadDailyProgress(); - 182→ if (current.completedAt !== null) return false; // 已完成不再累加 - 183→ const { justCompleted } = addBeaconProgress(current, challenge, delta); - 184→ return justCompleted; - 185→ } catch { - 186→ return false; - 187→ } - 188→} - 189→ - 190→/** 检查并解锁叙事碎片 */ - 191→function checkFragments(state: GameState): string[] { - 192→ const unlocked: string[] = []; - 193→ for (const f of FRAGMENTS) { - 194→ if (!state.fragments[f.id] && state.totalDecoded >= f.threshold) { - 195→ state.fragments[f.id] = true; - 196→ unlocked.push(f.id); - 197→ } - 198→ } - 199→ return unlocked; - 200→} - 201→ - 202→export const useGameStore = create()( - 203→ persist( - 204→ (set, get) => ({ - 205→ ...createInitialState(), - 206→ _lastAutoDecode: Date.now(), - 207→ _lastSpawn: Date.now(), - 208→ _combo: 0, - 209→ _lastPulse: 0, - 210→ _achievementQueue: [], - 211→ _tideEvents: [], - 212→ - 213→ init: () => { - 214→ const s = get(); - 215→ const now = Date.now(); - 216→ // 兼容旧存档:若存在不可解的活跃谜题(旧算法生成),直接清掉 - 217→ let activePuzzle = s.activePuzzle; - 218→ if (activePuzzle && !isSolvable(activePuzzle)) { - 219→ // 把晶体放回队列,避免玩家卡死 - 220→ const crystal: Crystal = { - 221→ id: `c_${now}_rec`, - 222→ tier: activePuzzle.tier, - 223→ value: CRYSTAL_VALUE[activePuzzle.tier].crystals, - 224→ createdAt: now, - 225→ }; - 226→ activePuzzle = null; - 227→ set({ - 228→ pendingCrystals: [...s.pendingCrystals, crystal].slice(-CRYSTAL_SPAWN.maxPending), - 229→ }); - 230→ } - 231→ // 兼容旧存档:补全 achievements / 星潮 / 星图 字段 - 232→ const achievements = s.achievements ?? {}; - 233→ const activeTide = s.activeTide ?? null; - 234→ const constellation = s.constellation ?? []; - 235→ const pendingPerkChoices = s.pendingPerkChoices ?? null; - 236→ // v0.4 编年史兼容:补全 chronicle / runStart / bossKills / starTidesEncountered - 237→ const migrated = migrateChronicleFields(s); - 238→ // v0.7 角色属性兼容:补全 attributes / attributeProgress / pendingAttrPoints - 239→ const attrMigrated = migrateAttributes(s); - 240→ // 星图「能量共振」天赋 +1 能量上限 - 241→ const cm = constellationBonuses(constellation); - 242→ const energyMax = (INITIAL_STATE.energyMax) + cm.energyMaxBonus; - 243→ // lastTideEnd:0 表示从未触发过星潮(首次游玩),保留 0 让 firstDelay 生效 - 244→ const lastTideEndRaw = s.lastTideEnd ?? 0; - 245→ // 若旧存档有已过期的星潮,清掉 - 246→ const tide = activeTide && activeTide.endsAt > now ? activeTide : null; - 247→ // 首次进入:补发离线收益 - 248→ const elapsed = Math.max(0, (now - s.lastTick) / 1000); - 249→ if (elapsed > 5) { - 250→ const cap = 8 * 3600; - 251→ const secs = Math.min(elapsed, cap); - 252→ const gain = s.crystalsPerSec * secs * s.offlineEff; - 253→ const crystalsBefore = s.crystals; - 254→ const crystalsAfter = Math.min(s.crystalCap, s.crystals + gain); - 255→ // 设置离线报告(v0.5.2):通知 UI 弹出"欢迎回来"对话框 - 256→ setPendingOfflineReport({ - 257→ elapsedSec: secs, - 258→ rawElapsedSec: elapsed, - 259→ gain: crystalsAfter - crystalsBefore, - 260→ rate: s.crystalsPerSec, - 261→ eff: s.offlineEff, - 262→ capped: elapsed > cap, - 263→ crystalsBefore, - 264→ crystalsAfter, - 265→ crystalCap: s.crystalCap, - 266→ }); - 267→ set({ - 268→ crystals: crystalsAfter, - 269→ lastTick: now, - 270→ activePuzzle, - 271→ achievements, - 272→ activeTide: tide, - 273→ lastTideEnd: tide ? lastTideEndRaw : lastTideEndRaw, - 274→ constellation, - 275→ pendingPerkChoices, - 276→ energyMax, - 277→ chronicle: migrated.chronicle, - 278→ runStart: migrated.runStart, - 279→ bossKills: migrated.bossKills, - 280→ starTidesEncountered: migrated.starTidesEncountered, - 281→ attributes: attrMigrated.attributes, - 282→ attributeProgress: attrMigrated.attributeProgress, - 283→ pendingAttrPoints: attrMigrated.pendingAttrPoints, - 284→ ...syncStats({ tech: s.tech, blueprints: s.blueprints, achievements, activeTide: tide, constellation, attributes: attrMigrated.attributes }), - 285→ }); - 286→ } else { - 287→ set({ lastTick: now, activePuzzle, achievements, activeTide: tide, lastTideEnd: tide ? lastTideEndRaw : lastTideEndRaw, constellation, pendingPerkChoices, energyMax, chronicle: migrated.chronicle, runStart: migrated.runStart, bossKills: migrated.bossKills, starTidesEncountered: migrated.starTidesEncountered, attributes: attrMigrated.attributes, attributeProgress: attrMigrated.attributeProgress, pendingAttrPoints: attrMigrated.pendingAttrPoints, ...syncStats({ tech: s.tech, blueprints: s.blueprints, achievements, activeTide: tide, constellation, attributes: attrMigrated.attributes }) }); - 288→ } - 289→ }, - 290→ - 291→ loadOnline: () => { - 292→ const s = get(); - 293→ set({ ...syncStats({ tech: s.tech, blueprints: s.blueprints, achievements: s.achievements, activeTide: s.activeTide, constellation: s.constellation, attributes: s.attributes }) }); - 294→ }, - 295→ - 296→ hardReset: () => { - 297→ set({ ...createInitialState(), _lastAutoDecode: Date.now(), _lastSpawn: Date.now(), _combo: 0, _lastPulse: 0, _achievementQueue: [], _tideEvents: [] }); - 298→ }, - 299→ - 300→ tickTide: (now) => { - 301→ const s = get(); - 302→ const tide = s.activeTide; - 303→ // 星图「星潮引导」减少间隙 - 304→ const cm = constellationBonuses(s.constellation ?? []); - 305→ // v0.7 灵感:星潮触发概率 +X%(缩短间隙) - 306→ const am = getAllBonuses(s.attributes ?? {}); - 307→ const tideGapReduction = Math.min(0.3, am.tideTriggerBonus); - 308→ const gap = Math.max(15000, (TIDE_CONFIG.gap + cm.tideGapDeltaSec * 1000) * (1 - tideGapReduction)); - 309→ // 1) 检查当前星潮是否结束 - 310→ if (tide && now >= tide.endsAt) { - 311→ const endedType = tide.type; - 312→ // 寂静期补偿洞见 - 313→ let silenceCompensation = 0; - 314→ if (tide.type === "silence") { - 315→ silenceCompensation = computeSilenceCompensation(tide); - 316→ } - 317→ const newInsights = s.insights + silenceCompensation; - 318→ const event: { started?: TideType; ended?: TideType; silenceCompensation?: number } = { ended: endedType }; - 319→ if (silenceCompensation > 0) event.silenceCompensation = silenceCompensation; - 320→ set({ - 321→ activeTide: null, - 322→ lastTideEnd: now, - 323→ insights: newInsights, - 324→ // 星潮结束后重算 stats(移除 contactRate/insight 修饰) - 325→ ...syncStats({ tech: s.tech, blueprints: s.blueprints, achievements: s.achievements, activeTide: null, constellation: s.constellation, attributes: s.attributes }), - 326→ _tideEvents: [...s._tideEvents, event], - 327→ }); - 328→ return event; - 329→ } - 330→ // 2) 检查是否该触发新星潮(间隙已过) - 331→ if (!tide) { - 332→ const since = now - s.lastTideEnd; - 333→ // 首次:以 createdAt 起算 firstDelay;之后以 lastTideEnd 起算 gap - 334→ const firstStart = s.lastTideEnd === 0 || s.lastTideEnd <= s.createdAt + 1000; - 335→ const need = firstStart ? TIDE_CONFIG.firstDelay : gap; - 336→ if (since >= need) { - 337→ const type = rollTide(); - 338→ const newTide: StarTide = { - 339→ id: `tide_${now}_${Math.random().toString(36).slice(2, 7)}`, - 340→ type, - 341→ startedAt: now, - 342→ endsAt: now + TIDE_CONFIG.duration, - 343→ }; - 344→ const event: { started?: TideType; ended?: TideType; silenceCompensation?: number } = { started: type }; - 345→ // v0.4 编年史:累计遇到的星潮 ID(去重) - 346→ const tidesAll = s.starTidesEncountered ?? []; - 347→ const tideId = `tide_${type}`; - 348→ const newTidesAll = tidesAll.includes(tideId) ? tidesAll : [...tidesAll, tideId]; - 349→ set({ - 350→ activeTide: newTide, - 351→ starTidesEncountered: newTidesAll, - 352→ // 星潮开始后重算 stats(应用 contactRate/insight 修饰) - 353→ ...syncStats({ tech: s.tech, blueprints: s.blueprints, achievements: s.achievements, activeTide: newTide, constellation: s.constellation, attributes: s.attributes }), - 354→ _tideEvents: [...s._tideEvents, event], - 355→ }); - 356→ return event; - 357→ } - 358→ } - 359→ return null; - 360→ }, - 361→ - 362→ consumeTideEvents: () => { - 363→ const s = get(); - 364→ if (s._tideEvents.length === 0) return []; - 365→ const items = s._tideEvents; - 366→ set({ _tideEvents: [] }); - 367→ return items; - 368→ }, - 369→ - 370→ tick: (now) => { - 371→ const s = get(); - 372→ const dt = Math.max(0, (now - s.lastTick) / 1000); - 373→ if (dt <= 0) return; - 374→ - 375→ // 星潮产能修饰(即时乘) - 376→ const tideMod = getTideModifiers(s.activeTide); - 377→ const effCps = s.crystalsPerSec * tideMod.crystalsPerSecMult; - 378→ // 产能累加(仅闲置产能受仓库上限;探险奖励可超出) - 379→ const newCrystals = - 380→ s.crystals >= s.crystalCap - 381→ ? s.crystals // 已达/超上限,不再自动产出 - 382→ : Math.min(s.crystalCap, s.crystals + effCps * dt); - 383→ - 384→ // 自动产出待解码晶体(每 baseInterval 秒一颗,受技术/飞升略提速) - 385→ const bpBoost = 1 + s.blueprints.length * 0.03; - 386→ const spawnInterval = (CRYSTAL_SPAWN.baseIntervalSec / bpBoost) * 1000; - 387→ let pending = s.pendingCrystals; - 388→ let lastSpawn = s._lastSpawn; - 389→ if ( - 390→ now - lastSpawn > spawnInterval && - 391→ pending.length < CRYSTAL_SPAWN.maxPending - 392→ ) { - 393→ // 星图「晶体富集」提升 T2/T3 概率 - 394→ const cm = constellationBonuses(s.constellation ?? []); - 395→ const tier = rollCrystalTierWithBonus(cm.t2t3BonusRate); - 396→ const crystal: Crystal = { - 397→ id: `c_${now}_${Math.random().toString(36).slice(2, 7)}`, - 398→ tier, - 399→ value: CRYSTAL_VALUE[tier].crystals, - 400→ createdAt: now, - 401→ }; - 402→ pending = [...pending, crystal]; - 403→ lastSpawn = now; - 404→ } - 405→ - 406→ // 能量恢复(探险系统) - 407→ let energy = s.energy; - 408→ let lastEnergyTick = s.lastEnergyTick; - 409→ if (energy < s.energyMax) { - 410→ const regen = computeEnergyRegen(lastEnergyTick, now, energy, s.energyMax); - 411→ energy = regen.energy; - 412→ lastEnergyTick = regen.lastTick; - 413→ } else { - 414→ lastEnergyTick = now; - 415→ } - 416→ - 417→ set({ - 418→ crystals: newCrystals, - 419→ lastTick: now, - 420→ pendingCrystals: pending, - 421→ _lastSpawn: lastSpawn, - 422→ energy, - 423→ lastEnergyTick, - 424→ }); - 425→ }, - 426→ - 427→ pulse: () => { - 428→ const s = get(); - 429→ const now = Date.now(); - 430→ // 连击 - 431→ let combo = 1; - 432→ if (now - s._lastPulse < 1500) { - 433→ combo = Math.min(10, s._combo + 1); - 434→ } - 435→ const mult = 1 + (combo - 1) * 0.15; - 436→ // 星潮脉冲威力修饰 - 437→ const tideMod = getTideModifiers(s.activeTide); - 438→ // v0.7 灵感:脉冲连击加成 +X% - 439→ const am = getAllBonuses(s.attributes ?? {}); - 440→ const comboBonusMult = 1 + am.pulseComboBonus * Math.max(0, combo - 1); - 441→ const gain = s.pulsePower * mult * tideMod.pulsePowerMult * comboBonusMult; - 442→ set({ - 443→ crystals: Math.min(s.crystalCap, s.crystals + gain), - 444→ _combo: combo, - 445→ _lastPulse: now, - 446→ }); - 447→ // 深空信标:脉冲任务进度 +1 - 448→ trackBeacon("pulse", 1); - 449→ // v0.7 角色属性:连击 ≥3 给灵感经验 - 450→ if (combo >= 3) { - 451→ const expGain = 1 + Math.floor(combo / 2); // 3 连击=2, 5 连击=3, 10 连击=6 - 452→ // 内联经验获取(避免递归调用 set) - 453→ const prog = s.attributeProgress?.inspiration ?? { exp: 0, level: s.attributes?.inspiration ?? 0 }; - 454→ const nextExp = prog.exp + expGain; - 455→ const lvlResult = levelUpCheck( - 456→ { exp: nextExp, level: s.attributes?.inspiration ?? 0 }, - 457→ ATTRIBUTE_HARD_CAP - 458→ ); - 459→ const newAttributes: CharacterAttributes = { - 460→ ...(s.attributes ?? createInitialAttributes()), - 461→ inspiration: lvlResult.newProgress.level, - 462→ }; - 463→ const newProgress: AttributeProgress = { - 464→ ...(s.attributeProgress ?? createInitialAttributeProgress()), - 465→ inspiration: lvlResult.newProgress, - 466→ }; - 467→ set({ - 468→ attributes: newAttributes, - 469→ attributeProgress: newProgress, - 470→ ...syncStats({ tech: s.tech, blueprints: s.blueprints, achievements: s.achievements, activeTide: s.activeTide, constellation: s.constellation, attributes: newAttributes }), - 471→ }); - 472→ } - 473→ return { gain, combo }; - 474→ }, - 475→ - 476→ startDecode: (crystalId) => { - 477→ const s = get(); - 478→ const crystal = s.pendingCrystals.find((c) => c.id === crystalId); - 479→ if (!crystal) return; - 480→ const puzzle = generatePuzzle(crystal.tier); - 481→ set({ - 482→ activePuzzle: puzzle, - 483→ pendingCrystals: s.pendingCrystals.filter((c) => c.id !== crystalId), - 484→ }); - 485→ }, - 486→ - 487→ clickNode: (nodeId) => { - 488→ const s = get(); - 489→ if (!s.activePuzzle) return { ok: false, finished: false, failReason: "无活跃谜题" }; - 490→ // 深拷贝谜题 - 491→ const puzzle: DecodePuzzle = JSON.parse(JSON.stringify(s.activePuzzle)); - 492→ const res = tryClickNode(puzzle, nodeId); - 493→ if (res.ok) { - 494→ if (res.finished) { - 495→ // 结算奖励(星潮解码奖励修饰 + 星图「二周目经验」) - 496→ const tideMod = getTideModifiers(s.activeTide); - 497→ const cm = constellationBonuses(s.constellation ?? []); - 498→ const base = decodeRewards(puzzle.tier, s.insightMult, s.contactRateMult); - 499→ const finalMult = tideMod.decodeRewardMult * cm.decodeRewardMult; - 500→ const rewards = { - 501→ crystals: Math.round(base.crystals * finalMult), - 502→ insights: Math.round(base.insights * finalMult), - 503→ contact: +(base.contact * finalMult).toFixed(2), - 504→ }; - 505→ const newTotal = s.totalDecoded + 1; - 506→ const newContact = Math.min(100, s.contact + rewards.contact); - 507→ const newInsights = s.insights + rewards.insights; - 508→ const newCrystals = s.crystals + rewards.crystals; - 509→ // 解锁碎片 - 510→ const tentative: GameState = { ...s, totalDecoded: newTotal, fragments: { ...s.fragments } }; - 511→ const unlocked = checkFragments(tentative); - 512→ // v0.7 角色属性:完成解码给智慧经验(tier 越高经验越多) - 513→ const wisdomExpGain = puzzle.tier * 2; - 514→ const curAttrs = s.attributes ?? createInitialAttributes(); - 515→ const curProg = s.attributeProgress ?? createInitialAttributeProgress(); - 516→ const progEntry = curProg.wisdom ?? { exp: 0, level: curAttrs.wisdom }; - 517→ const wisdomLvl = levelUpCheck( - 518→ { exp: progEntry.exp + wisdomExpGain, level: curAttrs.wisdom }, - 519→ ATTRIBUTE_HARD_CAP - 520→ ); - 521→ const newAttributes: CharacterAttributes = { - 522→ ...curAttrs, - 523→ wisdom: wisdomLvl.newProgress.level, - 524→ }; - 525→ const newProgress: AttributeProgress = { - 526→ ...curProg, - 527→ wisdom: wisdomLvl.newProgress, - 528→ }; - 529→ set({ - 530→ activePuzzle: null, - 531→ crystals: newCrystals, - 532→ insights: newInsights, - 533→ contact: newContact, - 534→ totalDecoded: newTotal, - 535→ fragments: tentative.fragments, - 536→ attributes: newAttributes, - 537→ attributeProgress: newProgress, - 538→ ...syncStats({ tech: s.tech, blueprints: s.blueprints, achievements: s.achievements, activeTide: s.activeTide, constellation: s.constellation, attributes: newAttributes }), - 539→ }); - 540→ // 深空信标:解码 +1,洞见累计 - 541→ trackBeacon("decode", 1); - 542→ trackBeacon("insight", rewards.insights); - 543→ return { ok: true, finished: true, failReason: unlocked.join(",") || undefined }; - 544→ } - 545→ // 点击成功但未完成:检测当前局面是否仍可解 - 546→ const solvable = isSolvable(puzzle); - 547→ set({ activePuzzle: puzzle }); - 548→ return { ok: true, finished: false, solvable }; - 549→ } - 550→ return res; - 551→ }, - 552→ - 553→ undoStep: () => { - 554→ const s = get(); - 555→ if (!s.activePuzzle || s.activePuzzle.path.length === 0) return; - 556→ const puzzle: DecodePuzzle = JSON.parse(JSON.stringify(s.activePuzzle)); - 557→ const lastId = puzzle.path.pop(); - 558→ if (lastId !== undefined) { - 559→ const node = puzzle.grid.find((n) => n.id === lastId); - 560→ if (node) node.used = false; - 561→ } - 562→ set({ activePuzzle: puzzle }); - 563→ }, - 564→ - 565→ retryPuzzle: () => { - 566→ const s = get(); - 567→ if (!s.activePuzzle) return; - 568→ set({ activePuzzle: resetPuz(s.activePuzzle) }); - 569→ }, - 570→ - 571→ abandonPuzzle: () => { - 572→ const s = get(); - 573→ if (!s.activePuzzle) return; - 574→ // 晶体放回队列末尾 - 575→ const crystal: Crystal = { - 576→ id: `c_${Date.now()}_ret`, - 577→ tier: s.activePuzzle.tier, - 578→ value: CRYSTAL_VALUE[s.activePuzzle.tier].crystals, - 579→ createdAt: Date.now(), - 580→ }; - 581→ set({ - 582→ activePuzzle: null, - 583→ pendingCrystals: [...s.pendingCrystals, crystal].slice(-CRYSTAL_SPAWN.maxPending), - 584→ }); - 585→ }, - 586→ - 587→ autoDecodeTick: () => { - 588→ const s = get(); - 589→ if (!s.autoDecode) return; - 590→ const now = Date.now(); - 591→ // 星图「自动校准」减少自动解码周期 - 592→ const cm = constellationBonuses(s.constellation ?? []); - 593→ // v0.7 智慧:自动解码周期 -X% - 594→ const am = getAllBonuses(s.attributes ?? {}); - 595→ const baseInterval = 12000 + cm.autoDecodeIntervalDeltaSec * 1000; - 596→ const interval = Math.max(5000, baseInterval * am.autoDecodeIntervalMult); - 597→ if (now - s._lastAutoDecode < interval) return; - 598→ // 找一颗 T1 晶体自动解码 - 599→ const idx = s.pendingCrystals.findIndex((c) => c.tier === 1); - 600→ if (idx < 0) return; - 601→ const crystal = s.pendingCrystals[idx]; - 602→ const tideMod = getTideModifiers(s.activeTide); - 603→ const base = decodeRewards(1, s.insightMult, s.contactRateMult); - 604→ const finalMult = tideMod.decodeRewardMult * cm.decodeRewardMult; - 605→ const rewards = { - 606→ crystals: Math.round(base.crystals * finalMult), - 607→ insights: Math.round(base.insights * finalMult), - 608→ contact: +(base.contact * finalMult).toFixed(2), - 609→ }; - 610→ const newTotal = s.totalDecoded + 1; - 611→ const tentative: GameState = { ...s, totalDecoded: newTotal, fragments: { ...s.fragments } }; - 612→ checkFragments(tentative); - 613→ // v0.7 角色属性:自动解码给智慧经验(少量) - 614→ const curAttrs = s.attributes ?? createInitialAttributes(); - 615→ const curProg = s.attributeProgress ?? createInitialAttributeProgress(); - 616→ const progEntry = curProg.wisdom ?? { exp: 0, level: curAttrs.wisdom }; - 617→ const wisdomLvl = levelUpCheck( - 618→ { exp: progEntry.exp + 1, level: curAttrs.wisdom }, - 619→ ATTRIBUTE_HARD_CAP - 620→ ); - 621→ const newAttributes: CharacterAttributes = { - 622→ ...curAttrs, - 623→ wisdom: wisdomLvl.newProgress.level, - 624→ }; - 625→ const newProgress: AttributeProgress = { - 626→ ...curProg, - 627→ wisdom: wisdomLvl.newProgress, - 628→ }; - 629→ set({ - 630→ pendingCrystals: s.pendingCrystals.filter((c) => c.id !== crystal.id), - 631→ crystals: s.crystals + rewards.crystals, - 632→ insights: s.insights + rewards.insights, - 633→ contact: Math.min(100, s.contact + rewards.contact), - 634→ totalDecoded: newTotal, - 635→ fragments: tentative.fragments, - 636→ _lastAutoDecode: now, - 637→ attributes: newAttributes, - 638→ attributeProgress: newProgress, - 639→ ...syncStats({ tech: s.tech, blueprints: s.blueprints, achievements: s.achievements, activeTide: s.activeTide, constellation: s.constellation, attributes: newAttributes }), - 640→ }); - 641→ // 深空信标:自动解码也算进度 - 642→ trackBeacon("decode", 1); - 643→ trackBeacon("insight", rewards.insights); - 644→ }, - 645→ - 646→ buyTech: (techId) => { - 647→ const s = get(); - 648→ const node = TECH_TREE.find((t) => t.id === techId); - 649→ if (!node) return false; - 650→ const cur = s.tech[techId] ?? 0; - 651→ if (cur >= 1) return false; // v0.1 每节点 1 级 - 652→ if (s.insights < node.cost) return false; - 653→ const newTech = { ...s.tech, [techId]: 1 }; - 654→ set({ - 655→ insights: s.insights - node.cost, - 656→ tech: newTech, - 657→ ...syncStats({ tech: newTech, blueprints: s.blueprints, achievements: s.achievements, activeTide: s.activeTide, constellation: s.constellation, attributes: s.attributes }), - 658→ }); - 659→ return true; - 660→ }, - 661→ - 662→ // ============ 探险系统 ============ - 663→ startExpedition: () => { - 664→ const s = get(); - 665→ if (s.activeExpedition && !s.activeExpedition.finished) { - 666→ return { ok: false, reason: "已有进行中的探险" }; - 667→ } - 668→ if (s.energy < EXPEDITION_CONFIG.energyCost) { - 669→ return { ok: false, reason: "能量不足" }; - 670→ } - 671→ const tideMod = getTideModifiers(s.activeTide); - 672→ // v0.7 角色属性:探索力 +X% 探险力,勇气 +X 探险生命 - 673→ const am = getAllBonuses(s.attributes ?? {}); - 674→ const basePower = computeExpeditionPower(s) + tideMod.expeditionPowerBonus; - 675→ const baseHp = computeExpeditionHp(s) + tideMod.expeditionHpBonus; - 676→ const power = Math.round(basePower * am.expeditionPowerMult); - 677→ const hp = baseHp + am.expeditionHpBonus; - 678→ const seed = Math.floor(Math.random() * 1e9); - 679→ const expedition = generateExpedition(seed, power, hp); - 680→ set({ - 681→ activeExpedition: expedition, - 682→ energy: s.energy - EXPEDITION_CONFIG.energyCost, - 683→ totalExpeditions: s.totalExpeditions + 1, - 684→ }); - 685→ return { ok: true }; - 686→ }, - 687→ - 688→ resolveCurrentNode: () => { - 689→ const s = get(); - 690→ if (!s.activeExpedition || s.activeExpedition.finished) return null; - 691→ // 深拷贝 - 692→ const exp = JSON.parse(JSON.stringify(s.activeExpedition)); - 693→ // v0.7 勇气:BOSS 战胜率 +X%(动态提高 RNG 阈值) - 694→ const nodeBefore = exp.nodes[exp.currentNode]; - 695→ const isBossNode = nodeBefore?.type === "boss"; - 696→ const am = getAllBonuses(s.attributes ?? {}); - 697→ const result = isBossNode - 698→ ? resolveNode(exp, () => { - 699→ // 单次 rng() 调用:B% 概率返回 0(必胜),其余情况返回 r-B(保持均匀分布) - 700→ const r = Math.random(); - 701→ const b = Math.min(0.95, am.bossWinRateBonus); - 702→ return r < b ? 0 : Math.min(1, r - b); - 703→ }) - 704→ : resolveNode(exp); - 705→ // 累计奖励 - 706→ if (result.crystals) exp.rewards.crystals += result.crystals; - 707→ if (result.insights) exp.rewards.insights += result.insights; - 708→ if (result.contact) exp.rewards.contact += result.contact; - 709→ if (result.fragments) exp.rewards.fragments.push(...result.fragments); - 710→ if (result.hpDelta) exp.hp = Math.max(0, Math.min(exp.maxHp, exp.hp + result.hpDelta)); - 711→ // 实时入账(玩家立即获得) - 712→ const newCrystals = s.crystals + (result.crystals || 0); - 713→ const newInsights = s.insights + (result.insights || 0); - 714→ const newContact = Math.min(100, s.contact + (result.contact || 0)); - 715→ // 碎片解锁 - 716→ const newFragments = { ...s.fragments }; - 717→ if (result.fragments) { - 718→ for (const fid of result.fragments) newFragments[fid] = true; - 719→ } - 720→ // 日志 - 721→ const logEntry = { - 722→ expeditionId: exp.id, - 723→ nodeType: nodeBefore?.type || "combat", - 724→ result: result.log, - 725→ rewards: [ - 726→ result.crystals ? `+${result.crystals}晶体` : "", - 727→ result.insights ? `+${result.insights}洞见` : "", - 728→ result.contact ? `+${result.contact.toFixed(1)}接触` : "", - 729→ result.hpDelta ? `${result.hpDelta > 0 ? "+" : ""}${result.hpDelta}生命` : "", - 730→ ].filter(Boolean).join(" "), - 731→ timestamp: Date.now(), - 732→ }; - 733→ const newLog = [logEntry, ...s.expeditionLog].slice(0, 30); - 734→ - 735→ if (result.ended) { - 736→ // 探险结束(胜利或失败) - 737→ exp.finished = true; - 738→ } - 739→ - 740→ // v0.4 编年史:击破 BOSS 时累计计数 - 741→ let bossKills = s.bossKills ?? 0; - 742→ let bossKilledThisNode = false; - 743→ if ( - 744→ result.ended && - 745→ result.endReason === "victory" && - 746→ nodeBefore?.type === "boss" - 747→ ) { - 748→ bossKills = bossKills + 1; - 749→ bossKilledThisNode = true; - 750→ } - 751→ - 752→ // v0.7 角色属性:战斗胜利给勇气+探索力经验;BOSS 额外奖励 - 753→ let newAttributes = s.attributes ?? createInitialAttributes(); - 754→ let newProgress = s.attributeProgress ?? createInitialAttributeProgress(); - 755→ let statsNeedResync = false; - 756→ // 战斗类节点(combat/boss)且胜利:勇气 + 探索力经验 - 757→ const isCombatVictory = - 758→ (nodeBefore?.type === "combat" || nodeBefore?.type === "boss") && - 759→ !result.ended; // 中途战斗胜利(未结束探险) - 760→ const isExpeditionVictory = - 761→ result.ended && result.endReason === "victory"; - 762→ if (isCombatVictory || bossKilledThisNode || isExpeditionVictory) { - 763→ const courageGain = bossKilledThisNode ? 8 : 2; - 764→ const explorationGain = bossKilledThisNode ? 6 : isExpeditionVictory ? 4 : 1; - 765→ const courageLvl = levelUpCheck( - 766→ { exp: newProgress.courage.exp + courageGain, level: newAttributes.courage }, - 767→ ATTRIBUTE_HARD_CAP - 768→ ); - 769→ const explLvl = levelUpCheck( - 770→ { exp: newProgress.exploration.exp + explorationGain, level: newAttributes.exploration }, - 771→ ATTRIBUTE_HARD_CAP - 772→ ); - 773→ newAttributes = { - 774→ ...newAttributes, - 775→ courage: courageLvl.newProgress.level, - 776→ exploration: explLvl.newProgress.level, - 777→ }; - 778→ newProgress = { - 779→ ...newProgress, - 780→ courage: courageLvl.newProgress, - 781→ exploration: explLvl.newProgress, - 782→ }; - 783→ statsNeedResync = true; - 784→ } - 785→ - 786→ set({ - 787→ activeExpedition: exp, - 788→ crystals: newCrystals, - 789→ insights: newInsights, - 790→ contact: newContact, - 791→ fragments: newFragments, - 792→ expeditionLog: newLog, - 793→ bossKills, - 794→ attributes: newAttributes, - 795→ attributeProgress: newProgress, - 796→ ...(statsNeedResync - 797→ ? syncStats({ tech: s.tech, blueprints: s.blueprints, achievements: s.achievements, activeTide: s.activeTide, constellation: s.constellation, attributes: newAttributes }) - 798→ : {}), - 799→ }); - 800→ // 深空信标:探险完成(胜负都计)+ 洞见累计 + BOSS 击破 - 801→ if (result.ended) { - 802→ trackBeacon("expedition", 1); - 803→ if (bossKilledThisNode) { - 804→ trackBeacon("boss", 1); - 805→ } - 806→ } - 807→ if (result.insights) trackBeacon("insight", result.insights); - 808→ return result; - 809→ }, - 810→ - 811→ advanceNode: () => { - 812→ const s = get(); - 813→ if (!s.activeExpedition || s.activeExpedition.finished) return; - 814→ const exp = JSON.parse(JSON.stringify(s.activeExpedition)); - 815→ const node = exp.nodes[exp.currentNode]; - 816→ if (!node || !node.cleared) return; // 当前节点未结算不能前进 - 817→ if (exp.currentNode >= exp.nodes.length - 1) return; - 818→ exp.currentNode++; - 819→ set({ activeExpedition: exp }); - 820→ }, - 821→ - 822→ abortExpedition: () => { - 823→ const s = get(); - 824→ if (!s.activeExpedition) return; - 825→ const exp = JSON.parse(JSON.stringify(s.activeExpedition)); - 826→ exp.finished = true; - 827→ const logEntry = { - 828→ expeditionId: exp.id, - 829→ nodeType: "rest" as const, - 830→ result: "探险队主动撤退,保留已获奖励。", - 831→ rewards: "", - 832→ timestamp: Date.now(), - 833→ }; - 834→ set({ - 835→ activeExpedition: exp, - 836→ expeditionLog: [logEntry, ...s.expeditionLog].slice(0, 30), - 837→ }); - 838→ }, - 839→ - 840→ doPrestige: () => { - 841→ const s = get(); - 842→ if (s.contact < CONTACT.prestigeMin) return null; - 843→ const newBp = computeNewBlueprints(s); - 844→ const next = performPrestige(s); - 845→ // 星图「能量共振」提升上限 - 846→ const cm = constellationBonuses(next.constellation ?? []); - 847→ const energyMax = INITIAL_STATE.energyMax + cm.energyMaxBonus; - 848→ set({ - 849→ ...next, - 850→ energyMax, - 851→ ...syncStats({ tech: next.tech, blueprints: next.blueprints, achievements: next.achievements, activeTide: next.activeTide, constellation: next.constellation, attributes: next.attributes }), - 852→ _lastAutoDecode: Date.now(), - 853→ _lastSpawn: Date.now(), - 854→ _combo: 0, - 855→ _lastPulse: 0, - 856→ _tideEvents: [], - 857→ }); - 858→ return { newBp }; - 859→ }, - 860→ - 861→ chooseConstellationPerk: (perkId) => { - 862→ const s = get(); - 863→ if (!s.pendingPerkChoices || !s.pendingPerkChoices.includes(perkId)) return false; - 864→ const perk = getPerk(perkId); - 865→ if (!perk) return false; - 866→ if (s.constellation?.includes(perkId)) return false; - 867→ const newConstellation = [...(s.constellation ?? []), perkId]; - 868→ const cm = constellationBonuses(newConstellation); - 869→ const energyMax = INITIAL_STATE.energyMax + cm.energyMaxBonus; - 870→ // v0.4 编年史:回填最近一条 entry 的 perksThisAscension - 871→ const chronicle = s.chronicle ?? []; - 872→ let newChronicle = chronicle; - 873→ if (chronicle.length > 0) { - 874→ const lastEntry = chronicle[chronicle.length - 1]; - 875→ const updatedLast = withPerks(lastEntry, [perkId]); - 876→ newChronicle = [...chronicle.slice(0, -1), updatedLast]; - 877→ } - 878→ set({ - 879→ constellation: newConstellation, - 880→ pendingPerkChoices: null, - 881→ energyMax, - 882→ chronicle: newChronicle, - 883→ ...syncStats({ tech: s.tech, blueprints: s.blueprints, achievements: s.achievements, activeTide: s.activeTide, constellation: newConstellation, attributes: s.attributes }), - 884→ }); - 885→ return true; - 886→ }, - 887→ - 888→ rerollPerkChoices: () => { - 889→ const s = get(); - 890→ if (!s.pendingPerkChoices) return; - 891→ const choices = rollPerkChoices(s.constellation ?? []); - 892→ if (choices.length > 0) set({ pendingPerkChoices: choices }); - 893→ }, - 894→ - 895→ checkAchievements: () => { - 896→ const s = get(); - 897→ const newlyUnlocked: Achievement[] = []; - 898→ const updated = { ...s.achievements }; - 899→ let crystals = s.crystals; - 900→ let insights = s.insights; - 901→ let contact = s.contact; - 902→ let statsDirty = false; - 903→ for (const a of ACHIEVEMENTS) { - 904→ if (updated[a.id]) continue; - 905→ if (a.check(s)) { - 906→ updated[a.id] = true; - 907→ newlyUnlocked.push(a); - 908→ // 发放即时奖励 - 909→ if (a.reward.crystals) crystals += a.reward.crystals; - 910→ if (a.reward.insights) insights += a.reward.insights; - 911→ if (a.reward.contact) contact = Math.min(100, contact + a.reward.contact); - 912→ if (a.reward.crystalsPerSecPct || a.reward.insightPct) statsDirty = true; - 913→ } - 914→ } - 915→ if (newlyUnlocked.length === 0) return []; - 916→ set({ - 917→ achievements: updated, - 918→ crystals, - 919→ insights, - 920→ contact, - 921→ ...(statsDirty - 922→ ? syncStats({ tech: s.tech, blueprints: s.blueprints, achievements: updated, activeTide: s.activeTide, constellation: s.constellation, attributes: s.attributes }) - 923→ : {}), - 924→ _achievementQueue: [...s._achievementQueue, ...newlyUnlocked], - 925→ }); - 926→ return newlyUnlocked; - 927→ }, - 928→ - 929→ consumeAchievementQueue: () => { - 930→ const s = get(); - 931→ if (s._achievementQueue.length === 0) return []; - 932→ const items = s._achievementQueue; - 933→ set({ _achievementQueue: [] }); - 934→ return items; - 935→ }, - 936→ - 937→ canPrestige: () => get().contact >= CONTACT.prestigeMin, - 938→ - 939→ toggleTheme: () => set({ theme: get().theme === "dark" ? "light" : "dark" }), - 940→ toggleSound: () => set({ soundOn: !get().soundOn }), - 941→ - 942→ // 深空信标:发放每日挑战奖励(v0.5) - 943→ grantBeaconReward: (insights, contact) => { - 944→ const s = get(); - 945→ set({ - 946→ insights: s.insights + Math.round(insights), - 947→ contact: Math.min(100, s.contact + contact), - 948→ }); - 949→ }, - 950→ - 951→ // 深空巡航:发放实时玩法奖励(v0.6) - 952→ grantCruiseReward: (rewards) => { - 953→ const s = get(); - 954→ const addCrystals = rewards.crystals ?? 0; - 955→ const addInsights = rewards.insights ?? 0; - 956→ const addContact = rewards.contact ?? 0; - 957→ // v0.7 角色属性:巡航通关给探索力+勇气经验(按晶体奖励量缩放) - 958→ const totalReward = addCrystals + addInsights * 10 + addContact * 10; - 959→ const expBase = Math.max(2, Math.floor(totalReward / 30)); - 960→ const curAttrs = s.attributes ?? createInitialAttributes(); - 961→ const curProg = s.attributeProgress ?? createInitialAttributeProgress(); - 962→ const explLvl = levelUpCheck( - 963→ { exp: curProg.exploration.exp + expBase, level: curAttrs.exploration }, - 964→ ATTRIBUTE_HARD_CAP - 965→ ); - 966→ const courageLvl = levelUpCheck( - 967→ { exp: curProg.courage.exp + Math.floor(expBase * 0.6), level: curAttrs.courage }, - 968→ ATTRIBUTE_HARD_CAP - 969→ ); - 970→ const newAttributes: CharacterAttributes = { - 971→ ...curAttrs, - 972→ exploration: explLvl.newProgress.level, - 973→ courage: courageLvl.newProgress.level, - 974→ }; - 975→ const newProgress: AttributeProgress = { - 976→ ...curProg, - 977→ exploration: explLvl.newProgress, - 978→ courage: courageLvl.newProgress, - 979→ }; - 980→ set({ - 981→ crystals: Math.min(s.crystalCap, s.crystals + addCrystals), - 982→ insights: s.insights + Math.round(addInsights), - 983→ contact: Math.min(100, s.contact + addContact), - 984→ attributes: newAttributes, - 985→ attributeProgress: newProgress, - 986→ ...syncStats({ tech: s.tech, blueprints: s.blueprints, achievements: s.achievements, activeTide: s.activeTide, constellation: s.constellation, attributes: newAttributes }), - 987→ }); - 988→ }, - 989→ - 990→ // ============ 角色属性系统(v0.7 P1) ============ - 991→ allocateAttribute: (attr, points = 1) => { - 992→ const s = get(); - 993→ const cur = s.attributes ?? createInitialAttributes(); - 994→ const curVal = cur[attr] ?? 0; - 995→ if (curVal >= ATTRIBUTE_HARD_CAP) { - 996→ return { ok: false, leveledUp: 0 }; - 997→ } - 998→ if ((s.pendingAttrPoints ?? 0) < points) { - 999→ return { ok: false, leveledUp: 0 }; - 1000→ } - 1001→ const alloc = Math.min(points, ATTRIBUTE_HARD_CAP - curVal, s.pendingAttrPoints); - 1002→ const newVal = curVal + alloc; - 1003→ const newAttributes: CharacterAttributes = { ...cur, [attr]: newVal }; - 1004→ // 同步经验进度 level 字段(保持一致) - 1005→ const curProg = s.attributeProgress ?? createInitialAttributeProgress(); - 1006→ const oldProg = curProg[attr] ?? { exp: 0, level: curVal }; - 1007→ const newProgress: AttributeProgress = { - 1008→ ...curProg, - 1009→ [attr]: { exp: oldProg.exp, level: newVal }, - 1010→ }; - 1011→ set({ - 1012→ attributes: newAttributes, - 1013→ attributeProgress: newProgress, - 1014→ pendingAttrPoints: s.pendingAttrPoints - alloc, - 1015→ ...syncStats({ tech: s.tech, blueprints: s.blueprints, achievements: s.achievements, activeTide: s.activeTide, constellation: s.constellation, attributes: newAttributes }), - 1016→ }); - 1017→ return { ok: true, leveledUp: alloc }; - 1018→ }, - 1019→ - 1020→ gainAttributeExp: (attr, amount) => { - 1021→ const s = get(); - 1022→ const cur = s.attributes ?? createInitialAttributes(); - 1023→ const curProg = s.attributeProgress ?? createInitialAttributeProgress(); - 1024→ const progEntry = curProg[attr] ?? { exp: 0, level: cur[attr] }; - 1025→ const result = levelUpCheck( - 1026→ { exp: progEntry.exp + amount, level: cur[attr] }, - 1027→ ATTRIBUTE_HARD_CAP - 1028→ ); - 1029→ const newAttributes: CharacterAttributes = { ...cur, [attr]: result.newProgress.level }; - 1030→ const newProgress: AttributeProgress = { - 1031→ ...curProg, - 1032→ [attr]: result.newProgress, - 1033→ }; - 1034→ set({ - 1035→ attributes: newAttributes, - 1036→ attributeProgress: newProgress, - 1037→ ...syncStats({ tech: s.tech, blueprints: s.blueprints, achievements: s.achievements, activeTide: s.activeTide, constellation: s.constellation, attributes: newAttributes }), - 1038→ }); - 1039→ return { leveledUp: result.levelsGained, newLevel: result.newProgress.level }; - 1040→ }, - 1041→ }), - 1042→ { - 1043→ name: "echo-nexus-save-v1", - 1044→ storage: createJSONStorage(() => localStorage), - 1045→ // 不持久化临时字段 - 1046→ partialize: (s) => { - 1047→ const { _lastAutoDecode, _lastSpawn, _combo, _lastPulse, _achievementQueue, _tideEvents, ...rest } = s; - 1048→ void _lastAutoDecode; void _lastSpawn; void _combo; void _lastPulse; void _achievementQueue; void _tideEvents; - 1049→ return rest as GameState; - 1050→ }, - 1051→ } - 1052→ ) - 1053→); - 1054→ - 1055→/** 选择器:未解锁碎片中下一个门槛 */ - 1056→export function nextFragmentThreshold(totalDecoded: number): number | null { - 1057→ for (const f of FRAGMENTS) { - 1058→ if (totalDecoded < f.threshold) return f.threshold; - 1059→ } - 1060→ return null; - 1061→} - 1062→ - 1063→export { FRAGMENTS, PRESTIGE, TECH_TREE }; - 1064→ - 1065→// 开发期调试:暴露 store 到 window,便于 QA 测试 - 1066→if (typeof window !== "undefined" && process.env.NODE_ENV !== "production") { - 1067→ (window as unknown as { __gameStore?: typeof useGameStore }).__gameStore = useGameStore; - 1068→} - 1069→ \ No newline at end of file diff --git a/tool-results/read_1782294652693_8a406bf6bfd8.txt b/tool-results/read_1782294652693_8a406bf6bfd8.txt deleted file mode 100755 index f57a195d9..000000000 --- a/tool-results/read_1782294652693_8a406bf6bfd8.txt +++ /dev/null @@ -1,1180 +0,0 @@ - 1→"use client"; - 2→// 回响星核 / Echo Nexus — Zustand 游戏状态管理 - 3→import { create } from "zustand"; - 4→import { persist, createJSONStorage } from "zustand/middleware"; - 5→import type { - 6→ GameState, - 7→ Crystal, - 8→ CrystalTier, - 9→ DecodePuzzle, - 10→ ExpeditionResult, - 11→} from "@/lib/game/types"; - 12→import { - 13→ INITIAL_STATE, - 14→ TECH_TREE, - 15→ CRYSTAL_VALUE, - 16→ CONTACT, - 17→ CRYSTAL_SPAWN, - 18→ FRAGMENTS, - 19→ PRESTIGE, - 20→} from "@/lib/game/config"; - 21→import { - 22→ createInitialState, - 23→ recomputeStats, - 24→ decodeRewards, - 25→ rollCrystalTierWithBonus, - 26→ computeNewBlueprints, - 27→ performPrestige, - 28→} from "@/lib/game/engine"; - 29→import { - 30→ generatePuzzle, - 31→ tryClickNode, - 32→ isSolvable, - 33→ resetPuzzle as resetPuz, - 34→} from "@/lib/game/decode"; - 35→import { - 36→ generateExpedition, - 37→ resolveNode, - 38→ advanceExpedition, - 39→ computeExpeditionPower, - 40→ computeExpeditionHp, - 41→ computeEnergyRegen, - 42→ computeEnergyRegenInterval, - 43→ EXPEDITION_CONFIG, - 44→} from "@/lib/game/expedition"; - 45→import { ACHIEVEMENTS, type Achievement } from "@/lib/game/achievements"; - 46→import { - 47→ TIDE_CONFIG, - 48→ rollTide, - 49→ getTideModifiers, - 50→ computeSilenceCompensation, - 51→ type StarTide, - 52→ type TideType, - 53→} from "@/lib/game/starTide"; - 54→import { - 55→ getPerk, - 56→ constellationBonuses, - 57→ rollPerkChoices, - 58→} from "@/lib/game/constellation"; - 59→import { - 60→ migrateChronicleFields, - 61→ withPerks, - 62→} from "@/lib/game/chronicle"; - 63→import { - 64→ generateDailyChallenge, - 65→ generateWeeklyChallenge, - 66→ loadDailyProgress, - 67→ loadWeeklyProgress, - 68→ loadChainState, - 69→ addBeaconProgress, - 70→ addWeeklyProgress, - 71→ recordChainCompletion, - 72→ claimWeeklyReward, - 73→ claimChainMilestone, - 74→ getTodayKey, - 75→ type BeaconDailyChallenge, - 76→ type BeaconDailyProgress, - 77→ type BeaconWeeklyChallenge, - 78→ type BeaconWeeklyProgress, - 79→} from "@/lib/game/beacon"; - 80→import { setPendingOfflineReport } from "@/lib/game/offlineReport"; - 81→import { - 82→ ATTRIBUTE_HARD_CAP, - 83→ migrateAttributes, - 84→ levelUpCheck, - 85→ getAllBonuses, - 86→ createInitialAttributes, - 87→ createInitialAttributeProgress, - 88→ type AttributeKey, - 89→ type CharacterAttributes, - 90→ type AttributeProgress, - 91→} from "@/lib/game/attributes"; - 92→ - 93→interface GameActions { - 94→ // 生命周期 - 95→ init: () => void; - 96→ loadOnline: () => void; - 97→ hardReset: () => void; - 98→ - 99→ // 主循环 - 100→ tick: (now: number) => void; - 101→ pulse: () => { gain: number; combo: number } | null; - 102→ - 103→ // 星潮 - 104→ tickTide: (now: number) => { started?: TideType; ended?: TideType; silenceCompensation?: number } | null; - 105→ consumeTideEvents: () => { started?: TideType; ended?: TideType; silenceCompensation?: number }[]; - 106→ - 107→ // 解码 - 108→ startDecode: (crystalId: string) => void; - 109→ clickNode: (nodeId: number) => { ok: boolean; finished: boolean; solvable?: boolean; failReason?: string }; - 110→ undoStep: () => void; - 111→ retryPuzzle: () => void; - 112→ abandonPuzzle: () => void; - 113→ /** 自动解码 T1(技术解锁后由 tick 调用) */ - 114→ autoDecodeTick: () => void; - 115→ - 116→ // 探险 - 117→ startExpedition: () => { ok: boolean; reason?: string }; - 118→ resolveCurrentNode: () => ExpeditionResult | null; - 119→ advanceNode: () => void; - 120→ abortExpedition: () => void; - 121→ - 122→ // 技术 - 123→ buyTech: (techId: string) => boolean; - 124→ - 125→ // 飞升 - 126→ doPrestige: () => { newBp: number } | null; - 127→ - 128→ // 星图天文台 - 129→ chooseConstellationPerk: (perkId: string) => boolean; - 130→ rerollPerkChoices: () => void; - 131→ - 132→ // 成就 - 133→ checkAchievements: () => Achievement[]; - 134→ consumeAchievementQueue: () => Achievement[]; - 135→ - 136→ // 设置 - 137→ toggleTheme: () => void; - 138→ toggleSound: () => void; - 139→ - 140→ // 深空信标奖励发放(v0.5) - 141→ grantBeaconReward: (insights: number, contact: number) => void; - 142→ - 143→ // 深空信标 · 周挑战领取 + 信标链里程碑领取(v0.8) - 144→ claimWeeklyBeacon: () => { - 145→ rewardInsight: number; - 146→ rewardContact: number; - 147→ score: number; - 148→ }; - 149→ claimChainReward: (milestone: number) => { - 150→ rewardInsight: number; - 151→ rewardContact: number; - 152→ label: string; - 153→ ok: boolean; - 154→ }; - 155→ - 156→ // 深空巡航奖励发放(v0.6 — 实时 Canvas 玩法) - 157→ grantCruiseReward: (rewards: { crystals?: number; insights?: number; contact?: number }) => void; - 158→ - 159→ // 角色属性(v0.7 P1) - 160→ allocateAttribute: (attr: AttributeKey, points?: number) => { ok: boolean; leveledUp?: number }; - 161→ gainAttributeExp: (attr: AttributeKey, amount: number) => { leveledUp: number; newLevel: number }; - 162→ - 163→ // 派生 - 164→ canPrestige: () => boolean; - 165→} - 166→ - 167→type Store = GameState & GameActions & { - 168→ _lastAutoDecode: number; - 169→ _lastSpawn: number; - 170→ _combo: number; - 171→ _lastPulse: number; - 172→ _achievementQueue: Achievement[]; - 173→ _tideEvents: { started?: TideType; ended?: TideType; silenceCompensation?: number }[]; - 174→}; - 175→ - 176→/** 计算并写回产能字段 */ - 177→function syncStats(state: Partial) { - 178→ const s = recomputeStats(state); - 179→ return { - 180→ crystalsPerSec: s.crystalsPerSec, - 181→ crystalCap: s.crystalCap, - 182→ pulsePower: s.pulsePower, - 183→ offlineEff: s.offlineEff, - 184→ insightMult: s.insightMult, - 185→ contactRateMult: s.contactRateMult, - 186→ autoDecode: s.autoDecode, - 187→ decodeStepsBonus: s.decodeStepsBonus, - 188→ }; - 189→} - 190→ - 191→/** - 192→ * 深空信标进度追踪(v0.5 → v0.8 扩展)。 - 193→ * 在游戏关键动作(脉冲/解码/探险/BOSS/洞见)后调用,同时更新: - 194→ * 1. 日挑战进度(按今日挑战类型增量) - 195→ * 2. 周挑战进度(按本周挑战类型增量) - 196→ * 3. 信标链:日挑战刚完成时记录一次完成(含 grace 续命逻辑) - 197→ * 进度独立存储于 localStorage,不污染 GameState。 - 198→ * @returns 三类状态变更供 UI 触发通知 - 199→ */ - 200→function trackBeacon( - 201→ type: "pulse" | "decode" | "expedition" | "boss" | "insight", - 202→ delta: number - 203→): { - 204→ dailyJustCompleted: boolean; - 205→ weeklyJustCompleted: boolean; - 206→ newChainMilestones: number[]; - 207→} { - 208→ const result = { - 209→ dailyJustCompleted: false, - 210→ weeklyJustCompleted: false, - 211→ newChainMilestones: [] as number[], - 212→ }; - 213→ if (typeof window === "undefined") return result; - 214→ try { - 215→ // ---- 日挑战 ---- - 216→ const challenge: BeaconDailyChallenge = generateDailyChallenge(); - 217→ if (challenge.type === type) { - 218→ const current: BeaconDailyProgress = loadDailyProgress(); - 219→ if (current.completedAt === null) { - 220→ const { justCompleted } = addBeaconProgress(current, challenge, delta); - 221→ result.dailyJustCompleted = justCompleted; - 222→ // 日挑战刚完成 → 更新信标链 - 223→ if (justCompleted) { - 224→ const { newMilestones } = recordChainCompletion(getTodayKey()); - 225→ result.newChainMilestones = newMilestones; - 226→ } - 227→ } - 228→ } - 229→ - 230→ // ---- 周挑战 ---- - 231→ const wChallenge: BeaconWeeklyChallenge = generateWeeklyChallenge(); - 232→ if (wChallenge.type === type) { - 233→ const wCurrent: BeaconWeeklyProgress = loadWeeklyProgress(); - 234→ if (wCurrent.completedAt === null) { - 235→ const { justCompleted } = addWeeklyProgress( - 236→ wCurrent, - 237→ wChallenge, - 238→ delta - 239→ ); - 240→ result.weeklyJustCompleted = justCompleted; - 241→ } - 242→ } - 243→ - 244→ return result; - 245→ } catch { - 246→ return result; - 247→ } - 248→} - 249→ - 250→/** 检查并解锁叙事碎片 */ - 251→function checkFragments(state: GameState): string[] { - 252→ const unlocked: string[] = []; - 253→ for (const f of FRAGMENTS) { - 254→ if (!state.fragments[f.id] && state.totalDecoded >= f.threshold) { - 255→ state.fragments[f.id] = true; - 256→ unlocked.push(f.id); - 257→ } - 258→ } - 259→ return unlocked; - 260→} - 261→ - 262→export const useGameStore = create()( - 263→ persist( - 264→ (set, get) => ({ - 265→ ...createInitialState(), - 266→ _lastAutoDecode: Date.now(), - 267→ _lastSpawn: Date.now(), - 268→ _combo: 0, - 269→ _lastPulse: 0, - 270→ _achievementQueue: [], - 271→ _tideEvents: [], - 272→ - 273→ init: () => { - 274→ const s = get(); - 275→ const now = Date.now(); - 276→ // 兼容旧存档:若存在不可解的活跃谜题(旧算法生成),直接清掉 - 277→ let activePuzzle = s.activePuzzle; - 278→ if (activePuzzle && !isSolvable(activePuzzle)) { - 279→ // 把晶体放回队列,避免玩家卡死 - 280→ const crystal: Crystal = { - 281→ id: `c_${now}_rec`, - 282→ tier: activePuzzle.tier, - 283→ value: CRYSTAL_VALUE[activePuzzle.tier].crystals, - 284→ createdAt: now, - 285→ }; - 286→ activePuzzle = null; - 287→ set({ - 288→ pendingCrystals: [...s.pendingCrystals, crystal].slice(-CRYSTAL_SPAWN.maxPending), - 289→ }); - 290→ } - 291→ // 兼容旧存档:补全 achievements / 星潮 / 星图 字段 - 292→ const achievements = s.achievements ?? {}; - 293→ const activeTide = s.activeTide ?? null; - 294→ const constellation = s.constellation ?? []; - 295→ const pendingPerkChoices = s.pendingPerkChoices ?? null; - 296→ // v0.4 编年史兼容:补全 chronicle / runStart / bossKills / starTidesEncountered - 297→ const migrated = migrateChronicleFields(s); - 298→ // v0.7 角色属性兼容:补全 attributes / attributeProgress / pendingAttrPoints - 299→ const attrMigrated = migrateAttributes(s); - 300→ // 星图「能量共振」天赋 +1 能量上限 - 301→ const cm = constellationBonuses(constellation); - 302→ const energyMax = (INITIAL_STATE.energyMax) + cm.energyMaxBonus; - 303→ // lastTideEnd:0 表示从未触发过星潮(首次游玩),保留 0 让 firstDelay 生效 - 304→ const lastTideEndRaw = s.lastTideEnd ?? 0; - 305→ // 若旧存档有已过期的星潮,清掉 - 306→ const tide = activeTide && activeTide.endsAt > now ? activeTide : null; - 307→ // 首次进入:补发离线收益 - 308→ const elapsed = Math.max(0, (now - s.lastTick) / 1000); - 309→ if (elapsed > 5) { - 310→ const cap = 8 * 3600; - 311→ const secs = Math.min(elapsed, cap); - 312→ const gain = s.crystalsPerSec * secs * s.offlineEff; - 313→ const crystalsBefore = s.crystals; - 314→ const crystalsAfter = Math.min(s.crystalCap, s.crystals + gain); - 315→ // 设置离线报告(v0.5.2):通知 UI 弹出"欢迎回来"对话框 - 316→ setPendingOfflineReport({ - 317→ elapsedSec: secs, - 318→ rawElapsedSec: elapsed, - 319→ gain: crystalsAfter - crystalsBefore, - 320→ rate: s.crystalsPerSec, - 321→ eff: s.offlineEff, - 322→ capped: elapsed > cap, - 323→ crystalsBefore, - 324→ crystalsAfter, - 325→ crystalCap: s.crystalCap, - 326→ }); - 327→ set({ - 328→ crystals: crystalsAfter, - 329→ lastTick: now, - 330→ activePuzzle, - 331→ achievements, - 332→ activeTide: tide, - 333→ lastTideEnd: tide ? lastTideEndRaw : lastTideEndRaw, - 334→ constellation, - 335→ pendingPerkChoices, - 336→ energyMax, - 337→ chronicle: migrated.chronicle, - 338→ runStart: migrated.runStart, - 339→ bossKills: migrated.bossKills, - 340→ starTidesEncountered: migrated.starTidesEncountered, - 341→ attributes: attrMigrated.attributes, - 342→ attributeProgress: attrMigrated.attributeProgress, - 343→ pendingAttrPoints: attrMigrated.pendingAttrPoints, - 344→ ...syncStats({ tech: s.tech, blueprints: s.blueprints, achievements, activeTide: tide, constellation, attributes: attrMigrated.attributes }), - 345→ }); - 346→ } else { - 347→ set({ lastTick: now, activePuzzle, achievements, activeTide: tide, lastTideEnd: tide ? lastTideEndRaw : lastTideEndRaw, constellation, pendingPerkChoices, energyMax, chronicle: migrated.chronicle, runStart: migrated.runStart, bossKills: migrated.bossKills, starTidesEncountered: migrated.starTidesEncountered, attributes: attrMigrated.attributes, attributeProgress: attrMigrated.attributeProgress, pendingAttrPoints: attrMigrated.pendingAttrPoints, ...syncStats({ tech: s.tech, blueprints: s.blueprints, achievements, activeTide: tide, constellation, attributes: attrMigrated.attributes }) }); - 348→ } - 349→ }, - 350→ - 351→ loadOnline: () => { - 352→ const s = get(); - 353→ set({ ...syncStats({ tech: s.tech, blueprints: s.blueprints, achievements: s.achievements, activeTide: s.activeTide, constellation: s.constellation, attributes: s.attributes }) }); - 354→ }, - 355→ - 356→ hardReset: () => { - 357→ set({ ...createInitialState(), _lastAutoDecode: Date.now(), _lastSpawn: Date.now(), _combo: 0, _lastPulse: 0, _achievementQueue: [], _tideEvents: [] }); - 358→ }, - 359→ - 360→ tickTide: (now) => { - 361→ const s = get(); - 362→ const tide = s.activeTide; - 363→ // 星图「星潮引导」减少间隙 - 364→ const cm = constellationBonuses(s.constellation ?? []); - 365→ // v0.7 灵感:星潮触发概率 +X%(缩短间隙) - 366→ const am = getAllBonuses(s.attributes ?? {}); - 367→ const tideGapReduction = Math.min(0.3, am.tideTriggerBonus); - 368→ const gap = Math.max(15000, (TIDE_CONFIG.gap + cm.tideGapDeltaSec * 1000) * (1 - tideGapReduction)); - 369→ // 1) 检查当前星潮是否结束 - 370→ if (tide && now >= tide.endsAt) { - 371→ const endedType = tide.type; - 372→ // 寂静期补偿洞见 - 373→ let silenceCompensation = 0; - 374→ if (tide.type === "silence") { - 375→ silenceCompensation = computeSilenceCompensation(tide); - 376→ } - 377→ const newInsights = s.insights + silenceCompensation; - 378→ const event: { started?: TideType; ended?: TideType; silenceCompensation?: number } = { ended: endedType }; - 379→ if (silenceCompensation > 0) event.silenceCompensation = silenceCompensation; - 380→ set({ - 381→ activeTide: null, - 382→ lastTideEnd: now, - 383→ insights: newInsights, - 384→ // 星潮结束后重算 stats(移除 contactRate/insight 修饰) - 385→ ...syncStats({ tech: s.tech, blueprints: s.blueprints, achievements: s.achievements, activeTide: null, constellation: s.constellation, attributes: s.attributes }), - 386→ _tideEvents: [...s._tideEvents, event], - 387→ }); - 388→ return event; - 389→ } - 390→ // 2) 检查是否该触发新星潮(间隙已过) - 391→ if (!tide) { - 392→ const since = now - s.lastTideEnd; - 393→ // 首次:以 createdAt 起算 firstDelay;之后以 lastTideEnd 起算 gap - 394→ const firstStart = s.lastTideEnd === 0 || s.lastTideEnd <= s.createdAt + 1000; - 395→ const need = firstStart ? TIDE_CONFIG.firstDelay : gap; - 396→ if (since >= need) { - 397→ const type = rollTide(); - 398→ const newTide: StarTide = { - 399→ id: `tide_${now}_${Math.random().toString(36).slice(2, 7)}`, - 400→ type, - 401→ startedAt: now, - 402→ endsAt: now + TIDE_CONFIG.duration, - 403→ }; - 404→ const event: { started?: TideType; ended?: TideType; silenceCompensation?: number } = { started: type }; - 405→ // v0.4 编年史:累计遇到的星潮 ID(去重) - 406→ const tidesAll = s.starTidesEncountered ?? []; - 407→ const tideId = `tide_${type}`; - 408→ const newTidesAll = tidesAll.includes(tideId) ? tidesAll : [...tidesAll, tideId]; - 409→ set({ - 410→ activeTide: newTide, - 411→ starTidesEncountered: newTidesAll, - 412→ // 星潮开始后重算 stats(应用 contactRate/insight 修饰) - 413→ ...syncStats({ tech: s.tech, blueprints: s.blueprints, achievements: s.achievements, activeTide: newTide, constellation: s.constellation, attributes: s.attributes }), - 414→ _tideEvents: [...s._tideEvents, event], - 415→ }); - 416→ return event; - 417→ } - 418→ } - 419→ return null; - 420→ }, - 421→ - 422→ consumeTideEvents: () => { - 423→ const s = get(); - 424→ if (s._tideEvents.length === 0) return []; - 425→ const items = s._tideEvents; - 426→ set({ _tideEvents: [] }); - 427→ return items; - 428→ }, - 429→ - 430→ tick: (now) => { - 431→ const s = get(); - 432→ const dt = Math.max(0, (now - s.lastTick) / 1000); - 433→ if (dt <= 0) return; - 434→ - 435→ // 星潮产能修饰(即时乘) - 436→ const tideMod = getTideModifiers(s.activeTide); - 437→ const effCps = s.crystalsPerSec * tideMod.crystalsPerSecMult; - 438→ // 产能累加(仅闲置产能受仓库上限;探险奖励可超出) - 439→ const newCrystals = - 440→ s.crystals >= s.crystalCap - 441→ ? s.crystals // 已达/超上限,不再自动产出 - 442→ : Math.min(s.crystalCap, s.crystals + effCps * dt); - 443→ - 444→ // 自动产出待解码晶体(每 baseInterval 秒一颗,受技术/飞升略提速) - 445→ const bpBoost = 1 + s.blueprints.length * 0.03; - 446→ const spawnInterval = (CRYSTAL_SPAWN.baseIntervalSec / bpBoost) * 1000; - 447→ let pending = s.pendingCrystals; - 448→ let lastSpawn = s._lastSpawn; - 449→ if ( - 450→ now - lastSpawn > spawnInterval && - 451→ pending.length < CRYSTAL_SPAWN.maxPending - 452→ ) { - 453→ // 星图「晶体富集」提升 T2/T3 概率 - 454→ const cm = constellationBonuses(s.constellation ?? []); - 455→ const tier = rollCrystalTierWithBonus(cm.t2t3BonusRate); - 456→ const crystal: Crystal = { - 457→ id: `c_${now}_${Math.random().toString(36).slice(2, 7)}`, - 458→ tier, - 459→ value: CRYSTAL_VALUE[tier].crystals, - 460→ createdAt: now, - 461→ }; - 462→ pending = [...pending, crystal]; - 463→ lastSpawn = now; - 464→ } - 465→ - 466→ // 能量恢复(探险系统,v0.8.1 动态间隔) - 467→ let energy = s.energy; - 468→ let lastEnergyTick = s.lastEnergyTick; - 469→ if (energy < s.energyMax) { - 470→ const intervalSec = computeEnergyRegenInterval(s); - 471→ const regen = computeEnergyRegen(lastEnergyTick, now, energy, s.energyMax, intervalSec); - 472→ energy = regen.energy; - 473→ lastEnergyTick = regen.lastTick; - 474→ } else { - 475→ lastEnergyTick = now; - 476→ } - 477→ - 478→ set({ - 479→ crystals: newCrystals, - 480→ lastTick: now, - 481→ pendingCrystals: pending, - 482→ _lastSpawn: lastSpawn, - 483→ energy, - 484→ lastEnergyTick, - 485→ }); - 486→ }, - 487→ - 488→ pulse: () => { - 489→ const s = get(); - 490→ const now = Date.now(); - 491→ // 连击 - 492→ let combo = 1; - 493→ if (now - s._lastPulse < 1500) { - 494→ combo = Math.min(10, s._combo + 1); - 495→ } - 496→ const mult = 1 + (combo - 1) * 0.15; - 497→ // 星潮脉冲威力修饰 - 498→ const tideMod = getTideModifiers(s.activeTide); - 499→ // v0.7 灵感:脉冲连击加成 +X% - 500→ const am = getAllBonuses(s.attributes ?? {}); - 501→ const comboBonusMult = 1 + am.pulseComboBonus * Math.max(0, combo - 1); - 502→ const gain = s.pulsePower * mult * tideMod.pulsePowerMult * comboBonusMult; - 503→ set({ - 504→ crystals: Math.min(s.crystalCap, s.crystals + gain), - 505→ _combo: combo, - 506→ _lastPulse: now, - 507→ }); - 508→ // 深空信标:脉冲任务进度 +1 - 509→ trackBeacon("pulse", 1); - 510→ // v0.7 角色属性:连击 ≥3 给灵感经验 - 511→ if (combo >= 3) { - 512→ const expGain = 1 + Math.floor(combo / 2); // 3 连击=2, 5 连击=3, 10 连击=6 - 513→ // 内联经验获取(避免递归调用 set) - 514→ const prog = s.attributeProgress?.inspiration ?? { exp: 0, level: s.attributes?.inspiration ?? 0 }; - 515→ const nextExp = prog.exp + expGain; - 516→ const lvlResult = levelUpCheck( - 517→ { exp: nextExp, level: s.attributes?.inspiration ?? 0 }, - 518→ ATTRIBUTE_HARD_CAP - 519→ ); - 520→ const newAttributes: CharacterAttributes = { - 521→ ...(s.attributes ?? createInitialAttributes()), - 522→ inspiration: lvlResult.newProgress.level, - 523→ }; - 524→ const newProgress: AttributeProgress = { - 525→ ...(s.attributeProgress ?? createInitialAttributeProgress()), - 526→ inspiration: lvlResult.newProgress, - 527→ }; - 528→ set({ - 529→ attributes: newAttributes, - 530→ attributeProgress: newProgress, - 531→ ...syncStats({ tech: s.tech, blueprints: s.blueprints, achievements: s.achievements, activeTide: s.activeTide, constellation: s.constellation, attributes: newAttributes }), - 532→ }); - 533→ } - 534→ return { gain, combo }; - 535→ }, - 536→ - 537→ startDecode: (crystalId) => { - 538→ const s = get(); - 539→ const crystal = s.pendingCrystals.find((c) => c.id === crystalId); - 540→ if (!crystal) return; - 541→ const puzzle = generatePuzzle(crystal.tier); - 542→ set({ - 543→ activePuzzle: puzzle, - 544→ pendingCrystals: s.pendingCrystals.filter((c) => c.id !== crystalId), - 545→ }); - 546→ }, - 547→ - 548→ clickNode: (nodeId) => { - 549→ const s = get(); - 550→ if (!s.activePuzzle) return { ok: false, finished: false, failReason: "无活跃谜题" }; - 551→ // 深拷贝谜题 - 552→ const puzzle: DecodePuzzle = JSON.parse(JSON.stringify(s.activePuzzle)); - 553→ const res = tryClickNode(puzzle, nodeId); - 554→ if (res.ok) { - 555→ if (res.finished) { - 556→ // 结算奖励(星潮解码奖励修饰 + 星图「二周目经验」) - 557→ const tideMod = getTideModifiers(s.activeTide); - 558→ const cm = constellationBonuses(s.constellation ?? []); - 559→ const base = decodeRewards(puzzle.tier, s.insightMult, s.contactRateMult); - 560→ const finalMult = tideMod.decodeRewardMult * cm.decodeRewardMult; - 561→ const rewards = { - 562→ crystals: Math.round(base.crystals * finalMult), - 563→ insights: Math.round(base.insights * finalMult), - 564→ contact: +(base.contact * finalMult).toFixed(2), - 565→ }; - 566→ const newTotal = s.totalDecoded + 1; - 567→ const newContact = Math.min(100, s.contact + rewards.contact); - 568→ const newInsights = s.insights + rewards.insights; - 569→ const newCrystals = s.crystals + rewards.crystals; - 570→ // 解锁碎片 - 571→ const tentative: GameState = { ...s, totalDecoded: newTotal, fragments: { ...s.fragments } }; - 572→ const unlocked = checkFragments(tentative); - 573→ // v0.7 角色属性:完成解码给智慧经验(tier 越高经验越多) - 574→ const wisdomExpGain = puzzle.tier * 2; - 575→ const curAttrs = s.attributes ?? createInitialAttributes(); - 576→ const curProg = s.attributeProgress ?? createInitialAttributeProgress(); - 577→ const progEntry = curProg.wisdom ?? { exp: 0, level: curAttrs.wisdom }; - 578→ const wisdomLvl = levelUpCheck( - 579→ { exp: progEntry.exp + wisdomExpGain, level: curAttrs.wisdom }, - 580→ ATTRIBUTE_HARD_CAP - 581→ ); - 582→ const newAttributes: CharacterAttributes = { - 583→ ...curAttrs, - 584→ wisdom: wisdomLvl.newProgress.level, - 585→ }; - 586→ const newProgress: AttributeProgress = { - 587→ ...curProg, - 588→ wisdom: wisdomLvl.newProgress, - 589→ }; - 590→ set({ - 591→ activePuzzle: null, - 592→ crystals: newCrystals, - 593→ insights: newInsights, - 594→ contact: newContact, - 595→ totalDecoded: newTotal, - 596→ fragments: tentative.fragments, - 597→ attributes: newAttributes, - 598→ attributeProgress: newProgress, - 599→ ...syncStats({ tech: s.tech, blueprints: s.blueprints, achievements: s.achievements, activeTide: s.activeTide, constellation: s.constellation, attributes: newAttributes }), - 600→ }); - 601→ // 深空信标:解码 +1,洞见累计 - 602→ trackBeacon("decode", 1); - 603→ trackBeacon("insight", rewards.insights); - 604→ return { ok: true, finished: true, failReason: unlocked.join(",") || undefined }; - 605→ } - 606→ // 点击成功但未完成:检测当前局面是否仍可解 - 607→ const solvable = isSolvable(puzzle); - 608→ set({ activePuzzle: puzzle }); - 609→ return { ok: true, finished: false, solvable }; - 610→ } - 611→ return res; - 612→ }, - 613→ - 614→ undoStep: () => { - 615→ const s = get(); - 616→ if (!s.activePuzzle || s.activePuzzle.path.length === 0) return; - 617→ const puzzle: DecodePuzzle = JSON.parse(JSON.stringify(s.activePuzzle)); - 618→ const lastId = puzzle.path.pop(); - 619→ if (lastId !== undefined) { - 620→ const node = puzzle.grid.find((n) => n.id === lastId); - 621→ if (node) node.used = false; - 622→ } - 623→ set({ activePuzzle: puzzle }); - 624→ }, - 625→ - 626→ retryPuzzle: () => { - 627→ const s = get(); - 628→ if (!s.activePuzzle) return; - 629→ set({ activePuzzle: resetPuz(s.activePuzzle) }); - 630→ }, - 631→ - 632→ abandonPuzzle: () => { - 633→ const s = get(); - 634→ if (!s.activePuzzle) return; - 635→ // 晶体放回队列末尾 - 636→ const crystal: Crystal = { - 637→ id: `c_${Date.now()}_ret`, - 638→ tier: s.activePuzzle.tier, - 639→ value: CRYSTAL_VALUE[s.activePuzzle.tier].crystals, - 640→ createdAt: Date.now(), - 641→ }; - 642→ set({ - 643→ activePuzzle: null, - 644→ pendingCrystals: [...s.pendingCrystals, crystal].slice(-CRYSTAL_SPAWN.maxPending), - 645→ }); - 646→ }, - 647→ - 648→ autoDecodeTick: () => { - 649→ const s = get(); - 650→ if (!s.autoDecode) return; - 651→ const now = Date.now(); - 652→ // 星图「自动校准」减少自动解码周期 - 653→ const cm = constellationBonuses(s.constellation ?? []); - 654→ // v0.7 智慧:自动解码周期 -X% - 655→ const am = getAllBonuses(s.attributes ?? {}); - 656→ const baseInterval = 12000 + cm.autoDecodeIntervalDeltaSec * 1000; - 657→ const interval = Math.max(5000, baseInterval * am.autoDecodeIntervalMult); - 658→ if (now - s._lastAutoDecode < interval) return; - 659→ // 找一颗 T1 晶体自动解码 - 660→ const idx = s.pendingCrystals.findIndex((c) => c.tier === 1); - 661→ if (idx < 0) return; - 662→ const crystal = s.pendingCrystals[idx]; - 663→ const tideMod = getTideModifiers(s.activeTide); - 664→ const base = decodeRewards(1, s.insightMult, s.contactRateMult); - 665→ const finalMult = tideMod.decodeRewardMult * cm.decodeRewardMult; - 666→ const rewards = { - 667→ crystals: Math.round(base.crystals * finalMult), - 668→ insights: Math.round(base.insights * finalMult), - 669→ contact: +(base.contact * finalMult).toFixed(2), - 670→ }; - 671→ const newTotal = s.totalDecoded + 1; - 672→ const tentative: GameState = { ...s, totalDecoded: newTotal, fragments: { ...s.fragments } }; - 673→ checkFragments(tentative); - 674→ // v0.7 角色属性:自动解码给智慧经验(少量) - 675→ const curAttrs = s.attributes ?? createInitialAttributes(); - 676→ const curProg = s.attributeProgress ?? createInitialAttributeProgress(); - 677→ const progEntry = curProg.wisdom ?? { exp: 0, level: curAttrs.wisdom }; - 678→ const wisdomLvl = levelUpCheck( - 679→ { exp: progEntry.exp + 1, level: curAttrs.wisdom }, - 680→ ATTRIBUTE_HARD_CAP - 681→ ); - 682→ const newAttributes: CharacterAttributes = { - 683→ ...curAttrs, - 684→ wisdom: wisdomLvl.newProgress.level, - 685→ }; - 686→ const newProgress: AttributeProgress = { - 687→ ...curProg, - 688→ wisdom: wisdomLvl.newProgress, - 689→ }; - 690→ set({ - 691→ pendingCrystals: s.pendingCrystals.filter((c) => c.id !== crystal.id), - 692→ crystals: s.crystals + rewards.crystals, - 693→ insights: s.insights + rewards.insights, - 694→ contact: Math.min(100, s.contact + rewards.contact), - 695→ totalDecoded: newTotal, - 696→ fragments: tentative.fragments, - 697→ _lastAutoDecode: now, - 698→ attributes: newAttributes, - 699→ attributeProgress: newProgress, - 700→ ...syncStats({ tech: s.tech, blueprints: s.blueprints, achievements: s.achievements, activeTide: s.activeTide, constellation: s.constellation, attributes: newAttributes }), - 701→ }); - 702→ // 深空信标:自动解码也算进度 - 703→ trackBeacon("decode", 1); - 704→ trackBeacon("insight", rewards.insights); - 705→ }, - 706→ - 707→ buyTech: (techId) => { - 708→ const s = get(); - 709→ const node = TECH_TREE.find((t) => t.id === techId); - 710→ if (!node) return false; - 711→ const cur = s.tech[techId] ?? 0; - 712→ if (cur >= 1) return false; // v0.1 每节点 1 级 - 713→ if (s.insights < node.cost) return false; - 714→ const newTech = { ...s.tech, [techId]: 1 }; - 715→ set({ - 716→ insights: s.insights - node.cost, - 717→ tech: newTech, - 718→ ...syncStats({ tech: newTech, blueprints: s.blueprints, achievements: s.achievements, activeTide: s.activeTide, constellation: s.constellation, attributes: s.attributes }), - 719→ }); - 720→ return true; - 721→ }, - 722→ - 723→ // ============ 探险系统 ============ - 724→ startExpedition: () => { - 725→ const s = get(); - 726→ if (s.activeExpedition && !s.activeExpedition.finished) { - 727→ return { ok: false, reason: "已有进行中的探险" }; - 728→ } - 729→ if (s.energy < EXPEDITION_CONFIG.energyCost) { - 730→ return { ok: false, reason: "能量不足" }; - 731→ } - 732→ const tideMod = getTideModifiers(s.activeTide); - 733→ // v0.7 角色属性:探索力 +X% 探险力,勇气 +X 探险生命 - 734→ const am = getAllBonuses(s.attributes ?? {}); - 735→ const basePower = computeExpeditionPower(s) + tideMod.expeditionPowerBonus; - 736→ const baseHp = computeExpeditionHp(s) + tideMod.expeditionHpBonus; - 737→ const power = Math.round(basePower * am.expeditionPowerMult); - 738→ const hp = baseHp + am.expeditionHpBonus; - 739→ const seed = Math.floor(Math.random() * 1e9); - 740→ const expedition = generateExpedition(seed, power, hp); - 741→ set({ - 742→ activeExpedition: expedition, - 743→ energy: s.energy - EXPEDITION_CONFIG.energyCost, - 744→ totalExpeditions: s.totalExpeditions + 1, - 745→ }); - 746→ return { ok: true }; - 747→ }, - 748→ - 749→ resolveCurrentNode: () => { - 750→ const s = get(); - 751→ if (!s.activeExpedition || s.activeExpedition.finished) return null; - 752→ // 深拷贝 - 753→ const exp = JSON.parse(JSON.stringify(s.activeExpedition)); - 754→ // v0.7 勇气:BOSS 战胜率 +X%(动态提高 RNG 阈值) - 755→ const nodeBefore = exp.nodes[exp.currentNode]; - 756→ const isBossNode = nodeBefore?.type === "boss"; - 757→ const am = getAllBonuses(s.attributes ?? {}); - 758→ const result = isBossNode - 759→ ? resolveNode(exp, () => { - 760→ // 单次 rng() 调用:B% 概率返回 0(必胜),其余情况返回 r-B(保持均匀分布) - 761→ const r = Math.random(); - 762→ const b = Math.min(0.95, am.bossWinRateBonus); - 763→ return r < b ? 0 : Math.min(1, r - b); - 764→ }) - 765→ : resolveNode(exp); - 766→ // 累计奖励 - 767→ if (result.crystals) exp.rewards.crystals += result.crystals; - 768→ if (result.insights) exp.rewards.insights += result.insights; - 769→ if (result.contact) exp.rewards.contact += result.contact; - 770→ if (result.fragments) exp.rewards.fragments.push(...result.fragments); - 771→ if (result.hpDelta) exp.hp = Math.max(0, Math.min(exp.maxHp, exp.hp + result.hpDelta)); - 772→ // 实时入账(玩家立即获得) - 773→ const newCrystals = s.crystals + (result.crystals || 0); - 774→ const newInsights = s.insights + (result.insights || 0); - 775→ const newContact = Math.min(100, s.contact + (result.contact || 0)); - 776→ // 碎片解锁 - 777→ const newFragments = { ...s.fragments }; - 778→ if (result.fragments) { - 779→ for (const fid of result.fragments) newFragments[fid] = true; - 780→ } - 781→ // 日志 - 782→ const logEntry = { - 783→ expeditionId: exp.id, - 784→ nodeType: nodeBefore?.type || "combat", - 785→ result: result.log, - 786→ rewards: [ - 787→ result.crystals ? `+${result.crystals}晶体` : "", - 788→ result.insights ? `+${result.insights}洞见` : "", - 789→ result.contact ? `+${result.contact.toFixed(1)}接触` : "", - 790→ result.hpDelta ? `${result.hpDelta > 0 ? "+" : ""}${result.hpDelta}生命` : "", - 791→ ].filter(Boolean).join(" "), - 792→ timestamp: Date.now(), - 793→ }; - 794→ const newLog = [logEntry, ...s.expeditionLog].slice(0, 30); - 795→ - 796→ if (result.ended) { - 797→ // 探险结束(胜利或失败) - 798→ exp.finished = true; - 799→ } - 800→ - 801→ // v0.4 编年史:击破 BOSS 时累计计数 - 802→ let bossKills = s.bossKills ?? 0; - 803→ let bossKilledThisNode = false; - 804→ if ( - 805→ result.ended && - 806→ result.endReason === "victory" && - 807→ nodeBefore?.type === "boss" - 808→ ) { - 809→ bossKills = bossKills + 1; - 810→ bossKilledThisNode = true; - 811→ } - 812→ - 813→ // v0.7 角色属性:战斗胜利给勇气+探索力经验;BOSS 额外奖励 - 814→ let newAttributes = s.attributes ?? createInitialAttributes(); - 815→ let newProgress = s.attributeProgress ?? createInitialAttributeProgress(); - 816→ let statsNeedResync = false; - 817→ // 战斗类节点(combat/boss)且胜利:勇气 + 探索力经验 - 818→ const isCombatVictory = - 819→ (nodeBefore?.type === "combat" || nodeBefore?.type === "boss") && - 820→ !result.ended; // 中途战斗胜利(未结束探险) - 821→ const isExpeditionVictory = - 822→ result.ended && result.endReason === "victory"; - 823→ if (isCombatVictory || bossKilledThisNode || isExpeditionVictory) { - 824→ const courageGain = bossKilledThisNode ? 8 : 2; - 825→ const explorationGain = bossKilledThisNode ? 6 : isExpeditionVictory ? 4 : 1; - 826→ const courageLvl = levelUpCheck( - 827→ { exp: newProgress.courage.exp + courageGain, level: newAttributes.courage }, - 828→ ATTRIBUTE_HARD_CAP - 829→ ); - 830→ const explLvl = levelUpCheck( - 831→ { exp: newProgress.exploration.exp + explorationGain, level: newAttributes.exploration }, - 832→ ATTRIBUTE_HARD_CAP - 833→ ); - 834→ newAttributes = { - 835→ ...newAttributes, - 836→ courage: courageLvl.newProgress.level, - 837→ exploration: explLvl.newProgress.level, - 838→ }; - 839→ newProgress = { - 840→ ...newProgress, - 841→ courage: courageLvl.newProgress, - 842→ exploration: explLvl.newProgress, - 843→ }; - 844→ statsNeedResync = true; - 845→ } - 846→ - 847→ set({ - 848→ activeExpedition: exp, - 849→ crystals: newCrystals, - 850→ insights: newInsights, - 851→ contact: newContact, - 852→ fragments: newFragments, - 853→ expeditionLog: newLog, - 854→ bossKills, - 855→ attributes: newAttributes, - 856→ attributeProgress: newProgress, - 857→ ...(statsNeedResync - 858→ ? syncStats({ tech: s.tech, blueprints: s.blueprints, achievements: s.achievements, activeTide: s.activeTide, constellation: s.constellation, attributes: newAttributes }) - 859→ : {}), - 860→ }); - 861→ // 深空信标:探险完成(胜负都计)+ 洞见累计 + BOSS 击破 - 862→ if (result.ended) { - 863→ trackBeacon("expedition", 1); - 864→ if (bossKilledThisNode) { - 865→ trackBeacon("boss", 1); - 866→ } - 867→ } - 868→ if (result.insights) trackBeacon("insight", result.insights); - 869→ return result; - 870→ }, - 871→ - 872→ advanceNode: () => { - 873→ const s = get(); - 874→ if (!s.activeExpedition || s.activeExpedition.finished) return; - 875→ const exp = JSON.parse(JSON.stringify(s.activeExpedition)); - 876→ const node = exp.nodes[exp.currentNode]; - 877→ if (!node || !node.cleared) return; // 当前节点未结算不能前进 - 878→ if (exp.currentNode >= exp.nodes.length - 1) return; - 879→ exp.currentNode++; - 880→ set({ activeExpedition: exp }); - 881→ }, - 882→ - 883→ abortExpedition: () => { - 884→ const s = get(); - 885→ if (!s.activeExpedition) return; - 886→ const exp = JSON.parse(JSON.stringify(s.activeExpedition)); - 887→ exp.finished = true; - 888→ const logEntry = { - 889→ expeditionId: exp.id, - 890→ nodeType: "rest" as const, - 891→ result: "探险队主动撤退,保留已获奖励。", - 892→ rewards: "", - 893→ timestamp: Date.now(), - 894→ }; - 895→ set({ - 896→ activeExpedition: exp, - 897→ expeditionLog: [logEntry, ...s.expeditionLog].slice(0, 30), - 898→ }); - 899→ }, - 900→ - 901→ doPrestige: () => { - 902→ const s = get(); - 903→ if (s.contact < CONTACT.prestigeMin) return null; - 904→ const newBp = computeNewBlueprints(s); - 905→ const next = performPrestige(s); - 906→ // 星图「能量共振」提升上限 - 907→ const cm = constellationBonuses(next.constellation ?? []); - 908→ const energyMax = INITIAL_STATE.energyMax + cm.energyMaxBonus; - 909→ set({ - 910→ ...next, - 911→ energyMax, - 912→ ...syncStats({ tech: next.tech, blueprints: next.blueprints, achievements: next.achievements, activeTide: next.activeTide, constellation: next.constellation, attributes: next.attributes }), - 913→ _lastAutoDecode: Date.now(), - 914→ _lastSpawn: Date.now(), - 915→ _combo: 0, - 916→ _lastPulse: 0, - 917→ _tideEvents: [], - 918→ }); - 919→ return { newBp }; - 920→ }, - 921→ - 922→ chooseConstellationPerk: (perkId) => { - 923→ const s = get(); - 924→ if (!s.pendingPerkChoices || !s.pendingPerkChoices.includes(perkId)) return false; - 925→ const perk = getPerk(perkId); - 926→ if (!perk) return false; - 927→ if (s.constellation?.includes(perkId)) return false; - 928→ const newConstellation = [...(s.constellation ?? []), perkId]; - 929→ const cm = constellationBonuses(newConstellation); - 930→ const energyMax = INITIAL_STATE.energyMax + cm.energyMaxBonus; - 931→ // v0.4 编年史:回填最近一条 entry 的 perksThisAscension - 932→ const chronicle = s.chronicle ?? []; - 933→ let newChronicle = chronicle; - 934→ if (chronicle.length > 0) { - 935→ const lastEntry = chronicle[chronicle.length - 1]; - 936→ const updatedLast = withPerks(lastEntry, [perkId]); - 937→ newChronicle = [...chronicle.slice(0, -1), updatedLast]; - 938→ } - 939→ set({ - 940→ constellation: newConstellation, - 941→ pendingPerkChoices: null, - 942→ energyMax, - 943→ chronicle: newChronicle, - 944→ ...syncStats({ tech: s.tech, blueprints: s.blueprints, achievements: s.achievements, activeTide: s.activeTide, constellation: newConstellation, attributes: s.attributes }), - 945→ }); - 946→ return true; - 947→ }, - 948→ - 949→ rerollPerkChoices: () => { - 950→ const s = get(); - 951→ if (!s.pendingPerkChoices) return; - 952→ const choices = rollPerkChoices(s.constellation ?? []); - 953→ if (choices.length > 0) set({ pendingPerkChoices: choices }); - 954→ }, - 955→ - 956→ checkAchievements: () => { - 957→ const s = get(); - 958→ const newlyUnlocked: Achievement[] = []; - 959→ const updated = { ...s.achievements }; - 960→ let crystals = s.crystals; - 961→ let insights = s.insights; - 962→ let contact = s.contact; - 963→ let statsDirty = false; - 964→ for (const a of ACHIEVEMENTS) { - 965→ if (updated[a.id]) continue; - 966→ if (a.check(s)) { - 967→ updated[a.id] = true; - 968→ newlyUnlocked.push(a); - 969→ // 发放即时奖励 - 970→ if (a.reward.crystals) crystals += a.reward.crystals; - 971→ if (a.reward.insights) insights += a.reward.insights; - 972→ if (a.reward.contact) contact = Math.min(100, contact + a.reward.contact); - 973→ if (a.reward.crystalsPerSecPct || a.reward.insightPct) statsDirty = true; - 974→ } - 975→ } - 976→ if (newlyUnlocked.length === 0) return []; - 977→ set({ - 978→ achievements: updated, - 979→ crystals, - 980→ insights, - 981→ contact, - 982→ ...(statsDirty - 983→ ? syncStats({ tech: s.tech, blueprints: s.blueprints, achievements: updated, activeTide: s.activeTide, constellation: s.constellation, attributes: s.attributes }) - 984→ : {}), - 985→ _achievementQueue: [...s._achievementQueue, ...newlyUnlocked], - 986→ }); - 987→ return newlyUnlocked; - 988→ }, - 989→ - 990→ consumeAchievementQueue: () => { - 991→ const s = get(); - 992→ if (s._achievementQueue.length === 0) return []; - 993→ const items = s._achievementQueue; - 994→ set({ _achievementQueue: [] }); - 995→ return items; - 996→ }, - 997→ - 998→ canPrestige: () => get().contact >= CONTACT.prestigeMin, - 999→ - 1000→ toggleTheme: () => set({ theme: get().theme === "dark" ? "light" : "dark" }), - 1001→ toggleSound: () => set({ soundOn: !get().soundOn }), - 1002→ - 1003→ // 深空信标:发放每日挑战奖励(v0.5) - 1004→ grantBeaconReward: (insights, contact) => { - 1005→ const s = get(); - 1006→ set({ - 1007→ insights: s.insights + Math.round(insights), - 1008→ contact: Math.min(100, s.contact + contact), - 1009→ }); - 1010→ }, - 1011→ - 1012→ // 深空信标:领取周挑战奖励(v0.8) - 1013→ claimWeeklyBeacon: () => { - 1014→ const challenge = generateWeeklyChallenge(); - 1015→ const progress = loadWeeklyProgress(); - 1016→ const res = claimWeeklyReward(challenge, progress); - 1017→ if (res.rewardInsight > 0 || res.rewardContact > 0) { - 1018→ const s = get(); - 1019→ set({ - 1020→ insights: s.insights + Math.round(res.rewardInsight), - 1021→ contact: Math.min(100, s.contact + res.rewardContact), - 1022→ }); - 1023→ } - 1024→ return { - 1025→ rewardInsight: res.rewardInsight, - 1026→ rewardContact: res.rewardContact, - 1027→ score: res.score, - 1028→ }; - 1029→ }, - 1030→ - 1031→ // 深空信标:领取信标链里程碑奖励(v0.8) - 1032→ claimChainReward: (milestone) => { - 1033→ // loadChainState 仅用于前置校验,真正的状态修改由 claimChainMilestone 完成 - 1034→ const pre = loadChainState(); - 1035→ if ( - 1036→ pre.currentStreak < milestone || - 1037→ pre.milestonesClaimed.includes(milestone) - 1038→ ) { - 1039→ return { - 1040→ rewardInsight: 0, - 1041→ rewardContact: 0, - 1042→ label: "", - 1043→ ok: false, - 1044→ }; - 1045→ } - 1046→ const res = claimChainMilestone(milestone); - 1047→ if (res.rewardInsight > 0 || res.rewardContact > 0) { - 1048→ const s = get(); - 1049→ set({ - 1050→ insights: s.insights + Math.round(res.rewardInsight), - 1051→ contact: Math.min(100, s.contact + res.rewardContact), - 1052→ }); - 1053→ } - 1054→ return { - 1055→ rewardInsight: res.rewardInsight, - 1056→ rewardContact: res.rewardContact, - 1057→ label: res.label, - 1058→ ok: res.rewardInsight > 0 || res.rewardContact > 0, - 1059→ }; - 1060→ }, - 1061→ - 1062→ // 深空巡航:发放实时玩法奖励(v0.6) - 1063→ grantCruiseReward: (rewards) => { - 1064→ const s = get(); - 1065→ const addCrystals = rewards.crystals ?? 0; - 1066→ const addInsights = rewards.insights ?? 0; - 1067→ const addContact = rewards.contact ?? 0; - 1068→ // v0.7 角色属性:巡航通关给探索力+勇气经验(按晶体奖励量缩放) - 1069→ const totalReward = addCrystals + addInsights * 10 + addContact * 10; - 1070→ const expBase = Math.max(2, Math.floor(totalReward / 30)); - 1071→ const curAttrs = s.attributes ?? createInitialAttributes(); - 1072→ const curProg = s.attributeProgress ?? createInitialAttributeProgress(); - 1073→ const explLvl = levelUpCheck( - 1074→ { exp: curProg.exploration.exp + expBase, level: curAttrs.exploration }, - 1075→ ATTRIBUTE_HARD_CAP - 1076→ ); - 1077→ const courageLvl = levelUpCheck( - 1078→ { exp: curProg.courage.exp + Math.floor(expBase * 0.6), level: curAttrs.courage }, - 1079→ ATTRIBUTE_HARD_CAP - 1080→ ); - 1081→ const newAttributes: CharacterAttributes = { - 1082→ ...curAttrs, - 1083→ exploration: explLvl.newProgress.level, - 1084→ courage: courageLvl.newProgress.level, - 1085→ }; - 1086→ const newProgress: AttributeProgress = { - 1087→ ...curProg, - 1088→ exploration: explLvl.newProgress, - 1089→ courage: courageLvl.newProgress, - 1090→ }; - 1091→ set({ - 1092→ crystals: Math.min(s.crystalCap, s.crystals + addCrystals), - 1093→ insights: s.insights + Math.round(addInsights), - 1094→ contact: Math.min(100, s.contact + addContact), - 1095→ attributes: newAttributes, - 1096→ attributeProgress: newProgress, - 1097→ ...syncStats({ tech: s.tech, blueprints: s.blueprints, achievements: s.achievements, activeTide: s.activeTide, constellation: s.constellation, attributes: newAttributes }), - 1098→ }); - 1099→ }, - 1100→ - 1101→ // ============ 角色属性系统(v0.7 P1) ============ - 1102→ allocateAttribute: (attr, points = 1) => { - 1103→ const s = get(); - 1104→ const cur = s.attributes ?? createInitialAttributes(); - 1105→ const curVal = cur[attr] ?? 0; - 1106→ if (curVal >= ATTRIBUTE_HARD_CAP) { - 1107→ return { ok: false, leveledUp: 0 }; - 1108→ } - 1109→ if ((s.pendingAttrPoints ?? 0) < points) { - 1110→ return { ok: false, leveledUp: 0 }; - 1111→ } - 1112→ const alloc = Math.min(points, ATTRIBUTE_HARD_CAP - curVal, s.pendingAttrPoints); - 1113→ const newVal = curVal + alloc; - 1114→ const newAttributes: CharacterAttributes = { ...cur, [attr]: newVal }; - 1115→ // 同步经验进度 level 字段(保持一致) - 1116→ const curProg = s.attributeProgress ?? createInitialAttributeProgress(); - 1117→ const oldProg = curProg[attr] ?? { exp: 0, level: curVal }; - 1118→ const newProgress: AttributeProgress = { - 1119→ ...curProg, - 1120→ [attr]: { exp: oldProg.exp, level: newVal }, - 1121→ }; - 1122→ set({ - 1123→ attributes: newAttributes, - 1124→ attributeProgress: newProgress, - 1125→ pendingAttrPoints: s.pendingAttrPoints - alloc, - 1126→ ...syncStats({ tech: s.tech, blueprints: s.blueprints, achievements: s.achievements, activeTide: s.activeTide, constellation: s.constellation, attributes: newAttributes }), - 1127→ }); - 1128→ return { ok: true, leveledUp: alloc }; - 1129→ }, - 1130→ - 1131→ gainAttributeExp: (attr, amount) => { - 1132→ const s = get(); - 1133→ const cur = s.attributes ?? createInitialAttributes(); - 1134→ const curProg = s.attributeProgress ?? createInitialAttributeProgress(); - 1135→ const progEntry = curProg[attr] ?? { exp: 0, level: cur[attr] }; - 1136→ const result = levelUpCheck( - 1137→ { exp: progEntry.exp + amount, level: cur[attr] }, - 1138→ ATTRIBUTE_HARD_CAP - 1139→ ); - 1140→ const newAttributes: CharacterAttributes = { ...cur, [attr]: result.newProgress.level }; - 1141→ const newProgress: AttributeProgress = { - 1142→ ...curProg, - 1143→ [attr]: result.newProgress, - 1144→ }; - 1145→ set({ - 1146→ attributes: newAttributes, - 1147→ attributeProgress: newProgress, - 1148→ ...syncStats({ tech: s.tech, blueprints: s.blueprints, achievements: s.achievements, activeTide: s.activeTide, constellation: s.constellation, attributes: newAttributes }), - 1149→ }); - 1150→ return { leveledUp: result.levelsGained, newLevel: result.newProgress.level }; - 1151→ }, - 1152→ }), - 1153→ { - 1154→ name: "echo-nexus-save-v1", - 1155→ storage: createJSONStorage(() => localStorage), - 1156→ // 不持久化临时字段 - 1157→ partialize: (s) => { - 1158→ const { _lastAutoDecode, _lastSpawn, _combo, _lastPulse, _achievementQueue, _tideEvents, ...rest } = s; - 1159→ void _lastAutoDecode; void _lastSpawn; void _combo; void _lastPulse; void _achievementQueue; void _tideEvents; - 1160→ return rest as GameState; - 1161→ }, - 1162→ } - 1163→ ) - 1164→); - 1165→ - 1166→/** 选择器:未解锁碎片中下一个门槛 */ - 1167→export function nextFragmentThreshold(totalDecoded: number): number | null { - 1168→ for (const f of FRAGMENTS) { - 1169→ if (totalDecoded < f.threshold) return f.threshold; - 1170→ } - 1171→ return null; - 1172→} - 1173→ - 1174→export { FRAGMENTS, PRESTIGE, TECH_TREE }; - 1175→ - 1176→// 开发期调试:暴露 store 到 window,便于 QA 测试 - 1177→if (typeof window !== "undefined" && process.env.NODE_ENV !== "production") { - 1178→ (window as unknown as { __gameStore?: typeof useGameStore }).__gameStore = useGameStore; - 1179→} - 1180→ \ No newline at end of file diff --git a/tool-results/read_1782302937374_13d66843559b.txt b/tool-results/read_1782302937374_13d66843559b.txt deleted file mode 100755 index b5ebf16c8..000000000 --- a/tool-results/read_1782302937374_13d66843559b.txt +++ /dev/null @@ -1,571 +0,0 @@ - 1→# 回响星核 / Echo Nexus — 开发工作日志 - 2→ - 3→> 持续性任务的工作交接文档。每个阶段更新,避免进度丢失。 - 4→ - 5→--- - 6→ - 7→## 一、项目当前状态描述 / 判断 - 8→ - 9→### 概况 - 10→- **项目**:回响星核 / Echo Nexus — 深空考古放置策略游戏 - 11→- **一句话**:自治无人机采矿 → 解码记忆晶体(原创共振谜题)→ 拼凑碎片叙事 → 飞升多周目。差异化:解码谜题驱动叙事涌现,全息晶体 + 深空粒子美学。 - 12→- **当前版本**:**v0.7**(CrystalOrb Canvas 粒子系统 + 角色属性系统) - 13→- **在线游玩**:https://gitea-pages.atdunbg.xyz/Super_Z/echo-nexus/ - 14→- **仓库**:https://git.atdunbg.xyz/Super_Z/echo-nexus - 15→- **技术栈**:Next.js 16 (App Router) + TypeScript + Tailwind + shadcn/ui + Canvas 2D + Zustand + Web Audio API - 16→- **定时任务**:每 15 分钟一次 `webDevReview`(`fixed_rate` + `"900"` 秒,priority=10,job_id 228266)。正常完成不会被删除,无需自持续机制。 - 17→ - 18→### 状态判断 - 19→- dev 服务器运行正常(HTTP 200,编译 < 250ms) - 20→- v0.7 两大新功能已完成:CrystalOrb Canvas 粒子系统升级(VLM 8/10)+ 角色属性系统(VLM 7/10) - 21→- 系统总体稳定,无阻塞性 bug,可继续推进新功能 - 22→ - 23→### 已完成版本里程碑(精简) - 24→| 版本 | 核心内容 | - 25→|------|---------| - 26→| v0.1 | MVP:放置采矿 + 解码共振谜题 + 技术树 + 记忆图谱 + 飞升 + 统计 | - 27→| v0.1.1 | 解码可解性修复(路径构造法生成器,100% 可解率)| - 28→| v0.2 | 遗迹探险肉鸽系统(能量/探险力/6 节点/程序化路径/BOSS)| - 29→| v0.2.1 | 程序化音频(15 种音效)+ 14 项成就 + 视觉打磨 | - 30→| v0.3 | 星潮事件系统(6 种星潮 + 全屏叠层 + 程序化音效)| - 31→| v0.3.1 | 星图天文台元进程(6 类 × 3 = 18 天赋 + 飞升后 3 选 1 draft + Canvas 星图)| - 32→| v0.4 | 回响编年史(5 纪元循环命名 + 模板化叙事 + 时间轴 UI + 6 项新成就 + 飞升 BUG 修复)| - 33→| v0.5 | 深空信标(每日挑战 + 本地排行榜 Top20 + 7 标签页 + 编年史历史 BUG 修复)| - 34→| v0.5.1 | 静态部署 Gitea Pages + 新手教程系统(7 步聚光灯引导)+ UI 间距优化 | - 35→| v0.5.2 | 离线收益报告弹窗 + 标签页 UI 重设计 + persist rehydrate 竞态修复 | - 36→| v0.6 | 深空巡航 Canvas 2D 实时玩法 + UI 偏移重叠修复 | - 37→| **v0.7** | **CrystalOrb Canvas 粒子系统升级 + 角色属性系统(四维)** | - 38→ - 39→### 核心系统清单(8 大系统) - 40→1. **放置采矿** — 中央晶体球 + 主动脉冲 + 连击加成(`CrystalOrb.tsx`) - 41→2. **谐振解码** — 原创路径谜题,路径构造法 100% 可解(`DecodeArray.tsx` + `engine.ts`) - 42→3. **技术树** — 三层科技解锁,提升产能/解码/探险(`TechTree.tsx`) - 43→4. **遗迹探险** — 肉鸽系统,6 种节点 + 程序化路径 + BOSS(`ExpeditionPanel.tsx` + `expedition.ts`) - 44→5. **星潮事件** — 6 种动态事件,为放置循环注入变化(`StarTide.tsx` + `starTide.ts`) - 45→6. **星图天文台** — 飞升后 3 选 1 天赋 draft,跨周目永久生效,18 个天赋(`ConstellationPanel.tsx` + `constellation.ts`) - 46→7. **回响编年史** — 跨周目叙事时间轴,5 纪元循环命名(`ChronicleDialog.tsx` + `chronicle.ts`) - 47→8. **深空信标** — 每日挑战 + 本地排行榜 Top20(`BeaconPanel.tsx` + `beacon.ts`) - 48→9. **深空巡航** — Canvas 2D 实时玩法,飞船操控/陨石躲避/星门通关(`CruiseMode.tsx` + `cruise.ts`)【v0.6】 - 49→10. **角色属性系统** — 四维属性(探索力/智慧/勇气/灵感),飞升获得属性点,影响所有系统(`AttributesPanel.tsx` + `attributes.ts`)【v0.7 新增】 - 50→ - 51→--- - 52→ - 53→## 二、当前目标 / 已完成的修改 / 验证结果 - 54→ - 55→### v0.7 CrystalOrb Canvas 粒子系统升级(已完成) - 56→**动机**:P0 优先级 — 将中央晶体球从 CSS 动画升级为 Canvas 粒子视觉盛宴,提升视觉震撼。 - 57→ - 58→**重写文件**:`src/components/game/CrystalOrb.tsx` - 59→- 从纯 CSS 动画升级为 Canvas 2D 粒子系统 - 60→- **多层粒子**: - 61→ - 环绕能量粒子(3层14/10/8个,emerald/fuchsia/rose三色,不同速度方向环绕晶核,带拖尾) - 62→ - 环境星尘(40个,缓慢漂移 + 闪烁) - 63→ - 点击爆发粒子(10-30个径向发散,颜色随连击等级变色) - 64→ - 冲击波环(点击触发1-3层错峰扩散,连击≥3/5解锁更多层) - 65→- **晶核升级**:径向渐变辉光 + 3层六边形纹理旋转 + 高光 + 鼠标视差偏移 - 66→- **呼吸动画**:晶核 + 外层辉光随时间呼吸(sin波) - 67→- **进度环**:SVG渐变环(emerald→fuchsia→rose)保留 - 68→- **交互保留**:点击脉冲、连击Toast、浮动数字、音效、`data-tut="crystal-orb"` 锚点 - 69→- **性能**:DPR cap 2,ResizeObserver 自适应,requestAnimationFrame 60fps - 70→ - 71→**QA 验证**(agent-browser + VLM): - 72→- VLM 视觉评分 **8/10**:晶体球清晰可见,多层辉光,环绕粒子动态自然,无渲染问题 - 73→- 点击交互:冲击波环 ✅ + 爆发粒子 ✅,评分 7/10 - 74→- lint 零错误;HTTP 200 - 75→ - 76→### v0.7 角色属性系统(已完成) - 77→**动机**:P1 优先级 — 增加RPG深度,四维属性影响所有游戏系统。 - 78→ - 79→**新增文件**: - 80→- `src/lib/game/attributes.ts`(~330行逻辑层):CharacterAttributes/AttributeProgress 类型、ATTRIBUTE_CONFIG 元信息、getAttributeBonus 两区加成公式(0-50线性+0.5%/点,50-100递减+0.2%/点)、getAllBonuses 12项修饰器、levelUpCheck 自动升级、computePrestigeAttrPoints、migrateAttributes 旧存档兼容 - 81→- `src/components/game/AttributesPanel.tsx`(~330行 UI):四维卡片网格(小屏2×2/大屏1×4)、图标+中英文名+Lv.badge+数值/100+加成%+经验条+影响列表+分配按钮、顶部待分配点数badge闪烁、底部总览+12项修饰器明细 - 82→ - 83→**修改文件**: - 84→- `types.ts`:GameState 新增 attributes/attributeProgress/pendingAttrPoints - 85→- `config.ts`:INITIAL_STATE 补全默认值 - 86→- `engine.ts`:recomputeStats 聚合属性加成;performPrestige 发放属性点(ascensions×2+1) - 87→- `gameStore.ts`:新增 allocateAttribute/gainAttributeExp actions;pulse/clickNode/autoDecodeTick/resolveCurrentNode/grantCruiseReward/tickTide 全部接入属性经验;init() 调用 migrateAttributes - 88→- `achievements.ts`:新增 ach_attr_total_50 + ach_attr_max_100 两项成就 - 89→- `page.tsx`:grid-cols-7→8,新增「角色」标签页(User图标),红点提示,StatsPanel新增5行属性行,版本号 v0.6→v0.7 - 90→ - 91→**四维属性设计**: - 92→- 探索力(emerald):探险力+X%/巡航飞船速度+X% - 93→- 智慧(fuchsia):解码步数+X/洞见+X%/自动解码周期-X% - 94→- 勇气(amber):探险生命+X/BOSS胜率+X%/巡航护盾+X - 95→- 灵感(rose):接触率+X/星潮概率+X/脉冲连击+X% - 96→ - 97→**QA 验证**(agent-browser + VLM): - 98→- VLM 视觉评分 **7/10**(面板)/ **8/10**(待分配状态):四维卡片可见、数值/等级/加成/经验条齐全、四色全息 - 99→- 功能验证:setState pendingAttrPoints=5 → 显示5点待分配 → 点击分配按钮 → exploration 0→1, pending 5→4 ✅ - 100→- lint 零错误;HTTP 200 - 101→ - 102→--- - 103→ - 104→### v0.6 深空巡航 Canvas 2D 实时玩法(历史记录) - 105→**动机**:回应用户反馈「玩法太单调(全是点点点)」+「视觉震撼不足」——首次引入实时操作玩法。 - 106→ - 107→**新增文件**: - 108→- `src/lib/game/cruise.ts`(~520 行逻辑层) - 109→ - 类型系统:CruiseEntity 联合类型(ship/asteroid/storm/crystal/insight/beacon/stargate/particle 共 8 种) - 110→ - mulberry32 + FNV-1a 种子化 RNG(`cruiseSeed(level)`) - 111→ - `generateLevel`:程序化生成陨石带(8+lvl×2)、虚空风暴(1+lvl/2)、晶体碎片(5+lvl)、洞见光球(2+lvl/3)、信标(1+lvl/4)、星门 - 112→ - `updateCruise`:物理推进 + 碰撞检测 + 粒子系统 + 震动 + 600ms 无敌帧 - 113→ - `computeRewards`:crystals=碎片×(8+lvl×2)、insights=洞见×(2+lvl×0.5)、contact=信标×(1.5+lvl×0.3);通关×1.5、失败×0.5 - 114→ - 独立 localStorage `echo-nexus-cruise-v1`:最高分/累计通关/最高关卡/累计奖励/最近 20 局 - 115→ - 116→- `src/components/game/CruiseMode.tsx`(~830 行渲染层) - 117→ - 全屏 fixed inset-0 z-50 Canvas,DPR cap 2,resize 监听 - 118→ - 3 层视差星空:远(100星)/中(60星)/近(30星),独立漂移 + 飞船速度视差 + 闪烁 - 119→ - 8 种实体全部 ctx.shadowBlur 辉光绘制 - 120→ - 粒子系统:尾焰/收集/碰撞/烟花,上限 200 - 121→ - 屏幕震动 280ms 衰减;低护盾(<30%)红色边框脉冲 - 122→ - HUD(HTML 叠层,glass+backdrop-blur,80ms 节流):护盾/能量/分数/用时/收集计数 - 123→ - 控制:桌面 WASD/方向键 8 方向 + 移动端虚拟摇杆 + P 暂停 + Esc 退出 + 失焦自动暂停 - 124→ - 阶段:ready → playing → won/lost(结算明细 + 奖励 + 下一关/重试/返回) - 125→ - 126→**修改文件**: - 127→- `src/app/page.tsx`:header 新增 amber 主题「巡航」按钮(Navigation 图标)+ cruiseOpen state + 条件渲染 CruiseMode - 128→- `src/store/gameStore.ts`:新增 `grantCruiseReward({crystals?, insights?, contact?})` action(crystals 受 crystalCap 限制,contact 受 100 上限) - 129→- 版本号 v0.5.2 → v0.6 - 130→ - 131→**UI 偏移/重叠 BUG 修复**(3 处): - 132→- `DecodeArray.tsx`:移除 className 冲突的 `-translate-x-1/2 -translate-y-1/2`,统一用 inline transform 定位+缩放 - 133→- `AchievementsPanel.tsx`:移除内层 `max-h-[300px]`,改 `flex-1 min-h-0` 自适应父容器 - 134→- `page.tsx`:调整三个 section 的 min-h,避免小屏单列时总高度超过手机视口 - 135→ - 136→**色彩规范**:严格遵循 emerald(#34d399) / rose(#fb7185) / amber(#fbbf24) / fuchsia(#e879f9) 四色全息色谱,零蓝色/靛色 - 137→ - 138→**QA 验证**(agent-browser + VLM): - 139→- lint 零错误;dev 服务器 HTTP 200;编译 < 250ms - 140→- VLM 确认主界面:巡航按钮存在、晶体球居中、解码区正常、无重叠偏移 - 141→- VLM 确认巡航玩法:飞船可见(绿色三角形)、HUD 完整、陨石+星空背景、粒子特效(黄色尾焰+紫色辉光) - 142→- WASD 控制飞船移动,按 W 直冲星门 → 通关,奖励 +30 晶体(2×10×1.5=30 计算正确) - 143→- localStorage 正确记录 highScore/totalRuns/totalWins/bestLevel - 144→- 奖励同步 gameStore(grantCruiseReward,满仓时 cap 逻辑正确) - 145→- 退出返回主界面,7 标签页 + 晶体球 + 解码面板均完好 - 146→ - 147→--- - 148→ - 149→## 三、未解决问题或风险 / 下一阶段优先事项 - 150→ - 151→### 已知问题 / 风险 - 152→1. **dev 服务器 Turbopack 缓存偶发损坏**:`Unable to open static sorted file` + 进程被杀,需 `rm -rf .next` 重启。静态导出构建稳定可靠。 - 153→2. **定时任务调度类型**:必须用 `fixed_rate` + `"900"` 秒(15分钟),不要用 cron 表达式。`webDevReview` 类型任务正常完成不会被删除,只有抛出未捕获异常才会被标记失败。 - 154→3. **v0.3 星潮为单机版**:原计划 socket 全局事件,后续可扩展为多人同步。 - 155→4. **v0.5 信标排行榜为本地版**(纯 localStorage):升级云排行榜需后端 API。 - 156→5. **v0.5 每日挑战仅 1 个/天**:可加入"周挑战"或"信标链"(连续完成 N 天奖励)。 - 157→6. **探险 BOSS 战胜率较低**:基础探索力 10 vs BOSS 难度 5-7,胜率约 25%,需玩家投资探险技术。 - 158→7. **探险能量恢复较慢**:45s/点,可加技术提升恢复速度。 - 159→8. **编年史上限 50 条**:超过自动丢弃最早的;v0.4 之前飞升无回填(空状态有提示)。 - 160→9. **5 纪元叙事为模板化生成**:v0.5+ 将加入手写剧情节点。 - 161→10. ~~**CrystalOrb 仍为 CSS 动画**~~ → ✅ **v0.7 已完成 Canvas 粒子系统升级**。 - 162→ - 163→### 下一阶段优先级(v0.7 后修正版) - 164→> 工单修正:原 P0 CrystalOrb Canvas + P1 角色属性两项均已在 v0.7 完成,现重新排序。 - 165→ - 166→1. **🔴 P0 — 巡航玩法大增强(v0.8 主线)**:BOSS 战关卡(每 5 关一个 BOSS,三阶段攻击模式 + 飞船射击系统)/ 道具掉落(5 种 powerup:护盾/能量/急速/超载/磁吸)/ 事件选择节点(每关通关后 3 选 1 强化牌,10 种强化)。这是当前最高优先级,能显著提升巡航玩法的深度与可玩性。 - 167→2. **🟠 P1 — 探险 BOSS 战胜率平衡 + 探险能量恢复**:修复已知问题 6/7(探险 BOSS 胜率 25% 偏低,能量 45s/点 偏慢),提升探险体验。 - 168→3. **🟠 P1 — 信标系统扩展**:周挑战 / 信标链(连续完成 N 天奖励),修复已知问题 5。 - 169→4. **🟡 P2 — UI 样式细节打磨**:标签页图标/文字比例、视觉层次强化、移动端响应式进一步优化、CrystalOrb 周边留白优化(VLM 指出主界面左侧有空白)。 - 170→5. **🟡 P2 — 云存档 + 云排行榜**:v0.5+ 后端 API(需评估是否引入 mini-service)。 - 171→6. **🟢 P3 — 全 5 纪元手写叙事节点**:替换模板化 lore。 - 172→7. **🟢 P3 — socket 多人同步星潮**:全局事件共享。 - 173→ - 174→### 定时任务(fixed_rate · 900s) - 175→- **当前 job_id**: **228357**(2026-06-24 13:25 重建) - 176→- **配置**: `fixed_rate` + `"900"`(15分钟间隔),`priority=10`(高优先级),`payload.kind=webDevReview`,`tz=Asia/Shanghai` - 177→- **任务名**: Echo Nexus - 任务审查与持续迭代 - 178→- **任务描述强制要求**:每次触发 MUST 先完整阅读 worklog.md 工单,以工单为准,不凭记忆工作 - 179→- **正常完成不会被删除**,只有未捕获异常会标记失败。若任务列表为空,需用 cron create 重建(schedule.kind=fixed_rate, expr="900", priority=10, payload.kind=webDevReview)。 - 180→- **job_id 历史**: 227909(失效) → 228130(失效) → 228172(失效) → 228186(失效) → 228218(删除重建) → 228236(cron版,删除重建) → 228266(fixed_rate,失效) → 228329(失效) → **228357(当前,重建)** - 181→ - 182→--- - 183→ - 184→## 定时任务描述模板(webDevReview · fixed_rate) - 185→ - 186→> 重建参数:schedule.kind=`fixed_rate`, schedule.expr=`"900"`, params.priority=`10`, payload.kind=`webDevReview` - 187→ - 188→``` - 189→请先判断当前项目状态,使用 agent-browser 进行必要测试和 QA,然后自主选择本轮最合适的工作重点并继续开发。 - 190→ - 191→要求: - 192→1. 开始任务时查看 /home/z/my-project/worklog.md 了解实际进展,拆解详细todo - 193→2. 使用 agent-browser 进行测试和QA,有bug优先修复 - 194→3. 如果稳定,自主提出新需求继续推进 - 195→4. 样式要越做细节越多!! - 196→5. 功能要越做越多!! - 197→6. 工作告一段落更新 /home/z/my-project/worklog.md - 198→7. 项目结束后一定要把工作记录写在 worklog.md 里 - 199→ - 200→交接文档建议包含三部分: - 201→- 项目当前状态描述/判断 - 202→- 当前目标/已完成的修改/验证结果 - 203→- 未解决问题或风险,建议下一阶段优先事项 - 204→``` - 205→ - 206→--- - 207→ - 208→## 历史详细版本记录(按需查阅 docs/repo/docs/) - 209→ - 210→- `04-解码系统修复-v0.1.1.md` - 211→- `05-遗迹探险系统-v0.2.md` - 212→- `06-音频与成就系统-v0.2.1.md` - 213→- `07-星潮事件系统-v0.3.md` - 214→- `08-星图天文台系统-v0.3.1.md` - 215→- `09-回响编年史系统-v0.4.md` - 216→- `10-深空信标系统-v0.5.md` - 217→ - 218→--- - 219→ - 220→Task ID: 7 - 221→Agent: full-stack-developer - 222→Task: 角色属性系统(探索力/智慧/勇气/灵感) - 223→ - 224→Work Log: - 225→- 阅读 worklog.md 了解 v0.6 项目背景,确认 8 大系统架构与四色全息色彩规范 - 226→- 新建 `src/lib/game/attributes.ts` 逻辑层(~330 行): - 227→ - 定义 CharacterAttributes/AttributeProgress/AttributeKey 类型 + ATTRIBUTE_CONFIG 元信息 - 228→ - getAttributeBonus 线性+递减两区加成公式(0-50 每 10 点 +5%,50-100 每点 +2%) - 229→ - getAllBonuses 返回 12 个修饰器(探险力/巡航速度/解码步数/洞见/自动解码周期/探险生命/BOSS 胜率/巡航护盾/接触率/星潮触发/脉冲连击/产能) - 230→ - levelUpCheck 自动跨多级 + expRequiredForLevel + computePrestigeAttrPoints + migrateAttributes 兼容 - 231→- 扩展 `src/lib/game/types.ts` GameState:新增 attributes/attributeProgress/pendingAttrPoints - 232→- 扩展 `src/lib/game/config.ts` INITIAL_STATE:补全默认值(0 数值 + 空 progress) - 233→- 扩展 `src/lib/game/engine.ts`: - 234→ - recomputeStats 末尾聚合 am.crystalsPerSecMult/insightMultAdd/contactRateMult/decodeStepsBonus - 235→ - performPrestige 保留 attributes 数值,清空 attributeProgress,发放 ascensions × 2 + 1 点属性点 - 236→ - createInitialState 每次返回全新对象避免引用共享 - 237→- 扩展 `src/store/gameStore.ts`(核心改造,10+ 处 syncStats 调用全部补 attributes 参数): - 238→ - pulse:连击 ≥3 给灵感经验 + 脉冲威力乘灵感连击加成 - 239→ - clickNode 完成:给智慧经验(tier × 2) - 240→ - autoDecodeTick:智慧经验 +1 + 自动解码周期受 am.autoDecodeIntervalMult 影响 - 241→ - startExpedition:探险力乘 am.expeditionPowerMult,生命加 am.expeditionHpBonus - 242→ - resolveCurrentNode:BOSS 节点包装 RNG 提升 +am.bossWinRateBonus 胜率;战斗胜利给勇气+探索力经验 - 243→ - grantCruiseReward:按总奖励量给探索力+勇气经验 - 244→ - tickTide:灵感 am.tideTriggerBonus 缩短星潮间隙(上限 30%) - 245→ - init() 调用 migrateAttributes 补全旧存档 - 246→ - 新增 allocateAttribute / gainAttributeExp 两个 action - 247→- 新建 `src/components/game/AttributesPanel.tsx` UI 面板(~330 行): - 248→ - 四维卡片网格(小屏 2×2,大屏 1×4) - 249→ - 每张卡片:图标 + 中英文名 + Lv.badge + 数值/100 + 加成% + 经验条 + 影响列表 + 「+分配」按钮 - 250→ - 顶部待分配点数 badge(pendingAttrPoints > 0 时 echo-pending-pulse 闪烁动画) - 251→ - 底部总等级/总加成概览 + 12 个修饰器明细 - 252→ - 4 色辉光边框(emerald/fuchsia/amber/rose),顶角光晕装饰 - 253→- 接入 `src/app/page.tsx`: - 254→ - grid-cols-7 → grid-cols-8,新增第 8 个「角色」TabsTrigger(User 图标,渐变主题) - 255→ - pendingAttrPoints > 0 时显示 rose 红点提示 - 256→ - 渲染 AttributesPanel - 257→ - StatsPanel 新增 5 行属性行(探索力/智慧/勇气/灵感/待分配点) - 258→ - 版本号 v0.6 → v0.7 - 259→- 新增 `src/lib/game/achievements.ts` 2 项成就: - 260→ - ach_attr_total_50(四维觉醒):总属性 ≥ 50 → 产能+6%/洞见+6% - 261→ - ach_attr_max_100(维度精通):任一属性 100 → 产能+12%/洞见+10% - 262→- 写入 `agent-ctx/7-full-stack-developer.md` 工作记录 - 263→- 最终验证:lint 零错误 + dev 服务器 HTTP 200 + 编译 < 250ms - 264→ - 265→Stage Summary: - 266→- ✅ 角色属性系统 v0.7 P1 全部完成 - 267→- ✅ 新增 2 文件(attributes.ts + AttributesPanel.tsx),修改 6 文件(types/config/engine/gameStore/achievements/page) - 268→- ✅ 四维属性完整接入游戏各系统(探险/解码/脉冲/巡航/星潮) - 269→- ✅ 飞升属性点公式 = ascensions × 2 + 1;经验累积自动升级(每级 10×level) - 270→- ✅ 旧存档兼容(migrateAttributes 补全字段,同步 level 与 attributes) - 271→- ✅ lint 零错误 + HTTP 200 + 编译 < 250ms - 272→- ✅ 严格四色全息(emerald/fuchsia/amber/rose),零蓝色/靛色 - 273→- ✅ 8 标签页 + 巡航按钮全部保留,data-tut 锚点未破坏 - 274→- ✅ 待分配点数时标签页 rose 红点提示 + 顶部 badge 闪烁 - 275→ - 276→--- - 277→Task ID: 8-deploy - 278→Agent: main - 279→Task: 重建定时任务 + 更新 README + 部署 gh-pages(v0.7) - 280→ - 281→Work Log: - 282→- 检查项目状态:CrystalOrb.tsx 已是 v0.7 Canvas 粒子系统(659 行),无活动 cron job - 283→- 重建定时任务:cron create fixed_rate="900" priority=10 webDevReview → job_id 228329(tz=Asia/Shanghai) - 284→- 重写 README.md: - 285→ - 顶部显著位置添加「在线游玩」链接(https://gitea-pages.atdunbg.xyz/Super_Z/echo-nexus/) - 286→ - 版本号 v0.1 → v0.7 - 287→ - 新增 10 大系统表格(含 v0.6 巡航 + v0.7 角色属性) - 288→ - 完整开发路线表(v0.1-v1.0,标记 ✅/🔜 状态) - 289→ - 新增本地开发命令 + Gitea Pages 部署指南 - 290→ - 文档索引扩展至 10 个版本文档 - 291→- 静态导出:BUILD_EXPORT=true bun run build:static → out/ 1.6M(HTTP 200 编译 4.3s) - 292→- 推送 gh-pages 分支:force push origin gh-pages(commit 8727fed "deploy: v0.7") - 293→- 提交 main:README 更新(commit 43dfc2d "docs: README 添加在线游玩链接 + v0.7 状态更新") - 294→- 验证 Gitea Pages:https://gitea-pages.atdunbg.xyz/Super_Z/echo-nexus/ → HTTP 200 ✅ - 295→- QA 验证:agent-browser 打开 http://localhost:3000/ → VLM 评分 8/10(主界面 polished,minor 空白) - 296→ - 297→Stage Summary: - 298→- ✅ 定时任务重建成功(job_id 228329,每 15 分钟自动审查迭代) - 299→- ✅ README 重写完成,网站链接显著展示,v0.7 状态完整 - 300→- ✅ gh-pages 部署成功,Gitea Pages HTTP 200 可访问 - 301→- ✅ main 分支已同步推送(README + worklog 更新) - 302→- ✅ dev 服务器健康(HTTP 200,编译 < 250ms) - 303→- ✅ CrystalOrb v0.7 Canvas 粒子系统已确认完整(659 行) - 304→- 下一步:v0.8 巡航玩法增强(BOSS 战 / 道具掉落 / 事件选择节点) - 305→ - 306→--- - 307→Task ID: 8 - 308→Agent: main - 309→Task: v0.8 巡航玩法大增强(BOSS 战 + 道具掉落 + 事件选择节点)+ 工单修正 + 审查流程重建 - 310→ - 311→Work Log: - 312→- 重读 worklog 工单,发现 v0.7 两项(CrystalOrb Canvas + 角色属性)已完成但工单"下一阶段优先级"未更新 - 313→- 修正 worklog 工单:标注 P0/P1 v0.7 已完成,重新排序下一阶段优先级(P0=巡航增强, P1=探险平衡/信标扩展, P2=UI打磨/云存档, P3=叙事/多人) - 314→- 重建审查流程 cron job:fixed_rate=900s, priority=10, webDevReview, tz=Asia/Shanghai, job_id=228357 - 315→ - 任务描述强制要求:每次触发 MUST 先完整阅读 worklog.md 工单,以工单为准,不凭记忆工作 - 316→- 发现之前被中断的 v0.8 subagent 已写入 2037 行代码(cruise.ts +1487 / CruiseMode.tsx +858 / page.tsx +2) - 317→- 修复 2 个 lint 错误(react-hooks/set-state-in-effect): - 318→ - isTouch: useState(false) + useEffect(setIsTouch) → useState initializer - 319→ - stats: useState(null) + useEffect(setStats(loadCruiseStats)) → useState initializer - 320→- QA 验证(agent-browser + VLM): - 321→ - 主界面 VLM 7/10:v0.8 版本号 ✅ + 巡航按钮 ✅ - 322→ - 巡航 READY 阶段 VLM 8/10:BOSS 战说明 ✅(每5关BOSS战/击败BOSS)+ 射击控制说明 ✅(空格/右下角射击按钮) - 323→ - 巡航第1关游戏 VLM 8/10:飞船(绿色三角)✅ + HUD(护盾/能量/分数/计时)✅ + 陨石/收集物/能量场 ✅ + 无渲染错误 - 324→- 提交 v0.8(commit 494bc5f)+ 推送 main - 325→- 静态导出 BUILD_EXPORT=true → out/ 1.6M(编译 4.1s) - 326→- 部署 gh-pages(force push, commit b865922)→ Gitea Pages HTTP 200 ✅ - 327→ - 328→Stage Summary: - 329→- ✅ 工单修正完成:v0.7 已完成项标注清楚,下一阶段优先级重新排序 - 330→- ✅ 审查流程重建:cron job_id 228357,每15分钟触发,强制读工单 - 331→- ✅ v0.8 巡航玩法大增强全部完成并部署: - 332→ - BOSS 战系统(每5关,三阶段攻击,HP条,击败奖励×3) - 333→ - 飞船射击系统(空格/射击按钮,能量消耗,子弹尾焰) - 334→ - 道具掉落(5种powerup:护盾/能量/急速/超载/磁吸) - 335→ - 事件选择节点(每关通关后3选1,10种强化牌) - 336→ - lint 零错误 + dev HTTP 200 + gh-pages HTTP 200 - 337→- ✅ VLM 评分:主界面 7/10 + 巡航READY 8/10 + 巡航游戏 8/10 - 338→- 在线游玩 v0.8:https://gitea-pages.atdunbg.xyz/Super_Z/echo-nexus/ - 339→- 下一阶段工单:P1 探险BOSS胜率平衡 + 探险能量恢复 / 信标系统扩展(周挑战/信标链) - 340→ - 341→--- - 342→Task ID: 9-b - 343→Agent: full-stack-developer - 344→Task: 信标系统扩展(周挑战 + 信标链连续奖励) - 345→ - 346→Work Log: - 347→- 阅读现有 `src/lib/game/beacon.ts`(v0.5 每日挑战 + 本地排行榜,358 行)+ `src/components/game/BeaconPanel.tsx` + `src/store/gameStore.ts` 的 `trackBeacon` 函数(约第 173 行)+ worklog v0.8 项目状态,确认四色全息规范与现有架构。 - 348→- **扩展 `src/lib/game/beacon.ts`(358 → 873 行,新增 ~515 行)**: - 349→ - **周挑战(WEEKLY CHALLENGE)**: - 350→ - `BeaconWeeklyChallenge` 接口(weekKey YYYY-Www / type / difficulty 强制 anomaly|singular / goal / rewardInsight / rewardContact / seed / title / desc) - 351→ - `BeaconWeeklyProgress` 接口(weekKey / progress / startedAt / completedAt / claimed / durationSec) - 352→ - `getWeekKey(now)`:ISO 8601 周键(周一为起点,含首个周四的周为第一周) - 353→ - `weekKeyToSeed`:FNV-1a 哈希 - 354→ - `generateWeeklyChallenge(now)`:基于 weekKey 种子确定性生成,难度加权 anomaly 60% / singular 40%,goal 为日基准 ×3-5 倍(decode 18-75 / expedition 3-15 / pulse 60-245 / boss 3-12 / insight 120-595) - 355→ - `loadWeeklyProgress` / `saveWeeklyProgress` / `addWeeklyProgress` / `claimWeeklyReward`(推送排行榜,entry 加 `isWeekly: true`) - 356→ - `msUntilNextWeek(now)`:距离下周一 UTC 0 点的毫秒数 - 357→ - `BEACON_WEEKLY_KEY = "echo-nexus-beacon-weekly-v1"` - 358→ - **信标链(BEACON CHAIN)**: - 359→ - `BeaconChainState` 接口(lastCompletedDateKey / currentStreak / longestStreak / totalCompletions / graceUsed / milestonesClaimed) - 360→ - `BEACON_CHAIN_KEY = "echo-nexus-beacon-chain-v1"` - 361→ - `BEACON_CHAIN_MILESTONES = [3, 7, 14, 30]` 常量 - 362→ - `BEACON_CHAIN_REWARDS`:4 个里程碑(三日谐振 +50/+5 / 七日回响 +120/+12 / 半月星潮 +280/+28 / 满月飞升 +680/+68) - 363→ - `loadChainState` / `saveChainState`(每次返回新对象避免引用共享) - 364→ - `recordChainCompletion(dateKey)` 核心逻辑:同日重复忽略;次日 streak++;隔一天 miss 且 graceUsed<1 时续命 streak++ graceUsed++;其他断链 streak=1 graceUsed=0;返回 `{ state, newMilestones }` - 365→ - `claimChainMilestone(milestone)` / `getNextMilestone(streak)` / `getChainProgress(streak)`(返回 current/next/prev/progressPct 用于 UI) - 366→ - `dateKeyToTimestamp` / `dateKeyDiffDays` 工具函数 - 367→ - `BeaconScoreEntry` 新增可选 `isWeekly?: boolean` 字段 - 368→- **扩展 `src/store/gameStore.ts`**: - 369→ - import 扩展:generateWeeklyChallenge / loadWeeklyProgress / loadChainState / addWeeklyProgress / recordChainCompletion / claimWeeklyReward / claimChainMilestone / getTodayKey + 类型 - 370→ - `trackBeacon(type, delta)` 函数签名从 `boolean` 改为 `{ dailyJustCompleted, weeklyJustCompleted, newChainMilestones }`: - 371→ - 日挑战类型匹配且未完成 → addBeaconProgress;若刚完成 → recordChainCompletion(getTodayKey()) 收集 newMilestones - 372→ - 周挑战类型匹配且未完成 → addWeeklyProgress - 373→ - 所有调用点(pulse/clickNode/autoDecodeTick/resolveCurrentNode 等 7 处)原本就忽略返回值,向后兼容 - 374→ - 新增 action `claimWeeklyBeacon`:调用 claimWeeklyReward → 发放 insights + contact 到 state,返回 `{ rewardInsight, rewardContact, score }` - 375→ - 新增 action `claimChainReward(milestone)`:前置校验 currentStreak 与 milestonesClaimed,调用 claimChainMilestone → 发放奖励,返回 `{ rewardInsight, rewardContact, label, ok }` - 376→ - GameActions 接口同步新增两个 action 签名 - 377→- **重写 `src/components/game/BeaconPanel.tsx`(301 → 638 行)**: - 378→ - 顶部头部 + 每日挑战卡片(保留 v0.5 完整功能 + 按钮主题色随难度色淡显) - 379→ - **新增周挑战区块**(fuchsia 主题):标题"周挑战 · WEEKLY" + weekKey + 倒计时 + 挑战卡片(类型/难度标签/标题/描述/进度条/奖励/领取按钮 emerald 主题)+ weekly-glow 动画 - 380→ - **新增信标链区块**(amber→rose 渐变):标题"信标链 · CHAIN" + 大字号 streak(chain-streak-text 渐变流动动画)+ 今日完成状态徽章 + 4 个里程碑节点(w-12 h-12 圆形,claimed=emerald/✓,reachable=rose 脉冲动画+领取按钮,inProgress=amber 半亮,未到达=muted 灰)+ 节点间连线(背景灰 + 已达成部分 amber→rose 渐变 + 辉光,基于 prev→next 插值定位)+ 进度条 + 底部统计(最长/累计/续命状态) - 381→ - 桌面端 lg:grid-cols-2 让周挑战+信标链并排,移动端单列 - 382→ - 排行榜条目区分日/周:周挑战 entry 显示 "WEEK" 徽章 + fuchsia 高亮背景 - 383→ - 空状态提示扩展为"完成今日或本周信标即可登榜" - 384→ - 4 个新动画:weekly-glow / chain-milestone-pulse / chain-streak-flux(背景渐变流动) - 385→- **QA 验证**: - 386→ - `bun run lint` 零错误(每次修改后均验证) - 387→ - dev.log 全程无错误,所有编译 < 300ms - 388→ - **信标链逻辑测试**(bun 直接运行 TS,5 个场景全 PASS): - 389→ 1. 昨日 streak=1 → 今日完成 → streak=2 ✅ - 390→ 2. 同日重复完成 → 忽略 ✅ - 391→ 3. 明日完成 → streak=3,无需 grace ✅ - 392→ 4. 隔一天 miss(lastCompleted + 2 天)→ streak=4,graceUsed=1 ✅ - 393→ 5. 再次 miss(grace 已用)→ 断链 streak=1,graceUsed=0 ✅ - 394→ - **UI 集成测试**(agent-browser): - 395→ - localStorage 模拟设置 chain state(lastCompletedDateKey=yesterday, currentStreak=1)→ reload → UI 正确显示 streak=1 - 396→ - 设置 streak=3 + milestonesClaimed=[] → milestone 3 节点显示 rose 脉冲动画 + "领取"按钮 - 397→ - 点击"领取"→ milestonesClaimed=[3] + Toast "✦ 三日谐振 已领取 +50 洞见 · +5.0 接触" + 按钮消失 ✅ - 398→ - **VLM 视觉评分**(目标 ≥7/10): - 399→ - 首屏截图:8/10(四色一致、布局合理、信标链清晰) - 400→ - 里程碑可领取状态:8/10(amber→rose 渐变醒目、里程碑层次分明、fuchsia 主题清晰) - 401→ - 重置后干净状态:8/10(WEEKLY 字体对比度可优化,但整体可读性强) - 402→ - **不破坏现有功能**:日挑战进度/领奖/排行榜流程全部保留并工作正常 - 403→ - 404→Stage Summary: - 405→- ✅ beacon.ts 扩展完成(358 → 873 行):周挑战 + 信标链全部函数与类型 - 406→- ✅ gameStore.ts 集成完成:trackBeacon 升级为对象返回值 + claimWeeklyBeacon + claimChainReward 两个新 action - 407→- ✅ BeaconPanel.tsx UI 完整重写(301 → 638 行):周挑战 fuchsia 卡片 + 信标链 amber→rose 渐变区块 - 408→- ✅ 严格四色全息:周挑战 fuchsia、信标链 amber→rose、领取按钮 emerald、无蓝/紫色违规 - 409→- ✅ localStorage 新增 2 个独立 key(weekly + chain),不污染旧 key - 410→- ✅ 确定性:周挑战同周同种子同结果(weekKey FNV-1a 种子) - 411→- ✅ 信标链 grace 续命机制工作正常(5 个场景 bun 测试全 PASS) - 412→- ✅ lint 零错误 + dev HTTP 200 + VLM 8/10 - 413→- ✅ 现有日挑战 + 排行榜 + 领奖流程全部保留,无回归 - 414→ - 415→--- - 416→Task ID: 9 - 417→Agent: main + subagent(9-b) - 418→Task: v0.8.1 P1 双任务 — 探险 BOSS 胜率平衡 + 能量恢复 + 信标系统扩展(周挑战 + 信标链) - 419→ - 420→Work Log: - 421→- 重读工单确认 P1 两项为下一阶段优先级 - 422→- P1-a 探险平衡(main 自己做,平衡需要精细控制): - 423→ - expedition.ts: combatDifficultyScale 8→4, combatWinRateFloor 0.25→0.35, BOSS difficulty 5-7→3-5 - 424→ - 新增 computeEnergyRegenInterval(state): exp_2 -30%, exp_3 -20%, 探索力属性 -最高30%, 下限12s - 425→ - computeEnergyRegen 接受 intervalSec 参数 - 426→ - config.ts: exp_2/exp_3 描述加能量恢复说明 - 427→ - gameStore.ts tick: 传入动态间隔 - 428→ - ExpeditionPanel.tsx: 显示实际恢复速度 + '已加速'标记 - 429→- P1-b 信标系统扩展(dispatch subagent 9-b,并行完成): - 430→ - beacon.ts 358→873行: 周挑战(getWeekKey ISO 8601 + 确定性生成) + 信标链(4里程碑+grace续命) - 431→ - BeaconPanel.tsx 301→638行: 周挑战fuchsia主题 + 信标链amber→rose里程碑节点 - 432→ - gameStore.ts: trackBeacon升级 + claimWeeklyBeacon/claimChainReward actions - 433→- QA 验证: - 434→ - lint 零错误 - 435→ - 探险面板 VLM 7/10(能量条 + 恢复速度显示正常) - 436→ - 信标面板 VLM 8/10(周挑战 WEEKLY + 信标链 3/7/14/30 + 排行榜全部可见) - 437→ - subagent 5场景 bun 测试全 PASS(信标链断链/续命逻辑) - 438→- 提交 v0.8.1(commit 71ca5b4)+ 推送 main - 439→- 静态导出 + 部署 gh-pages(commit 97eecaa)→ HTTP 200 - 440→ - 441→Stage Summary: - 442→- ✅ P1-a 探险平衡完成: BOSS 基础胜率 25%→35-45%, 配合技术/属性可达 55-70% - 443→- ✅ P1-a 能量恢复: 固定45s → 动态12-45s(技术+属性加速) - 444→- ✅ P1-b 周挑战: 每周一UTC刷新, fuchsia主题, goal×3-5倍 - 445→- ✅ P1-b 信标链: 4里程碑(3/7/14/30天) + grace续命, amber→rose渐变 - 446→- ✅ 在线游玩 v0.8.1: https://gitea-pages.atdunbg.xyz/Super_Z/echo-nexus/ - 447→- 下一阶段工单: P2 UI样式细节打磨 / P2 云存档+云排行榜 / P3 全纪元叙事 - 448→ - 449→--- - 450→Task ID: v0.14-reimpl - 451→Agent: full-stack-developer - 452→Task: 放置系统 (Idle Operations) — 采矿无人机舰队 + 6放置工程 + 永久产能加成 + idle徽章 - 453→ - 454→Work Log: - 455→- 阅读 worklog.md 了解 v0.8.1 项目状态(10 大系统 + 四色全息规范 + 8 标签页架构) - 456→- 类型扩展 `src/lib/game/types.ts`: - 457→ - 新增 `IdleProjectDef` 接口(id/name/desc/durationSec/reward/minAscensions/minCrystalsPerSec/icon/color/order) - 458→ - 新增 `IdleProjectSlot` 接口(projectId/startedAt/finishesAt/remainingSec/completed) - 459→ - GameState 接口新增 4 字段:idleProjectSlots (3 槽位数组) / idleProjectHistory / idlePermanentBonus / idleStats - 460→- 新建 `src/lib/game/idle.ts` (~290 行): - 461→ - 6 个放置工程定义(idle_scan 60s / idle_refine 180s / idle_archive 600s / idle_anchor 1200s / idle_drones 1800s / idle_resonance 3600s) - 462→ - 奖励类型:crystals / insights / energy / contact / crystalsPerSecPermanent / randomFragment - 463→ - 解锁逻辑:minAscensions 与 minCrystalsPerSec 为 OR 关系(满足任一已设置条件即可) - 464→ - 工具函数:getIdleProject / getUnlockedIdleProjects / getLockedIdleProjects / isIdleProjectUnlocked / formatRemaining (12s/3m 45s/1h 12m) / formatReward / deriveMinerFleet (派生 min_1..min_5 采矿无人机,icon: ⛏️🌋🤖💠☀️,无任何采矿技术时返回休眠 min_1) - 465→ - 颜色映射:IDLE_COLOR_CLASSES (text/border/bg/bgSoft/glow/ring/dot) + COLOR_HEX (用于 inline style 绕过 Tailwind 动态类限制) - 466→- 引擎集成 `src/lib/game/engine.ts`: - 467→ - recomputeStats() 末尾 `crystalsPerSec += state.idlePermanentBonus ?? 0` (放置永久加成叠加) - 468→ - performPrestige() 清空 idleProjectSlots (中断当前周目工程),保留 idlePermanentBonus/idleProjectHistory/idleStats (跨周目永久);recomputeStats 调用传 idlePermanentBonus - 469→ - createInitialState() 新增 4 字段默认值 - 470→- Store 集成 `src/store/gameStore.ts` (核心改造): - 471→ - 新增 4 action:startIdleProject (校验+写入槽位) / cancelIdleProject (置 null) / claimIdleProject (应用奖励 + permBonus 累加 + randomFragment 解锁 + history 追加 + Toast 提示) / tickIdleProjects (每 tick 更新 remainingSec,归零标记 completed) - 472→ - **persist migrate 函数**:persist 配置增加 `version: 1` + `migrate` 函数自动补全 4 个 idle 字段,旧存档加载不崩溃 - 473→ - 14 处 syncStats 调用全部更新为传入 `idlePermanentBonus: s.idlePermanentBonus ?? 0`,确保 recomputeStats 计算时纳入永久加成 - 474→ - init() 中也补全 4 个 idle 字段(defense in depth) - 475→- 主循环 `src/hooks/useGameLoop.ts`:新增 tickIdleProjects 选择器,setInterval 回调中 autoDecodeTick 后调用,visibilitychange 也调用 - 476→- 新建 `src/components/game/IdleOperationsPanel.tsx` (~490 行): - 477→ - 4 section 在 max-h-[520px] overflow-y-auto 容器中: - 478→ 1. 放置收益概览:3 stat tiles (放置产能/永久加成/完成工程) + 离线效率 progress bar + 累计放置晶体统计 - 479→ 2. 采矿无人机舰队:grid 展示 deriveMinerFleet,每卡 emoji+name+Lv+output/s+状态点 (active=emerald ping 脉冲, dormant=灰) - 480→ 3. 放置工程槽位:3 张卡(空槽=虚线占位,运行中=大字号倒计时+自定义进度条+取消按钮,已完成=奖励预览+领取按钮带 glow) - 481→ 4. 可派遣工程:2 列 grid 展示已解锁工程(icon+name+duration+desc+reward+3 数字派遣按钮 1/2/3),下方列出未解锁工程及解锁条件 - 482→ - useShallow 订阅 + 本地 now state 每 1s 刷新倒计时 - 483→ - 严格 4 色全息,自定义 fuchsia 滚动条 - 484→- 新建 `src/components/game/IdleStatusBadge.tsx` (~100 行): - 485→ - 紧凑徽章 h-8 px-2.5 text-[11px]:脉冲点 (emerald 若 cps>0) + "放置中 +X/s" (或 "休眠中") - 486→ - Tooltip 悬停展示分解:基础产能/永久加成/运行中工程/待领取工程数 - 487→ - data-tut="idle-status-badge" 锚点 + onClick 切换到放置 tab - 488→- 新建 `src/components/game/IdleProjectBar.tsx` (~90 行): - 489→ - 晶体球下方细长进度条,最多 3 个工程 mini pill `[icon] name 12s ▓▓▓░░` - 490→ - 完成时显示 "✓ 待领取" + glow 辉光;无工程返回 null - 491→ - 进度条颜色用 inline style (COLOR_HEX) 控制,每 1s 刷新倒计时 - 492→- 主页面集成 `src/app/page.tsx`: - 493→ - 引入 3 新组件 + Clock 图标 + getUnlockedIdleProjects - 494→ - 新增 activeTab/tabInited state + controlled Tabs (`value={activeTab} onValueChange={setActiveTab}`) - 495→ - 挂载后 useEffect 一次性设置默认 tab:hasActiveExpedition→"expedition",hasPendingPerk→"constellation",否则→"idle" - 496→ - 版本号 v0.8 → v0.14 - 497→ - Header 在 StarTideIndicator 后加 IdleStatusBadge - 498→ - 左侧 CrystalOrb 后加 IdleProjectBar - 499→ - TabsList grid-cols-8 → grid-cols-9,新增 value="idle" 的 TabsTrigger 作为第一个 tab(Clock 图标,emerald 主题,待领取时显示数量红点) - 500→ - 新增 TabsContent value="idle" 渲染 IdleOperationsPanel - 501→ - 新增 idle 相关 goal 提示(高优先级):待领取 > 0 → "✦ 放置工程已完成 X 项,请前往「放置」标签领取奖励";全空+有解锁+时长>60s → "「放置」标签可派遣工程项目,离线自动产出" - 502→ - StatsPanel 新增 3 行:放置永久加成 / 完成放置工程 / 放置产出晶体 - 503→- 教程更新 `src/lib/game/tutorial.ts`:TUTORIAL_STEPS 在 decode 与 tech 之间插入新步骤 (id="idle", target="tab-idle", 标题"③ 放置工程 · 离线产出"),原 tech/expedition/prestige 编号顺延为 ④⑤⑥ - 504→- 写入 `agent-ctx/v0.14-reimpl-full-stack-developer.md` 工作记录 - 505→- 最终验证: - 506→ - lint 零错误零警告 ✅ - 507→ - dev 服务器 HTTP 200,编译 < 250ms ✅ - 508→ - agent-browser 烟雾测试全流程通过(默认 idle tab + 头部徽章 + 4 section + 派遣→倒计时→完成→领取→永久加成应用) ✅ - 509→ - 510→Stage Summary: - 511→- ✅ v0.14 放置系统 (Idle Operations) 全部完成 - 512→- ✅ 新增 4 文件(idle.ts 290行 + IdleOperationsPanel.tsx 490行 + IdleStatusBadge.tsx 100行 + IdleProjectBar.tsx 90行),修改 6 文件(types/engine/gameStore/useGameLoop/page/tutorial) - 513→- ✅ 6 个放置工程:勘探扫描 / 精炼校准 / 碎片整理 / 锚点部署 / 无人机扩编 / 谐振标定 - 514→- ✅ 3 个槽位并行 + 6 工程解锁逻辑 (OR) + 永久产能加成 (permBonus 累加跨周目) - 515→- ✅ 采矿无人机舰队派生 (min_1..min_5) + 休眠状态显示 - 516→- ✅ 头部 idle 徽章 + 默认放置 tab + 晶体球下方进度条 (5 秒内可见三要素) - 517→- ✅ persist migrate 函数 (version 1) 旧存档兼容 - 518→- ✅ 14 处 syncStats 调用全部传 idlePermanentBonus,recomputeStats 永久加成不丢失 - 519→- ✅ 飞升时清空活跃槽位但保留永久加成 / 历史 / 统计 - 520→- ✅ 严格 4 色全息 (emerald/rose/amber/fuchsia),零蓝/靛 - 521→- ✅ lint 零错误 + HTTP 200 + agent-browser 全流程烟雾测试通过 - 522→- ✅ 9 标签页 (新增"放置"为第一个) + 巡航按钮 + 教程新增 idle 步骤 - 523→ - 524→--- - 525→Task ID: v0.14-release - 526→Agent: 主控 Z.ai Code - 527→Task: v0.14 放置系统发布 — README/docs 更新 + 端到端验证 + git commit - 528→ - 529→Work Log: - 530→- 发现项目被环境重置回 v0.8.1 状态(v0.14 代码全部丢失,worklog 也被截断到 447 行) - 531→- 重新委派 full-stack-developer subagent (v0.14-reimpl) 在 v0.8.1 代码库上重新实现放置系统 - 532→- subagent 交付 10 个文件(4 新建 + 6 修改),lint 零错误,agent-browser smoke test 通过,git commit 17820b1 - 533→- 主控独立验证: - 534→ * git log 确认 commit 17820b1 存在 ✓ - 535→ * bun run lint 零错误 ✓ - 536→ * HTTP 200 ✓ - 537→ * agent-browser: v0.14 标签 ✓ + 放置中徽章 ✓ + 默认 idle tab ✓ + 4 段面板完整 ✓ - 538→ * 派遣 idle_scan → slot 0 显示 60s 倒计时 ✓ - 539→ * 快进完成 → 领取 → insights 79→87 (+8) + projectsCompleted 0→1 ✓ + 槽位清空 ✓ - 540→- 主控更新 README.md → v0.14:突出 12 大系统、3 层 idle 节奏、灵感来源、健康设计声明 - 541→- 主控创建 docs/11-放置系统-v0.14.md:完整设计文档 - 542→- 主控创建每 15 分钟触发的 webDevReview cron job - 543→ - 544→Stage Summary: - 545→ - 546→## v0.14 放置系统已重新上线并 git commit 保固 - 547→ - 548→### 核心交付 - 549→- **3 层 idle 节奏**:被动产能(无人机舰队)+ 放置工程(6 种 × 3 槽位)+ 离线收益(8h cap) - 550→- **始终可见的 idle 元素**:Header「放置中 +X/s」徽章 + 默认 tab = 放置 + 晶体球下方工程进度 pill - 551→- **永久产能加成**:通过工程奖励累积,集成进 recomputeStats,跨周目保留 - 552→- **存档安全**:persist version 1 + migrate 函数 + 4 字段 fallback,老存档零崩溃 - 553→ - 554→### Git 保固 - 555→- commit 17820b1: "v0.14: 放置系统 (Idle Operations) — 采矿无人机舰队 + 6放置工程 + 永久产能加成 + idle徽章" - 556→- 即使环境再次重置,git checkout 17820b1 即可恢复 - 557→ - 558→### 验证结果 - 559→- ✅ bun run lint 零错误零警告 - 560→- ✅ HTTP 200,dev.log 无运行时错误 - 561→- ✅ agent-browser 端到端 smoke test 全通过(派遣→倒计时→完成→领取→insights+8) - 562→- ✅ Sticky footer/header 验证通过 - 563→ - 564→### 未解决问题 / v0.15 优先项 - 565→1. 🟡 永久产能加成可能需要飞升次数 softcap(防 idle_resonance ×N 滚雪球) - 566→2. 🟡 idleProjectHistory / crystalsFromIdle 已在 store 累加但 UI 未展示 - 567→3. 🟡 缺「批量领取全部」按钮 + 工程完成 toast 通知 - 568→4. 🟡 遗迹图鉴多层文本(Dark Souls 式 loreFragments)—— v0.15 计划 - 569→5. 🟡 解码阈值释放叙事碎片 + 星潮三频段切换 —— v0.16 计划 - 570→6. 🟡 健康设计:每日软上限 + "星核建议休眠"提醒 —— v0.17 计划 - 571→ \ No newline at end of file diff --git a/tool-results/read_1782303131371_13d66843559b.txt b/tool-results/read_1782303131371_13d66843559b.txt deleted file mode 100755 index b5ebf16c8..000000000 --- a/tool-results/read_1782303131371_13d66843559b.txt +++ /dev/null @@ -1,571 +0,0 @@ - 1→# 回响星核 / Echo Nexus — 开发工作日志 - 2→ - 3→> 持续性任务的工作交接文档。每个阶段更新,避免进度丢失。 - 4→ - 5→--- - 6→ - 7→## 一、项目当前状态描述 / 判断 - 8→ - 9→### 概况 - 10→- **项目**:回响星核 / Echo Nexus — 深空考古放置策略游戏 - 11→- **一句话**:自治无人机采矿 → 解码记忆晶体(原创共振谜题)→ 拼凑碎片叙事 → 飞升多周目。差异化:解码谜题驱动叙事涌现,全息晶体 + 深空粒子美学。 - 12→- **当前版本**:**v0.7**(CrystalOrb Canvas 粒子系统 + 角色属性系统) - 13→- **在线游玩**:https://gitea-pages.atdunbg.xyz/Super_Z/echo-nexus/ - 14→- **仓库**:https://git.atdunbg.xyz/Super_Z/echo-nexus - 15→- **技术栈**:Next.js 16 (App Router) + TypeScript + Tailwind + shadcn/ui + Canvas 2D + Zustand + Web Audio API - 16→- **定时任务**:每 15 分钟一次 `webDevReview`(`fixed_rate` + `"900"` 秒,priority=10,job_id 228266)。正常完成不会被删除,无需自持续机制。 - 17→ - 18→### 状态判断 - 19→- dev 服务器运行正常(HTTP 200,编译 < 250ms) - 20→- v0.7 两大新功能已完成:CrystalOrb Canvas 粒子系统升级(VLM 8/10)+ 角色属性系统(VLM 7/10) - 21→- 系统总体稳定,无阻塞性 bug,可继续推进新功能 - 22→ - 23→### 已完成版本里程碑(精简) - 24→| 版本 | 核心内容 | - 25→|------|---------| - 26→| v0.1 | MVP:放置采矿 + 解码共振谜题 + 技术树 + 记忆图谱 + 飞升 + 统计 | - 27→| v0.1.1 | 解码可解性修复(路径构造法生成器,100% 可解率)| - 28→| v0.2 | 遗迹探险肉鸽系统(能量/探险力/6 节点/程序化路径/BOSS)| - 29→| v0.2.1 | 程序化音频(15 种音效)+ 14 项成就 + 视觉打磨 | - 30→| v0.3 | 星潮事件系统(6 种星潮 + 全屏叠层 + 程序化音效)| - 31→| v0.3.1 | 星图天文台元进程(6 类 × 3 = 18 天赋 + 飞升后 3 选 1 draft + Canvas 星图)| - 32→| v0.4 | 回响编年史(5 纪元循环命名 + 模板化叙事 + 时间轴 UI + 6 项新成就 + 飞升 BUG 修复)| - 33→| v0.5 | 深空信标(每日挑战 + 本地排行榜 Top20 + 7 标签页 + 编年史历史 BUG 修复)| - 34→| v0.5.1 | 静态部署 Gitea Pages + 新手教程系统(7 步聚光灯引导)+ UI 间距优化 | - 35→| v0.5.2 | 离线收益报告弹窗 + 标签页 UI 重设计 + persist rehydrate 竞态修复 | - 36→| v0.6 | 深空巡航 Canvas 2D 实时玩法 + UI 偏移重叠修复 | - 37→| **v0.7** | **CrystalOrb Canvas 粒子系统升级 + 角色属性系统(四维)** | - 38→ - 39→### 核心系统清单(8 大系统) - 40→1. **放置采矿** — 中央晶体球 + 主动脉冲 + 连击加成(`CrystalOrb.tsx`) - 41→2. **谐振解码** — 原创路径谜题,路径构造法 100% 可解(`DecodeArray.tsx` + `engine.ts`) - 42→3. **技术树** — 三层科技解锁,提升产能/解码/探险(`TechTree.tsx`) - 43→4. **遗迹探险** — 肉鸽系统,6 种节点 + 程序化路径 + BOSS(`ExpeditionPanel.tsx` + `expedition.ts`) - 44→5. **星潮事件** — 6 种动态事件,为放置循环注入变化(`StarTide.tsx` + `starTide.ts`) - 45→6. **星图天文台** — 飞升后 3 选 1 天赋 draft,跨周目永久生效,18 个天赋(`ConstellationPanel.tsx` + `constellation.ts`) - 46→7. **回响编年史** — 跨周目叙事时间轴,5 纪元循环命名(`ChronicleDialog.tsx` + `chronicle.ts`) - 47→8. **深空信标** — 每日挑战 + 本地排行榜 Top20(`BeaconPanel.tsx` + `beacon.ts`) - 48→9. **深空巡航** — Canvas 2D 实时玩法,飞船操控/陨石躲避/星门通关(`CruiseMode.tsx` + `cruise.ts`)【v0.6】 - 49→10. **角色属性系统** — 四维属性(探索力/智慧/勇气/灵感),飞升获得属性点,影响所有系统(`AttributesPanel.tsx` + `attributes.ts`)【v0.7 新增】 - 50→ - 51→--- - 52→ - 53→## 二、当前目标 / 已完成的修改 / 验证结果 - 54→ - 55→### v0.7 CrystalOrb Canvas 粒子系统升级(已完成) - 56→**动机**:P0 优先级 — 将中央晶体球从 CSS 动画升级为 Canvas 粒子视觉盛宴,提升视觉震撼。 - 57→ - 58→**重写文件**:`src/components/game/CrystalOrb.tsx` - 59→- 从纯 CSS 动画升级为 Canvas 2D 粒子系统 - 60→- **多层粒子**: - 61→ - 环绕能量粒子(3层14/10/8个,emerald/fuchsia/rose三色,不同速度方向环绕晶核,带拖尾) - 62→ - 环境星尘(40个,缓慢漂移 + 闪烁) - 63→ - 点击爆发粒子(10-30个径向发散,颜色随连击等级变色) - 64→ - 冲击波环(点击触发1-3层错峰扩散,连击≥3/5解锁更多层) - 65→- **晶核升级**:径向渐变辉光 + 3层六边形纹理旋转 + 高光 + 鼠标视差偏移 - 66→- **呼吸动画**:晶核 + 外层辉光随时间呼吸(sin波) - 67→- **进度环**:SVG渐变环(emerald→fuchsia→rose)保留 - 68→- **交互保留**:点击脉冲、连击Toast、浮动数字、音效、`data-tut="crystal-orb"` 锚点 - 69→- **性能**:DPR cap 2,ResizeObserver 自适应,requestAnimationFrame 60fps - 70→ - 71→**QA 验证**(agent-browser + VLM): - 72→- VLM 视觉评分 **8/10**:晶体球清晰可见,多层辉光,环绕粒子动态自然,无渲染问题 - 73→- 点击交互:冲击波环 ✅ + 爆发粒子 ✅,评分 7/10 - 74→- lint 零错误;HTTP 200 - 75→ - 76→### v0.7 角色属性系统(已完成) - 77→**动机**:P1 优先级 — 增加RPG深度,四维属性影响所有游戏系统。 - 78→ - 79→**新增文件**: - 80→- `src/lib/game/attributes.ts`(~330行逻辑层):CharacterAttributes/AttributeProgress 类型、ATTRIBUTE_CONFIG 元信息、getAttributeBonus 两区加成公式(0-50线性+0.5%/点,50-100递减+0.2%/点)、getAllBonuses 12项修饰器、levelUpCheck 自动升级、computePrestigeAttrPoints、migrateAttributes 旧存档兼容 - 81→- `src/components/game/AttributesPanel.tsx`(~330行 UI):四维卡片网格(小屏2×2/大屏1×4)、图标+中英文名+Lv.badge+数值/100+加成%+经验条+影响列表+分配按钮、顶部待分配点数badge闪烁、底部总览+12项修饰器明细 - 82→ - 83→**修改文件**: - 84→- `types.ts`:GameState 新增 attributes/attributeProgress/pendingAttrPoints - 85→- `config.ts`:INITIAL_STATE 补全默认值 - 86→- `engine.ts`:recomputeStats 聚合属性加成;performPrestige 发放属性点(ascensions×2+1) - 87→- `gameStore.ts`:新增 allocateAttribute/gainAttributeExp actions;pulse/clickNode/autoDecodeTick/resolveCurrentNode/grantCruiseReward/tickTide 全部接入属性经验;init() 调用 migrateAttributes - 88→- `achievements.ts`:新增 ach_attr_total_50 + ach_attr_max_100 两项成就 - 89→- `page.tsx`:grid-cols-7→8,新增「角色」标签页(User图标),红点提示,StatsPanel新增5行属性行,版本号 v0.6→v0.7 - 90→ - 91→**四维属性设计**: - 92→- 探索力(emerald):探险力+X%/巡航飞船速度+X% - 93→- 智慧(fuchsia):解码步数+X/洞见+X%/自动解码周期-X% - 94→- 勇气(amber):探险生命+X/BOSS胜率+X%/巡航护盾+X - 95→- 灵感(rose):接触率+X/星潮概率+X/脉冲连击+X% - 96→ - 97→**QA 验证**(agent-browser + VLM): - 98→- VLM 视觉评分 **7/10**(面板)/ **8/10**(待分配状态):四维卡片可见、数值/等级/加成/经验条齐全、四色全息 - 99→- 功能验证:setState pendingAttrPoints=5 → 显示5点待分配 → 点击分配按钮 → exploration 0→1, pending 5→4 ✅ - 100→- lint 零错误;HTTP 200 - 101→ - 102→--- - 103→ - 104→### v0.6 深空巡航 Canvas 2D 实时玩法(历史记录) - 105→**动机**:回应用户反馈「玩法太单调(全是点点点)」+「视觉震撼不足」——首次引入实时操作玩法。 - 106→ - 107→**新增文件**: - 108→- `src/lib/game/cruise.ts`(~520 行逻辑层) - 109→ - 类型系统:CruiseEntity 联合类型(ship/asteroid/storm/crystal/insight/beacon/stargate/particle 共 8 种) - 110→ - mulberry32 + FNV-1a 种子化 RNG(`cruiseSeed(level)`) - 111→ - `generateLevel`:程序化生成陨石带(8+lvl×2)、虚空风暴(1+lvl/2)、晶体碎片(5+lvl)、洞见光球(2+lvl/3)、信标(1+lvl/4)、星门 - 112→ - `updateCruise`:物理推进 + 碰撞检测 + 粒子系统 + 震动 + 600ms 无敌帧 - 113→ - `computeRewards`:crystals=碎片×(8+lvl×2)、insights=洞见×(2+lvl×0.5)、contact=信标×(1.5+lvl×0.3);通关×1.5、失败×0.5 - 114→ - 独立 localStorage `echo-nexus-cruise-v1`:最高分/累计通关/最高关卡/累计奖励/最近 20 局 - 115→ - 116→- `src/components/game/CruiseMode.tsx`(~830 行渲染层) - 117→ - 全屏 fixed inset-0 z-50 Canvas,DPR cap 2,resize 监听 - 118→ - 3 层视差星空:远(100星)/中(60星)/近(30星),独立漂移 + 飞船速度视差 + 闪烁 - 119→ - 8 种实体全部 ctx.shadowBlur 辉光绘制 - 120→ - 粒子系统:尾焰/收集/碰撞/烟花,上限 200 - 121→ - 屏幕震动 280ms 衰减;低护盾(<30%)红色边框脉冲 - 122→ - HUD(HTML 叠层,glass+backdrop-blur,80ms 节流):护盾/能量/分数/用时/收集计数 - 123→ - 控制:桌面 WASD/方向键 8 方向 + 移动端虚拟摇杆 + P 暂停 + Esc 退出 + 失焦自动暂停 - 124→ - 阶段:ready → playing → won/lost(结算明细 + 奖励 + 下一关/重试/返回) - 125→ - 126→**修改文件**: - 127→- `src/app/page.tsx`:header 新增 amber 主题「巡航」按钮(Navigation 图标)+ cruiseOpen state + 条件渲染 CruiseMode - 128→- `src/store/gameStore.ts`:新增 `grantCruiseReward({crystals?, insights?, contact?})` action(crystals 受 crystalCap 限制,contact 受 100 上限) - 129→- 版本号 v0.5.2 → v0.6 - 130→ - 131→**UI 偏移/重叠 BUG 修复**(3 处): - 132→- `DecodeArray.tsx`:移除 className 冲突的 `-translate-x-1/2 -translate-y-1/2`,统一用 inline transform 定位+缩放 - 133→- `AchievementsPanel.tsx`:移除内层 `max-h-[300px]`,改 `flex-1 min-h-0` 自适应父容器 - 134→- `page.tsx`:调整三个 section 的 min-h,避免小屏单列时总高度超过手机视口 - 135→ - 136→**色彩规范**:严格遵循 emerald(#34d399) / rose(#fb7185) / amber(#fbbf24) / fuchsia(#e879f9) 四色全息色谱,零蓝色/靛色 - 137→ - 138→**QA 验证**(agent-browser + VLM): - 139→- lint 零错误;dev 服务器 HTTP 200;编译 < 250ms - 140→- VLM 确认主界面:巡航按钮存在、晶体球居中、解码区正常、无重叠偏移 - 141→- VLM 确认巡航玩法:飞船可见(绿色三角形)、HUD 完整、陨石+星空背景、粒子特效(黄色尾焰+紫色辉光) - 142→- WASD 控制飞船移动,按 W 直冲星门 → 通关,奖励 +30 晶体(2×10×1.5=30 计算正确) - 143→- localStorage 正确记录 highScore/totalRuns/totalWins/bestLevel - 144→- 奖励同步 gameStore(grantCruiseReward,满仓时 cap 逻辑正确) - 145→- 退出返回主界面,7 标签页 + 晶体球 + 解码面板均完好 - 146→ - 147→--- - 148→ - 149→## 三、未解决问题或风险 / 下一阶段优先事项 - 150→ - 151→### 已知问题 / 风险 - 152→1. **dev 服务器 Turbopack 缓存偶发损坏**:`Unable to open static sorted file` + 进程被杀,需 `rm -rf .next` 重启。静态导出构建稳定可靠。 - 153→2. **定时任务调度类型**:必须用 `fixed_rate` + `"900"` 秒(15分钟),不要用 cron 表达式。`webDevReview` 类型任务正常完成不会被删除,只有抛出未捕获异常才会被标记失败。 - 154→3. **v0.3 星潮为单机版**:原计划 socket 全局事件,后续可扩展为多人同步。 - 155→4. **v0.5 信标排行榜为本地版**(纯 localStorage):升级云排行榜需后端 API。 - 156→5. **v0.5 每日挑战仅 1 个/天**:可加入"周挑战"或"信标链"(连续完成 N 天奖励)。 - 157→6. **探险 BOSS 战胜率较低**:基础探索力 10 vs BOSS 难度 5-7,胜率约 25%,需玩家投资探险技术。 - 158→7. **探险能量恢复较慢**:45s/点,可加技术提升恢复速度。 - 159→8. **编年史上限 50 条**:超过自动丢弃最早的;v0.4 之前飞升无回填(空状态有提示)。 - 160→9. **5 纪元叙事为模板化生成**:v0.5+ 将加入手写剧情节点。 - 161→10. ~~**CrystalOrb 仍为 CSS 动画**~~ → ✅ **v0.7 已完成 Canvas 粒子系统升级**。 - 162→ - 163→### 下一阶段优先级(v0.7 后修正版) - 164→> 工单修正:原 P0 CrystalOrb Canvas + P1 角色属性两项均已在 v0.7 完成,现重新排序。 - 165→ - 166→1. **🔴 P0 — 巡航玩法大增强(v0.8 主线)**:BOSS 战关卡(每 5 关一个 BOSS,三阶段攻击模式 + 飞船射击系统)/ 道具掉落(5 种 powerup:护盾/能量/急速/超载/磁吸)/ 事件选择节点(每关通关后 3 选 1 强化牌,10 种强化)。这是当前最高优先级,能显著提升巡航玩法的深度与可玩性。 - 167→2. **🟠 P1 — 探险 BOSS 战胜率平衡 + 探险能量恢复**:修复已知问题 6/7(探险 BOSS 胜率 25% 偏低,能量 45s/点 偏慢),提升探险体验。 - 168→3. **🟠 P1 — 信标系统扩展**:周挑战 / 信标链(连续完成 N 天奖励),修复已知问题 5。 - 169→4. **🟡 P2 — UI 样式细节打磨**:标签页图标/文字比例、视觉层次强化、移动端响应式进一步优化、CrystalOrb 周边留白优化(VLM 指出主界面左侧有空白)。 - 170→5. **🟡 P2 — 云存档 + 云排行榜**:v0.5+ 后端 API(需评估是否引入 mini-service)。 - 171→6. **🟢 P3 — 全 5 纪元手写叙事节点**:替换模板化 lore。 - 172→7. **🟢 P3 — socket 多人同步星潮**:全局事件共享。 - 173→ - 174→### 定时任务(fixed_rate · 900s) - 175→- **当前 job_id**: **228357**(2026-06-24 13:25 重建) - 176→- **配置**: `fixed_rate` + `"900"`(15分钟间隔),`priority=10`(高优先级),`payload.kind=webDevReview`,`tz=Asia/Shanghai` - 177→- **任务名**: Echo Nexus - 任务审查与持续迭代 - 178→- **任务描述强制要求**:每次触发 MUST 先完整阅读 worklog.md 工单,以工单为准,不凭记忆工作 - 179→- **正常完成不会被删除**,只有未捕获异常会标记失败。若任务列表为空,需用 cron create 重建(schedule.kind=fixed_rate, expr="900", priority=10, payload.kind=webDevReview)。 - 180→- **job_id 历史**: 227909(失效) → 228130(失效) → 228172(失效) → 228186(失效) → 228218(删除重建) → 228236(cron版,删除重建) → 228266(fixed_rate,失效) → 228329(失效) → **228357(当前,重建)** - 181→ - 182→--- - 183→ - 184→## 定时任务描述模板(webDevReview · fixed_rate) - 185→ - 186→> 重建参数:schedule.kind=`fixed_rate`, schedule.expr=`"900"`, params.priority=`10`, payload.kind=`webDevReview` - 187→ - 188→``` - 189→请先判断当前项目状态,使用 agent-browser 进行必要测试和 QA,然后自主选择本轮最合适的工作重点并继续开发。 - 190→ - 191→要求: - 192→1. 开始任务时查看 /home/z/my-project/worklog.md 了解实际进展,拆解详细todo - 193→2. 使用 agent-browser 进行测试和QA,有bug优先修复 - 194→3. 如果稳定,自主提出新需求继续推进 - 195→4. 样式要越做细节越多!! - 196→5. 功能要越做越多!! - 197→6. 工作告一段落更新 /home/z/my-project/worklog.md - 198→7. 项目结束后一定要把工作记录写在 worklog.md 里 - 199→ - 200→交接文档建议包含三部分: - 201→- 项目当前状态描述/判断 - 202→- 当前目标/已完成的修改/验证结果 - 203→- 未解决问题或风险,建议下一阶段优先事项 - 204→``` - 205→ - 206→--- - 207→ - 208→## 历史详细版本记录(按需查阅 docs/repo/docs/) - 209→ - 210→- `04-解码系统修复-v0.1.1.md` - 211→- `05-遗迹探险系统-v0.2.md` - 212→- `06-音频与成就系统-v0.2.1.md` - 213→- `07-星潮事件系统-v0.3.md` - 214→- `08-星图天文台系统-v0.3.1.md` - 215→- `09-回响编年史系统-v0.4.md` - 216→- `10-深空信标系统-v0.5.md` - 217→ - 218→--- - 219→ - 220→Task ID: 7 - 221→Agent: full-stack-developer - 222→Task: 角色属性系统(探索力/智慧/勇气/灵感) - 223→ - 224→Work Log: - 225→- 阅读 worklog.md 了解 v0.6 项目背景,确认 8 大系统架构与四色全息色彩规范 - 226→- 新建 `src/lib/game/attributes.ts` 逻辑层(~330 行): - 227→ - 定义 CharacterAttributes/AttributeProgress/AttributeKey 类型 + ATTRIBUTE_CONFIG 元信息 - 228→ - getAttributeBonus 线性+递减两区加成公式(0-50 每 10 点 +5%,50-100 每点 +2%) - 229→ - getAllBonuses 返回 12 个修饰器(探险力/巡航速度/解码步数/洞见/自动解码周期/探险生命/BOSS 胜率/巡航护盾/接触率/星潮触发/脉冲连击/产能) - 230→ - levelUpCheck 自动跨多级 + expRequiredForLevel + computePrestigeAttrPoints + migrateAttributes 兼容 - 231→- 扩展 `src/lib/game/types.ts` GameState:新增 attributes/attributeProgress/pendingAttrPoints - 232→- 扩展 `src/lib/game/config.ts` INITIAL_STATE:补全默认值(0 数值 + 空 progress) - 233→- 扩展 `src/lib/game/engine.ts`: - 234→ - recomputeStats 末尾聚合 am.crystalsPerSecMult/insightMultAdd/contactRateMult/decodeStepsBonus - 235→ - performPrestige 保留 attributes 数值,清空 attributeProgress,发放 ascensions × 2 + 1 点属性点 - 236→ - createInitialState 每次返回全新对象避免引用共享 - 237→- 扩展 `src/store/gameStore.ts`(核心改造,10+ 处 syncStats 调用全部补 attributes 参数): - 238→ - pulse:连击 ≥3 给灵感经验 + 脉冲威力乘灵感连击加成 - 239→ - clickNode 完成:给智慧经验(tier × 2) - 240→ - autoDecodeTick:智慧经验 +1 + 自动解码周期受 am.autoDecodeIntervalMult 影响 - 241→ - startExpedition:探险力乘 am.expeditionPowerMult,生命加 am.expeditionHpBonus - 242→ - resolveCurrentNode:BOSS 节点包装 RNG 提升 +am.bossWinRateBonus 胜率;战斗胜利给勇气+探索力经验 - 243→ - grantCruiseReward:按总奖励量给探索力+勇气经验 - 244→ - tickTide:灵感 am.tideTriggerBonus 缩短星潮间隙(上限 30%) - 245→ - init() 调用 migrateAttributes 补全旧存档 - 246→ - 新增 allocateAttribute / gainAttributeExp 两个 action - 247→- 新建 `src/components/game/AttributesPanel.tsx` UI 面板(~330 行): - 248→ - 四维卡片网格(小屏 2×2,大屏 1×4) - 249→ - 每张卡片:图标 + 中英文名 + Lv.badge + 数值/100 + 加成% + 经验条 + 影响列表 + 「+分配」按钮 - 250→ - 顶部待分配点数 badge(pendingAttrPoints > 0 时 echo-pending-pulse 闪烁动画) - 251→ - 底部总等级/总加成概览 + 12 个修饰器明细 - 252→ - 4 色辉光边框(emerald/fuchsia/amber/rose),顶角光晕装饰 - 253→- 接入 `src/app/page.tsx`: - 254→ - grid-cols-7 → grid-cols-8,新增第 8 个「角色」TabsTrigger(User 图标,渐变主题) - 255→ - pendingAttrPoints > 0 时显示 rose 红点提示 - 256→ - 渲染 AttributesPanel - 257→ - StatsPanel 新增 5 行属性行(探索力/智慧/勇气/灵感/待分配点) - 258→ - 版本号 v0.6 → v0.7 - 259→- 新增 `src/lib/game/achievements.ts` 2 项成就: - 260→ - ach_attr_total_50(四维觉醒):总属性 ≥ 50 → 产能+6%/洞见+6% - 261→ - ach_attr_max_100(维度精通):任一属性 100 → 产能+12%/洞见+10% - 262→- 写入 `agent-ctx/7-full-stack-developer.md` 工作记录 - 263→- 最终验证:lint 零错误 + dev 服务器 HTTP 200 + 编译 < 250ms - 264→ - 265→Stage Summary: - 266→- ✅ 角色属性系统 v0.7 P1 全部完成 - 267→- ✅ 新增 2 文件(attributes.ts + AttributesPanel.tsx),修改 6 文件(types/config/engine/gameStore/achievements/page) - 268→- ✅ 四维属性完整接入游戏各系统(探险/解码/脉冲/巡航/星潮) - 269→- ✅ 飞升属性点公式 = ascensions × 2 + 1;经验累积自动升级(每级 10×level) - 270→- ✅ 旧存档兼容(migrateAttributes 补全字段,同步 level 与 attributes) - 271→- ✅ lint 零错误 + HTTP 200 + 编译 < 250ms - 272→- ✅ 严格四色全息(emerald/fuchsia/amber/rose),零蓝色/靛色 - 273→- ✅ 8 标签页 + 巡航按钮全部保留,data-tut 锚点未破坏 - 274→- ✅ 待分配点数时标签页 rose 红点提示 + 顶部 badge 闪烁 - 275→ - 276→--- - 277→Task ID: 8-deploy - 278→Agent: main - 279→Task: 重建定时任务 + 更新 README + 部署 gh-pages(v0.7) - 280→ - 281→Work Log: - 282→- 检查项目状态:CrystalOrb.tsx 已是 v0.7 Canvas 粒子系统(659 行),无活动 cron job - 283→- 重建定时任务:cron create fixed_rate="900" priority=10 webDevReview → job_id 228329(tz=Asia/Shanghai) - 284→- 重写 README.md: - 285→ - 顶部显著位置添加「在线游玩」链接(https://gitea-pages.atdunbg.xyz/Super_Z/echo-nexus/) - 286→ - 版本号 v0.1 → v0.7 - 287→ - 新增 10 大系统表格(含 v0.6 巡航 + v0.7 角色属性) - 288→ - 完整开发路线表(v0.1-v1.0,标记 ✅/🔜 状态) - 289→ - 新增本地开发命令 + Gitea Pages 部署指南 - 290→ - 文档索引扩展至 10 个版本文档 - 291→- 静态导出:BUILD_EXPORT=true bun run build:static → out/ 1.6M(HTTP 200 编译 4.3s) - 292→- 推送 gh-pages 分支:force push origin gh-pages(commit 8727fed "deploy: v0.7") - 293→- 提交 main:README 更新(commit 43dfc2d "docs: README 添加在线游玩链接 + v0.7 状态更新") - 294→- 验证 Gitea Pages:https://gitea-pages.atdunbg.xyz/Super_Z/echo-nexus/ → HTTP 200 ✅ - 295→- QA 验证:agent-browser 打开 http://localhost:3000/ → VLM 评分 8/10(主界面 polished,minor 空白) - 296→ - 297→Stage Summary: - 298→- ✅ 定时任务重建成功(job_id 228329,每 15 分钟自动审查迭代) - 299→- ✅ README 重写完成,网站链接显著展示,v0.7 状态完整 - 300→- ✅ gh-pages 部署成功,Gitea Pages HTTP 200 可访问 - 301→- ✅ main 分支已同步推送(README + worklog 更新) - 302→- ✅ dev 服务器健康(HTTP 200,编译 < 250ms) - 303→- ✅ CrystalOrb v0.7 Canvas 粒子系统已确认完整(659 行) - 304→- 下一步:v0.8 巡航玩法增强(BOSS 战 / 道具掉落 / 事件选择节点) - 305→ - 306→--- - 307→Task ID: 8 - 308→Agent: main - 309→Task: v0.8 巡航玩法大增强(BOSS 战 + 道具掉落 + 事件选择节点)+ 工单修正 + 审查流程重建 - 310→ - 311→Work Log: - 312→- 重读 worklog 工单,发现 v0.7 两项(CrystalOrb Canvas + 角色属性)已完成但工单"下一阶段优先级"未更新 - 313→- 修正 worklog 工单:标注 P0/P1 v0.7 已完成,重新排序下一阶段优先级(P0=巡航增强, P1=探险平衡/信标扩展, P2=UI打磨/云存档, P3=叙事/多人) - 314→- 重建审查流程 cron job:fixed_rate=900s, priority=10, webDevReview, tz=Asia/Shanghai, job_id=228357 - 315→ - 任务描述强制要求:每次触发 MUST 先完整阅读 worklog.md 工单,以工单为准,不凭记忆工作 - 316→- 发现之前被中断的 v0.8 subagent 已写入 2037 行代码(cruise.ts +1487 / CruiseMode.tsx +858 / page.tsx +2) - 317→- 修复 2 个 lint 错误(react-hooks/set-state-in-effect): - 318→ - isTouch: useState(false) + useEffect(setIsTouch) → useState initializer - 319→ - stats: useState(null) + useEffect(setStats(loadCruiseStats)) → useState initializer - 320→- QA 验证(agent-browser + VLM): - 321→ - 主界面 VLM 7/10:v0.8 版本号 ✅ + 巡航按钮 ✅ - 322→ - 巡航 READY 阶段 VLM 8/10:BOSS 战说明 ✅(每5关BOSS战/击败BOSS)+ 射击控制说明 ✅(空格/右下角射击按钮) - 323→ - 巡航第1关游戏 VLM 8/10:飞船(绿色三角)✅ + HUD(护盾/能量/分数/计时)✅ + 陨石/收集物/能量场 ✅ + 无渲染错误 - 324→- 提交 v0.8(commit 494bc5f)+ 推送 main - 325→- 静态导出 BUILD_EXPORT=true → out/ 1.6M(编译 4.1s) - 326→- 部署 gh-pages(force push, commit b865922)→ Gitea Pages HTTP 200 ✅ - 327→ - 328→Stage Summary: - 329→- ✅ 工单修正完成:v0.7 已完成项标注清楚,下一阶段优先级重新排序 - 330→- ✅ 审查流程重建:cron job_id 228357,每15分钟触发,强制读工单 - 331→- ✅ v0.8 巡航玩法大增强全部完成并部署: - 332→ - BOSS 战系统(每5关,三阶段攻击,HP条,击败奖励×3) - 333→ - 飞船射击系统(空格/射击按钮,能量消耗,子弹尾焰) - 334→ - 道具掉落(5种powerup:护盾/能量/急速/超载/磁吸) - 335→ - 事件选择节点(每关通关后3选1,10种强化牌) - 336→ - lint 零错误 + dev HTTP 200 + gh-pages HTTP 200 - 337→- ✅ VLM 评分:主界面 7/10 + 巡航READY 8/10 + 巡航游戏 8/10 - 338→- 在线游玩 v0.8:https://gitea-pages.atdunbg.xyz/Super_Z/echo-nexus/ - 339→- 下一阶段工单:P1 探险BOSS胜率平衡 + 探险能量恢复 / 信标系统扩展(周挑战/信标链) - 340→ - 341→--- - 342→Task ID: 9-b - 343→Agent: full-stack-developer - 344→Task: 信标系统扩展(周挑战 + 信标链连续奖励) - 345→ - 346→Work Log: - 347→- 阅读现有 `src/lib/game/beacon.ts`(v0.5 每日挑战 + 本地排行榜,358 行)+ `src/components/game/BeaconPanel.tsx` + `src/store/gameStore.ts` 的 `trackBeacon` 函数(约第 173 行)+ worklog v0.8 项目状态,确认四色全息规范与现有架构。 - 348→- **扩展 `src/lib/game/beacon.ts`(358 → 873 行,新增 ~515 行)**: - 349→ - **周挑战(WEEKLY CHALLENGE)**: - 350→ - `BeaconWeeklyChallenge` 接口(weekKey YYYY-Www / type / difficulty 强制 anomaly|singular / goal / rewardInsight / rewardContact / seed / title / desc) - 351→ - `BeaconWeeklyProgress` 接口(weekKey / progress / startedAt / completedAt / claimed / durationSec) - 352→ - `getWeekKey(now)`:ISO 8601 周键(周一为起点,含首个周四的周为第一周) - 353→ - `weekKeyToSeed`:FNV-1a 哈希 - 354→ - `generateWeeklyChallenge(now)`:基于 weekKey 种子确定性生成,难度加权 anomaly 60% / singular 40%,goal 为日基准 ×3-5 倍(decode 18-75 / expedition 3-15 / pulse 60-245 / boss 3-12 / insight 120-595) - 355→ - `loadWeeklyProgress` / `saveWeeklyProgress` / `addWeeklyProgress` / `claimWeeklyReward`(推送排行榜,entry 加 `isWeekly: true`) - 356→ - `msUntilNextWeek(now)`:距离下周一 UTC 0 点的毫秒数 - 357→ - `BEACON_WEEKLY_KEY = "echo-nexus-beacon-weekly-v1"` - 358→ - **信标链(BEACON CHAIN)**: - 359→ - `BeaconChainState` 接口(lastCompletedDateKey / currentStreak / longestStreak / totalCompletions / graceUsed / milestonesClaimed) - 360→ - `BEACON_CHAIN_KEY = "echo-nexus-beacon-chain-v1"` - 361→ - `BEACON_CHAIN_MILESTONES = [3, 7, 14, 30]` 常量 - 362→ - `BEACON_CHAIN_REWARDS`:4 个里程碑(三日谐振 +50/+5 / 七日回响 +120/+12 / 半月星潮 +280/+28 / 满月飞升 +680/+68) - 363→ - `loadChainState` / `saveChainState`(每次返回新对象避免引用共享) - 364→ - `recordChainCompletion(dateKey)` 核心逻辑:同日重复忽略;次日 streak++;隔一天 miss 且 graceUsed<1 时续命 streak++ graceUsed++;其他断链 streak=1 graceUsed=0;返回 `{ state, newMilestones }` - 365→ - `claimChainMilestone(milestone)` / `getNextMilestone(streak)` / `getChainProgress(streak)`(返回 current/next/prev/progressPct 用于 UI) - 366→ - `dateKeyToTimestamp` / `dateKeyDiffDays` 工具函数 - 367→ - `BeaconScoreEntry` 新增可选 `isWeekly?: boolean` 字段 - 368→- **扩展 `src/store/gameStore.ts`**: - 369→ - import 扩展:generateWeeklyChallenge / loadWeeklyProgress / loadChainState / addWeeklyProgress / recordChainCompletion / claimWeeklyReward / claimChainMilestone / getTodayKey + 类型 - 370→ - `trackBeacon(type, delta)` 函数签名从 `boolean` 改为 `{ dailyJustCompleted, weeklyJustCompleted, newChainMilestones }`: - 371→ - 日挑战类型匹配且未完成 → addBeaconProgress;若刚完成 → recordChainCompletion(getTodayKey()) 收集 newMilestones - 372→ - 周挑战类型匹配且未完成 → addWeeklyProgress - 373→ - 所有调用点(pulse/clickNode/autoDecodeTick/resolveCurrentNode 等 7 处)原本就忽略返回值,向后兼容 - 374→ - 新增 action `claimWeeklyBeacon`:调用 claimWeeklyReward → 发放 insights + contact 到 state,返回 `{ rewardInsight, rewardContact, score }` - 375→ - 新增 action `claimChainReward(milestone)`:前置校验 currentStreak 与 milestonesClaimed,调用 claimChainMilestone → 发放奖励,返回 `{ rewardInsight, rewardContact, label, ok }` - 376→ - GameActions 接口同步新增两个 action 签名 - 377→- **重写 `src/components/game/BeaconPanel.tsx`(301 → 638 行)**: - 378→ - 顶部头部 + 每日挑战卡片(保留 v0.5 完整功能 + 按钮主题色随难度色淡显) - 379→ - **新增周挑战区块**(fuchsia 主题):标题"周挑战 · WEEKLY" + weekKey + 倒计时 + 挑战卡片(类型/难度标签/标题/描述/进度条/奖励/领取按钮 emerald 主题)+ weekly-glow 动画 - 380→ - **新增信标链区块**(amber→rose 渐变):标题"信标链 · CHAIN" + 大字号 streak(chain-streak-text 渐变流动动画)+ 今日完成状态徽章 + 4 个里程碑节点(w-12 h-12 圆形,claimed=emerald/✓,reachable=rose 脉冲动画+领取按钮,inProgress=amber 半亮,未到达=muted 灰)+ 节点间连线(背景灰 + 已达成部分 amber→rose 渐变 + 辉光,基于 prev→next 插值定位)+ 进度条 + 底部统计(最长/累计/续命状态) - 381→ - 桌面端 lg:grid-cols-2 让周挑战+信标链并排,移动端单列 - 382→ - 排行榜条目区分日/周:周挑战 entry 显示 "WEEK" 徽章 + fuchsia 高亮背景 - 383→ - 空状态提示扩展为"完成今日或本周信标即可登榜" - 384→ - 4 个新动画:weekly-glow / chain-milestone-pulse / chain-streak-flux(背景渐变流动) - 385→- **QA 验证**: - 386→ - `bun run lint` 零错误(每次修改后均验证) - 387→ - dev.log 全程无错误,所有编译 < 300ms - 388→ - **信标链逻辑测试**(bun 直接运行 TS,5 个场景全 PASS): - 389→ 1. 昨日 streak=1 → 今日完成 → streak=2 ✅ - 390→ 2. 同日重复完成 → 忽略 ✅ - 391→ 3. 明日完成 → streak=3,无需 grace ✅ - 392→ 4. 隔一天 miss(lastCompleted + 2 天)→ streak=4,graceUsed=1 ✅ - 393→ 5. 再次 miss(grace 已用)→ 断链 streak=1,graceUsed=0 ✅ - 394→ - **UI 集成测试**(agent-browser): - 395→ - localStorage 模拟设置 chain state(lastCompletedDateKey=yesterday, currentStreak=1)→ reload → UI 正确显示 streak=1 - 396→ - 设置 streak=3 + milestonesClaimed=[] → milestone 3 节点显示 rose 脉冲动画 + "领取"按钮 - 397→ - 点击"领取"→ milestonesClaimed=[3] + Toast "✦ 三日谐振 已领取 +50 洞见 · +5.0 接触" + 按钮消失 ✅ - 398→ - **VLM 视觉评分**(目标 ≥7/10): - 399→ - 首屏截图:8/10(四色一致、布局合理、信标链清晰) - 400→ - 里程碑可领取状态:8/10(amber→rose 渐变醒目、里程碑层次分明、fuchsia 主题清晰) - 401→ - 重置后干净状态:8/10(WEEKLY 字体对比度可优化,但整体可读性强) - 402→ - **不破坏现有功能**:日挑战进度/领奖/排行榜流程全部保留并工作正常 - 403→ - 404→Stage Summary: - 405→- ✅ beacon.ts 扩展完成(358 → 873 行):周挑战 + 信标链全部函数与类型 - 406→- ✅ gameStore.ts 集成完成:trackBeacon 升级为对象返回值 + claimWeeklyBeacon + claimChainReward 两个新 action - 407→- ✅ BeaconPanel.tsx UI 完整重写(301 → 638 行):周挑战 fuchsia 卡片 + 信标链 amber→rose 渐变区块 - 408→- ✅ 严格四色全息:周挑战 fuchsia、信标链 amber→rose、领取按钮 emerald、无蓝/紫色违规 - 409→- ✅ localStorage 新增 2 个独立 key(weekly + chain),不污染旧 key - 410→- ✅ 确定性:周挑战同周同种子同结果(weekKey FNV-1a 种子) - 411→- ✅ 信标链 grace 续命机制工作正常(5 个场景 bun 测试全 PASS) - 412→- ✅ lint 零错误 + dev HTTP 200 + VLM 8/10 - 413→- ✅ 现有日挑战 + 排行榜 + 领奖流程全部保留,无回归 - 414→ - 415→--- - 416→Task ID: 9 - 417→Agent: main + subagent(9-b) - 418→Task: v0.8.1 P1 双任务 — 探险 BOSS 胜率平衡 + 能量恢复 + 信标系统扩展(周挑战 + 信标链) - 419→ - 420→Work Log: - 421→- 重读工单确认 P1 两项为下一阶段优先级 - 422→- P1-a 探险平衡(main 自己做,平衡需要精细控制): - 423→ - expedition.ts: combatDifficultyScale 8→4, combatWinRateFloor 0.25→0.35, BOSS difficulty 5-7→3-5 - 424→ - 新增 computeEnergyRegenInterval(state): exp_2 -30%, exp_3 -20%, 探索力属性 -最高30%, 下限12s - 425→ - computeEnergyRegen 接受 intervalSec 参数 - 426→ - config.ts: exp_2/exp_3 描述加能量恢复说明 - 427→ - gameStore.ts tick: 传入动态间隔 - 428→ - ExpeditionPanel.tsx: 显示实际恢复速度 + '已加速'标记 - 429→- P1-b 信标系统扩展(dispatch subagent 9-b,并行完成): - 430→ - beacon.ts 358→873行: 周挑战(getWeekKey ISO 8601 + 确定性生成) + 信标链(4里程碑+grace续命) - 431→ - BeaconPanel.tsx 301→638行: 周挑战fuchsia主题 + 信标链amber→rose里程碑节点 - 432→ - gameStore.ts: trackBeacon升级 + claimWeeklyBeacon/claimChainReward actions - 433→- QA 验证: - 434→ - lint 零错误 - 435→ - 探险面板 VLM 7/10(能量条 + 恢复速度显示正常) - 436→ - 信标面板 VLM 8/10(周挑战 WEEKLY + 信标链 3/7/14/30 + 排行榜全部可见) - 437→ - subagent 5场景 bun 测试全 PASS(信标链断链/续命逻辑) - 438→- 提交 v0.8.1(commit 71ca5b4)+ 推送 main - 439→- 静态导出 + 部署 gh-pages(commit 97eecaa)→ HTTP 200 - 440→ - 441→Stage Summary: - 442→- ✅ P1-a 探险平衡完成: BOSS 基础胜率 25%→35-45%, 配合技术/属性可达 55-70% - 443→- ✅ P1-a 能量恢复: 固定45s → 动态12-45s(技术+属性加速) - 444→- ✅ P1-b 周挑战: 每周一UTC刷新, fuchsia主题, goal×3-5倍 - 445→- ✅ P1-b 信标链: 4里程碑(3/7/14/30天) + grace续命, amber→rose渐变 - 446→- ✅ 在线游玩 v0.8.1: https://gitea-pages.atdunbg.xyz/Super_Z/echo-nexus/ - 447→- 下一阶段工单: P2 UI样式细节打磨 / P2 云存档+云排行榜 / P3 全纪元叙事 - 448→ - 449→--- - 450→Task ID: v0.14-reimpl - 451→Agent: full-stack-developer - 452→Task: 放置系统 (Idle Operations) — 采矿无人机舰队 + 6放置工程 + 永久产能加成 + idle徽章 - 453→ - 454→Work Log: - 455→- 阅读 worklog.md 了解 v0.8.1 项目状态(10 大系统 + 四色全息规范 + 8 标签页架构) - 456→- 类型扩展 `src/lib/game/types.ts`: - 457→ - 新增 `IdleProjectDef` 接口(id/name/desc/durationSec/reward/minAscensions/minCrystalsPerSec/icon/color/order) - 458→ - 新增 `IdleProjectSlot` 接口(projectId/startedAt/finishesAt/remainingSec/completed) - 459→ - GameState 接口新增 4 字段:idleProjectSlots (3 槽位数组) / idleProjectHistory / idlePermanentBonus / idleStats - 460→- 新建 `src/lib/game/idle.ts` (~290 行): - 461→ - 6 个放置工程定义(idle_scan 60s / idle_refine 180s / idle_archive 600s / idle_anchor 1200s / idle_drones 1800s / idle_resonance 3600s) - 462→ - 奖励类型:crystals / insights / energy / contact / crystalsPerSecPermanent / randomFragment - 463→ - 解锁逻辑:minAscensions 与 minCrystalsPerSec 为 OR 关系(满足任一已设置条件即可) - 464→ - 工具函数:getIdleProject / getUnlockedIdleProjects / getLockedIdleProjects / isIdleProjectUnlocked / formatRemaining (12s/3m 45s/1h 12m) / formatReward / deriveMinerFleet (派生 min_1..min_5 采矿无人机,icon: ⛏️🌋🤖💠☀️,无任何采矿技术时返回休眠 min_1) - 465→ - 颜色映射:IDLE_COLOR_CLASSES (text/border/bg/bgSoft/glow/ring/dot) + COLOR_HEX (用于 inline style 绕过 Tailwind 动态类限制) - 466→- 引擎集成 `src/lib/game/engine.ts`: - 467→ - recomputeStats() 末尾 `crystalsPerSec += state.idlePermanentBonus ?? 0` (放置永久加成叠加) - 468→ - performPrestige() 清空 idleProjectSlots (中断当前周目工程),保留 idlePermanentBonus/idleProjectHistory/idleStats (跨周目永久);recomputeStats 调用传 idlePermanentBonus - 469→ - createInitialState() 新增 4 字段默认值 - 470→- Store 集成 `src/store/gameStore.ts` (核心改造): - 471→ - 新增 4 action:startIdleProject (校验+写入槽位) / cancelIdleProject (置 null) / claimIdleProject (应用奖励 + permBonus 累加 + randomFragment 解锁 + history 追加 + Toast 提示) / tickIdleProjects (每 tick 更新 remainingSec,归零标记 completed) - 472→ - **persist migrate 函数**:persist 配置增加 `version: 1` + `migrate` 函数自动补全 4 个 idle 字段,旧存档加载不崩溃 - 473→ - 14 处 syncStats 调用全部更新为传入 `idlePermanentBonus: s.idlePermanentBonus ?? 0`,确保 recomputeStats 计算时纳入永久加成 - 474→ - init() 中也补全 4 个 idle 字段(defense in depth) - 475→- 主循环 `src/hooks/useGameLoop.ts`:新增 tickIdleProjects 选择器,setInterval 回调中 autoDecodeTick 后调用,visibilitychange 也调用 - 476→- 新建 `src/components/game/IdleOperationsPanel.tsx` (~490 行): - 477→ - 4 section 在 max-h-[520px] overflow-y-auto 容器中: - 478→ 1. 放置收益概览:3 stat tiles (放置产能/永久加成/完成工程) + 离线效率 progress bar + 累计放置晶体统计 - 479→ 2. 采矿无人机舰队:grid 展示 deriveMinerFleet,每卡 emoji+name+Lv+output/s+状态点 (active=emerald ping 脉冲, dormant=灰) - 480→ 3. 放置工程槽位:3 张卡(空槽=虚线占位,运行中=大字号倒计时+自定义进度条+取消按钮,已完成=奖励预览+领取按钮带 glow) - 481→ 4. 可派遣工程:2 列 grid 展示已解锁工程(icon+name+duration+desc+reward+3 数字派遣按钮 1/2/3),下方列出未解锁工程及解锁条件 - 482→ - useShallow 订阅 + 本地 now state 每 1s 刷新倒计时 - 483→ - 严格 4 色全息,自定义 fuchsia 滚动条 - 484→- 新建 `src/components/game/IdleStatusBadge.tsx` (~100 行): - 485→ - 紧凑徽章 h-8 px-2.5 text-[11px]:脉冲点 (emerald 若 cps>0) + "放置中 +X/s" (或 "休眠中") - 486→ - Tooltip 悬停展示分解:基础产能/永久加成/运行中工程/待领取工程数 - 487→ - data-tut="idle-status-badge" 锚点 + onClick 切换到放置 tab - 488→- 新建 `src/components/game/IdleProjectBar.tsx` (~90 行): - 489→ - 晶体球下方细长进度条,最多 3 个工程 mini pill `[icon] name 12s ▓▓▓░░` - 490→ - 完成时显示 "✓ 待领取" + glow 辉光;无工程返回 null - 491→ - 进度条颜色用 inline style (COLOR_HEX) 控制,每 1s 刷新倒计时 - 492→- 主页面集成 `src/app/page.tsx`: - 493→ - 引入 3 新组件 + Clock 图标 + getUnlockedIdleProjects - 494→ - 新增 activeTab/tabInited state + controlled Tabs (`value={activeTab} onValueChange={setActiveTab}`) - 495→ - 挂载后 useEffect 一次性设置默认 tab:hasActiveExpedition→"expedition",hasPendingPerk→"constellation",否则→"idle" - 496→ - 版本号 v0.8 → v0.14 - 497→ - Header 在 StarTideIndicator 后加 IdleStatusBadge - 498→ - 左侧 CrystalOrb 后加 IdleProjectBar - 499→ - TabsList grid-cols-8 → grid-cols-9,新增 value="idle" 的 TabsTrigger 作为第一个 tab(Clock 图标,emerald 主题,待领取时显示数量红点) - 500→ - 新增 TabsContent value="idle" 渲染 IdleOperationsPanel - 501→ - 新增 idle 相关 goal 提示(高优先级):待领取 > 0 → "✦ 放置工程已完成 X 项,请前往「放置」标签领取奖励";全空+有解锁+时长>60s → "「放置」标签可派遣工程项目,离线自动产出" - 502→ - StatsPanel 新增 3 行:放置永久加成 / 完成放置工程 / 放置产出晶体 - 503→- 教程更新 `src/lib/game/tutorial.ts`:TUTORIAL_STEPS 在 decode 与 tech 之间插入新步骤 (id="idle", target="tab-idle", 标题"③ 放置工程 · 离线产出"),原 tech/expedition/prestige 编号顺延为 ④⑤⑥ - 504→- 写入 `agent-ctx/v0.14-reimpl-full-stack-developer.md` 工作记录 - 505→- 最终验证: - 506→ - lint 零错误零警告 ✅ - 507→ - dev 服务器 HTTP 200,编译 < 250ms ✅ - 508→ - agent-browser 烟雾测试全流程通过(默认 idle tab + 头部徽章 + 4 section + 派遣→倒计时→完成→领取→永久加成应用) ✅ - 509→ - 510→Stage Summary: - 511→- ✅ v0.14 放置系统 (Idle Operations) 全部完成 - 512→- ✅ 新增 4 文件(idle.ts 290行 + IdleOperationsPanel.tsx 490行 + IdleStatusBadge.tsx 100行 + IdleProjectBar.tsx 90行),修改 6 文件(types/engine/gameStore/useGameLoop/page/tutorial) - 513→- ✅ 6 个放置工程:勘探扫描 / 精炼校准 / 碎片整理 / 锚点部署 / 无人机扩编 / 谐振标定 - 514→- ✅ 3 个槽位并行 + 6 工程解锁逻辑 (OR) + 永久产能加成 (permBonus 累加跨周目) - 515→- ✅ 采矿无人机舰队派生 (min_1..min_5) + 休眠状态显示 - 516→- ✅ 头部 idle 徽章 + 默认放置 tab + 晶体球下方进度条 (5 秒内可见三要素) - 517→- ✅ persist migrate 函数 (version 1) 旧存档兼容 - 518→- ✅ 14 处 syncStats 调用全部传 idlePermanentBonus,recomputeStats 永久加成不丢失 - 519→- ✅ 飞升时清空活跃槽位但保留永久加成 / 历史 / 统计 - 520→- ✅ 严格 4 色全息 (emerald/rose/amber/fuchsia),零蓝/靛 - 521→- ✅ lint 零错误 + HTTP 200 + agent-browser 全流程烟雾测试通过 - 522→- ✅ 9 标签页 (新增"放置"为第一个) + 巡航按钮 + 教程新增 idle 步骤 - 523→ - 524→--- - 525→Task ID: v0.14-release - 526→Agent: 主控 Z.ai Code - 527→Task: v0.14 放置系统发布 — README/docs 更新 + 端到端验证 + git commit - 528→ - 529→Work Log: - 530→- 发现项目被环境重置回 v0.8.1 状态(v0.14 代码全部丢失,worklog 也被截断到 447 行) - 531→- 重新委派 full-stack-developer subagent (v0.14-reimpl) 在 v0.8.1 代码库上重新实现放置系统 - 532→- subagent 交付 10 个文件(4 新建 + 6 修改),lint 零错误,agent-browser smoke test 通过,git commit 17820b1 - 533→- 主控独立验证: - 534→ * git log 确认 commit 17820b1 存在 ✓ - 535→ * bun run lint 零错误 ✓ - 536→ * HTTP 200 ✓ - 537→ * agent-browser: v0.14 标签 ✓ + 放置中徽章 ✓ + 默认 idle tab ✓ + 4 段面板完整 ✓ - 538→ * 派遣 idle_scan → slot 0 显示 60s 倒计时 ✓ - 539→ * 快进完成 → 领取 → insights 79→87 (+8) + projectsCompleted 0→1 ✓ + 槽位清空 ✓ - 540→- 主控更新 README.md → v0.14:突出 12 大系统、3 层 idle 节奏、灵感来源、健康设计声明 - 541→- 主控创建 docs/11-放置系统-v0.14.md:完整设计文档 - 542→- 主控创建每 15 分钟触发的 webDevReview cron job - 543→ - 544→Stage Summary: - 545→ - 546→## v0.14 放置系统已重新上线并 git commit 保固 - 547→ - 548→### 核心交付 - 549→- **3 层 idle 节奏**:被动产能(无人机舰队)+ 放置工程(6 种 × 3 槽位)+ 离线收益(8h cap) - 550→- **始终可见的 idle 元素**:Header「放置中 +X/s」徽章 + 默认 tab = 放置 + 晶体球下方工程进度 pill - 551→- **永久产能加成**:通过工程奖励累积,集成进 recomputeStats,跨周目保留 - 552→- **存档安全**:persist version 1 + migrate 函数 + 4 字段 fallback,老存档零崩溃 - 553→ - 554→### Git 保固 - 555→- commit 17820b1: "v0.14: 放置系统 (Idle Operations) — 采矿无人机舰队 + 6放置工程 + 永久产能加成 + idle徽章" - 556→- 即使环境再次重置,git checkout 17820b1 即可恢复 - 557→ - 558→### 验证结果 - 559→- ✅ bun run lint 零错误零警告 - 560→- ✅ HTTP 200,dev.log 无运行时错误 - 561→- ✅ agent-browser 端到端 smoke test 全通过(派遣→倒计时→完成→领取→insights+8) - 562→- ✅ Sticky footer/header 验证通过 - 563→ - 564→### 未解决问题 / v0.15 优先项 - 565→1. 🟡 永久产能加成可能需要飞升次数 softcap(防 idle_resonance ×N 滚雪球) - 566→2. 🟡 idleProjectHistory / crystalsFromIdle 已在 store 累加但 UI 未展示 - 567→3. 🟡 缺「批量领取全部」按钮 + 工程完成 toast 通知 - 568→4. 🟡 遗迹图鉴多层文本(Dark Souls 式 loreFragments)—— v0.15 计划 - 569→5. 🟡 解码阈值释放叙事碎片 + 星潮三频段切换 —— v0.16 计划 - 570→6. 🟡 健康设计:每日软上限 + "星核建议休眠"提醒 —— v0.17 计划 - 571→ \ No newline at end of file diff --git a/tool-results/read_1782303134275_13d66843559b.txt b/tool-results/read_1782303134275_13d66843559b.txt deleted file mode 100755 index b5ebf16c8..000000000 --- a/tool-results/read_1782303134275_13d66843559b.txt +++ /dev/null @@ -1,571 +0,0 @@ - 1→# 回响星核 / Echo Nexus — 开发工作日志 - 2→ - 3→> 持续性任务的工作交接文档。每个阶段更新,避免进度丢失。 - 4→ - 5→--- - 6→ - 7→## 一、项目当前状态描述 / 判断 - 8→ - 9→### 概况 - 10→- **项目**:回响星核 / Echo Nexus — 深空考古放置策略游戏 - 11→- **一句话**:自治无人机采矿 → 解码记忆晶体(原创共振谜题)→ 拼凑碎片叙事 → 飞升多周目。差异化:解码谜题驱动叙事涌现,全息晶体 + 深空粒子美学。 - 12→- **当前版本**:**v0.7**(CrystalOrb Canvas 粒子系统 + 角色属性系统) - 13→- **在线游玩**:https://gitea-pages.atdunbg.xyz/Super_Z/echo-nexus/ - 14→- **仓库**:https://git.atdunbg.xyz/Super_Z/echo-nexus - 15→- **技术栈**:Next.js 16 (App Router) + TypeScript + Tailwind + shadcn/ui + Canvas 2D + Zustand + Web Audio API - 16→- **定时任务**:每 15 分钟一次 `webDevReview`(`fixed_rate` + `"900"` 秒,priority=10,job_id 228266)。正常完成不会被删除,无需自持续机制。 - 17→ - 18→### 状态判断 - 19→- dev 服务器运行正常(HTTP 200,编译 < 250ms) - 20→- v0.7 两大新功能已完成:CrystalOrb Canvas 粒子系统升级(VLM 8/10)+ 角色属性系统(VLM 7/10) - 21→- 系统总体稳定,无阻塞性 bug,可继续推进新功能 - 22→ - 23→### 已完成版本里程碑(精简) - 24→| 版本 | 核心内容 | - 25→|------|---------| - 26→| v0.1 | MVP:放置采矿 + 解码共振谜题 + 技术树 + 记忆图谱 + 飞升 + 统计 | - 27→| v0.1.1 | 解码可解性修复(路径构造法生成器,100% 可解率)| - 28→| v0.2 | 遗迹探险肉鸽系统(能量/探险力/6 节点/程序化路径/BOSS)| - 29→| v0.2.1 | 程序化音频(15 种音效)+ 14 项成就 + 视觉打磨 | - 30→| v0.3 | 星潮事件系统(6 种星潮 + 全屏叠层 + 程序化音效)| - 31→| v0.3.1 | 星图天文台元进程(6 类 × 3 = 18 天赋 + 飞升后 3 选 1 draft + Canvas 星图)| - 32→| v0.4 | 回响编年史(5 纪元循环命名 + 模板化叙事 + 时间轴 UI + 6 项新成就 + 飞升 BUG 修复)| - 33→| v0.5 | 深空信标(每日挑战 + 本地排行榜 Top20 + 7 标签页 + 编年史历史 BUG 修复)| - 34→| v0.5.1 | 静态部署 Gitea Pages + 新手教程系统(7 步聚光灯引导)+ UI 间距优化 | - 35→| v0.5.2 | 离线收益报告弹窗 + 标签页 UI 重设计 + persist rehydrate 竞态修复 | - 36→| v0.6 | 深空巡航 Canvas 2D 实时玩法 + UI 偏移重叠修复 | - 37→| **v0.7** | **CrystalOrb Canvas 粒子系统升级 + 角色属性系统(四维)** | - 38→ - 39→### 核心系统清单(8 大系统) - 40→1. **放置采矿** — 中央晶体球 + 主动脉冲 + 连击加成(`CrystalOrb.tsx`) - 41→2. **谐振解码** — 原创路径谜题,路径构造法 100% 可解(`DecodeArray.tsx` + `engine.ts`) - 42→3. **技术树** — 三层科技解锁,提升产能/解码/探险(`TechTree.tsx`) - 43→4. **遗迹探险** — 肉鸽系统,6 种节点 + 程序化路径 + BOSS(`ExpeditionPanel.tsx` + `expedition.ts`) - 44→5. **星潮事件** — 6 种动态事件,为放置循环注入变化(`StarTide.tsx` + `starTide.ts`) - 45→6. **星图天文台** — 飞升后 3 选 1 天赋 draft,跨周目永久生效,18 个天赋(`ConstellationPanel.tsx` + `constellation.ts`) - 46→7. **回响编年史** — 跨周目叙事时间轴,5 纪元循环命名(`ChronicleDialog.tsx` + `chronicle.ts`) - 47→8. **深空信标** — 每日挑战 + 本地排行榜 Top20(`BeaconPanel.tsx` + `beacon.ts`) - 48→9. **深空巡航** — Canvas 2D 实时玩法,飞船操控/陨石躲避/星门通关(`CruiseMode.tsx` + `cruise.ts`)【v0.6】 - 49→10. **角色属性系统** — 四维属性(探索力/智慧/勇气/灵感),飞升获得属性点,影响所有系统(`AttributesPanel.tsx` + `attributes.ts`)【v0.7 新增】 - 50→ - 51→--- - 52→ - 53→## 二、当前目标 / 已完成的修改 / 验证结果 - 54→ - 55→### v0.7 CrystalOrb Canvas 粒子系统升级(已完成) - 56→**动机**:P0 优先级 — 将中央晶体球从 CSS 动画升级为 Canvas 粒子视觉盛宴,提升视觉震撼。 - 57→ - 58→**重写文件**:`src/components/game/CrystalOrb.tsx` - 59→- 从纯 CSS 动画升级为 Canvas 2D 粒子系统 - 60→- **多层粒子**: - 61→ - 环绕能量粒子(3层14/10/8个,emerald/fuchsia/rose三色,不同速度方向环绕晶核,带拖尾) - 62→ - 环境星尘(40个,缓慢漂移 + 闪烁) - 63→ - 点击爆发粒子(10-30个径向发散,颜色随连击等级变色) - 64→ - 冲击波环(点击触发1-3层错峰扩散,连击≥3/5解锁更多层) - 65→- **晶核升级**:径向渐变辉光 + 3层六边形纹理旋转 + 高光 + 鼠标视差偏移 - 66→- **呼吸动画**:晶核 + 外层辉光随时间呼吸(sin波) - 67→- **进度环**:SVG渐变环(emerald→fuchsia→rose)保留 - 68→- **交互保留**:点击脉冲、连击Toast、浮动数字、音效、`data-tut="crystal-orb"` 锚点 - 69→- **性能**:DPR cap 2,ResizeObserver 自适应,requestAnimationFrame 60fps - 70→ - 71→**QA 验证**(agent-browser + VLM): - 72→- VLM 视觉评分 **8/10**:晶体球清晰可见,多层辉光,环绕粒子动态自然,无渲染问题 - 73→- 点击交互:冲击波环 ✅ + 爆发粒子 ✅,评分 7/10 - 74→- lint 零错误;HTTP 200 - 75→ - 76→### v0.7 角色属性系统(已完成) - 77→**动机**:P1 优先级 — 增加RPG深度,四维属性影响所有游戏系统。 - 78→ - 79→**新增文件**: - 80→- `src/lib/game/attributes.ts`(~330行逻辑层):CharacterAttributes/AttributeProgress 类型、ATTRIBUTE_CONFIG 元信息、getAttributeBonus 两区加成公式(0-50线性+0.5%/点,50-100递减+0.2%/点)、getAllBonuses 12项修饰器、levelUpCheck 自动升级、computePrestigeAttrPoints、migrateAttributes 旧存档兼容 - 81→- `src/components/game/AttributesPanel.tsx`(~330行 UI):四维卡片网格(小屏2×2/大屏1×4)、图标+中英文名+Lv.badge+数值/100+加成%+经验条+影响列表+分配按钮、顶部待分配点数badge闪烁、底部总览+12项修饰器明细 - 82→ - 83→**修改文件**: - 84→- `types.ts`:GameState 新增 attributes/attributeProgress/pendingAttrPoints - 85→- `config.ts`:INITIAL_STATE 补全默认值 - 86→- `engine.ts`:recomputeStats 聚合属性加成;performPrestige 发放属性点(ascensions×2+1) - 87→- `gameStore.ts`:新增 allocateAttribute/gainAttributeExp actions;pulse/clickNode/autoDecodeTick/resolveCurrentNode/grantCruiseReward/tickTide 全部接入属性经验;init() 调用 migrateAttributes - 88→- `achievements.ts`:新增 ach_attr_total_50 + ach_attr_max_100 两项成就 - 89→- `page.tsx`:grid-cols-7→8,新增「角色」标签页(User图标),红点提示,StatsPanel新增5行属性行,版本号 v0.6→v0.7 - 90→ - 91→**四维属性设计**: - 92→- 探索力(emerald):探险力+X%/巡航飞船速度+X% - 93→- 智慧(fuchsia):解码步数+X/洞见+X%/自动解码周期-X% - 94→- 勇气(amber):探险生命+X/BOSS胜率+X%/巡航护盾+X - 95→- 灵感(rose):接触率+X/星潮概率+X/脉冲连击+X% - 96→ - 97→**QA 验证**(agent-browser + VLM): - 98→- VLM 视觉评分 **7/10**(面板)/ **8/10**(待分配状态):四维卡片可见、数值/等级/加成/经验条齐全、四色全息 - 99→- 功能验证:setState pendingAttrPoints=5 → 显示5点待分配 → 点击分配按钮 → exploration 0→1, pending 5→4 ✅ - 100→- lint 零错误;HTTP 200 - 101→ - 102→--- - 103→ - 104→### v0.6 深空巡航 Canvas 2D 实时玩法(历史记录) - 105→**动机**:回应用户反馈「玩法太单调(全是点点点)」+「视觉震撼不足」——首次引入实时操作玩法。 - 106→ - 107→**新增文件**: - 108→- `src/lib/game/cruise.ts`(~520 行逻辑层) - 109→ - 类型系统:CruiseEntity 联合类型(ship/asteroid/storm/crystal/insight/beacon/stargate/particle 共 8 种) - 110→ - mulberry32 + FNV-1a 种子化 RNG(`cruiseSeed(level)`) - 111→ - `generateLevel`:程序化生成陨石带(8+lvl×2)、虚空风暴(1+lvl/2)、晶体碎片(5+lvl)、洞见光球(2+lvl/3)、信标(1+lvl/4)、星门 - 112→ - `updateCruise`:物理推进 + 碰撞检测 + 粒子系统 + 震动 + 600ms 无敌帧 - 113→ - `computeRewards`:crystals=碎片×(8+lvl×2)、insights=洞见×(2+lvl×0.5)、contact=信标×(1.5+lvl×0.3);通关×1.5、失败×0.5 - 114→ - 独立 localStorage `echo-nexus-cruise-v1`:最高分/累计通关/最高关卡/累计奖励/最近 20 局 - 115→ - 116→- `src/components/game/CruiseMode.tsx`(~830 行渲染层) - 117→ - 全屏 fixed inset-0 z-50 Canvas,DPR cap 2,resize 监听 - 118→ - 3 层视差星空:远(100星)/中(60星)/近(30星),独立漂移 + 飞船速度视差 + 闪烁 - 119→ - 8 种实体全部 ctx.shadowBlur 辉光绘制 - 120→ - 粒子系统:尾焰/收集/碰撞/烟花,上限 200 - 121→ - 屏幕震动 280ms 衰减;低护盾(<30%)红色边框脉冲 - 122→ - HUD(HTML 叠层,glass+backdrop-blur,80ms 节流):护盾/能量/分数/用时/收集计数 - 123→ - 控制:桌面 WASD/方向键 8 方向 + 移动端虚拟摇杆 + P 暂停 + Esc 退出 + 失焦自动暂停 - 124→ - 阶段:ready → playing → won/lost(结算明细 + 奖励 + 下一关/重试/返回) - 125→ - 126→**修改文件**: - 127→- `src/app/page.tsx`:header 新增 amber 主题「巡航」按钮(Navigation 图标)+ cruiseOpen state + 条件渲染 CruiseMode - 128→- `src/store/gameStore.ts`:新增 `grantCruiseReward({crystals?, insights?, contact?})` action(crystals 受 crystalCap 限制,contact 受 100 上限) - 129→- 版本号 v0.5.2 → v0.6 - 130→ - 131→**UI 偏移/重叠 BUG 修复**(3 处): - 132→- `DecodeArray.tsx`:移除 className 冲突的 `-translate-x-1/2 -translate-y-1/2`,统一用 inline transform 定位+缩放 - 133→- `AchievementsPanel.tsx`:移除内层 `max-h-[300px]`,改 `flex-1 min-h-0` 自适应父容器 - 134→- `page.tsx`:调整三个 section 的 min-h,避免小屏单列时总高度超过手机视口 - 135→ - 136→**色彩规范**:严格遵循 emerald(#34d399) / rose(#fb7185) / amber(#fbbf24) / fuchsia(#e879f9) 四色全息色谱,零蓝色/靛色 - 137→ - 138→**QA 验证**(agent-browser + VLM): - 139→- lint 零错误;dev 服务器 HTTP 200;编译 < 250ms - 140→- VLM 确认主界面:巡航按钮存在、晶体球居中、解码区正常、无重叠偏移 - 141→- VLM 确认巡航玩法:飞船可见(绿色三角形)、HUD 完整、陨石+星空背景、粒子特效(黄色尾焰+紫色辉光) - 142→- WASD 控制飞船移动,按 W 直冲星门 → 通关,奖励 +30 晶体(2×10×1.5=30 计算正确) - 143→- localStorage 正确记录 highScore/totalRuns/totalWins/bestLevel - 144→- 奖励同步 gameStore(grantCruiseReward,满仓时 cap 逻辑正确) - 145→- 退出返回主界面,7 标签页 + 晶体球 + 解码面板均完好 - 146→ - 147→--- - 148→ - 149→## 三、未解决问题或风险 / 下一阶段优先事项 - 150→ - 151→### 已知问题 / 风险 - 152→1. **dev 服务器 Turbopack 缓存偶发损坏**:`Unable to open static sorted file` + 进程被杀,需 `rm -rf .next` 重启。静态导出构建稳定可靠。 - 153→2. **定时任务调度类型**:必须用 `fixed_rate` + `"900"` 秒(15分钟),不要用 cron 表达式。`webDevReview` 类型任务正常完成不会被删除,只有抛出未捕获异常才会被标记失败。 - 154→3. **v0.3 星潮为单机版**:原计划 socket 全局事件,后续可扩展为多人同步。 - 155→4. **v0.5 信标排行榜为本地版**(纯 localStorage):升级云排行榜需后端 API。 - 156→5. **v0.5 每日挑战仅 1 个/天**:可加入"周挑战"或"信标链"(连续完成 N 天奖励)。 - 157→6. **探险 BOSS 战胜率较低**:基础探索力 10 vs BOSS 难度 5-7,胜率约 25%,需玩家投资探险技术。 - 158→7. **探险能量恢复较慢**:45s/点,可加技术提升恢复速度。 - 159→8. **编年史上限 50 条**:超过自动丢弃最早的;v0.4 之前飞升无回填(空状态有提示)。 - 160→9. **5 纪元叙事为模板化生成**:v0.5+ 将加入手写剧情节点。 - 161→10. ~~**CrystalOrb 仍为 CSS 动画**~~ → ✅ **v0.7 已完成 Canvas 粒子系统升级**。 - 162→ - 163→### 下一阶段优先级(v0.7 后修正版) - 164→> 工单修正:原 P0 CrystalOrb Canvas + P1 角色属性两项均已在 v0.7 完成,现重新排序。 - 165→ - 166→1. **🔴 P0 — 巡航玩法大增强(v0.8 主线)**:BOSS 战关卡(每 5 关一个 BOSS,三阶段攻击模式 + 飞船射击系统)/ 道具掉落(5 种 powerup:护盾/能量/急速/超载/磁吸)/ 事件选择节点(每关通关后 3 选 1 强化牌,10 种强化)。这是当前最高优先级,能显著提升巡航玩法的深度与可玩性。 - 167→2. **🟠 P1 — 探险 BOSS 战胜率平衡 + 探险能量恢复**:修复已知问题 6/7(探险 BOSS 胜率 25% 偏低,能量 45s/点 偏慢),提升探险体验。 - 168→3. **🟠 P1 — 信标系统扩展**:周挑战 / 信标链(连续完成 N 天奖励),修复已知问题 5。 - 169→4. **🟡 P2 — UI 样式细节打磨**:标签页图标/文字比例、视觉层次强化、移动端响应式进一步优化、CrystalOrb 周边留白优化(VLM 指出主界面左侧有空白)。 - 170→5. **🟡 P2 — 云存档 + 云排行榜**:v0.5+ 后端 API(需评估是否引入 mini-service)。 - 171→6. **🟢 P3 — 全 5 纪元手写叙事节点**:替换模板化 lore。 - 172→7. **🟢 P3 — socket 多人同步星潮**:全局事件共享。 - 173→ - 174→### 定时任务(fixed_rate · 900s) - 175→- **当前 job_id**: **228357**(2026-06-24 13:25 重建) - 176→- **配置**: `fixed_rate` + `"900"`(15分钟间隔),`priority=10`(高优先级),`payload.kind=webDevReview`,`tz=Asia/Shanghai` - 177→- **任务名**: Echo Nexus - 任务审查与持续迭代 - 178→- **任务描述强制要求**:每次触发 MUST 先完整阅读 worklog.md 工单,以工单为准,不凭记忆工作 - 179→- **正常完成不会被删除**,只有未捕获异常会标记失败。若任务列表为空,需用 cron create 重建(schedule.kind=fixed_rate, expr="900", priority=10, payload.kind=webDevReview)。 - 180→- **job_id 历史**: 227909(失效) → 228130(失效) → 228172(失效) → 228186(失效) → 228218(删除重建) → 228236(cron版,删除重建) → 228266(fixed_rate,失效) → 228329(失效) → **228357(当前,重建)** - 181→ - 182→--- - 183→ - 184→## 定时任务描述模板(webDevReview · fixed_rate) - 185→ - 186→> 重建参数:schedule.kind=`fixed_rate`, schedule.expr=`"900"`, params.priority=`10`, payload.kind=`webDevReview` - 187→ - 188→``` - 189→请先判断当前项目状态,使用 agent-browser 进行必要测试和 QA,然后自主选择本轮最合适的工作重点并继续开发。 - 190→ - 191→要求: - 192→1. 开始任务时查看 /home/z/my-project/worklog.md 了解实际进展,拆解详细todo - 193→2. 使用 agent-browser 进行测试和QA,有bug优先修复 - 194→3. 如果稳定,自主提出新需求继续推进 - 195→4. 样式要越做细节越多!! - 196→5. 功能要越做越多!! - 197→6. 工作告一段落更新 /home/z/my-project/worklog.md - 198→7. 项目结束后一定要把工作记录写在 worklog.md 里 - 199→ - 200→交接文档建议包含三部分: - 201→- 项目当前状态描述/判断 - 202→- 当前目标/已完成的修改/验证结果 - 203→- 未解决问题或风险,建议下一阶段优先事项 - 204→``` - 205→ - 206→--- - 207→ - 208→## 历史详细版本记录(按需查阅 docs/repo/docs/) - 209→ - 210→- `04-解码系统修复-v0.1.1.md` - 211→- `05-遗迹探险系统-v0.2.md` - 212→- `06-音频与成就系统-v0.2.1.md` - 213→- `07-星潮事件系统-v0.3.md` - 214→- `08-星图天文台系统-v0.3.1.md` - 215→- `09-回响编年史系统-v0.4.md` - 216→- `10-深空信标系统-v0.5.md` - 217→ - 218→--- - 219→ - 220→Task ID: 7 - 221→Agent: full-stack-developer - 222→Task: 角色属性系统(探索力/智慧/勇气/灵感) - 223→ - 224→Work Log: - 225→- 阅读 worklog.md 了解 v0.6 项目背景,确认 8 大系统架构与四色全息色彩规范 - 226→- 新建 `src/lib/game/attributes.ts` 逻辑层(~330 行): - 227→ - 定义 CharacterAttributes/AttributeProgress/AttributeKey 类型 + ATTRIBUTE_CONFIG 元信息 - 228→ - getAttributeBonus 线性+递减两区加成公式(0-50 每 10 点 +5%,50-100 每点 +2%) - 229→ - getAllBonuses 返回 12 个修饰器(探险力/巡航速度/解码步数/洞见/自动解码周期/探险生命/BOSS 胜率/巡航护盾/接触率/星潮触发/脉冲连击/产能) - 230→ - levelUpCheck 自动跨多级 + expRequiredForLevel + computePrestigeAttrPoints + migrateAttributes 兼容 - 231→- 扩展 `src/lib/game/types.ts` GameState:新增 attributes/attributeProgress/pendingAttrPoints - 232→- 扩展 `src/lib/game/config.ts` INITIAL_STATE:补全默认值(0 数值 + 空 progress) - 233→- 扩展 `src/lib/game/engine.ts`: - 234→ - recomputeStats 末尾聚合 am.crystalsPerSecMult/insightMultAdd/contactRateMult/decodeStepsBonus - 235→ - performPrestige 保留 attributes 数值,清空 attributeProgress,发放 ascensions × 2 + 1 点属性点 - 236→ - createInitialState 每次返回全新对象避免引用共享 - 237→- 扩展 `src/store/gameStore.ts`(核心改造,10+ 处 syncStats 调用全部补 attributes 参数): - 238→ - pulse:连击 ≥3 给灵感经验 + 脉冲威力乘灵感连击加成 - 239→ - clickNode 完成:给智慧经验(tier × 2) - 240→ - autoDecodeTick:智慧经验 +1 + 自动解码周期受 am.autoDecodeIntervalMult 影响 - 241→ - startExpedition:探险力乘 am.expeditionPowerMult,生命加 am.expeditionHpBonus - 242→ - resolveCurrentNode:BOSS 节点包装 RNG 提升 +am.bossWinRateBonus 胜率;战斗胜利给勇气+探索力经验 - 243→ - grantCruiseReward:按总奖励量给探索力+勇气经验 - 244→ - tickTide:灵感 am.tideTriggerBonus 缩短星潮间隙(上限 30%) - 245→ - init() 调用 migrateAttributes 补全旧存档 - 246→ - 新增 allocateAttribute / gainAttributeExp 两个 action - 247→- 新建 `src/components/game/AttributesPanel.tsx` UI 面板(~330 行): - 248→ - 四维卡片网格(小屏 2×2,大屏 1×4) - 249→ - 每张卡片:图标 + 中英文名 + Lv.badge + 数值/100 + 加成% + 经验条 + 影响列表 + 「+分配」按钮 - 250→ - 顶部待分配点数 badge(pendingAttrPoints > 0 时 echo-pending-pulse 闪烁动画) - 251→ - 底部总等级/总加成概览 + 12 个修饰器明细 - 252→ - 4 色辉光边框(emerald/fuchsia/amber/rose),顶角光晕装饰 - 253→- 接入 `src/app/page.tsx`: - 254→ - grid-cols-7 → grid-cols-8,新增第 8 个「角色」TabsTrigger(User 图标,渐变主题) - 255→ - pendingAttrPoints > 0 时显示 rose 红点提示 - 256→ - 渲染 AttributesPanel - 257→ - StatsPanel 新增 5 行属性行(探索力/智慧/勇气/灵感/待分配点) - 258→ - 版本号 v0.6 → v0.7 - 259→- 新增 `src/lib/game/achievements.ts` 2 项成就: - 260→ - ach_attr_total_50(四维觉醒):总属性 ≥ 50 → 产能+6%/洞见+6% - 261→ - ach_attr_max_100(维度精通):任一属性 100 → 产能+12%/洞见+10% - 262→- 写入 `agent-ctx/7-full-stack-developer.md` 工作记录 - 263→- 最终验证:lint 零错误 + dev 服务器 HTTP 200 + 编译 < 250ms - 264→ - 265→Stage Summary: - 266→- ✅ 角色属性系统 v0.7 P1 全部完成 - 267→- ✅ 新增 2 文件(attributes.ts + AttributesPanel.tsx),修改 6 文件(types/config/engine/gameStore/achievements/page) - 268→- ✅ 四维属性完整接入游戏各系统(探险/解码/脉冲/巡航/星潮) - 269→- ✅ 飞升属性点公式 = ascensions × 2 + 1;经验累积自动升级(每级 10×level) - 270→- ✅ 旧存档兼容(migrateAttributes 补全字段,同步 level 与 attributes) - 271→- ✅ lint 零错误 + HTTP 200 + 编译 < 250ms - 272→- ✅ 严格四色全息(emerald/fuchsia/amber/rose),零蓝色/靛色 - 273→- ✅ 8 标签页 + 巡航按钮全部保留,data-tut 锚点未破坏 - 274→- ✅ 待分配点数时标签页 rose 红点提示 + 顶部 badge 闪烁 - 275→ - 276→--- - 277→Task ID: 8-deploy - 278→Agent: main - 279→Task: 重建定时任务 + 更新 README + 部署 gh-pages(v0.7) - 280→ - 281→Work Log: - 282→- 检查项目状态:CrystalOrb.tsx 已是 v0.7 Canvas 粒子系统(659 行),无活动 cron job - 283→- 重建定时任务:cron create fixed_rate="900" priority=10 webDevReview → job_id 228329(tz=Asia/Shanghai) - 284→- 重写 README.md: - 285→ - 顶部显著位置添加「在线游玩」链接(https://gitea-pages.atdunbg.xyz/Super_Z/echo-nexus/) - 286→ - 版本号 v0.1 → v0.7 - 287→ - 新增 10 大系统表格(含 v0.6 巡航 + v0.7 角色属性) - 288→ - 完整开发路线表(v0.1-v1.0,标记 ✅/🔜 状态) - 289→ - 新增本地开发命令 + Gitea Pages 部署指南 - 290→ - 文档索引扩展至 10 个版本文档 - 291→- 静态导出:BUILD_EXPORT=true bun run build:static → out/ 1.6M(HTTP 200 编译 4.3s) - 292→- 推送 gh-pages 分支:force push origin gh-pages(commit 8727fed "deploy: v0.7") - 293→- 提交 main:README 更新(commit 43dfc2d "docs: README 添加在线游玩链接 + v0.7 状态更新") - 294→- 验证 Gitea Pages:https://gitea-pages.atdunbg.xyz/Super_Z/echo-nexus/ → HTTP 200 ✅ - 295→- QA 验证:agent-browser 打开 http://localhost:3000/ → VLM 评分 8/10(主界面 polished,minor 空白) - 296→ - 297→Stage Summary: - 298→- ✅ 定时任务重建成功(job_id 228329,每 15 分钟自动审查迭代) - 299→- ✅ README 重写完成,网站链接显著展示,v0.7 状态完整 - 300→- ✅ gh-pages 部署成功,Gitea Pages HTTP 200 可访问 - 301→- ✅ main 分支已同步推送(README + worklog 更新) - 302→- ✅ dev 服务器健康(HTTP 200,编译 < 250ms) - 303→- ✅ CrystalOrb v0.7 Canvas 粒子系统已确认完整(659 行) - 304→- 下一步:v0.8 巡航玩法增强(BOSS 战 / 道具掉落 / 事件选择节点) - 305→ - 306→--- - 307→Task ID: 8 - 308→Agent: main - 309→Task: v0.8 巡航玩法大增强(BOSS 战 + 道具掉落 + 事件选择节点)+ 工单修正 + 审查流程重建 - 310→ - 311→Work Log: - 312→- 重读 worklog 工单,发现 v0.7 两项(CrystalOrb Canvas + 角色属性)已完成但工单"下一阶段优先级"未更新 - 313→- 修正 worklog 工单:标注 P0/P1 v0.7 已完成,重新排序下一阶段优先级(P0=巡航增强, P1=探险平衡/信标扩展, P2=UI打磨/云存档, P3=叙事/多人) - 314→- 重建审查流程 cron job:fixed_rate=900s, priority=10, webDevReview, tz=Asia/Shanghai, job_id=228357 - 315→ - 任务描述强制要求:每次触发 MUST 先完整阅读 worklog.md 工单,以工单为准,不凭记忆工作 - 316→- 发现之前被中断的 v0.8 subagent 已写入 2037 行代码(cruise.ts +1487 / CruiseMode.tsx +858 / page.tsx +2) - 317→- 修复 2 个 lint 错误(react-hooks/set-state-in-effect): - 318→ - isTouch: useState(false) + useEffect(setIsTouch) → useState initializer - 319→ - stats: useState(null) + useEffect(setStats(loadCruiseStats)) → useState initializer - 320→- QA 验证(agent-browser + VLM): - 321→ - 主界面 VLM 7/10:v0.8 版本号 ✅ + 巡航按钮 ✅ - 322→ - 巡航 READY 阶段 VLM 8/10:BOSS 战说明 ✅(每5关BOSS战/击败BOSS)+ 射击控制说明 ✅(空格/右下角射击按钮) - 323→ - 巡航第1关游戏 VLM 8/10:飞船(绿色三角)✅ + HUD(护盾/能量/分数/计时)✅ + 陨石/收集物/能量场 ✅ + 无渲染错误 - 324→- 提交 v0.8(commit 494bc5f)+ 推送 main - 325→- 静态导出 BUILD_EXPORT=true → out/ 1.6M(编译 4.1s) - 326→- 部署 gh-pages(force push, commit b865922)→ Gitea Pages HTTP 200 ✅ - 327→ - 328→Stage Summary: - 329→- ✅ 工单修正完成:v0.7 已完成项标注清楚,下一阶段优先级重新排序 - 330→- ✅ 审查流程重建:cron job_id 228357,每15分钟触发,强制读工单 - 331→- ✅ v0.8 巡航玩法大增强全部完成并部署: - 332→ - BOSS 战系统(每5关,三阶段攻击,HP条,击败奖励×3) - 333→ - 飞船射击系统(空格/射击按钮,能量消耗,子弹尾焰) - 334→ - 道具掉落(5种powerup:护盾/能量/急速/超载/磁吸) - 335→ - 事件选择节点(每关通关后3选1,10种强化牌) - 336→ - lint 零错误 + dev HTTP 200 + gh-pages HTTP 200 - 337→- ✅ VLM 评分:主界面 7/10 + 巡航READY 8/10 + 巡航游戏 8/10 - 338→- 在线游玩 v0.8:https://gitea-pages.atdunbg.xyz/Super_Z/echo-nexus/ - 339→- 下一阶段工单:P1 探险BOSS胜率平衡 + 探险能量恢复 / 信标系统扩展(周挑战/信标链) - 340→ - 341→--- - 342→Task ID: 9-b - 343→Agent: full-stack-developer - 344→Task: 信标系统扩展(周挑战 + 信标链连续奖励) - 345→ - 346→Work Log: - 347→- 阅读现有 `src/lib/game/beacon.ts`(v0.5 每日挑战 + 本地排行榜,358 行)+ `src/components/game/BeaconPanel.tsx` + `src/store/gameStore.ts` 的 `trackBeacon` 函数(约第 173 行)+ worklog v0.8 项目状态,确认四色全息规范与现有架构。 - 348→- **扩展 `src/lib/game/beacon.ts`(358 → 873 行,新增 ~515 行)**: - 349→ - **周挑战(WEEKLY CHALLENGE)**: - 350→ - `BeaconWeeklyChallenge` 接口(weekKey YYYY-Www / type / difficulty 强制 anomaly|singular / goal / rewardInsight / rewardContact / seed / title / desc) - 351→ - `BeaconWeeklyProgress` 接口(weekKey / progress / startedAt / completedAt / claimed / durationSec) - 352→ - `getWeekKey(now)`:ISO 8601 周键(周一为起点,含首个周四的周为第一周) - 353→ - `weekKeyToSeed`:FNV-1a 哈希 - 354→ - `generateWeeklyChallenge(now)`:基于 weekKey 种子确定性生成,难度加权 anomaly 60% / singular 40%,goal 为日基准 ×3-5 倍(decode 18-75 / expedition 3-15 / pulse 60-245 / boss 3-12 / insight 120-595) - 355→ - `loadWeeklyProgress` / `saveWeeklyProgress` / `addWeeklyProgress` / `claimWeeklyReward`(推送排行榜,entry 加 `isWeekly: true`) - 356→ - `msUntilNextWeek(now)`:距离下周一 UTC 0 点的毫秒数 - 357→ - `BEACON_WEEKLY_KEY = "echo-nexus-beacon-weekly-v1"` - 358→ - **信标链(BEACON CHAIN)**: - 359→ - `BeaconChainState` 接口(lastCompletedDateKey / currentStreak / longestStreak / totalCompletions / graceUsed / milestonesClaimed) - 360→ - `BEACON_CHAIN_KEY = "echo-nexus-beacon-chain-v1"` - 361→ - `BEACON_CHAIN_MILESTONES = [3, 7, 14, 30]` 常量 - 362→ - `BEACON_CHAIN_REWARDS`:4 个里程碑(三日谐振 +50/+5 / 七日回响 +120/+12 / 半月星潮 +280/+28 / 满月飞升 +680/+68) - 363→ - `loadChainState` / `saveChainState`(每次返回新对象避免引用共享) - 364→ - `recordChainCompletion(dateKey)` 核心逻辑:同日重复忽略;次日 streak++;隔一天 miss 且 graceUsed<1 时续命 streak++ graceUsed++;其他断链 streak=1 graceUsed=0;返回 `{ state, newMilestones }` - 365→ - `claimChainMilestone(milestone)` / `getNextMilestone(streak)` / `getChainProgress(streak)`(返回 current/next/prev/progressPct 用于 UI) - 366→ - `dateKeyToTimestamp` / `dateKeyDiffDays` 工具函数 - 367→ - `BeaconScoreEntry` 新增可选 `isWeekly?: boolean` 字段 - 368→- **扩展 `src/store/gameStore.ts`**: - 369→ - import 扩展:generateWeeklyChallenge / loadWeeklyProgress / loadChainState / addWeeklyProgress / recordChainCompletion / claimWeeklyReward / claimChainMilestone / getTodayKey + 类型 - 370→ - `trackBeacon(type, delta)` 函数签名从 `boolean` 改为 `{ dailyJustCompleted, weeklyJustCompleted, newChainMilestones }`: - 371→ - 日挑战类型匹配且未完成 → addBeaconProgress;若刚完成 → recordChainCompletion(getTodayKey()) 收集 newMilestones - 372→ - 周挑战类型匹配且未完成 → addWeeklyProgress - 373→ - 所有调用点(pulse/clickNode/autoDecodeTick/resolveCurrentNode 等 7 处)原本就忽略返回值,向后兼容 - 374→ - 新增 action `claimWeeklyBeacon`:调用 claimWeeklyReward → 发放 insights + contact 到 state,返回 `{ rewardInsight, rewardContact, score }` - 375→ - 新增 action `claimChainReward(milestone)`:前置校验 currentStreak 与 milestonesClaimed,调用 claimChainMilestone → 发放奖励,返回 `{ rewardInsight, rewardContact, label, ok }` - 376→ - GameActions 接口同步新增两个 action 签名 - 377→- **重写 `src/components/game/BeaconPanel.tsx`(301 → 638 行)**: - 378→ - 顶部头部 + 每日挑战卡片(保留 v0.5 完整功能 + 按钮主题色随难度色淡显) - 379→ - **新增周挑战区块**(fuchsia 主题):标题"周挑战 · WEEKLY" + weekKey + 倒计时 + 挑战卡片(类型/难度标签/标题/描述/进度条/奖励/领取按钮 emerald 主题)+ weekly-glow 动画 - 380→ - **新增信标链区块**(amber→rose 渐变):标题"信标链 · CHAIN" + 大字号 streak(chain-streak-text 渐变流动动画)+ 今日完成状态徽章 + 4 个里程碑节点(w-12 h-12 圆形,claimed=emerald/✓,reachable=rose 脉冲动画+领取按钮,inProgress=amber 半亮,未到达=muted 灰)+ 节点间连线(背景灰 + 已达成部分 amber→rose 渐变 + 辉光,基于 prev→next 插值定位)+ 进度条 + 底部统计(最长/累计/续命状态) - 381→ - 桌面端 lg:grid-cols-2 让周挑战+信标链并排,移动端单列 - 382→ - 排行榜条目区分日/周:周挑战 entry 显示 "WEEK" 徽章 + fuchsia 高亮背景 - 383→ - 空状态提示扩展为"完成今日或本周信标即可登榜" - 384→ - 4 个新动画:weekly-glow / chain-milestone-pulse / chain-streak-flux(背景渐变流动) - 385→- **QA 验证**: - 386→ - `bun run lint` 零错误(每次修改后均验证) - 387→ - dev.log 全程无错误,所有编译 < 300ms - 388→ - **信标链逻辑测试**(bun 直接运行 TS,5 个场景全 PASS): - 389→ 1. 昨日 streak=1 → 今日完成 → streak=2 ✅ - 390→ 2. 同日重复完成 → 忽略 ✅ - 391→ 3. 明日完成 → streak=3,无需 grace ✅ - 392→ 4. 隔一天 miss(lastCompleted + 2 天)→ streak=4,graceUsed=1 ✅ - 393→ 5. 再次 miss(grace 已用)→ 断链 streak=1,graceUsed=0 ✅ - 394→ - **UI 集成测试**(agent-browser): - 395→ - localStorage 模拟设置 chain state(lastCompletedDateKey=yesterday, currentStreak=1)→ reload → UI 正确显示 streak=1 - 396→ - 设置 streak=3 + milestonesClaimed=[] → milestone 3 节点显示 rose 脉冲动画 + "领取"按钮 - 397→ - 点击"领取"→ milestonesClaimed=[3] + Toast "✦ 三日谐振 已领取 +50 洞见 · +5.0 接触" + 按钮消失 ✅ - 398→ - **VLM 视觉评分**(目标 ≥7/10): - 399→ - 首屏截图:8/10(四色一致、布局合理、信标链清晰) - 400→ - 里程碑可领取状态:8/10(amber→rose 渐变醒目、里程碑层次分明、fuchsia 主题清晰) - 401→ - 重置后干净状态:8/10(WEEKLY 字体对比度可优化,但整体可读性强) - 402→ - **不破坏现有功能**:日挑战进度/领奖/排行榜流程全部保留并工作正常 - 403→ - 404→Stage Summary: - 405→- ✅ beacon.ts 扩展完成(358 → 873 行):周挑战 + 信标链全部函数与类型 - 406→- ✅ gameStore.ts 集成完成:trackBeacon 升级为对象返回值 + claimWeeklyBeacon + claimChainReward 两个新 action - 407→- ✅ BeaconPanel.tsx UI 完整重写(301 → 638 行):周挑战 fuchsia 卡片 + 信标链 amber→rose 渐变区块 - 408→- ✅ 严格四色全息:周挑战 fuchsia、信标链 amber→rose、领取按钮 emerald、无蓝/紫色违规 - 409→- ✅ localStorage 新增 2 个独立 key(weekly + chain),不污染旧 key - 410→- ✅ 确定性:周挑战同周同种子同结果(weekKey FNV-1a 种子) - 411→- ✅ 信标链 grace 续命机制工作正常(5 个场景 bun 测试全 PASS) - 412→- ✅ lint 零错误 + dev HTTP 200 + VLM 8/10 - 413→- ✅ 现有日挑战 + 排行榜 + 领奖流程全部保留,无回归 - 414→ - 415→--- - 416→Task ID: 9 - 417→Agent: main + subagent(9-b) - 418→Task: v0.8.1 P1 双任务 — 探险 BOSS 胜率平衡 + 能量恢复 + 信标系统扩展(周挑战 + 信标链) - 419→ - 420→Work Log: - 421→- 重读工单确认 P1 两项为下一阶段优先级 - 422→- P1-a 探险平衡(main 自己做,平衡需要精细控制): - 423→ - expedition.ts: combatDifficultyScale 8→4, combatWinRateFloor 0.25→0.35, BOSS difficulty 5-7→3-5 - 424→ - 新增 computeEnergyRegenInterval(state): exp_2 -30%, exp_3 -20%, 探索力属性 -最高30%, 下限12s - 425→ - computeEnergyRegen 接受 intervalSec 参数 - 426→ - config.ts: exp_2/exp_3 描述加能量恢复说明 - 427→ - gameStore.ts tick: 传入动态间隔 - 428→ - ExpeditionPanel.tsx: 显示实际恢复速度 + '已加速'标记 - 429→- P1-b 信标系统扩展(dispatch subagent 9-b,并行完成): - 430→ - beacon.ts 358→873行: 周挑战(getWeekKey ISO 8601 + 确定性生成) + 信标链(4里程碑+grace续命) - 431→ - BeaconPanel.tsx 301→638行: 周挑战fuchsia主题 + 信标链amber→rose里程碑节点 - 432→ - gameStore.ts: trackBeacon升级 + claimWeeklyBeacon/claimChainReward actions - 433→- QA 验证: - 434→ - lint 零错误 - 435→ - 探险面板 VLM 7/10(能量条 + 恢复速度显示正常) - 436→ - 信标面板 VLM 8/10(周挑战 WEEKLY + 信标链 3/7/14/30 + 排行榜全部可见) - 437→ - subagent 5场景 bun 测试全 PASS(信标链断链/续命逻辑) - 438→- 提交 v0.8.1(commit 71ca5b4)+ 推送 main - 439→- 静态导出 + 部署 gh-pages(commit 97eecaa)→ HTTP 200 - 440→ - 441→Stage Summary: - 442→- ✅ P1-a 探险平衡完成: BOSS 基础胜率 25%→35-45%, 配合技术/属性可达 55-70% - 443→- ✅ P1-a 能量恢复: 固定45s → 动态12-45s(技术+属性加速) - 444→- ✅ P1-b 周挑战: 每周一UTC刷新, fuchsia主题, goal×3-5倍 - 445→- ✅ P1-b 信标链: 4里程碑(3/7/14/30天) + grace续命, amber→rose渐变 - 446→- ✅ 在线游玩 v0.8.1: https://gitea-pages.atdunbg.xyz/Super_Z/echo-nexus/ - 447→- 下一阶段工单: P2 UI样式细节打磨 / P2 云存档+云排行榜 / P3 全纪元叙事 - 448→ - 449→--- - 450→Task ID: v0.14-reimpl - 451→Agent: full-stack-developer - 452→Task: 放置系统 (Idle Operations) — 采矿无人机舰队 + 6放置工程 + 永久产能加成 + idle徽章 - 453→ - 454→Work Log: - 455→- 阅读 worklog.md 了解 v0.8.1 项目状态(10 大系统 + 四色全息规范 + 8 标签页架构) - 456→- 类型扩展 `src/lib/game/types.ts`: - 457→ - 新增 `IdleProjectDef` 接口(id/name/desc/durationSec/reward/minAscensions/minCrystalsPerSec/icon/color/order) - 458→ - 新增 `IdleProjectSlot` 接口(projectId/startedAt/finishesAt/remainingSec/completed) - 459→ - GameState 接口新增 4 字段:idleProjectSlots (3 槽位数组) / idleProjectHistory / idlePermanentBonus / idleStats - 460→- 新建 `src/lib/game/idle.ts` (~290 行): - 461→ - 6 个放置工程定义(idle_scan 60s / idle_refine 180s / idle_archive 600s / idle_anchor 1200s / idle_drones 1800s / idle_resonance 3600s) - 462→ - 奖励类型:crystals / insights / energy / contact / crystalsPerSecPermanent / randomFragment - 463→ - 解锁逻辑:minAscensions 与 minCrystalsPerSec 为 OR 关系(满足任一已设置条件即可) - 464→ - 工具函数:getIdleProject / getUnlockedIdleProjects / getLockedIdleProjects / isIdleProjectUnlocked / formatRemaining (12s/3m 45s/1h 12m) / formatReward / deriveMinerFleet (派生 min_1..min_5 采矿无人机,icon: ⛏️🌋🤖💠☀️,无任何采矿技术时返回休眠 min_1) - 465→ - 颜色映射:IDLE_COLOR_CLASSES (text/border/bg/bgSoft/glow/ring/dot) + COLOR_HEX (用于 inline style 绕过 Tailwind 动态类限制) - 466→- 引擎集成 `src/lib/game/engine.ts`: - 467→ - recomputeStats() 末尾 `crystalsPerSec += state.idlePermanentBonus ?? 0` (放置永久加成叠加) - 468→ - performPrestige() 清空 idleProjectSlots (中断当前周目工程),保留 idlePermanentBonus/idleProjectHistory/idleStats (跨周目永久);recomputeStats 调用传 idlePermanentBonus - 469→ - createInitialState() 新增 4 字段默认值 - 470→- Store 集成 `src/store/gameStore.ts` (核心改造): - 471→ - 新增 4 action:startIdleProject (校验+写入槽位) / cancelIdleProject (置 null) / claimIdleProject (应用奖励 + permBonus 累加 + randomFragment 解锁 + history 追加 + Toast 提示) / tickIdleProjects (每 tick 更新 remainingSec,归零标记 completed) - 472→ - **persist migrate 函数**:persist 配置增加 `version: 1` + `migrate` 函数自动补全 4 个 idle 字段,旧存档加载不崩溃 - 473→ - 14 处 syncStats 调用全部更新为传入 `idlePermanentBonus: s.idlePermanentBonus ?? 0`,确保 recomputeStats 计算时纳入永久加成 - 474→ - init() 中也补全 4 个 idle 字段(defense in depth) - 475→- 主循环 `src/hooks/useGameLoop.ts`:新增 tickIdleProjects 选择器,setInterval 回调中 autoDecodeTick 后调用,visibilitychange 也调用 - 476→- 新建 `src/components/game/IdleOperationsPanel.tsx` (~490 行): - 477→ - 4 section 在 max-h-[520px] overflow-y-auto 容器中: - 478→ 1. 放置收益概览:3 stat tiles (放置产能/永久加成/完成工程) + 离线效率 progress bar + 累计放置晶体统计 - 479→ 2. 采矿无人机舰队:grid 展示 deriveMinerFleet,每卡 emoji+name+Lv+output/s+状态点 (active=emerald ping 脉冲, dormant=灰) - 480→ 3. 放置工程槽位:3 张卡(空槽=虚线占位,运行中=大字号倒计时+自定义进度条+取消按钮,已完成=奖励预览+领取按钮带 glow) - 481→ 4. 可派遣工程:2 列 grid 展示已解锁工程(icon+name+duration+desc+reward+3 数字派遣按钮 1/2/3),下方列出未解锁工程及解锁条件 - 482→ - useShallow 订阅 + 本地 now state 每 1s 刷新倒计时 - 483→ - 严格 4 色全息,自定义 fuchsia 滚动条 - 484→- 新建 `src/components/game/IdleStatusBadge.tsx` (~100 行): - 485→ - 紧凑徽章 h-8 px-2.5 text-[11px]:脉冲点 (emerald 若 cps>0) + "放置中 +X/s" (或 "休眠中") - 486→ - Tooltip 悬停展示分解:基础产能/永久加成/运行中工程/待领取工程数 - 487→ - data-tut="idle-status-badge" 锚点 + onClick 切换到放置 tab - 488→- 新建 `src/components/game/IdleProjectBar.tsx` (~90 行): - 489→ - 晶体球下方细长进度条,最多 3 个工程 mini pill `[icon] name 12s ▓▓▓░░` - 490→ - 完成时显示 "✓ 待领取" + glow 辉光;无工程返回 null - 491→ - 进度条颜色用 inline style (COLOR_HEX) 控制,每 1s 刷新倒计时 - 492→- 主页面集成 `src/app/page.tsx`: - 493→ - 引入 3 新组件 + Clock 图标 + getUnlockedIdleProjects - 494→ - 新增 activeTab/tabInited state + controlled Tabs (`value={activeTab} onValueChange={setActiveTab}`) - 495→ - 挂载后 useEffect 一次性设置默认 tab:hasActiveExpedition→"expedition",hasPendingPerk→"constellation",否则→"idle" - 496→ - 版本号 v0.8 → v0.14 - 497→ - Header 在 StarTideIndicator 后加 IdleStatusBadge - 498→ - 左侧 CrystalOrb 后加 IdleProjectBar - 499→ - TabsList grid-cols-8 → grid-cols-9,新增 value="idle" 的 TabsTrigger 作为第一个 tab(Clock 图标,emerald 主题,待领取时显示数量红点) - 500→ - 新增 TabsContent value="idle" 渲染 IdleOperationsPanel - 501→ - 新增 idle 相关 goal 提示(高优先级):待领取 > 0 → "✦ 放置工程已完成 X 项,请前往「放置」标签领取奖励";全空+有解锁+时长>60s → "「放置」标签可派遣工程项目,离线自动产出" - 502→ - StatsPanel 新增 3 行:放置永久加成 / 完成放置工程 / 放置产出晶体 - 503→- 教程更新 `src/lib/game/tutorial.ts`:TUTORIAL_STEPS 在 decode 与 tech 之间插入新步骤 (id="idle", target="tab-idle", 标题"③ 放置工程 · 离线产出"),原 tech/expedition/prestige 编号顺延为 ④⑤⑥ - 504→- 写入 `agent-ctx/v0.14-reimpl-full-stack-developer.md` 工作记录 - 505→- 最终验证: - 506→ - lint 零错误零警告 ✅ - 507→ - dev 服务器 HTTP 200,编译 < 250ms ✅ - 508→ - agent-browser 烟雾测试全流程通过(默认 idle tab + 头部徽章 + 4 section + 派遣→倒计时→完成→领取→永久加成应用) ✅ - 509→ - 510→Stage Summary: - 511→- ✅ v0.14 放置系统 (Idle Operations) 全部完成 - 512→- ✅ 新增 4 文件(idle.ts 290行 + IdleOperationsPanel.tsx 490行 + IdleStatusBadge.tsx 100行 + IdleProjectBar.tsx 90行),修改 6 文件(types/engine/gameStore/useGameLoop/page/tutorial) - 513→- ✅ 6 个放置工程:勘探扫描 / 精炼校准 / 碎片整理 / 锚点部署 / 无人机扩编 / 谐振标定 - 514→- ✅ 3 个槽位并行 + 6 工程解锁逻辑 (OR) + 永久产能加成 (permBonus 累加跨周目) - 515→- ✅ 采矿无人机舰队派生 (min_1..min_5) + 休眠状态显示 - 516→- ✅ 头部 idle 徽章 + 默认放置 tab + 晶体球下方进度条 (5 秒内可见三要素) - 517→- ✅ persist migrate 函数 (version 1) 旧存档兼容 - 518→- ✅ 14 处 syncStats 调用全部传 idlePermanentBonus,recomputeStats 永久加成不丢失 - 519→- ✅ 飞升时清空活跃槽位但保留永久加成 / 历史 / 统计 - 520→- ✅ 严格 4 色全息 (emerald/rose/amber/fuchsia),零蓝/靛 - 521→- ✅ lint 零错误 + HTTP 200 + agent-browser 全流程烟雾测试通过 - 522→- ✅ 9 标签页 (新增"放置"为第一个) + 巡航按钮 + 教程新增 idle 步骤 - 523→ - 524→--- - 525→Task ID: v0.14-release - 526→Agent: 主控 Z.ai Code - 527→Task: v0.14 放置系统发布 — README/docs 更新 + 端到端验证 + git commit - 528→ - 529→Work Log: - 530→- 发现项目被环境重置回 v0.8.1 状态(v0.14 代码全部丢失,worklog 也被截断到 447 行) - 531→- 重新委派 full-stack-developer subagent (v0.14-reimpl) 在 v0.8.1 代码库上重新实现放置系统 - 532→- subagent 交付 10 个文件(4 新建 + 6 修改),lint 零错误,agent-browser smoke test 通过,git commit 17820b1 - 533→- 主控独立验证: - 534→ * git log 确认 commit 17820b1 存在 ✓ - 535→ * bun run lint 零错误 ✓ - 536→ * HTTP 200 ✓ - 537→ * agent-browser: v0.14 标签 ✓ + 放置中徽章 ✓ + 默认 idle tab ✓ + 4 段面板完整 ✓ - 538→ * 派遣 idle_scan → slot 0 显示 60s 倒计时 ✓ - 539→ * 快进完成 → 领取 → insights 79→87 (+8) + projectsCompleted 0→1 ✓ + 槽位清空 ✓ - 540→- 主控更新 README.md → v0.14:突出 12 大系统、3 层 idle 节奏、灵感来源、健康设计声明 - 541→- 主控创建 docs/11-放置系统-v0.14.md:完整设计文档 - 542→- 主控创建每 15 分钟触发的 webDevReview cron job - 543→ - 544→Stage Summary: - 545→ - 546→## v0.14 放置系统已重新上线并 git commit 保固 - 547→ - 548→### 核心交付 - 549→- **3 层 idle 节奏**:被动产能(无人机舰队)+ 放置工程(6 种 × 3 槽位)+ 离线收益(8h cap) - 550→- **始终可见的 idle 元素**:Header「放置中 +X/s」徽章 + 默认 tab = 放置 + 晶体球下方工程进度 pill - 551→- **永久产能加成**:通过工程奖励累积,集成进 recomputeStats,跨周目保留 - 552→- **存档安全**:persist version 1 + migrate 函数 + 4 字段 fallback,老存档零崩溃 - 553→ - 554→### Git 保固 - 555→- commit 17820b1: "v0.14: 放置系统 (Idle Operations) — 采矿无人机舰队 + 6放置工程 + 永久产能加成 + idle徽章" - 556→- 即使环境再次重置,git checkout 17820b1 即可恢复 - 557→ - 558→### 验证结果 - 559→- ✅ bun run lint 零错误零警告 - 560→- ✅ HTTP 200,dev.log 无运行时错误 - 561→- ✅ agent-browser 端到端 smoke test 全通过(派遣→倒计时→完成→领取→insights+8) - 562→- ✅ Sticky footer/header 验证通过 - 563→ - 564→### 未解决问题 / v0.15 优先项 - 565→1. 🟡 永久产能加成可能需要飞升次数 softcap(防 idle_resonance ×N 滚雪球) - 566→2. 🟡 idleProjectHistory / crystalsFromIdle 已在 store 累加但 UI 未展示 - 567→3. 🟡 缺「批量领取全部」按钮 + 工程完成 toast 通知 - 568→4. 🟡 遗迹图鉴多层文本(Dark Souls 式 loreFragments)—— v0.15 计划 - 569→5. 🟡 解码阈值释放叙事碎片 + 星潮三频段切换 —— v0.16 计划 - 570→6. 🟡 健康设计:每日软上限 + "星核建议休眠"提醒 —— v0.17 计划 - 571→ \ No newline at end of file diff --git a/tool-results/read_1782303134532_3b27027251cf.txt b/tool-results/read_1782303134532_3b27027251cf.txt deleted file mode 100755 index 7d664b109..000000000 --- a/tool-results/read_1782303134532_3b27027251cf.txt +++ /dev/null @@ -1,571 +0,0 @@ - 1→ 1→# 回响星核 / Echo Nexus — 开发工作日志 - 2→ 2→ - 3→ 3→> 持续性任务的工作交接文档。每个阶段更新,避免进度丢失。 - 4→ 4→ - 5→ 5→--- - 6→ 6→ - 7→ 7→## 一、项目当前状态描述 / 判断 - 8→ 8→ - 9→ 9→### 概况 - 10→ 10→- **项目**:回响星核 / Echo Nexus — 深空考古放置策略游戏 - 11→ 11→- **一句话**:自治无人机采矿 → 解码记忆晶体(原创共振谜题)→ 拼凑碎片叙事 → 飞升多周目。差异化:解码谜题驱动叙事涌现,全息晶体 + 深空粒子美学。 - 12→ 12→- **当前版本**:**v0.7**(CrystalOrb Canvas 粒子系统 + 角色属性系统) - 13→ 13→- **在线游玩**:https://gitea-pages.atdunbg.xyz/Super_Z/echo-nexus/ - 14→ 14→- **仓库**:https://git.atdunbg.xyz/Super_Z/echo-nexus - 15→ 15→- **技术栈**:Next.js 16 (App Router) + TypeScript + Tailwind + shadcn/ui + Canvas 2D + Zustand + Web Audio API - 16→ 16→- **定时任务**:每 15 分钟一次 `webDevReview`(`fixed_rate` + `"900"` 秒,priority=10,job_id 228266)。正常完成不会被删除,无需自持续机制。 - 17→ 17→ - 18→ 18→### 状态判断 - 19→ 19→- dev 服务器运行正常(HTTP 200,编译 < 250ms) - 20→ 20→- v0.7 两大新功能已完成:CrystalOrb Canvas 粒子系统升级(VLM 8/10)+ 角色属性系统(VLM 7/10) - 21→ 21→- 系统总体稳定,无阻塞性 bug,可继续推进新功能 - 22→ 22→ - 23→ 23→### 已完成版本里程碑(精简) - 24→ 24→| 版本 | 核心内容 | - 25→ 25→|------|---------| - 26→ 26→| v0.1 | MVP:放置采矿 + 解码共振谜题 + 技术树 + 记忆图谱 + 飞升 + 统计 | - 27→ 27→| v0.1.1 | 解码可解性修复(路径构造法生成器,100% 可解率)| - 28→ 28→| v0.2 | 遗迹探险肉鸽系统(能量/探险力/6 节点/程序化路径/BOSS)| - 29→ 29→| v0.2.1 | 程序化音频(15 种音效)+ 14 项成就 + 视觉打磨 | - 30→ 30→| v0.3 | 星潮事件系统(6 种星潮 + 全屏叠层 + 程序化音效)| - 31→ 31→| v0.3.1 | 星图天文台元进程(6 类 × 3 = 18 天赋 + 飞升后 3 选 1 draft + Canvas 星图)| - 32→ 32→| v0.4 | 回响编年史(5 纪元循环命名 + 模板化叙事 + 时间轴 UI + 6 项新成就 + 飞升 BUG 修复)| - 33→ 33→| v0.5 | 深空信标(每日挑战 + 本地排行榜 Top20 + 7 标签页 + 编年史历史 BUG 修复)| - 34→ 34→| v0.5.1 | 静态部署 Gitea Pages + 新手教程系统(7 步聚光灯引导)+ UI 间距优化 | - 35→ 35→| v0.5.2 | 离线收益报告弹窗 + 标签页 UI 重设计 + persist rehydrate 竞态修复 | - 36→ 36→| v0.6 | 深空巡航 Canvas 2D 实时玩法 + UI 偏移重叠修复 | - 37→ 37→| **v0.7** | **CrystalOrb Canvas 粒子系统升级 + 角色属性系统(四维)** | - 38→ 38→ - 39→ 39→### 核心系统清单(8 大系统) - 40→ 40→1. **放置采矿** — 中央晶体球 + 主动脉冲 + 连击加成(`CrystalOrb.tsx`) - 41→ 41→2. **谐振解码** — 原创路径谜题,路径构造法 100% 可解(`DecodeArray.tsx` + `engine.ts`) - 42→ 42→3. **技术树** — 三层科技解锁,提升产能/解码/探险(`TechTree.tsx`) - 43→ 43→4. **遗迹探险** — 肉鸽系统,6 种节点 + 程序化路径 + BOSS(`ExpeditionPanel.tsx` + `expedition.ts`) - 44→ 44→5. **星潮事件** — 6 种动态事件,为放置循环注入变化(`StarTide.tsx` + `starTide.ts`) - 45→ 45→6. **星图天文台** — 飞升后 3 选 1 天赋 draft,跨周目永久生效,18 个天赋(`ConstellationPanel.tsx` + `constellation.ts`) - 46→ 46→7. **回响编年史** — 跨周目叙事时间轴,5 纪元循环命名(`ChronicleDialog.tsx` + `chronicle.ts`) - 47→ 47→8. **深空信标** — 每日挑战 + 本地排行榜 Top20(`BeaconPanel.tsx` + `beacon.ts`) - 48→ 48→9. **深空巡航** — Canvas 2D 实时玩法,飞船操控/陨石躲避/星门通关(`CruiseMode.tsx` + `cruise.ts`)【v0.6】 - 49→ 49→10. **角色属性系统** — 四维属性(探索力/智慧/勇气/灵感),飞升获得属性点,影响所有系统(`AttributesPanel.tsx` + `attributes.ts`)【v0.7 新增】 - 50→ 50→ - 51→ 51→--- - 52→ 52→ - 53→ 53→## 二、当前目标 / 已完成的修改 / 验证结果 - 54→ 54→ - 55→ 55→### v0.7 CrystalOrb Canvas 粒子系统升级(已完成) - 56→ 56→**动机**:P0 优先级 — 将中央晶体球从 CSS 动画升级为 Canvas 粒子视觉盛宴,提升视觉震撼。 - 57→ 57→ - 58→ 58→**重写文件**:`src/components/game/CrystalOrb.tsx` - 59→ 59→- 从纯 CSS 动画升级为 Canvas 2D 粒子系统 - 60→ 60→- **多层粒子**: - 61→ 61→ - 环绕能量粒子(3层14/10/8个,emerald/fuchsia/rose三色,不同速度方向环绕晶核,带拖尾) - 62→ 62→ - 环境星尘(40个,缓慢漂移 + 闪烁) - 63→ 63→ - 点击爆发粒子(10-30个径向发散,颜色随连击等级变色) - 64→ 64→ - 冲击波环(点击触发1-3层错峰扩散,连击≥3/5解锁更多层) - 65→ 65→- **晶核升级**:径向渐变辉光 + 3层六边形纹理旋转 + 高光 + 鼠标视差偏移 - 66→ 66→- **呼吸动画**:晶核 + 外层辉光随时间呼吸(sin波) - 67→ 67→- **进度环**:SVG渐变环(emerald→fuchsia→rose)保留 - 68→ 68→- **交互保留**:点击脉冲、连击Toast、浮动数字、音效、`data-tut="crystal-orb"` 锚点 - 69→ 69→- **性能**:DPR cap 2,ResizeObserver 自适应,requestAnimationFrame 60fps - 70→ 70→ - 71→ 71→**QA 验证**(agent-browser + VLM): - 72→ 72→- VLM 视觉评分 **8/10**:晶体球清晰可见,多层辉光,环绕粒子动态自然,无渲染问题 - 73→ 73→- 点击交互:冲击波环 ✅ + 爆发粒子 ✅,评分 7/10 - 74→ 74→- lint 零错误;HTTP 200 - 75→ 75→ - 76→ 76→### v0.7 角色属性系统(已完成) - 77→ 77→**动机**:P1 优先级 — 增加RPG深度,四维属性影响所有游戏系统。 - 78→ 78→ - 79→ 79→**新增文件**: - 80→ 80→- `src/lib/game/attributes.ts`(~330行逻辑层):CharacterAttributes/AttributeProgress 类型、ATTRIBUTE_CONFIG 元信息、getAttributeBonus 两区加成公式(0-50线性+0.5%/点,50-100递减+0.2%/点)、getAllBonuses 12项修饰器、levelUpCheck 自动升级、computePrestigeAttrPoints、migrateAttributes 旧存档兼容 - 81→ 81→- `src/components/game/AttributesPanel.tsx`(~330行 UI):四维卡片网格(小屏2×2/大屏1×4)、图标+中英文名+Lv.badge+数值/100+加成%+经验条+影响列表+分配按钮、顶部待分配点数badge闪烁、底部总览+12项修饰器明细 - 82→ 82→ - 83→ 83→**修改文件**: - 84→ 84→- `types.ts`:GameState 新增 attributes/attributeProgress/pendingAttrPoints - 85→ 85→- `config.ts`:INITIAL_STATE 补全默认值 - 86→ 86→- `engine.ts`:recomputeStats 聚合属性加成;performPrestige 发放属性点(ascensions×2+1) - 87→ 87→- `gameStore.ts`:新增 allocateAttribute/gainAttributeExp actions;pulse/clickNode/autoDecodeTick/resolveCurrentNode/grantCruiseReward/tickTide 全部接入属性经验;init() 调用 migrateAttributes - 88→ 88→- `achievements.ts`:新增 ach_attr_total_50 + ach_attr_max_100 两项成就 - 89→ 89→- `page.tsx`:grid-cols-7→8,新增「角色」标签页(User图标),红点提示,StatsPanel新增5行属性行,版本号 v0.6→v0.7 - 90→ 90→ - 91→ 91→**四维属性设计**: - 92→ 92→- 探索力(emerald):探险力+X%/巡航飞船速度+X% - 93→ 93→- 智慧(fuchsia):解码步数+X/洞见+X%/自动解码周期-X% - 94→ 94→- 勇气(amber):探险生命+X/BOSS胜率+X%/巡航护盾+X - 95→ 95→- 灵感(rose):接触率+X/星潮概率+X/脉冲连击+X% - 96→ 96→ - 97→ 97→**QA 验证**(agent-browser + VLM): - 98→ 98→- VLM 视觉评分 **7/10**(面板)/ **8/10**(待分配状态):四维卡片可见、数值/等级/加成/经验条齐全、四色全息 - 99→ 99→- 功能验证:setState pendingAttrPoints=5 → 显示5点待分配 → 点击分配按钮 → exploration 0→1, pending 5→4 ✅ - 100→ 100→- lint 零错误;HTTP 200 - 101→ 101→ - 102→ 102→--- - 103→ 103→ - 104→ 104→### v0.6 深空巡航 Canvas 2D 实时玩法(历史记录) - 105→ 105→**动机**:回应用户反馈「玩法太单调(全是点点点)」+「视觉震撼不足」——首次引入实时操作玩法。 - 106→ 106→ - 107→ 107→**新增文件**: - 108→ 108→- `src/lib/game/cruise.ts`(~520 行逻辑层) - 109→ 109→ - 类型系统:CruiseEntity 联合类型(ship/asteroid/storm/crystal/insight/beacon/stargate/particle 共 8 种) - 110→ 110→ - mulberry32 + FNV-1a 种子化 RNG(`cruiseSeed(level)`) - 111→ 111→ - `generateLevel`:程序化生成陨石带(8+lvl×2)、虚空风暴(1+lvl/2)、晶体碎片(5+lvl)、洞见光球(2+lvl/3)、信标(1+lvl/4)、星门 - 112→ 112→ - `updateCruise`:物理推进 + 碰撞检测 + 粒子系统 + 震动 + 600ms 无敌帧 - 113→ 113→ - `computeRewards`:crystals=碎片×(8+lvl×2)、insights=洞见×(2+lvl×0.5)、contact=信标×(1.5+lvl×0.3);通关×1.5、失败×0.5 - 114→ 114→ - 独立 localStorage `echo-nexus-cruise-v1`:最高分/累计通关/最高关卡/累计奖励/最近 20 局 - 115→ 115→ - 116→ 116→- `src/components/game/CruiseMode.tsx`(~830 行渲染层) - 117→ 117→ - 全屏 fixed inset-0 z-50 Canvas,DPR cap 2,resize 监听 - 118→ 118→ - 3 层视差星空:远(100星)/中(60星)/近(30星),独立漂移 + 飞船速度视差 + 闪烁 - 119→ 119→ - 8 种实体全部 ctx.shadowBlur 辉光绘制 - 120→ 120→ - 粒子系统:尾焰/收集/碰撞/烟花,上限 200 - 121→ 121→ - 屏幕震动 280ms 衰减;低护盾(<30%)红色边框脉冲 - 122→ 122→ - HUD(HTML 叠层,glass+backdrop-blur,80ms 节流):护盾/能量/分数/用时/收集计数 - 123→ 123→ - 控制:桌面 WASD/方向键 8 方向 + 移动端虚拟摇杆 + P 暂停 + Esc 退出 + 失焦自动暂停 - 124→ 124→ - 阶段:ready → playing → won/lost(结算明细 + 奖励 + 下一关/重试/返回) - 125→ 125→ - 126→ 126→**修改文件**: - 127→ 127→- `src/app/page.tsx`:header 新增 amber 主题「巡航」按钮(Navigation 图标)+ cruiseOpen state + 条件渲染 CruiseMode - 128→ 128→- `src/store/gameStore.ts`:新增 `grantCruiseReward({crystals?, insights?, contact?})` action(crystals 受 crystalCap 限制,contact 受 100 上限) - 129→ 129→- 版本号 v0.5.2 → v0.6 - 130→ 130→ - 131→ 131→**UI 偏移/重叠 BUG 修复**(3 处): - 132→ 132→- `DecodeArray.tsx`:移除 className 冲突的 `-translate-x-1/2 -translate-y-1/2`,统一用 inline transform 定位+缩放 - 133→ 133→- `AchievementsPanel.tsx`:移除内层 `max-h-[300px]`,改 `flex-1 min-h-0` 自适应父容器 - 134→ 134→- `page.tsx`:调整三个 section 的 min-h,避免小屏单列时总高度超过手机视口 - 135→ 135→ - 136→ 136→**色彩规范**:严格遵循 emerald(#34d399) / rose(#fb7185) / amber(#fbbf24) / fuchsia(#e879f9) 四色全息色谱,零蓝色/靛色 - 137→ 137→ - 138→ 138→**QA 验证**(agent-browser + VLM): - 139→ 139→- lint 零错误;dev 服务器 HTTP 200;编译 < 250ms - 140→ 140→- VLM 确认主界面:巡航按钮存在、晶体球居中、解码区正常、无重叠偏移 - 141→ 141→- VLM 确认巡航玩法:飞船可见(绿色三角形)、HUD 完整、陨石+星空背景、粒子特效(黄色尾焰+紫色辉光) - 142→ 142→- WASD 控制飞船移动,按 W 直冲星门 → 通关,奖励 +30 晶体(2×10×1.5=30 计算正确) - 143→ 143→- localStorage 正确记录 highScore/totalRuns/totalWins/bestLevel - 144→ 144→- 奖励同步 gameStore(grantCruiseReward,满仓时 cap 逻辑正确) - 145→ 145→- 退出返回主界面,7 标签页 + 晶体球 + 解码面板均完好 - 146→ 146→ - 147→ 147→--- - 148→ 148→ - 149→ 149→## 三、未解决问题或风险 / 下一阶段优先事项 - 150→ 150→ - 151→ 151→### 已知问题 / 风险 - 152→ 152→1. **dev 服务器 Turbopack 缓存偶发损坏**:`Unable to open static sorted file` + 进程被杀,需 `rm -rf .next` 重启。静态导出构建稳定可靠。 - 153→ 153→2. **定时任务调度类型**:必须用 `fixed_rate` + `"900"` 秒(15分钟),不要用 cron 表达式。`webDevReview` 类型任务正常完成不会被删除,只有抛出未捕获异常才会被标记失败。 - 154→ 154→3. **v0.3 星潮为单机版**:原计划 socket 全局事件,后续可扩展为多人同步。 - 155→ 155→4. **v0.5 信标排行榜为本地版**(纯 localStorage):升级云排行榜需后端 API。 - 156→ 156→5. **v0.5 每日挑战仅 1 个/天**:可加入"周挑战"或"信标链"(连续完成 N 天奖励)。 - 157→ 157→6. **探险 BOSS 战胜率较低**:基础探索力 10 vs BOSS 难度 5-7,胜率约 25%,需玩家投资探险技术。 - 158→ 158→7. **探险能量恢复较慢**:45s/点,可加技术提升恢复速度。 - 159→ 159→8. **编年史上限 50 条**:超过自动丢弃最早的;v0.4 之前飞升无回填(空状态有提示)。 - 160→ 160→9. **5 纪元叙事为模板化生成**:v0.5+ 将加入手写剧情节点。 - 161→ 161→10. ~~**CrystalOrb 仍为 CSS 动画**~~ → ✅ **v0.7 已完成 Canvas 粒子系统升级**。 - 162→ 162→ - 163→ 163→### 下一阶段优先级(v0.7 后修正版) - 164→ 164→> 工单修正:原 P0 CrystalOrb Canvas + P1 角色属性两项均已在 v0.7 完成,现重新排序。 - 165→ 165→ - 166→ 166→1. **🔴 P0 — 巡航玩法大增强(v0.8 主线)**:BOSS 战关卡(每 5 关一个 BOSS,三阶段攻击模式 + 飞船射击系统)/ 道具掉落(5 种 powerup:护盾/能量/急速/超载/磁吸)/ 事件选择节点(每关通关后 3 选 1 强化牌,10 种强化)。这是当前最高优先级,能显著提升巡航玩法的深度与可玩性。 - 167→ 167→2. **🟠 P1 — 探险 BOSS 战胜率平衡 + 探险能量恢复**:修复已知问题 6/7(探险 BOSS 胜率 25% 偏低,能量 45s/点 偏慢),提升探险体验。 - 168→ 168→3. **🟠 P1 — 信标系统扩展**:周挑战 / 信标链(连续完成 N 天奖励),修复已知问题 5。 - 169→ 169→4. **🟡 P2 — UI 样式细节打磨**:标签页图标/文字比例、视觉层次强化、移动端响应式进一步优化、CrystalOrb 周边留白优化(VLM 指出主界面左侧有空白)。 - 170→ 170→5. **🟡 P2 — 云存档 + 云排行榜**:v0.5+ 后端 API(需评估是否引入 mini-service)。 - 171→ 171→6. **🟢 P3 — 全 5 纪元手写叙事节点**:替换模板化 lore。 - 172→ 172→7. **🟢 P3 — socket 多人同步星潮**:全局事件共享。 - 173→ 173→ - 174→ 174→### 定时任务(fixed_rate · 900s) - 175→ 175→- **当前 job_id**: **228357**(2026-06-24 13:25 重建) - 176→ 176→- **配置**: `fixed_rate` + `"900"`(15分钟间隔),`priority=10`(高优先级),`payload.kind=webDevReview`,`tz=Asia/Shanghai` - 177→ 177→- **任务名**: Echo Nexus - 任务审查与持续迭代 - 178→ 178→- **任务描述强制要求**:每次触发 MUST 先完整阅读 worklog.md 工单,以工单为准,不凭记忆工作 - 179→ 179→- **正常完成不会被删除**,只有未捕获异常会标记失败。若任务列表为空,需用 cron create 重建(schedule.kind=fixed_rate, expr="900", priority=10, payload.kind=webDevReview)。 - 180→ 180→- **job_id 历史**: 227909(失效) → 228130(失效) → 228172(失效) → 228186(失效) → 228218(删除重建) → 228236(cron版,删除重建) → 228266(fixed_rate,失效) → 228329(失效) → **228357(当前,重建)** - 181→ 181→ - 182→ 182→--- - 183→ 183→ - 184→ 184→## 定时任务描述模板(webDevReview · fixed_rate) - 185→ 185→ - 186→ 186→> 重建参数:schedule.kind=`fixed_rate`, schedule.expr=`"900"`, params.priority=`10`, payload.kind=`webDevReview` - 187→ 187→ - 188→ 188→``` - 189→ 189→请先判断当前项目状态,使用 agent-browser 进行必要测试和 QA,然后自主选择本轮最合适的工作重点并继续开发。 - 190→ 190→ - 191→ 191→要求: - 192→ 192→1. 开始任务时查看 /home/z/my-project/worklog.md 了解实际进展,拆解详细todo - 193→ 193→2. 使用 agent-browser 进行测试和QA,有bug优先修复 - 194→ 194→3. 如果稳定,自主提出新需求继续推进 - 195→ 195→4. 样式要越做细节越多!! - 196→ 196→5. 功能要越做越多!! - 197→ 197→6. 工作告一段落更新 /home/z/my-project/worklog.md - 198→ 198→7. 项目结束后一定要把工作记录写在 worklog.md 里 - 199→ 199→ - 200→ 200→交接文档建议包含三部分: - 201→ 201→- 项目当前状态描述/判断 - 202→ 202→- 当前目标/已完成的修改/验证结果 - 203→ 203→- 未解决问题或风险,建议下一阶段优先事项 - 204→ 204→``` - 205→ 205→ - 206→ 206→--- - 207→ 207→ - 208→ 208→## 历史详细版本记录(按需查阅 docs/repo/docs/) - 209→ 209→ - 210→ 210→- `04-解码系统修复-v0.1.1.md` - 211→ 211→- `05-遗迹探险系统-v0.2.md` - 212→ 212→- `06-音频与成就系统-v0.2.1.md` - 213→ 213→- `07-星潮事件系统-v0.3.md` - 214→ 214→- `08-星图天文台系统-v0.3.1.md` - 215→ 215→- `09-回响编年史系统-v0.4.md` - 216→ 216→- `10-深空信标系统-v0.5.md` - 217→ 217→ - 218→ 218→--- - 219→ 219→ - 220→ 220→Task ID: 7 - 221→ 221→Agent: full-stack-developer - 222→ 222→Task: 角色属性系统(探索力/智慧/勇气/灵感) - 223→ 223→ - 224→ 224→Work Log: - 225→ 225→- 阅读 worklog.md 了解 v0.6 项目背景,确认 8 大系统架构与四色全息色彩规范 - 226→ 226→- 新建 `src/lib/game/attributes.ts` 逻辑层(~330 行): - 227→ 227→ - 定义 CharacterAttributes/AttributeProgress/AttributeKey 类型 + ATTRIBUTE_CONFIG 元信息 - 228→ 228→ - getAttributeBonus 线性+递减两区加成公式(0-50 每 10 点 +5%,50-100 每点 +2%) - 229→ 229→ - getAllBonuses 返回 12 个修饰器(探险力/巡航速度/解码步数/洞见/自动解码周期/探险生命/BOSS 胜率/巡航护盾/接触率/星潮触发/脉冲连击/产能) - 230→ 230→ - levelUpCheck 自动跨多级 + expRequiredForLevel + computePrestigeAttrPoints + migrateAttributes 兼容 - 231→ 231→- 扩展 `src/lib/game/types.ts` GameState:新增 attributes/attributeProgress/pendingAttrPoints - 232→ 232→- 扩展 `src/lib/game/config.ts` INITIAL_STATE:补全默认值(0 数值 + 空 progress) - 233→ 233→- 扩展 `src/lib/game/engine.ts`: - 234→ 234→ - recomputeStats 末尾聚合 am.crystalsPerSecMult/insightMultAdd/contactRateMult/decodeStepsBonus - 235→ 235→ - performPrestige 保留 attributes 数值,清空 attributeProgress,发放 ascensions × 2 + 1 点属性点 - 236→ 236→ - createInitialState 每次返回全新对象避免引用共享 - 237→ 237→- 扩展 `src/store/gameStore.ts`(核心改造,10+ 处 syncStats 调用全部补 attributes 参数): - 238→ 238→ - pulse:连击 ≥3 给灵感经验 + 脉冲威力乘灵感连击加成 - 239→ 239→ - clickNode 完成:给智慧经验(tier × 2) - 240→ 240→ - autoDecodeTick:智慧经验 +1 + 自动解码周期受 am.autoDecodeIntervalMult 影响 - 241→ 241→ - startExpedition:探险力乘 am.expeditionPowerMult,生命加 am.expeditionHpBonus - 242→ 242→ - resolveCurrentNode:BOSS 节点包装 RNG 提升 +am.bossWinRateBonus 胜率;战斗胜利给勇气+探索力经验 - 243→ 243→ - grantCruiseReward:按总奖励量给探索力+勇气经验 - 244→ 244→ - tickTide:灵感 am.tideTriggerBonus 缩短星潮间隙(上限 30%) - 245→ 245→ - init() 调用 migrateAttributes 补全旧存档 - 246→ 246→ - 新增 allocateAttribute / gainAttributeExp 两个 action - 247→ 247→- 新建 `src/components/game/AttributesPanel.tsx` UI 面板(~330 行): - 248→ 248→ - 四维卡片网格(小屏 2×2,大屏 1×4) - 249→ 249→ - 每张卡片:图标 + 中英文名 + Lv.badge + 数值/100 + 加成% + 经验条 + 影响列表 + 「+分配」按钮 - 250→ 250→ - 顶部待分配点数 badge(pendingAttrPoints > 0 时 echo-pending-pulse 闪烁动画) - 251→ 251→ - 底部总等级/总加成概览 + 12 个修饰器明细 - 252→ 252→ - 4 色辉光边框(emerald/fuchsia/amber/rose),顶角光晕装饰 - 253→ 253→- 接入 `src/app/page.tsx`: - 254→ 254→ - grid-cols-7 → grid-cols-8,新增第 8 个「角色」TabsTrigger(User 图标,渐变主题) - 255→ 255→ - pendingAttrPoints > 0 时显示 rose 红点提示 - 256→ 256→ - 渲染 AttributesPanel - 257→ 257→ - StatsPanel 新增 5 行属性行(探索力/智慧/勇气/灵感/待分配点) - 258→ 258→ - 版本号 v0.6 → v0.7 - 259→ 259→- 新增 `src/lib/game/achievements.ts` 2 项成就: - 260→ 260→ - ach_attr_total_50(四维觉醒):总属性 ≥ 50 → 产能+6%/洞见+6% - 261→ 261→ - ach_attr_max_100(维度精通):任一属性 100 → 产能+12%/洞见+10% - 262→ 262→- 写入 `agent-ctx/7-full-stack-developer.md` 工作记录 - 263→ 263→- 最终验证:lint 零错误 + dev 服务器 HTTP 200 + 编译 < 250ms - 264→ 264→ - 265→ 265→Stage Summary: - 266→ 266→- ✅ 角色属性系统 v0.7 P1 全部完成 - 267→ 267→- ✅ 新增 2 文件(attributes.ts + AttributesPanel.tsx),修改 6 文件(types/config/engine/gameStore/achievements/page) - 268→ 268→- ✅ 四维属性完整接入游戏各系统(探险/解码/脉冲/巡航/星潮) - 269→ 269→- ✅ 飞升属性点公式 = ascensions × 2 + 1;经验累积自动升级(每级 10×level) - 270→ 270→- ✅ 旧存档兼容(migrateAttributes 补全字段,同步 level 与 attributes) - 271→ 271→- ✅ lint 零错误 + HTTP 200 + 编译 < 250ms - 272→ 272→- ✅ 严格四色全息(emerald/fuchsia/amber/rose),零蓝色/靛色 - 273→ 273→- ✅ 8 标签页 + 巡航按钮全部保留,data-tut 锚点未破坏 - 274→ 274→- ✅ 待分配点数时标签页 rose 红点提示 + 顶部 badge 闪烁 - 275→ 275→ - 276→ 276→--- - 277→ 277→Task ID: 8-deploy - 278→ 278→Agent: main - 279→ 279→Task: 重建定时任务 + 更新 README + 部署 gh-pages(v0.7) - 280→ 280→ - 281→ 281→Work Log: - 282→ 282→- 检查项目状态:CrystalOrb.tsx 已是 v0.7 Canvas 粒子系统(659 行),无活动 cron job - 283→ 283→- 重建定时任务:cron create fixed_rate="900" priority=10 webDevReview → job_id 228329(tz=Asia/Shanghai) - 284→ 284→- 重写 README.md: - 285→ 285→ - 顶部显著位置添加「在线游玩」链接(https://gitea-pages.atdunbg.xyz/Super_Z/echo-nexus/) - 286→ 286→ - 版本号 v0.1 → v0.7 - 287→ 287→ - 新增 10 大系统表格(含 v0.6 巡航 + v0.7 角色属性) - 288→ 288→ - 完整开发路线表(v0.1-v1.0,标记 ✅/🔜 状态) - 289→ 289→ - 新增本地开发命令 + Gitea Pages 部署指南 - 290→ 290→ - 文档索引扩展至 10 个版本文档 - 291→ 291→- 静态导出:BUILD_EXPORT=true bun run build:static → out/ 1.6M(HTTP 200 编译 4.3s) - 292→ 292→- 推送 gh-pages 分支:force push origin gh-pages(commit 8727fed "deploy: v0.7") - 293→ 293→- 提交 main:README 更新(commit 43dfc2d "docs: README 添加在线游玩链接 + v0.7 状态更新") - 294→ 294→- 验证 Gitea Pages:https://gitea-pages.atdunbg.xyz/Super_Z/echo-nexus/ → HTTP 200 ✅ - 295→ 295→- QA 验证:agent-browser 打开 http://localhost:3000/ → VLM 评分 8/10(主界面 polished,minor 空白) - 296→ 296→ - 297→ 297→Stage Summary: - 298→ 298→- ✅ 定时任务重建成功(job_id 228329,每 15 分钟自动审查迭代) - 299→ 299→- ✅ README 重写完成,网站链接显著展示,v0.7 状态完整 - 300→ 300→- ✅ gh-pages 部署成功,Gitea Pages HTTP 200 可访问 - 301→ 301→- ✅ main 分支已同步推送(README + worklog 更新) - 302→ 302→- ✅ dev 服务器健康(HTTP 200,编译 < 250ms) - 303→ 303→- ✅ CrystalOrb v0.7 Canvas 粒子系统已确认完整(659 行) - 304→ 304→- 下一步:v0.8 巡航玩法增强(BOSS 战 / 道具掉落 / 事件选择节点) - 305→ 305→ - 306→ 306→--- - 307→ 307→Task ID: 8 - 308→ 308→Agent: main - 309→ 309→Task: v0.8 巡航玩法大增强(BOSS 战 + 道具掉落 + 事件选择节点)+ 工单修正 + 审查流程重建 - 310→ 310→ - 311→ 311→Work Log: - 312→ 312→- 重读 worklog 工单,发现 v0.7 两项(CrystalOrb Canvas + 角色属性)已完成但工单"下一阶段优先级"未更新 - 313→ 313→- 修正 worklog 工单:标注 P0/P1 v0.7 已完成,重新排序下一阶段优先级(P0=巡航增强, P1=探险平衡/信标扩展, P2=UI打磨/云存档, P3=叙事/多人) - 314→ 314→- 重建审查流程 cron job:fixed_rate=900s, priority=10, webDevReview, tz=Asia/Shanghai, job_id=228357 - 315→ 315→ - 任务描述强制要求:每次触发 MUST 先完整阅读 worklog.md 工单,以工单为准,不凭记忆工作 - 316→ 316→- 发现之前被中断的 v0.8 subagent 已写入 2037 行代码(cruise.ts +1487 / CruiseMode.tsx +858 / page.tsx +2) - 317→ 317→- 修复 2 个 lint 错误(react-hooks/set-state-in-effect): - 318→ 318→ - isTouch: useState(false) + useEffect(setIsTouch) → useState initializer - 319→ 319→ - stats: useState(null) + useEffect(setStats(loadCruiseStats)) → useState initializer - 320→ 320→- QA 验证(agent-browser + VLM): - 321→ 321→ - 主界面 VLM 7/10:v0.8 版本号 ✅ + 巡航按钮 ✅ - 322→ 322→ - 巡航 READY 阶段 VLM 8/10:BOSS 战说明 ✅(每5关BOSS战/击败BOSS)+ 射击控制说明 ✅(空格/右下角射击按钮) - 323→ 323→ - 巡航第1关游戏 VLM 8/10:飞船(绿色三角)✅ + HUD(护盾/能量/分数/计时)✅ + 陨石/收集物/能量场 ✅ + 无渲染错误 - 324→ 324→- 提交 v0.8(commit 494bc5f)+ 推送 main - 325→ 325→- 静态导出 BUILD_EXPORT=true → out/ 1.6M(编译 4.1s) - 326→ 326→- 部署 gh-pages(force push, commit b865922)→ Gitea Pages HTTP 200 ✅ - 327→ 327→ - 328→ 328→Stage Summary: - 329→ 329→- ✅ 工单修正完成:v0.7 已完成项标注清楚,下一阶段优先级重新排序 - 330→ 330→- ✅ 审查流程重建:cron job_id 228357,每15分钟触发,强制读工单 - 331→ 331→- ✅ v0.8 巡航玩法大增强全部完成并部署: - 332→ 332→ - BOSS 战系统(每5关,三阶段攻击,HP条,击败奖励×3) - 333→ 333→ - 飞船射击系统(空格/射击按钮,能量消耗,子弹尾焰) - 334→ 334→ - 道具掉落(5种powerup:护盾/能量/急速/超载/磁吸) - 335→ 335→ - 事件选择节点(每关通关后3选1,10种强化牌) - 336→ 336→ - lint 零错误 + dev HTTP 200 + gh-pages HTTP 200 - 337→ 337→- ✅ VLM 评分:主界面 7/10 + 巡航READY 8/10 + 巡航游戏 8/10 - 338→ 338→- 在线游玩 v0.8:https://gitea-pages.atdunbg.xyz/Super_Z/echo-nexus/ - 339→ 339→- 下一阶段工单:P1 探险BOSS胜率平衡 + 探险能量恢复 / 信标系统扩展(周挑战/信标链) - 340→ 340→ - 341→ 341→--- - 342→ 342→Task ID: 9-b - 343→ 343→Agent: full-stack-developer - 344→ 344→Task: 信标系统扩展(周挑战 + 信标链连续奖励) - 345→ 345→ - 346→ 346→Work Log: - 347→ 347→- 阅读现有 `src/lib/game/beacon.ts`(v0.5 每日挑战 + 本地排行榜,358 行)+ `src/components/game/BeaconPanel.tsx` + `src/store/gameStore.ts` 的 `trackBeacon` 函数(约第 173 行)+ worklog v0.8 项目状态,确认四色全息规范与现有架构。 - 348→ 348→- **扩展 `src/lib/game/beacon.ts`(358 → 873 行,新增 ~515 行)**: - 349→ 349→ - **周挑战(WEEKLY CHALLENGE)**: - 350→ 350→ - `BeaconWeeklyChallenge` 接口(weekKey YYYY-Www / type / difficulty 强制 anomaly|singular / goal / rewardInsight / rewardContact / seed / title / desc) - 351→ 351→ - `BeaconWeeklyProgress` 接口(weekKey / progress / startedAt / completedAt / claimed / durationSec) - 352→ 352→ - `getWeekKey(now)`:ISO 8601 周键(周一为起点,含首个周四的周为第一周) - 353→ 353→ - `weekKeyToSeed`:FNV-1a 哈希 - 354→ 354→ - `generateWeeklyChallenge(now)`:基于 weekKey 种子确定性生成,难度加权 anomaly 60% / singular 40%,goal 为日基准 ×3-5 倍(decode 18-75 / expedition 3-15 / pulse 60-245 / boss 3-12 / insight 120-595) - 355→ 355→ - `loadWeeklyProgress` / `saveWeeklyProgress` / `addWeeklyProgress` / `claimWeeklyReward`(推送排行榜,entry 加 `isWeekly: true`) - 356→ 356→ - `msUntilNextWeek(now)`:距离下周一 UTC 0 点的毫秒数 - 357→ 357→ - `BEACON_WEEKLY_KEY = "echo-nexus-beacon-weekly-v1"` - 358→ 358→ - **信标链(BEACON CHAIN)**: - 359→ 359→ - `BeaconChainState` 接口(lastCompletedDateKey / currentStreak / longestStreak / totalCompletions / graceUsed / milestonesClaimed) - 360→ 360→ - `BEACON_CHAIN_KEY = "echo-nexus-beacon-chain-v1"` - 361→ 361→ - `BEACON_CHAIN_MILESTONES = [3, 7, 14, 30]` 常量 - 362→ 362→ - `BEACON_CHAIN_REWARDS`:4 个里程碑(三日谐振 +50/+5 / 七日回响 +120/+12 / 半月星潮 +280/+28 / 满月飞升 +680/+68) - 363→ 363→ - `loadChainState` / `saveChainState`(每次返回新对象避免引用共享) - 364→ 364→ - `recordChainCompletion(dateKey)` 核心逻辑:同日重复忽略;次日 streak++;隔一天 miss 且 graceUsed<1 时续命 streak++ graceUsed++;其他断链 streak=1 graceUsed=0;返回 `{ state, newMilestones }` - 365→ 365→ - `claimChainMilestone(milestone)` / `getNextMilestone(streak)` / `getChainProgress(streak)`(返回 current/next/prev/progressPct 用于 UI) - 366→ 366→ - `dateKeyToTimestamp` / `dateKeyDiffDays` 工具函数 - 367→ 367→ - `BeaconScoreEntry` 新增可选 `isWeekly?: boolean` 字段 - 368→ 368→- **扩展 `src/store/gameStore.ts`**: - 369→ 369→ - import 扩展:generateWeeklyChallenge / loadWeeklyProgress / loadChainState / addWeeklyProgress / recordChainCompletion / claimWeeklyReward / claimChainMilestone / getTodayKey + 类型 - 370→ 370→ - `trackBeacon(type, delta)` 函数签名从 `boolean` 改为 `{ dailyJustCompleted, weeklyJustCompleted, newChainMilestones }`: - 371→ 371→ - 日挑战类型匹配且未完成 → addBeaconProgress;若刚完成 → recordChainCompletion(getTodayKey()) 收集 newMilestones - 372→ 372→ - 周挑战类型匹配且未完成 → addWeeklyProgress - 373→ 373→ - 所有调用点(pulse/clickNode/autoDecodeTick/resolveCurrentNode 等 7 处)原本就忽略返回值,向后兼容 - 374→ 374→ - 新增 action `claimWeeklyBeacon`:调用 claimWeeklyReward → 发放 insights + contact 到 state,返回 `{ rewardInsight, rewardContact, score }` - 375→ 375→ - 新增 action `claimChainReward(milestone)`:前置校验 currentStreak 与 milestonesClaimed,调用 claimChainMilestone → 发放奖励,返回 `{ rewardInsight, rewardContact, label, ok }` - 376→ 376→ - GameActions 接口同步新增两个 action 签名 - 377→ 377→- **重写 `src/components/game/BeaconPanel.tsx`(301 → 638 行)**: - 378→ 378→ - 顶部头部 + 每日挑战卡片(保留 v0.5 完整功能 + 按钮主题色随难度色淡显) - 379→ 379→ - **新增周挑战区块**(fuchsia 主题):标题"周挑战 · WEEKLY" + weekKey + 倒计时 + 挑战卡片(类型/难度标签/标题/描述/进度条/奖励/领取按钮 emerald 主题)+ weekly-glow 动画 - 380→ 380→ - **新增信标链区块**(amber→rose 渐变):标题"信标链 · CHAIN" + 大字号 streak(chain-streak-text 渐变流动动画)+ 今日完成状态徽章 + 4 个里程碑节点(w-12 h-12 圆形,claimed=emerald/✓,reachable=rose 脉冲动画+领取按钮,inProgress=amber 半亮,未到达=muted 灰)+ 节点间连线(背景灰 + 已达成部分 amber→rose 渐变 + 辉光,基于 prev→next 插值定位)+ 进度条 + 底部统计(最长/累计/续命状态) - 381→ 381→ - 桌面端 lg:grid-cols-2 让周挑战+信标链并排,移动端单列 - 382→ 382→ - 排行榜条目区分日/周:周挑战 entry 显示 "WEEK" 徽章 + fuchsia 高亮背景 - 383→ 383→ - 空状态提示扩展为"完成今日或本周信标即可登榜" - 384→ 384→ - 4 个新动画:weekly-glow / chain-milestone-pulse / chain-streak-flux(背景渐变流动) - 385→ 385→- **QA 验证**: - 386→ 386→ - `bun run lint` 零错误(每次修改后均验证) - 387→ 387→ - dev.log 全程无错误,所有编译 < 300ms - 388→ 388→ - **信标链逻辑测试**(bun 直接运行 TS,5 个场景全 PASS): - 389→ 389→ 1. 昨日 streak=1 → 今日完成 → streak=2 ✅ - 390→ 390→ 2. 同日重复完成 → 忽略 ✅ - 391→ 391→ 3. 明日完成 → streak=3,无需 grace ✅ - 392→ 392→ 4. 隔一天 miss(lastCompleted + 2 天)→ streak=4,graceUsed=1 ✅ - 393→ 393→ 5. 再次 miss(grace 已用)→ 断链 streak=1,graceUsed=0 ✅ - 394→ 394→ - **UI 集成测试**(agent-browser): - 395→ 395→ - localStorage 模拟设置 chain state(lastCompletedDateKey=yesterday, currentStreak=1)→ reload → UI 正确显示 streak=1 - 396→ 396→ - 设置 streak=3 + milestonesClaimed=[] → milestone 3 节点显示 rose 脉冲动画 + "领取"按钮 - 397→ 397→ - 点击"领取"→ milestonesClaimed=[3] + Toast "✦ 三日谐振 已领取 +50 洞见 · +5.0 接触" + 按钮消失 ✅ - 398→ 398→ - **VLM 视觉评分**(目标 ≥7/10): - 399→ 399→ - 首屏截图:8/10(四色一致、布局合理、信标链清晰) - 400→ 400→ - 里程碑可领取状态:8/10(amber→rose 渐变醒目、里程碑层次分明、fuchsia 主题清晰) - 401→ 401→ - 重置后干净状态:8/10(WEEKLY 字体对比度可优化,但整体可读性强) - 402→ 402→ - **不破坏现有功能**:日挑战进度/领奖/排行榜流程全部保留并工作正常 - 403→ 403→ - 404→ 404→Stage Summary: - 405→ 405→- ✅ beacon.ts 扩展完成(358 → 873 行):周挑战 + 信标链全部函数与类型 - 406→ 406→- ✅ gameStore.ts 集成完成:trackBeacon 升级为对象返回值 + claimWeeklyBeacon + claimChainReward 两个新 action - 407→ 407→- ✅ BeaconPanel.tsx UI 完整重写(301 → 638 行):周挑战 fuchsia 卡片 + 信标链 amber→rose 渐变区块 - 408→ 408→- ✅ 严格四色全息:周挑战 fuchsia、信标链 amber→rose、领取按钮 emerald、无蓝/紫色违规 - 409→ 409→- ✅ localStorage 新增 2 个独立 key(weekly + chain),不污染旧 key - 410→ 410→- ✅ 确定性:周挑战同周同种子同结果(weekKey FNV-1a 种子) - 411→ 411→- ✅ 信标链 grace 续命机制工作正常(5 个场景 bun 测试全 PASS) - 412→ 412→- ✅ lint 零错误 + dev HTTP 200 + VLM 8/10 - 413→ 413→- ✅ 现有日挑战 + 排行榜 + 领奖流程全部保留,无回归 - 414→ 414→ - 415→ 415→--- - 416→ 416→Task ID: 9 - 417→ 417→Agent: main + subagent(9-b) - 418→ 418→Task: v0.8.1 P1 双任务 — 探险 BOSS 胜率平衡 + 能量恢复 + 信标系统扩展(周挑战 + 信标链) - 419→ 419→ - 420→ 420→Work Log: - 421→ 421→- 重读工单确认 P1 两项为下一阶段优先级 - 422→ 422→- P1-a 探险平衡(main 自己做,平衡需要精细控制): - 423→ 423→ - expedition.ts: combatDifficultyScale 8→4, combatWinRateFloor 0.25→0.35, BOSS difficulty 5-7→3-5 - 424→ 424→ - 新增 computeEnergyRegenInterval(state): exp_2 -30%, exp_3 -20%, 探索力属性 -最高30%, 下限12s - 425→ 425→ - computeEnergyRegen 接受 intervalSec 参数 - 426→ 426→ - config.ts: exp_2/exp_3 描述加能量恢复说明 - 427→ 427→ - gameStore.ts tick: 传入动态间隔 - 428→ 428→ - ExpeditionPanel.tsx: 显示实际恢复速度 + '已加速'标记 - 429→ 429→- P1-b 信标系统扩展(dispatch subagent 9-b,并行完成): - 430→ 430→ - beacon.ts 358→873行: 周挑战(getWeekKey ISO 8601 + 确定性生成) + 信标链(4里程碑+grace续命) - 431→ 431→ - BeaconPanel.tsx 301→638行: 周挑战fuchsia主题 + 信标链amber→rose里程碑节点 - 432→ 432→ - gameStore.ts: trackBeacon升级 + claimWeeklyBeacon/claimChainReward actions - 433→ 433→- QA 验证: - 434→ 434→ - lint 零错误 - 435→ 435→ - 探险面板 VLM 7/10(能量条 + 恢复速度显示正常) - 436→ 436→ - 信标面板 VLM 8/10(周挑战 WEEKLY + 信标链 3/7/14/30 + 排行榜全部可见) - 437→ 437→ - subagent 5场景 bun 测试全 PASS(信标链断链/续命逻辑) - 438→ 438→- 提交 v0.8.1(commit 71ca5b4)+ 推送 main - 439→ 439→- 静态导出 + 部署 gh-pages(commit 97eecaa)→ HTTP 200 - 440→ 440→ - 441→ 441→Stage Summary: - 442→ 442→- ✅ P1-a 探险平衡完成: BOSS 基础胜率 25%→35-45%, 配合技术/属性可达 55-70% - 443→ 443→- ✅ P1-a 能量恢复: 固定45s → 动态12-45s(技术+属性加速) - 444→ 444→- ✅ P1-b 周挑战: 每周一UTC刷新, fuchsia主题, goal×3-5倍 - 445→ 445→- ✅ P1-b 信标链: 4里程碑(3/7/14/30天) + grace续命, amber→rose渐变 - 446→ 446→- ✅ 在线游玩 v0.8.1: https://gitea-pages.atdunbg.xyz/Super_Z/echo-nexus/ - 447→ 447→- 下一阶段工单: P2 UI样式细节打磨 / P2 云存档+云排行榜 / P3 全纪元叙事 - 448→ 448→ - 449→ 449→--- - 450→ 450→Task ID: v0.14-reimpl - 451→ 451→Agent: full-stack-developer - 452→ 452→Task: 放置系统 (Idle Operations) — 采矿无人机舰队 + 6放置工程 + 永久产能加成 + idle徽章 - 453→ 453→ - 454→ 454→Work Log: - 455→ 455→- 阅读 worklog.md 了解 v0.8.1 项目状态(10 大系统 + 四色全息规范 + 8 标签页架构) - 456→ 456→- 类型扩展 `src/lib/game/types.ts`: - 457→ 457→ - 新增 `IdleProjectDef` 接口(id/name/desc/durationSec/reward/minAscensions/minCrystalsPerSec/icon/color/order) - 458→ 458→ - 新增 `IdleProjectSlot` 接口(projectId/startedAt/finishesAt/remainingSec/completed) - 459→ 459→ - GameState 接口新增 4 字段:idleProjectSlots (3 槽位数组) / idleProjectHistory / idlePermanentBonus / idleStats - 460→ 460→- 新建 `src/lib/game/idle.ts` (~290 行): - 461→ 461→ - 6 个放置工程定义(idle_scan 60s / idle_refine 180s / idle_archive 600s / idle_anchor 1200s / idle_drones 1800s / idle_resonance 3600s) - 462→ 462→ - 奖励类型:crystals / insights / energy / contact / crystalsPerSecPermanent / randomFragment - 463→ 463→ - 解锁逻辑:minAscensions 与 minCrystalsPerSec 为 OR 关系(满足任一已设置条件即可) - 464→ 464→ - 工具函数:getIdleProject / getUnlockedIdleProjects / getLockedIdleProjects / isIdleProjectUnlocked / formatRemaining (12s/3m 45s/1h 12m) / formatReward / deriveMinerFleet (派生 min_1..min_5 采矿无人机,icon: ⛏️🌋🤖💠☀️,无任何采矿技术时返回休眠 min_1) - 465→ 465→ - 颜色映射:IDLE_COLOR_CLASSES (text/border/bg/bgSoft/glow/ring/dot) + COLOR_HEX (用于 inline style 绕过 Tailwind 动态类限制) - 466→ 466→- 引擎集成 `src/lib/game/engine.ts`: - 467→ 467→ - recomputeStats() 末尾 `crystalsPerSec += state.idlePermanentBonus ?? 0` (放置永久加成叠加) - 468→ 468→ - performPrestige() 清空 idleProjectSlots (中断当前周目工程),保留 idlePermanentBonus/idleProjectHistory/idleStats (跨周目永久);recomputeStats 调用传 idlePermanentBonus - 469→ 469→ - createInitialState() 新增 4 字段默认值 - 470→ 470→- Store 集成 `src/store/gameStore.ts` (核心改造): - 471→ 471→ - 新增 4 action:startIdleProject (校验+写入槽位) / cancelIdleProject (置 null) / claimIdleProject (应用奖励 + permBonus 累加 + randomFragment 解锁 + history 追加 + Toast 提示) / tickIdleProjects (每 tick 更新 remainingSec,归零标记 completed) - 472→ 472→ - **persist migrate 函数**:persist 配置增加 `version: 1` + `migrate` 函数自动补全 4 个 idle 字段,旧存档加载不崩溃 - 473→ 473→ - 14 处 syncStats 调用全部更新为传入 `idlePermanentBonus: s.idlePermanentBonus ?? 0`,确保 recomputeStats 计算时纳入永久加成 - 474→ 474→ - init() 中也补全 4 个 idle 字段(defense in depth) - 475→ 475→- 主循环 `src/hooks/useGameLoop.ts`:新增 tickIdleProjects 选择器,setInterval 回调中 autoDecodeTick 后调用,visibilitychange 也调用 - 476→ 476→- 新建 `src/components/game/IdleOperationsPanel.tsx` (~490 行): - 477→ 477→ - 4 section 在 max-h-[520px] overflow-y-auto 容器中: - 478→ 478→ 1. 放置收益概览:3 stat tiles (放置产能/永久加成/完成工程) + 离线效率 progress bar + 累计放置晶体统计 - 479→ 479→ 2. 采矿无人机舰队:grid 展示 deriveMinerFleet,每卡 emoji+name+Lv+output/s+状态点 (active=emerald ping 脉冲, dormant=灰) - 480→ 480→ 3. 放置工程槽位:3 张卡(空槽=虚线占位,运行中=大字号倒计时+自定义进度条+取消按钮,已完成=奖励预览+领取按钮带 glow) - 481→ 481→ 4. 可派遣工程:2 列 grid 展示已解锁工程(icon+name+duration+desc+reward+3 数字派遣按钮 1/2/3),下方列出未解锁工程及解锁条件 - 482→ 482→ - useShallow 订阅 + 本地 now state 每 1s 刷新倒计时 - 483→ 483→ - 严格 4 色全息,自定义 fuchsia 滚动条 - 484→ 484→- 新建 `src/components/game/IdleStatusBadge.tsx` (~100 行): - 485→ 485→ - 紧凑徽章 h-8 px-2.5 text-[11px]:脉冲点 (emerald 若 cps>0) + "放置中 +X/s" (或 "休眠中") - 486→ 486→ - Tooltip 悬停展示分解:基础产能/永久加成/运行中工程/待领取工程数 - 487→ 487→ - data-tut="idle-status-badge" 锚点 + onClick 切换到放置 tab - 488→ 488→- 新建 `src/components/game/IdleProjectBar.tsx` (~90 行): - 489→ 489→ - 晶体球下方细长进度条,最多 3 个工程 mini pill `[icon] name 12s ▓▓▓░░` - 490→ 490→ - 完成时显示 "✓ 待领取" + glow 辉光;无工程返回 null - 491→ 491→ - 进度条颜色用 inline style (COLOR_HEX) 控制,每 1s 刷新倒计时 - 492→ 492→- 主页面集成 `src/app/page.tsx`: - 493→ 493→ - 引入 3 新组件 + Clock 图标 + getUnlockedIdleProjects - 494→ 494→ - 新增 activeTab/tabInited state + controlled Tabs (`value={activeTab} onValueChange={setActiveTab}`) - 495→ 495→ - 挂载后 useEffect 一次性设置默认 tab:hasActiveExpedition→"expedition",hasPendingPerk→"constellation",否则→"idle" - 496→ 496→ - 版本号 v0.8 → v0.14 - 497→ 497→ - Header 在 StarTideIndicator 后加 IdleStatusBadge - 498→ 498→ - 左侧 CrystalOrb 后加 IdleProjectBar - 499→ 499→ - TabsList grid-cols-8 → grid-cols-9,新增 value="idle" 的 TabsTrigger 作为第一个 tab(Clock 图标,emerald 主题,待领取时显示数量红点) - 500→ 500→ - 新增 TabsContent value="idle" 渲染 IdleOperationsPanel - 501→ 501→ - 新增 idle 相关 goal 提示(高优先级):待领取 > 0 → "✦ 放置工程已完成 X 项,请前往「放置」标签领取奖励";全空+有解锁+时长>60s → "「放置」标签可派遣工程项目,离线自动产出" - 502→ 502→ - StatsPanel 新增 3 行:放置永久加成 / 完成放置工程 / 放置产出晶体 - 503→ 503→- 教程更新 `src/lib/game/tutorial.ts`:TUTORIAL_STEPS 在 decode 与 tech 之间插入新步骤 (id="idle", target="tab-idle", 标题"③ 放置工程 · 离线产出"),原 tech/expedition/prestige 编号顺延为 ④⑤⑥ - 504→ 504→- 写入 `agent-ctx/v0.14-reimpl-full-stack-developer.md` 工作记录 - 505→ 505→- 最终验证: - 506→ 506→ - lint 零错误零警告 ✅ - 507→ 507→ - dev 服务器 HTTP 200,编译 < 250ms ✅ - 508→ 508→ - agent-browser 烟雾测试全流程通过(默认 idle tab + 头部徽章 + 4 section + 派遣→倒计时→完成→领取→永久加成应用) ✅ - 509→ 509→ - 510→ 510→Stage Summary: - 511→ 511→- ✅ v0.14 放置系统 (Idle Operations) 全部完成 - 512→ 512→- ✅ 新增 4 文件(idle.ts 290行 + IdleOperationsPanel.tsx 490行 + IdleStatusBadge.tsx 100行 + IdleProjectBar.tsx 90行),修改 6 文件(types/engine/gameStore/useGameLoop/page/tutorial) - 513→ 513→- ✅ 6 个放置工程:勘探扫描 / 精炼校准 / 碎片整理 / 锚点部署 / 无人机扩编 / 谐振标定 - 514→ 514→- ✅ 3 个槽位并行 + 6 工程解锁逻辑 (OR) + 永久产能加成 (permBonus 累加跨周目) - 515→ 515→- ✅ 采矿无人机舰队派生 (min_1..min_5) + 休眠状态显示 - 516→ 516→- ✅ 头部 idle 徽章 + 默认放置 tab + 晶体球下方进度条 (5 秒内可见三要素) - 517→ 517→- ✅ persist migrate 函数 (version 1) 旧存档兼容 - 518→ 518→- ✅ 14 处 syncStats 调用全部传 idlePermanentBonus,recomputeStats 永久加成不丢失 - 519→ 519→- ✅ 飞升时清空活跃槽位但保留永久加成 / 历史 / 统计 - 520→ 520→- ✅ 严格 4 色全息 (emerald/rose/amber/fuchsia),零蓝/靛 - 521→ 521→- ✅ lint 零错误 + HTTP 200 + agent-browser 全流程烟雾测试通过 - 522→ 522→- ✅ 9 标签页 (新增"放置"为第一个) + 巡航按钮 + 教程新增 idle 步骤 - 523→ 523→ - 524→ 524→--- - 525→ 525→Task ID: v0.14-release - 526→ 526→Agent: 主控 Z.ai Code - 527→ 527→Task: v0.14 放置系统发布 — README/docs 更新 + 端到端验证 + git commit - 528→ 528→ - 529→ 529→Work Log: - 530→ 530→- 发现项目被环境重置回 v0.8.1 状态(v0.14 代码全部丢失,worklog 也被截断到 447 行) - 531→ 531→- 重新委派 full-stack-developer subagent (v0.14-reimpl) 在 v0.8.1 代码库上重新实现放置系统 - 532→ 532→- subagent 交付 10 个文件(4 新建 + 6 修改),lint 零错误,agent-browser smoke test 通过,git commit 17820b1 - 533→ 533→- 主控独立验证: - 534→ 534→ * git log 确认 commit 17820b1 存在 ✓ - 535→ 535→ * bun run lint 零错误 ✓ - 536→ 536→ * HTTP 200 ✓ - 537→ 537→ * agent-browser: v0.14 标签 ✓ + 放置中徽章 ✓ + 默认 idle tab ✓ + 4 段面板完整 ✓ - 538→ 538→ * 派遣 idle_scan → slot 0 显示 60s 倒计时 ✓ - 539→ 539→ * 快进完成 → 领取 → insights 79→87 (+8) + projectsCompleted 0→1 ✓ + 槽位清空 ✓ - 540→ 540→- 主控更新 README.md → v0.14:突出 12 大系统、3 层 idle 节奏、灵感来源、健康设计声明 - 541→ 541→- 主控创建 docs/11-放置系统-v0.14.md:完整设计文档 - 542→ 542→- 主控创建每 15 分钟触发的 webDevReview cron job - 543→ 543→ - 544→ 544→Stage Summary: - 545→ 545→ - 546→ 546→## v0.14 放置系统已重新上线并 git commit 保固 - 547→ 547→ - 548→ 548→### 核心交付 - 549→ 549→- **3 层 idle 节奏**:被动产能(无人机舰队)+ 放置工程(6 种 × 3 槽位)+ 离线收益(8h cap) - 550→ 550→- **始终可见的 idle 元素**:Header「放置中 +X/s」徽章 + 默认 tab = 放置 + 晶体球下方工程进度 pill - 551→ 551→- **永久产能加成**:通过工程奖励累积,集成进 recomputeStats,跨周目保留 - 552→ 552→- **存档安全**:persist version 1 + migrate 函数 + 4 字段 fallback,老存档零崩溃 - 553→ 553→ - 554→ 554→### Git 保固 - 555→ 555→- commit 17820b1: "v0.14: 放置系统 (Idle Operations) — 采矿无人机舰队 + 6放置工程 + 永久产能加成 + idle徽章" - 556→ 556→- 即使环境再次重置,git checkout 17820b1 即可恢复 - 557→ 557→ - 558→ 558→### 验证结果 - 559→ 559→- ✅ bun run lint 零错误零警告 - 560→ 560→- ✅ HTTP 200,dev.log 无运行时错误 - 561→ 561→- ✅ agent-browser 端到端 smoke test 全通过(派遣→倒计时→完成→领取→insights+8) - 562→ 562→- ✅ Sticky footer/header 验证通过 - 563→ 563→ - 564→ 564→### 未解决问题 / v0.15 优先项 - 565→ 565→1. 🟡 永久产能加成可能需要飞升次数 softcap(防 idle_resonance ×N 滚雪球) - 566→ 566→2. 🟡 idleProjectHistory / crystalsFromIdle 已在 store 累加但 UI 未展示 - 567→ 567→3. 🟡 缺「批量领取全部」按钮 + 工程完成 toast 通知 - 568→ 568→4. 🟡 遗迹图鉴多层文本(Dark Souls 式 loreFragments)—— v0.15 计划 - 569→ 569→5. 🟡 解码阈值释放叙事碎片 + 星潮三频段切换 —— v0.16 计划 - 570→ 570→6. 🟡 健康设计:每日软上限 + "星核建议休眠"提醒 —— v0.17 计划 - 571→ 571→ \ No newline at end of file diff --git a/worklog.md b/worklog.md index e4e658f9f..699c1ec4e 100755 --- a/worklog.md +++ b/worklog.md @@ -753,3 +753,232 @@ Stage Summary: - ✅ 线上 https://gitea-pages.atdunbg.xyz/Super_Z/echo-nexus/ 可游玩 v0.15 完整功能 - ✅ dev 服务器已重启(端口 3000,bun run dev 后台) - ⚠️ 未解决:本地 main 与远程 main 分叉(远程有 v0.9/v0.11/v0.12,本地有 v0.14/v0.15,共同祖先是 v0.8.1)。需 cron job 后续 rebase 或 merge,评估 v0.9-v0.12 的小改进是否要 cherry-pick 进 v0.15 线。本地 v0.15 代码完整(lint 通过 + agent-browser 验证通过),不缺失关键功能。 + + +--- +Task ID: 2-a +Agent: full-stack-developer +Task: v0.16 解码阈值叙事事件 — 全屏叙事碎片释放叠层(5 里程碑) + +Work Log: +- 阅读上下文:worklog.md (v0.15-release/deploy 段) + gameStore.ts line 575-720 两个解码完成点 + config.ts FRAGMENTS line 254-320 (threshold 1/3/6/10/15/22/32/45) + StarTide.tsx (StarTideOverlay 全屏叠层 z-0 参考) + page.tsx line 30-50/185-215 (StarTideOverlay 挂载 + activeTide goal 提示) + types.ts GameState 接口 +- 子任务 1:新建 `src/lib/game/narrativeMilestones.ts` + * 定义 `MilestoneColor` 联合类型(emerald/rose/amber/fuchsia,严格 4 色全息) + * 定义 `NarrativeMilestone` 接口(threshold/id/title/subtitle/body/color) + * 导出 `NARRATIVE_MILESTONES` 5 个里程碑:ms_5/12/25/40/60,阈值严格避开 FRAGMENTS 的 1/3/6/10/15/22/32/45 + * 每个里程碑 body 150-250 字回顾性咏叹文本,与 v0.15 loreLayers 5 条暗线呼应: + - ms_5 emerald 身份觉醒(你是回响) + - ms_12 rose 代价感知(解码在消耗锚痕) + - ms_25 amber 信任的重量("信任"是钩上的光) + - ms_40 fuchsia 身后的东西(整齐的虚空) + - ms_60 emerald 回归的预兆(借形之物) + * 导出 `findMilestoneForTotal(totalDecoded, alreadyTriggered)` 纯函数,调用方用于检测阈值触发 +- 子任务 2:扩展 `src/lib/game/types.ts` GameState 接口 + * 在 readLoreLayers 字段后新增 2 字段: + - `triggeredMilestones: string[]` 已触发 milestone id 集合(跨周目永久) + - `pendingMilestone: import("./narrativeMilestones").NarrativeMilestone | null` 待展示里程碑(叠层读取) + * 使用 inline import("./narrativeMilestones") 避免顶层循环依赖 +- 子任务 3:修改 `src/lib/game/engine.ts` + * `createInitialState()` 末尾加 `triggeredMilestones: []` 和 `pendingMilestone: null` 默认值 + * `performPrestige()` 末尾加 `triggeredMilestones` 保留(Array.isArray 兜底) + `pendingMilestone: null`(飞升时清空,避免飞升瞬间弹旧过场) +- 子任务 4:修改 `src/store/gameStore.ts` + * import `findMilestoneForTotal` from "@/lib/game/narrativeMilestones" + * GameActions 接口加 `dismissMilestone: () => void` 签名 + * clickNode 手动解码完成点(line 588 附近):`newTotal` 计算后调用 `findMilestoneForTotal(newTotal, s.triggeredMilestones ?? [])`,命中则在 `set()` payload 通过条件展开带上 `triggeredMilestones: [...prev, milestoneHit.id]` 和 `pendingMilestone: milestoneHit` + * autoDecodeTick 自动解码完成点(line 707 附近):同上检测逻辑,与手动解码一致 + * 新增 `dismissMilestone` action:null 检查后 `set({ pendingMilestone: null })` + * migrate 函数补全 `triggeredMilestones: Array.isArray(s.triggeredMilestones) ? s.triggeredMilestones : []` 和 `pendingMilestone: s.pendingMilestone ?? null`(persist version 仍为 1,新字段靠 migrate 兜底) +- 子任务 5:新建 `src/components/game/NarrativeMilestoneOverlay.tsx` 全屏叠层组件 + * 外层 `NarrativeMilestoneOverlay` 单独 selector 订阅 `pendingMilestone` + `dismiss`,非空时挂载内层 + * 内层 `NarrativeMilestoneOverlayInner` 以 `key={milestone.id}` 重挂,state 在挂载时初始化为 0(避免 effect 体内 setState 触发 react-hooks/set-state-in-effect 错误) + * `COLOR_THEME` 4 色全息映射(emerald/rose/amber/fuchsia),每个 color 派生 titleText/subtitleText/bodyText/cursor/cardBorder/cardBg/glow + * 视觉:`fixed inset-0 z-50`(高于 StarTideOverlay 的 z-0) + `bg-black/80 backdrop-blur-sm` + 全屏 4 色粒子呼吸光晕(box-shadow inset + narrative-breathe 3.5s 动画) + * 中央卡片:max-w-2xl,px-4 py-6(移动端)/ px-8 py-10(sm+),4 色主题渐变背景 + 主题色 border + 60px 主题色 box-shadow + * 顶部副标题 text-[10px] sm:text-xs font-mono tracking-[0.3em] uppercase(主题色/80) + * 大字号标题 text-3xl sm:text-4xl md:text-5xl font-serif font-bold(375px text-3xl 适配) + * 装饰分隔线 linear-gradient transparent→glow→transparent + * 主体文本 text-sm sm:text-base md:text-lg leading-relaxed whitespace-pre-line(支持 \n\n 分段),逐字浮现(30ms/字 setTimeout 推进,总时长约 5-8s 视字数) + * 输入光标:未完成时显示 inline-block w-0.5 h-4 sm:h-5 主题色,narrative-cursor 1s steps(2) 闪烁 + * 入场动画:narrative-fade-in 300ms ease-out(容器)+ narrative-scale-in 300ms cubic-bezier(0.16,1,0.3,1)(卡片 scale 1.05→1) + * 交互:点击任意处或按 Esc → handleAction;若未 fullyRevealed 则先 skip 到全文(skipRef + setRevealed(fullLen)),再次触发才真正 dismiss;底部提示根据状态切换"点击跳过逐字显示"/"点击任意处或按 Esc 继续" + * ARIA:role="dialog" aria-modal="true" aria-labelledby="narrative-milestone-title" +- 子任务 6:修改 `src/app/page.tsx` 挂载叠层 + * 加 `import { NarrativeMilestoneOverlay } from "@/components/game/NarrativeMilestoneOverlay"` + * 在 `{cruiseOpen && }` 之后挂载 ``(它内部自己读 pendingMilestone 决定是否渲染,模态会盖住一切) + +Stage Summary: +- ✅ 5 个里程碑阈值与主题: + - ms_5 threshold=5 color=emerald "回响 · 初鸣" 身份觉醒(你是回响) + - ms_12 threshold=12 color=rose "代价 · 锚痕渐浅" 代价感知(解码在消耗锚痕) + - ms_25 threshold=25 color=amber "信任 · 钩上的光" 信任的重量("信任"实为引诱) + - ms_40 threshold=40 color=fuchsia "身后 · 整齐的虚空" 身后的东西(追逐者逼近) + - ms_60 threshold=60 color=emerald "回归 · 借形之物" 回归的预兆(接触满溢借形回归) +- ✅ 阈值严格避开 FRAGMENTS 的 1/3/6/10/15/22/32/45,是额外叙事节奏点(不解锁新碎片,回顾性咏叹) +- ✅ 修改/新建文件清单(6 个): + - 新建 `/home/z/my-project/src/lib/game/narrativeMilestones.ts`(5 里程碑定义 + findMilestoneForTotal 纯函数) + - 新建 `/home/z/my-project/src/components/game/NarrativeMilestoneOverlay.tsx`(全屏叠层组件,外层+内层 key 重挂结构) + - 修改 `/home/z/my-project/src/lib/game/types.ts`(GameState 加 triggeredMilestones + pendingMilestone 2 字段) + - 修改 `/home/z/my-project/src/lib/game/engine.ts`(createInitialState 默认值 + performPrestige 保留/清空) + - 修改 `/home/z/my-project/src/store/gameStore.ts`(GameActions 加 dismissMilestone + 2 解码点检测 + migrate 补全 + 实现 dismissMilestone action) + - 修改 `/home/z/my-project/src/app/page.tsx`(挂载叠层组件) +- ✅ lint 零错误(`bun run lint` 0 errors)。唯一警告 `Codex.tsx:203:5 unused eslint-disable directive` 来自子代理 B 在 v0.16 加的 readLoreLayers useMemo,本任务约束禁止改 Codex.tsx,已记录由子代理 B 处理 +- ✅ dev.log 干净:HTTP 200,编译 < 4.5s(首次编译),后续 hot reload < 80ms,无运行时错误 +- ✅ persist version 仍为 1,新字段(triggeredMilestones / pendingMilestone)靠 migrate 函数兜底,老存档兼容 +- ✅ performPrestige 保留 triggeredMilestones(玩家不应重复看同样过场)+ 清空 pendingMilestone(飞升瞬间不弹旧过场) +- ✅ 严格 4 色全息(emerald/rose/amber/fuchsia),零蓝/靛 +- ✅ useShallow 不适用(叠层组件用单独 selector 订阅 pendingMilestone,更轻量) +- ✅ 移动端适配:375px 宽度下卡片 px-4 py-6、标题 text-3xl、正文 text-sm,逐字光标 h-4,可读不溢出 +- ✅ 入场动画:fade-in + scale-105→100(300ms cubic-bezier),呼吸光晕 3.5s 循环,光标 1s steps(2) 闪烁 +- ✅ 交互:点击任意处或 Esc 关闭;首次点击若文本未完成则先跳到全文,再次触发才 dismiss;底部提示根据状态切换文案 +- ⚠️ 注意点:Codex.tsx 203:5 的 unused eslint-disable 警告需子代理 B 修复(位置错误:disable 应在 useMemo 调用上方而非依赖数组下方);该警告不影响功能,仅 lint 视觉噪音 +- ⚠️ 注意点:v0.15 子代理 B 在 types.ts / engine.ts / gameStore.ts 也加了 readLoreLayers 字段(与本任务并行),两套 v0.16 字段共存无冲突(不同字段名、不同功能),migrate 函数已同时兜底两套 + +--- +Task ID: 2-b +Agent: full-stack-developer +Task: v0.16 完善 — loreLayer 已读标记 + idleProjectHistory 详情展开 + 移动端 375px 适配审计 + +Work Log: +- 必读上下文:worklog v0.15-release 段 + Codex.tsx(v0.15 loreLayers 分层渲染)+ StatsPanel.tsx(v0.15 抽出独立组件)+ types.ts GameState + config.ts FRAGMENTS/loreLayers/isLoreLayerUnlocked + gameStore.ts(actions + persist migrate)+ IdleOperationsPanel.tsx + BeaconPanel.tsx + ExpeditionPanel.tsx +- 子任务 1:loreLayer 已读标记 + - 新建 `src/lib/game/format.ts` 导出 `formatRelativeTime(ts)` 工具函数(<60s "刚刚" / <60m "X 分钟前" / <24h "X 小时前" / <7d "X 天前" / 否则具体日期) + - `types.ts`:GameState 接口新增 `readLoreLayers: string[]`(位置:v0.14 idle 字段之后、元字段之前,注释标 v0.16 loreLayer 已读) + - `engine.ts`: + * `createInitialState` 末尾加 `readLoreLayers: []`(在 idleStats 之后) + * `performPrestige` 返回对象加 `readLoreLayers: Array.isArray(state.readLoreLayers) ? state.readLoreLayers : []`(跨周目永久保留,与 idle 永久字段同一区域) + - `gameStore.ts`: + * GameActions 接口加 `markLoreLayerRead: (fragmentId: string, layer: 1 | 2) => void`(在 tickIdleProjects 之后) + * 实现该 action:构造 key `${fragmentId}_L${layer}`,已存在则 no-op,否则 set 新数组 + * persist migrate 函数末尾加 `readLoreLayers: Array.isArray(s.readLoreLayers) ? s.readLoreLayers : []`(不改 version,仍是 1) + - `Codex.tsx`: + * useShallow 订阅新增 `readLoreLayers` 与 `markLoreLayerRead` + * LoreLayerBlock 接收 `isRead` + `onMarkRead` props: + - 已读:层标题旁加 muted "✓ 已读" 标记 + 边框从 amber/fuchsia 高亮降级为 `border-white/10` + - 未读但已解锁:保持 v0.15 高亮 + 标题旁加 `animate-pulse` 脉冲小圆点 + "新" 字提示 + - 文本区域包成 `