Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5bed2599f5 | ||
|
|
174d1227c0 | ||
|
|
774ddaf9d1 | ||
|
|
22588425cf | ||
|
|
96748d726f | ||
|
|
20dc1f252f | ||
|
|
17820b1f28 | ||
|
|
1701a908c9 | ||
|
|
71ca5b48a9 | ||
|
|
a3aa381656 | ||
|
|
494bc5ff64 | ||
|
|
43dfc2d41b | ||
|
|
3d916a1574 | ||
|
|
4545fe380b | ||
|
|
8474f4caa7 | ||
|
|
c0535e6ebe | ||
|
|
7111e154b2 | ||
|
|
72e1f3fc3c | ||
|
|
fafa2ee20a | ||
|
|
fcf94b6c3f | ||
|
|
296c609479 | ||
|
|
bd28219b65 | ||
|
|
8605f9bb5f | ||
|
|
00a46a0bf9 | ||
|
|
084387174b | ||
|
|
ab511fe9e0 | ||
|
|
60445abd42 | ||
|
|
04f6a398f4 | ||
|
|
a02b6191c5 | ||
|
|
5bc041ac0b | ||
|
|
697e1f360a | ||
|
|
e6ec22132e | ||
|
|
e69c48ba69 | ||
|
|
98fbbf1634 | ||
|
|
9a17849859 | ||
|
|
cc02fc2e7f | ||
|
|
9f4d839c6b | ||
|
|
433c19cc73 | ||
|
|
f3d2e53e4f | ||
|
|
27bb1555e5 | ||
|
|
da18f32df2 | ||
|
|
51714d2532 | ||
|
|
674775be0e | ||
|
|
84749c218a | ||
|
|
f73b96026c | ||
|
|
5eb303c389 | ||
|
|
9e520a34c6 | ||
|
|
1e706b2ec0 | ||
|
|
e193530f24 |
@@ -1 +1 @@
|
||||
12512
|
||||
1061
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
# 回响星核 / Echo Nexus
|
||||
|
||||
> 原创深空考古放置策略游戏 · Original deep-space archaeology idle strategy game
|
||||
> 原创深空考古 **放置策略**游戏 · Original deep-space archaeology **idle strategy** game
|
||||
|
||||
你是一名深空考古指挥官,驾驶自治勘探舰回收已「飞升」的以太文明遗留的记忆晶体,解码碎片拼凑出他们的故事,并最终跨越维度发起「接触」。
|
||||
|
||||
**关掉浏览器,星核仍在为你工作**——采矿无人机舰队 24h 不间断采矿,放置工程在离线时持续推进,回来后一键领取积累的奖励。
|
||||
|
||||
---
|
||||
|
||||
## 🎮 在线游玩
|
||||
@@ -16,7 +18,7 @@
|
||||
|
||||
## 项目状态
|
||||
|
||||
- **版本**:**v0.7** — CrystalOrb Canvas 粒子系统 + 四维角色属性
|
||||
- **版本**:**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/)
|
||||
@@ -26,29 +28,60 @@
|
||||
|
||||
## 核心特色
|
||||
|
||||
- **解码共振谜题**:把放置游戏的「等待」变成有节奏的主动小谜题(路径构造法生成器,100% 可解)
|
||||
- **碎片涌现叙事**:故事从解码中自然浮现,5 纪元循环命名 + 模板化 lore,不打断玩法
|
||||
- **深空全息美学**:晶体 + Canvas 粒子 + 共振波纹的差异化视觉(严格 emerald/rose/amber/fuchsia 四色全息色谱,零蓝色/靛色)
|
||||
- **飞升多周目**:终局「接触」后保留蓝图开启新周目,星图天文台 18 个天赋跨周目永久生效
|
||||
- **实时巡航玩法**:Canvas 2D 飞船操控 + 8 种实体 + 3 层视差星空,打破纯放置节奏
|
||||
- **角色 RPG 深度**:四维属性(探索力/智慧/勇气/灵感)影响所有游戏系统
|
||||
- **📡 采矿无人机舰队**:每解锁一个采矿分支技术,就有一台自动采矿无人机加入舰队,24h 持续产出晶体(无需点击)
|
||||
- **⚙️ 放置工程项目**:6 种工程,派遣后**离线/在线自动推进**,完成领取奖励(晶体/洞见/能量/永久产能加成/随机碎片)
|
||||
- **🔓 离线收益**:关闭浏览器后产能按 `offlineEff × crystalsPerSec` 持续累积(上限 8h),重新打开弹窗结算「沉睡期间」收益
|
||||
- **🎯 始终可见的放置徽章**:Header 永远显示「放置中 +X/s」徽章,悬停查看产能构成明细——一眼就知道这是个 idle game
|
||||
- **🔍 解码共振谜题**:把放置游戏的「等待」变成有节奏的主动小谜题(路径构造法生成器,100% 可解)
|
||||
- **📜 碎片涌现叙事**:故事从解码中自然浮现,5 纪元循环命名 + 模板化 lore,不打断玩法
|
||||
- **💎 深空全息美学**:晶体 + Canvas 粒子 + 共振波纹的差异化视觉(严格 emerald/rose/amber/fuchsia 四色全息色谱,零蓝色/靛色)
|
||||
- **🔄 飞升多周目**:终局「接触」后保留蓝图开启新周目,星图天文台 18 个天赋跨周目永久生效
|
||||
- **🚀 实时巡航玩法**:Canvas 2D 飞船操控 + 8 种实体 + 3 层视差星空,打破纯放置节奏
|
||||
- **🧬 角色 RPG 深度**:四维属性(探索力/智慧/勇气/灵感)影响所有游戏系统
|
||||
|
||||
---
|
||||
|
||||
## 游戏系统(10 大系统)
|
||||
## 游戏系统(11 大系统)
|
||||
|
||||
| # | 系统 | 简介 |
|
||||
|---|------|------|
|
||||
| 1 | 放置采矿 | 中央晶体球 Canvas 粒子系统 + 主动脉冲 + 连击加成 |
|
||||
| 2 | 谐振解码 | 原创路径谜题,100% 可解率 |
|
||||
| 3 | 技术树 | 三层科技解锁,提升产能/解码/探险 |
|
||||
| 4 | 遗迹探险 | 肉鸽系统,6 种节点 + 程序化路径 + BOSS |
|
||||
| 5 | 星潮事件 | 6 种动态事件,全屏叠层 + 程序化音效 |
|
||||
| 6 | 星图天文台 | 飞升后 3 选 1 天赋 draft,18 个跨周目天赋 |
|
||||
| 7 | 回响编年史 | 跨周目叙事时间轴,5 纪元循环命名 |
|
||||
| 8 | 深空信标 | 每日挑战 + 本地排行榜 Top20 |
|
||||
| 9 | 深空巡航 | Canvas 2D 实时玩法,飞船操控/陨石躲避/星门通关 |
|
||||
| 10 | 角色属性 | 四维属性(探索力/智慧/勇气/灵感),飞升获得属性点 |
|
||||
| # | 系统 | 简介 | 类型 |
|
||||
|---|------|------|------|
|
||||
| 1 | **📡 放置采矿** | 中央晶体球 Canvas 粒子系统 + 主动脉冲 + 连击加成 + **自动采矿无人机舰队** | 放置 |
|
||||
| 2 | **⚙️ 放置工程** | 6 种工程 × 3 槽位,**离线/在线自动推进**,完成领取奖励 | **放置** |
|
||||
| 3 | **🔓 离线收益** | 关闭浏览器后产能按 `eff × cps` 累积(8h 上限),重新打开结算 | **放置** |
|
||||
| 4 | 谐振解码 | 原创路径谜题,100% 可解率 | 主动 |
|
||||
| 5 | 技术树 | 三层科技解锁,提升产能/解码/探险 | 主动 |
|
||||
| 6 | 遗迹探险 | 肉鸽系统,6 种节点 + 程序化路径 + BOSS | 主动 |
|
||||
| 7 | 星潮事件 | 6 种动态事件,全屏叠层 + 程序化音效 | 被动 |
|
||||
| 8 | 星图天文台 | 飞升后 3 选 1 天赋 draft,18 个跨周目天赋 | 元进程 |
|
||||
| 9 | 回响编年史 | 跨周目叙事时间轴,5 纪元循环命名 | 元进程 |
|
||||
| 10 | 深空信标 | 每日挑战 + 本地排行榜 Top20 | 每日 |
|
||||
| 11 | 深空巡航 | Canvas 2D 实时玩法,飞船操控/陨石躲避/星门通关 | 实时 |
|
||||
| 12 | 角色属性 | 四维属性(探索力/智慧/勇气/灵感),飞升获得属性点 | 元进程 |
|
||||
|
||||
---
|
||||
|
||||
## 放置系统设计哲学(v0.14 + v0.15 平衡)
|
||||
|
||||
回响星核的「放置」不是简单地把数字挂机增长,而是分层呈现 **3 种 idle 节奏**:
|
||||
|
||||
### 节奏 1:被动产能(最慢,最持续)
|
||||
- 采矿无人机舰队 = 你已解锁的采矿技术节点
|
||||
- 每台无人机 24h 持续产出晶体,无需任何操作
|
||||
- 晶体进入仓库(受 `crystalCap` 限制),仓库满后停止产出(防溢出)
|
||||
- **永远在线,离线也累积**
|
||||
|
||||
### 节奏 2:放置工程(中等周期,需要决策)
|
||||
- 6 种工程,时长 1min ~ 1h 不等
|
||||
- 玩家选择派遣到 3 个槽位之一 → 启动后**离线/在线自动推进**
|
||||
- 完成后必须**手动领取**(防玩家错过奖励时机)
|
||||
- 奖励类型多样:晶体 / 洞见 / 能量 / 永久产能加成 / 随机碎片
|
||||
- **核心 idle loop:派遣 → 离开 → 回来 → 领取 → 再派遣**
|
||||
|
||||
### 节奏 3:离线收益(最长周期,回归奖励)
|
||||
- 关闭浏览器后,被动产能按 `offlineEff`(默认 50%,可通过技术提升至 100%)继续累积
|
||||
- 上限 8 小时(防极端挂机)
|
||||
- 重新打开页面弹窗结算:「你离开了 X 小时,星核为你产出 Y 颗晶体」
|
||||
- **回归玩家的"啊我回来了"瞬间**
|
||||
|
||||
---
|
||||
|
||||
@@ -67,9 +100,13 @@
|
||||
| v0.5.1 | Gitea Pages 静态部署 + 新手教程 | ✅ |
|
||||
| v0.5.2 | 离线收益报告 + 标签页 UI 重设计 | ✅ |
|
||||
| v0.6 | 深空巡航 Canvas 2D 实时玩法 | ✅ |
|
||||
| **v0.7** | **CrystalOrb Canvas 粒子系统 + 四维角色属性** | ✅ |
|
||||
| v0.8 | 巡航玩法增强 / BOSS 关卡 / 道具掉落 | 🔜 |
|
||||
| v0.9 | 云存档 + 云排行榜 | 🔜 |
|
||||
| v0.7 | CrystalOrb Canvas 粒子系统 + 四维角色属性 | ✅ |
|
||||
| v0.8 | 巡航玩法增强 / BOSS 关卡 / 道具掉落 | ✅ |
|
||||
| v0.8.1 | 探险平衡 + 信标系统扩展(周挑战 + 信标链) | ✅ |
|
||||
| **v0.14** | **放置系统:采矿无人机舰队 + 6 种放置工程 + 永久产能加成 + 始终可见 idle 徽章** | ✅ |
|
||||
| **v0.15** | **叙事深化(8 碎片 × 2 层 = 16 层 loreLayers)+ 永久加成 softcap + 批量领取 + 完成 toast + StatsPanel idle 统计** | ✅ |
|
||||
| **v0.16** | **解码阈值叙事事件(5 里程碑全屏叠层)+ loreLayer 已读标记 + idleHistory 详情展开 + 移动端 375px 适配审计** | ✅ |
|
||||
| v0.17 | 星潮三频段切换 + 健康设计(每日软上限 + 星核建议休眠) | 🔜 |
|
||||
| v1.0 | 全 5 纪元手写叙事 + 多人同步星潮 | 🔜 |
|
||||
|
||||
---
|
||||
@@ -86,6 +123,9 @@
|
||||
- [`docs/08-星图天文台系统-v0.3.1.md`](docs/08-星图天文台系统-v0.3.1.md) — v0.3.1 天文台
|
||||
- [`docs/09-回响编年史系统-v0.4.md`](docs/09-回响编年史系统-v0.4.md) — v0.4 编年史
|
||||
- [`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 已读 + 移动端适配**
|
||||
|
||||
---
|
||||
|
||||
@@ -118,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.7"
|
||||
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
|
||||
```
|
||||
@@ -127,4 +167,31 @@ Gitea Pages 会自动从 `gh-pages` 分支提供静态网站服务。
|
||||
|
||||
---
|
||||
|
||||
## 灵感来源(Inspired by)
|
||||
|
||||
- **Antimatter Dimensions** — 数值膨胀美学 + 终端 UI
|
||||
- **Universal Paperclips** — 叙事驱动的放置游戏(Frank Lantz)
|
||||
- **A Dark Room** — 极简文字放置游戏(Michael Townsend)
|
||||
- **Melvor Idle** — 离线收益 + 多技能树
|
||||
- **Outer Wilds** — 知识解锁式考古
|
||||
- **Cookie Clicker** — Orteil 的放置游戏范式定义
|
||||
|
||||
---
|
||||
|
||||
## 健康游戏设计声明
|
||||
|
||||
放置游戏天然具有"低投入高回报"的成瘾性,可能对学业/工作压力大的青年造成时间碎片化风险。回响星核主动采取以下健康设计:
|
||||
|
||||
- ✅ **离线收益上限 8h**——防止 24h 挂机
|
||||
- ✅ **工程完成需手动领取**——避免"打开就有奖励"的条件反射
|
||||
- ✅ **无广告、无内购**——纯粹的单机体验
|
||||
- 🔜 **v0.17 计划**:每日软上限 + "星核建议休眠"提醒(每 1 小时主动提示休息)
|
||||
|
||||
参考学术研究:
|
||||
- Frank Lantz GDC 2018 "This Is Your Brain on Games"
|
||||
- Reuters 2025 "Cozy video games can quell stress and anxiety"
|
||||
- DOI:10.3389/fpsyg.2026.1774030 TikTok 时代学术倦怠研究
|
||||
|
||||
---
|
||||
|
||||
© Super_Z · 持续开发中
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
1:"$Sreact.fragment"
|
||||
2:I[47257,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"ClientPageRoot"]
|
||||
3:I[52683,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js","/Super_Z/echo-nexus/_next/static/chunks/38c9b86c7986e4d2.js","/Super_Z/echo-nexus/_next/static/chunks/dad63bc31a306c06.js","/Super_Z/echo-nexus/_next/static/chunks/868e0df8b5a7f188.js","/Super_Z/echo-nexus/_next/static/chunks/421819b51db6b69f.js"],"default"]
|
||||
6:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"OutletBoundary"]
|
||||
7:"$Sreact.suspense"
|
||||
0:{"buildId":"yNvWrGxQ0RM8HHWxdOJQM","rsc":["$","$1","c",{"children":[["$","$L2",null,{"Component":"$3","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@4","$@5"]}}],[["$","script","script-0",{"src":"/Super_Z/echo-nexus/_next/static/chunks/38c9b86c7986e4d2.js","async":true}],["$","script","script-1",{"src":"/Super_Z/echo-nexus/_next/static/chunks/dad63bc31a306c06.js","async":true}],["$","script","script-2",{"src":"/Super_Z/echo-nexus/_next/static/chunks/868e0df8b5a7f188.js","async":true}],["$","script","script-3",{"src":"/Super_Z/echo-nexus/_next/static/chunks/421819b51db6b69f.js","async":true}]],["$","$L6",null,{"children":["$","$7",null,{"name":"Next.MetadataOutlet","children":"$@8"}]}]]}],"loading":null,"isPartial":false}
|
||||
4:{}
|
||||
5:"$0:rsc:props:children:0:props:serverProvidedParams:params"
|
||||
8:null
|
||||
@@ -1,23 +0,0 @@
|
||||
1:"$Sreact.fragment"
|
||||
2:I[39756,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"default"]
|
||||
3:I[37457,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"default"]
|
||||
4:I[77855,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"Toaster"]
|
||||
5:I[47257,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"ClientPageRoot"]
|
||||
6:I[52683,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js","/Super_Z/echo-nexus/_next/static/chunks/38c9b86c7986e4d2.js","/Super_Z/echo-nexus/_next/static/chunks/dad63bc31a306c06.js","/Super_Z/echo-nexus/_next/static/chunks/868e0df8b5a7f188.js","/Super_Z/echo-nexus/_next/static/chunks/421819b51db6b69f.js"],"default"]
|
||||
9:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"OutletBoundary"]
|
||||
a:"$Sreact.suspense"
|
||||
c:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"ViewportBoundary"]
|
||||
e:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"MetadataBoundary"]
|
||||
10:I[68027,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"default"]
|
||||
:HL["/Super_Z/echo-nexus/_next/static/chunks/59368da722d2753f.css","style"]
|
||||
:HL["/Super_Z/echo-nexus/_next/static/chunks/7bbd6f879a3901f6.css","style"]
|
||||
:HL["/Super_Z/echo-nexus/_next/static/media/0acc7fdf55eb3220-s.p.532ccaa1.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
|
||||
:HL["/Super_Z/echo-nexus/_next/static/media/797e433ab948586e-s.p.29207c2f.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
|
||||
:HL["/Super_Z/echo-nexus/_next/static/media/caa3a2e1cccd8315-s.p.3b6cae6d.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
|
||||
0:{"P":null,"b":"yNvWrGxQ0RM8HHWxdOJQM","c":["",""],"q":"","i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/Super_Z/echo-nexus/_next/static/chunks/59368da722d2753f.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/Super_Z/echo-nexus/_next/static/chunks/7bbd6f879a3901f6.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"zh-CN","suppressHydrationWarning":true,"className":"dark","children":["$","body",null,{"className":"geist_a71539c9-module__T19VSG__variable geist_mono_8d43a2aa-module__8Li5zG__variable orbitron_da1c3699-module__wSKb_W__variable antialiased bg-background text-foreground","children":[["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}],["$","$L4",null,{}]]}]}]]}],{"children":[["$","$1","c",{"children":[["$","$L5",null,{"Component":"$6","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@7","$@8"]}}],[["$","script","script-0",{"src":"/Super_Z/echo-nexus/_next/static/chunks/38c9b86c7986e4d2.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/Super_Z/echo-nexus/_next/static/chunks/dad63bc31a306c06.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/Super_Z/echo-nexus/_next/static/chunks/868e0df8b5a7f188.js","async":true,"nonce":"$undefined"}],["$","script","script-3",{"src":"/Super_Z/echo-nexus/_next/static/chunks/421819b51db6b69f.js","async":true,"nonce":"$undefined"}]],["$","$L9",null,{"children":["$","$a",null,{"name":"Next.MetadataOutlet","children":"$@b"}]}]]}],{},null,false,false]},null,false,false],["$","$1","h",{"children":[null,["$","$Lc",null,{"children":"$Ld"}],["$","div",null,{"hidden":true,"children":["$","$Le",null,{"children":["$","$a",null,{"name":"Next.Metadata","children":"$Lf"}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],false]],"m":"$undefined","G":["$10",[]],"S":true}
|
||||
7:{}
|
||||
8:"$0:f:0:1:1:children:0:props:children:0:props:serverProvidedParams:params"
|
||||
d:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
|
||||
11:I[27201,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"IconMark"]
|
||||
b:null
|
||||
f:[["$","title","0",{"children":"回响星核 · Echo Nexus"}],["$","meta","1",{"name":"description","content":"原创深空考古放置策略游戏 — 解码记忆晶体,拼凑以太文明的回响,跨越维度发起接触。"}],["$","meta","2",{"name":"author","content":"Super_Z"}],["$","meta","3",{"name":"keywords","content":"Echo Nexus,回响星核,idle game,browser game,放置游戏,解谜,深空考古"}],["$","meta","4",{"property":"og:title","content":"回响星核 · Echo Nexus"}],["$","meta","5",{"property":"og:description","content":"原创深空考古放置策略游戏"}],["$","meta","6",{"property":"og:site_name","content":"Echo Nexus"}],["$","meta","7",{"property":"og:type","content":"website"}],["$","meta","8",{"name":"twitter:card","content":"summary_large_image"}],["$","meta","9",{"name":"twitter:title","content":"回响星核 · Echo Nexus"}],["$","meta","10",{"name":"twitter:description","content":"原创深空考古放置策略游戏"}],["$","link","11",{"rel":"icon","href":"https://z-cdn.chatglm.cn/z-ai/static/logo.svg"}],["$","$L11","12",{}]]
|
||||
@@ -1,6 +0,0 @@
|
||||
1:"$Sreact.fragment"
|
||||
2:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"ViewportBoundary"]
|
||||
3:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"MetadataBoundary"]
|
||||
4:"$Sreact.suspense"
|
||||
5:I[27201,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"IconMark"]
|
||||
0:{"buildId":"yNvWrGxQ0RM8HHWxdOJQM","rsc":["$","$1","h",{"children":[null,["$","$L2",null,{"children":[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]}],["$","div",null,{"hidden":true,"children":["$","$L3",null,{"children":["$","$4",null,{"name":"Next.Metadata","children":[["$","title","0",{"children":"回响星核 · Echo Nexus"}],["$","meta","1",{"name":"description","content":"原创深空考古放置策略游戏 — 解码记忆晶体,拼凑以太文明的回响,跨越维度发起接触。"}],["$","meta","2",{"name":"author","content":"Super_Z"}],["$","meta","3",{"name":"keywords","content":"Echo Nexus,回响星核,idle game,browser game,放置游戏,解谜,深空考古"}],["$","meta","4",{"property":"og:title","content":"回响星核 · Echo Nexus"}],["$","meta","5",{"property":"og:description","content":"原创深空考古放置策略游戏"}],["$","meta","6",{"property":"og:site_name","content":"Echo Nexus"}],["$","meta","7",{"property":"og:type","content":"website"}],["$","meta","8",{"name":"twitter:card","content":"summary_large_image"}],["$","meta","9",{"name":"twitter:title","content":"回响星核 · Echo Nexus"}],["$","meta","10",{"name":"twitter:description","content":"原创深空考古放置策略游戏"}],["$","link","11",{"rel":"icon","href":"https://z-cdn.chatglm.cn/z-ai/static/logo.svg"}],["$","$L5","12",{}]]}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],"loading":null,"isPartial":false}
|
||||
@@ -1,7 +0,0 @@
|
||||
1:"$Sreact.fragment"
|
||||
2:I[39756,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"default"]
|
||||
3:I[37457,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"default"]
|
||||
4:I[77855,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"Toaster"]
|
||||
:HL["/Super_Z/echo-nexus/_next/static/chunks/59368da722d2753f.css","style"]
|
||||
:HL["/Super_Z/echo-nexus/_next/static/chunks/7bbd6f879a3901f6.css","style"]
|
||||
0:{"buildId":"yNvWrGxQ0RM8HHWxdOJQM","rsc":["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/Super_Z/echo-nexus/_next/static/chunks/59368da722d2753f.css","precedence":"next"}],["$","link","1",{"rel":"stylesheet","href":"/Super_Z/echo-nexus/_next/static/chunks/7bbd6f879a3901f6.css","precedence":"next"}],["$","script","script-0",{"src":"/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","async":true}],["$","script","script-1",{"src":"/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js","async":true}]],["$","html",null,{"lang":"zh-CN","suppressHydrationWarning":true,"className":"dark","children":["$","body",null,{"className":"geist_a71539c9-module__T19VSG__variable geist_mono_8d43a2aa-module__8Li5zG__variable orbitron_da1c3699-module__wSKb_W__variable antialiased bg-background text-foreground","children":[["$","$L2",null,{"parallelRouterKey":"children","template":["$","$L3",null,{}],"notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]]}],["$","$L4",null,{}]]}]}]]}],"loading":null,"isPartial":false}
|
||||
@@ -1,6 +0,0 @@
|
||||
:HL["/Super_Z/echo-nexus/_next/static/chunks/59368da722d2753f.css","style"]
|
||||
:HL["/Super_Z/echo-nexus/_next/static/chunks/7bbd6f879a3901f6.css","style"]
|
||||
:HL["/Super_Z/echo-nexus/_next/static/media/0acc7fdf55eb3220-s.p.532ccaa1.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
|
||||
:HL["/Super_Z/echo-nexus/_next/static/media/797e433ab948586e-s.p.29207c2f.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
|
||||
:HL["/Super_Z/echo-nexus/_next/static/media/caa3a2e1cccd8315-s.p.3b6cae6d.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
|
||||
0:{"buildId":"yNvWrGxQ0RM8HHWxdOJQM","tree":{"name":"","paramType":null,"paramKey":"","hasRuntimePrefetch":false,"slots":{"children":{"name":"__PAGE__","paramType":null,"paramKey":"__PAGE__","hasRuntimePrefetch":false,"slots":null,"isRootLayout":false}},"isRootLayout":true},"staleTime":300}
|
||||
@@ -1,11 +0,0 @@
|
||||
self.__BUILD_MANIFEST = {
|
||||
"__rewrites": {
|
||||
"afterFiles": [],
|
||||
"beforeFiles": [],
|
||||
"fallback": []
|
||||
},
|
||||
"sortedPages": [
|
||||
"/_app",
|
||||
"/_error"
|
||||
]
|
||||
};self.__BUILD_MANIFEST_CB && self.__BUILD_MANIFEST_CB()
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
@@ -1 +0,0 @@
|
||||
self.__SSG_MANIFEST=new Set([]);self.__SSG_MANIFEST_CB&&self.__SSG_MANIFEST_CB()
|
||||
@@ -1,11 +0,0 @@
|
||||
self.__BUILD_MANIFEST = {
|
||||
"__rewrites": {
|
||||
"afterFiles": [],
|
||||
"beforeFiles": [],
|
||||
"fallback": []
|
||||
},
|
||||
"sortedPages": [
|
||||
"/_app",
|
||||
"/_error"
|
||||
]
|
||||
};self.__BUILD_MANIFEST_CB && self.__BUILD_MANIFEST_CB()
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
@@ -1 +0,0 @@
|
||||
self.__SSG_MANIFEST=new Set([]);self.__SSG_MANIFEST_CB&&self.__SSG_MANIFEST_CB()
|
||||
@@ -1,2 +0,0 @@
|
||||
@font-face{font-family:Geist;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/fef07dbb0973bf53-s.518e079e.woff2)format("woff2");unicode-range:U+460-52F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Geist;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/8a480f0b521d4e75-s.ea323500.woff2)format("woff2");unicode-range:U+301,U+400-45F,U+490-491,U+4B0-4B1,U+2116}@font-face{font-family:Geist;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/53b9e256198e5412-s.853d50a3.woff2)format("woff2");unicode-range:U+102-103,U+110-111,U+128-129,U+168-169,U+1A0-1A1,U+1AF-1B0,U+300-301,U+303-304,U+308-309,U+323,U+329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Geist;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/7178b3e590c64307-s.55554cd0.woff2)format("woff2");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Geist;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/caa3a2e1cccd8315-s.p.3b6cae6d.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Geist Fallback;src:local(Arial);ascent-override:95.94%;descent-override:28.16%;line-gap-override:0.0%;size-adjust:104.76%}.geist_a71539c9-module__T19VSG__className{font-family:Geist,Geist Fallback;font-style:normal}.geist_a71539c9-module__T19VSG__variable{--font-geist-sans:"Geist","Geist Fallback"}
|
||||
@font-face{font-family:Geist Mono;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/5ce348bf30bf5439-s.56c1f21e.woff2)format("woff2");unicode-range:U+460-52F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Geist Mono;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/4fa387ec64143e14-s.3b336396.woff2)format("woff2");unicode-range:U+301,U+400-45F,U+490-491,U+4B0-4B1,U+2116}@font-face{font-family:Geist Mono;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/6306c77e7c8268e4-s.e3369375.woff2)format("woff2");unicode-range:U+2000-2001,U+2004-2008,U+200A,U+23B8-23BD,U+2500-259F}@font-face{font-family:Geist Mono;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/7d817b4c03b0c5f1-s.a40b9a8b.woff2)format("woff2");unicode-range:U+102-103,U+110-111,U+128-129,U+168-169,U+1A0-1A1,U+1AF-1B0,U+300-301,U+303-304,U+308-309,U+323,U+329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Geist Mono;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/bbc41e54d2fcbd21-s.fe42ddf4.woff2)format("woff2");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Geist Mono;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/797e433ab948586e-s.p.29207c2f.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Geist Mono Fallback;src:local(Arial);ascent-override:74.67%;descent-override:21.92%;line-gap-override:0.0%;size-adjust:134.59%}.geist_mono_8d43a2aa-module__8Li5zG__className{font-family:Geist Mono,Geist Mono Fallback;font-style:normal}.geist_mono_8d43a2aa-module__8Li5zG__variable{--font-geist-mono:"Geist Mono","Geist Mono Fallback"}
|
||||
@@ -1,3 +0,0 @@
|
||||
@font-face{font-family:Geist;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/fef07dbb0973bf53-s.518e079e.woff2)format("woff2");unicode-range:U+460-52F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Geist;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/8a480f0b521d4e75-s.ea323500.woff2)format("woff2");unicode-range:U+301,U+400-45F,U+490-491,U+4B0-4B1,U+2116}@font-face{font-family:Geist;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/53b9e256198e5412-s.853d50a3.woff2)format("woff2");unicode-range:U+102-103,U+110-111,U+128-129,U+168-169,U+1A0-1A1,U+1AF-1B0,U+300-301,U+303-304,U+308-309,U+323,U+329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Geist;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/7178b3e590c64307-s.55554cd0.woff2)format("woff2");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Geist;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/caa3a2e1cccd8315-s.p.3b6cae6d.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Geist Fallback;src:local(Arial);ascent-override:95.94%;descent-override:28.16%;line-gap-override:0.0%;size-adjust:104.76%}.geist_a71539c9-module__T19VSG__className{font-family:Geist,Geist Fallback;font-style:normal}.geist_a71539c9-module__T19VSG__variable{--font-geist-sans:"Geist","Geist Fallback"}
|
||||
@font-face{font-family:Geist Mono;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/5ce348bf30bf5439-s.56c1f21e.woff2)format("woff2");unicode-range:U+460-52F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Geist Mono;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/4fa387ec64143e14-s.3b336396.woff2)format("woff2");unicode-range:U+301,U+400-45F,U+490-491,U+4B0-4B1,U+2116}@font-face{font-family:Geist Mono;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/6306c77e7c8268e4-s.e3369375.woff2)format("woff2");unicode-range:U+2000-2001,U+2004-2008,U+200A,U+23B8-23BD,U+2500-259F}@font-face{font-family:Geist Mono;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/7d817b4c03b0c5f1-s.a40b9a8b.woff2)format("woff2");unicode-range:U+102-103,U+110-111,U+128-129,U+168-169,U+1A0-1A1,U+1AF-1B0,U+300-301,U+303-304,U+308-309,U+323,U+329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Geist Mono;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/bbc41e54d2fcbd21-s.fe42ddf4.woff2)format("woff2");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Geist Mono;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/797e433ab948586e-s.p.29207c2f.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Geist Mono Fallback;src:local(Arial);ascent-override:74.67%;descent-override:21.92%;line-gap-override:0.0%;size-adjust:134.59%}.geist_mono_8d43a2aa-module__8Li5zG__className{font-family:Geist Mono,Geist Mono Fallback;font-style:normal}.geist_mono_8d43a2aa-module__8Li5zG__variable{--font-geist-mono:"Geist Mono","Geist Mono Fallback"}
|
||||
@font-face{font-family:Orbitron;font-style:normal;font-weight:400;font-display:swap;src:url(../media/0acc7fdf55eb3220-s.p.532ccaa1.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Orbitron;font-style:normal;font-weight:500;font-display:swap;src:url(../media/0acc7fdf55eb3220-s.p.532ccaa1.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Orbitron;font-style:normal;font-weight:600;font-display:swap;src:url(../media/0acc7fdf55eb3220-s.p.532ccaa1.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Orbitron;font-style:normal;font-weight:700;font-display:swap;src:url(../media/0acc7fdf55eb3220-s.p.532ccaa1.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Orbitron;font-style:normal;font-weight:900;font-display:swap;src:url(../media/0acc7fdf55eb3220-s.p.532ccaa1.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Orbitron Fallback;src:local(Arial);ascent-override:81.5%;descent-override:19.59%;line-gap-override:0.0%;size-adjust:124.05%}.orbitron_da1c3699-module__wSKb_W__className{font-family:Orbitron,Orbitron Fallback;font-style:normal}.orbitron_da1c3699-module__wSKb_W__variable{--font-display:"Orbitron","Orbitron Fallback"}
|
||||
@@ -1 +0,0 @@
|
||||
(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,95203,e=>{"use strict";var s=e.i(43476),t=e.i(71645),l=e.i(76639),a=e.i(19455),o=e.i(18533),r=e.i(43625),n=e.i(31334),i=e.i(83086),d=e.i(16715);function c({open:e,onOpenChange:c}){let x=(0,o.useGameStore)(e=>e.pendingPerkChoices),h=(0,o.useGameStore)(e=>e.constellation??[]),m=(0,o.useGameStore)(e=>e.chooseConstellationPerk),u=(0,o.useGameStore)(e=>e.rerollPerkChoices),[f,g]=(0,t.useState)(null);if((0,t.useEffect)(()=>{e||g(null)},[e]),(0,t.useEffect)(()=>{x&&x.length>0&&!e&&c(!0),(!x||0===x.length)&&e&&c(!1)},[x,e,c]),!x||0===x.length)return null;let p=(0,r.constellationProgress)(h);return(0,s.jsx)(l.Dialog,{open:e,onOpenChange:c,children:(0,s.jsxs)(l.DialogContent,{className:"max-w-md border-fuchsia-400/30 bg-gradient-to-br from-[#0a0820]/95 to-[#1a0f30]/95 backdrop-blur-md",children:[(0,s.jsxs)(l.DialogHeader,{children:[(0,s.jsxs)(l.DialogTitle,{className:"flex items-center gap-2 text-fuchsia-200",children:[(0,s.jsx)(i.Sparkles,{className:"h-4 w-4"}),"星图觉醒"]}),(0,s.jsxs)(l.DialogDescription,{className:"text-muted-foreground text-xs",children:["飞升带来的回响在星图上凝聚。从 3 道星辉中选择 1 道,永久获得其加成。",(0,s.jsx)("br",{}),(0,s.jsxs)("span",{className:"text-fuchsia-300/80",children:["当前已觉醒 ",p.unlocked,"/",p.total]})]})]}),(0,s.jsx)("div",{className:"grid grid-cols-1 gap-2 mt-1",children:x.map(e=>{let t=(0,r.getPerk)(e);if(!t)return null;let l=r.CONSTELLATION_CATEGORY_META[t.category],a=f===e;return(0,s.jsxs)("button",{onClick:()=>{g(e),m(e)&&((0,n.sfx)("constellation"),setTimeout(()=>c(!1),500))},disabled:!!f,className:`group relative w-full text-left rounded-lg border px-3 py-2.5 transition-all overflow-hidden ${a?"border-fuchsia-400/80 bg-fuchsia-500/10 scale-[1.02]":f?"border-white/5 opacity-40":"border-white/10 hover:border-white/30 hover:bg-white/5"}`,style:{boxShadow:a?`0 0 20px ${l.glow}`:void 0},children:[(0,s.jsx)("div",{className:"absolute left-0 top-0 bottom-0 w-1",style:{background:l.hex,boxShadow:`0 0 8px ${l.hex}`}}),(0,s.jsxs)("div",{className:"pl-2 flex items-start gap-3",children:[(0,s.jsx)("div",{className:"flex-shrink-0 mt-0.5 h-8 w-8 rounded-full flex items-center justify-center text-sm font-bold",style:{background:`${l.hex}20`,color:l.hex,border:`1px solid ${l.hex}40`},children:l.name.charAt(0)}),(0,s.jsxs)("div",{className:"flex-1 min-w-0",children:[(0,s.jsxs)("div",{className:"flex items-center gap-2 mb-0.5",children:[(0,s.jsx)("span",{className:"text-sm font-bold text-foreground",children:t.name}),(0,s.jsx)("span",{className:"text-[9px] px-1 py-0.5 rounded font-mono",style:{background:`${l.hex}20`,color:l.hex},children:l.name})]}),(0,s.jsx)("div",{className:"text-[11px] text-muted-foreground",children:t.desc})]})]}),a&&(0,s.jsx)("div",{className:"absolute inset-0 pointer-events-none",children:[...Array(8)].map((e,t)=>(0,s.jsx)("span",{className:"absolute h-1 w-1 rounded-full animate-ping",style:{background:l.hex,left:`${20+8*t}%`,top:`${30+t%3*20}%`,animationDelay:`${50*t}ms`,animationDuration:"800ms"}},t))})]},e)})}),(0,s.jsxs)("div",{className:"flex items-center justify-between mt-3 pt-2 border-t border-white/5",children:[(0,s.jsxs)(a.Button,{variant:"ghost",size:"sm",onClick:()=>{u(),(0,n.sfx)("uiClick")},disabled:!!f,className:"text-muted-foreground hover:text-foreground h-7 text-[11px]",children:[(0,s.jsx)(d.RefreshCw,{className:"h-3 w-3 mr-1"}),"重新抽取"]}),(0,s.jsx)("span",{className:"text-[10px] text-muted-foreground/60",children:"✦ 选择后永久保留,跨周目生效"})]})]})})}e.s(["ConstellationDialog",()=>c])},76103,e=>{e.n(e.i(95203))}]);
|
||||
@@ -1,11 +0,0 @@
|
||||
self.__BUILD_MANIFEST = {
|
||||
"__rewrites": {
|
||||
"afterFiles": [],
|
||||
"beforeFiles": [],
|
||||
"fallback": []
|
||||
},
|
||||
"sortedPages": [
|
||||
"/_app",
|
||||
"/_error"
|
||||
]
|
||||
};self.__BUILD_MANIFEST_CB && self.__BUILD_MANIFEST_CB()
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
@@ -1 +0,0 @@
|
||||
self.__SSG_MANIFEST=new Set([]);self.__SSG_MANIFEST_CB&&self.__SSG_MANIFEST_CB()
|
||||
@@ -1,16 +0,0 @@
|
||||
1:"$Sreact.fragment"
|
||||
2:I[39756,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"default"]
|
||||
3:I[37457,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"default"]
|
||||
4:I[77855,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"Toaster"]
|
||||
5:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"OutletBoundary"]
|
||||
6:"$Sreact.suspense"
|
||||
8:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"ViewportBoundary"]
|
||||
a:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"MetadataBoundary"]
|
||||
c:I[68027,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"default"]
|
||||
:HL["/Super_Z/echo-nexus/_next/static/chunks/59368da722d2753f.css","style"]
|
||||
:HL["/Super_Z/echo-nexus/_next/static/chunks/7bbd6f879a3901f6.css","style"]
|
||||
0:{"P":null,"b":"yNvWrGxQ0RM8HHWxdOJQM","c":["","_not-found",""],"q":"","i":false,"f":[[["",{"children":["/_not-found",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/Super_Z/echo-nexus/_next/static/chunks/59368da722d2753f.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/Super_Z/echo-nexus/_next/static/chunks/7bbd6f879a3901f6.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"zh-CN","suppressHydrationWarning":true,"className":"dark","children":["$","body",null,{"className":"geist_a71539c9-module__T19VSG__variable geist_mono_8d43a2aa-module__8Li5zG__variable orbitron_da1c3699-module__wSKb_W__variable antialiased bg-background text-foreground","children":[["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}],["$","$L4",null,{}]]}]}]]}],{"children":[["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":[["$","$1","c",{"children":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:0:props:notFound:0:1:props:style","children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":"$0:f:0:1:0:props:children:1:props:children:props:children:0:props:notFound:0:1:props:children:props:children:1:props:style","children":404}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:0:props:notFound:0:1:props:children:props:children:2:props:style","children":["$","h2",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:0:props:notFound:0:1:props:children:props:children:2:props:children:props:style","children":"This page could not be found."}]}]]}]}]],null,["$","$L5",null,{"children":["$","$6",null,{"name":"Next.MetadataOutlet","children":"$@7"}]}]]}],{},null,false,false]},null,false,false]},null,false,false],["$","$1","h",{"children":[["$","meta",null,{"name":"robots","content":"noindex"}],["$","$L8",null,{"children":"$L9"}],["$","div",null,{"hidden":true,"children":["$","$La",null,{"children":["$","$6",null,{"name":"Next.Metadata","children":"$Lb"}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],false]],"m":"$undefined","G":["$c","$undefined"],"S":true}
|
||||
9:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
|
||||
d:I[27201,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"IconMark"]
|
||||
7:null
|
||||
b:[["$","title","0",{"children":"回响星核 · Echo Nexus"}],["$","meta","1",{"name":"description","content":"原创深空考古放置策略游戏 — 解码记忆晶体,拼凑以太文明的回响,跨越维度发起接触。"}],["$","meta","2",{"name":"author","content":"Super_Z"}],["$","meta","3",{"name":"keywords","content":"Echo Nexus,回响星核,idle game,browser game,放置游戏,解谜,深空考古"}],["$","meta","4",{"property":"og:title","content":"回响星核 · Echo Nexus"}],["$","meta","5",{"property":"og:description","content":"原创深空考古放置策略游戏"}],["$","meta","6",{"property":"og:site_name","content":"Echo Nexus"}],["$","meta","7",{"property":"og:type","content":"website"}],["$","meta","8",{"name":"twitter:card","content":"summary_large_image"}],["$","meta","9",{"name":"twitter:title","content":"回响星核 · Echo Nexus"}],["$","meta","10",{"name":"twitter:description","content":"原创深空考古放置策略游戏"}],["$","link","11",{"rel":"icon","href":"https://z-cdn.chatglm.cn/z-ai/static/logo.svg"}],["$","$Ld","12",{}]]
|
||||
@@ -1,6 +0,0 @@
|
||||
1:"$Sreact.fragment"
|
||||
2:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"ViewportBoundary"]
|
||||
3:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"MetadataBoundary"]
|
||||
4:"$Sreact.suspense"
|
||||
5:I[27201,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"IconMark"]
|
||||
0:{"buildId":"yNvWrGxQ0RM8HHWxdOJQM","rsc":["$","$1","h",{"children":[["$","meta",null,{"name":"robots","content":"noindex"}],["$","$L2",null,{"children":[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]}],["$","div",null,{"hidden":true,"children":["$","$L3",null,{"children":["$","$4",null,{"name":"Next.Metadata","children":[["$","title","0",{"children":"回响星核 · Echo Nexus"}],["$","meta","1",{"name":"description","content":"原创深空考古放置策略游戏 — 解码记忆晶体,拼凑以太文明的回响,跨越维度发起接触。"}],["$","meta","2",{"name":"author","content":"Super_Z"}],["$","meta","3",{"name":"keywords","content":"Echo Nexus,回响星核,idle game,browser game,放置游戏,解谜,深空考古"}],["$","meta","4",{"property":"og:title","content":"回响星核 · Echo Nexus"}],["$","meta","5",{"property":"og:description","content":"原创深空考古放置策略游戏"}],["$","meta","6",{"property":"og:site_name","content":"Echo Nexus"}],["$","meta","7",{"property":"og:type","content":"website"}],["$","meta","8",{"name":"twitter:card","content":"summary_large_image"}],["$","meta","9",{"name":"twitter:title","content":"回响星核 · Echo Nexus"}],["$","meta","10",{"name":"twitter:description","content":"原创深空考古放置策略游戏"}],["$","link","11",{"rel":"icon","href":"https://z-cdn.chatglm.cn/z-ai/static/logo.svg"}],["$","$L5","12",{}]]}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],"loading":null,"isPartial":false}
|
||||
@@ -1,7 +0,0 @@
|
||||
1:"$Sreact.fragment"
|
||||
2:I[39756,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"default"]
|
||||
3:I[37457,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"default"]
|
||||
4:I[77855,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"Toaster"]
|
||||
:HL["/Super_Z/echo-nexus/_next/static/chunks/59368da722d2753f.css","style"]
|
||||
:HL["/Super_Z/echo-nexus/_next/static/chunks/7bbd6f879a3901f6.css","style"]
|
||||
0:{"buildId":"yNvWrGxQ0RM8HHWxdOJQM","rsc":["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/Super_Z/echo-nexus/_next/static/chunks/59368da722d2753f.css","precedence":"next"}],["$","link","1",{"rel":"stylesheet","href":"/Super_Z/echo-nexus/_next/static/chunks/7bbd6f879a3901f6.css","precedence":"next"}],["$","script","script-0",{"src":"/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","async":true}],["$","script","script-1",{"src":"/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","async":true}],["$","script","script-2",{"src":"/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js","async":true}]],["$","html",null,{"lang":"zh-CN","suppressHydrationWarning":true,"className":"dark","children":["$","body",null,{"className":"geist_a71539c9-module__T19VSG__variable geist_mono_8d43a2aa-module__8Li5zG__variable orbitron_da1c3699-module__wSKb_W__variable antialiased bg-background text-foreground","children":[["$","$L2",null,{"parallelRouterKey":"children","template":["$","$L3",null,{}],"notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]]}],["$","$L4",null,{}]]}]}]]}],"loading":null,"isPartial":false}
|
||||
@@ -1,5 +0,0 @@
|
||||
1:"$Sreact.fragment"
|
||||
2:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"OutletBoundary"]
|
||||
3:"$Sreact.suspense"
|
||||
0:{"buildId":"yNvWrGxQ0RM8HHWxdOJQM","rsc":["$","$1","c",{"children":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],null,["$","$L2",null,{"children":["$","$3",null,{"name":"Next.MetadataOutlet","children":"$@4"}]}]]}],"loading":null,"isPartial":false}
|
||||
4:null
|
||||
@@ -1,4 +0,0 @@
|
||||
1:"$Sreact.fragment"
|
||||
2:I[39756,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"default"]
|
||||
3:I[37457,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"default"]
|
||||
0:{"buildId":"yNvWrGxQ0RM8HHWxdOJQM","rsc":["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","template":["$","$L3",null,{}]}]]}],"loading":null,"isPartial":false}
|
||||
@@ -1,3 +0,0 @@
|
||||
:HL["/Super_Z/echo-nexus/_next/static/chunks/59368da722d2753f.css","style"]
|
||||
:HL["/Super_Z/echo-nexus/_next/static/chunks/7bbd6f879a3901f6.css","style"]
|
||||
0:{"buildId":"yNvWrGxQ0RM8HHWxdOJQM","tree":{"name":"","paramType":null,"paramKey":"","hasRuntimePrefetch":false,"slots":{"children":{"name":"/_not-found","paramType":null,"paramKey":"/_not-found","hasRuntimePrefetch":false,"slots":{"children":{"name":"__PAGE__","paramType":null,"paramKey":"__PAGE__","hasRuntimePrefetch":false,"slots":null,"isRootLayout":false}},"isRootLayout":false}},"isRootLayout":true},"staleTime":300}
|
||||
@@ -1,16 +0,0 @@
|
||||
1:"$Sreact.fragment"
|
||||
2:I[39756,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"default"]
|
||||
3:I[37457,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"default"]
|
||||
4:I[77855,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"Toaster"]
|
||||
5:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"OutletBoundary"]
|
||||
6:"$Sreact.suspense"
|
||||
8:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"ViewportBoundary"]
|
||||
a:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"MetadataBoundary"]
|
||||
c:I[68027,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"default"]
|
||||
:HL["/Super_Z/echo-nexus/_next/static/chunks/59368da722d2753f.css","style"]
|
||||
:HL["/Super_Z/echo-nexus/_next/static/chunks/7bbd6f879a3901f6.css","style"]
|
||||
0:{"P":null,"b":"yNvWrGxQ0RM8HHWxdOJQM","c":["","_not-found",""],"q":"","i":false,"f":[[["",{"children":["/_not-found",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/Super_Z/echo-nexus/_next/static/chunks/59368da722d2753f.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/Super_Z/echo-nexus/_next/static/chunks/7bbd6f879a3901f6.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"zh-CN","suppressHydrationWarning":true,"className":"dark","children":["$","body",null,{"className":"geist_a71539c9-module__T19VSG__variable geist_mono_8d43a2aa-module__8Li5zG__variable orbitron_da1c3699-module__wSKb_W__variable antialiased bg-background text-foreground","children":[["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}],["$","$L4",null,{}]]}]}]]}],{"children":[["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":[["$","$1","c",{"children":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:0:props:notFound:0:1:props:style","children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":"$0:f:0:1:0:props:children:1:props:children:props:children:0:props:notFound:0:1:props:children:props:children:1:props:style","children":404}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:0:props:notFound:0:1:props:children:props:children:2:props:style","children":["$","h2",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:0:props:notFound:0:1:props:children:props:children:2:props:children:props:style","children":"This page could not be found."}]}]]}]}]],null,["$","$L5",null,{"children":["$","$6",null,{"name":"Next.MetadataOutlet","children":"$@7"}]}]]}],{},null,false,false]},null,false,false]},null,false,false],["$","$1","h",{"children":[["$","meta",null,{"name":"robots","content":"noindex"}],["$","$L8",null,{"children":"$L9"}],["$","div",null,{"hidden":true,"children":["$","$La",null,{"children":["$","$6",null,{"name":"Next.Metadata","children":"$Lb"}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],false]],"m":"$undefined","G":["$c","$undefined"],"S":true}
|
||||
9:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
|
||||
d:I[27201,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"IconMark"]
|
||||
7:null
|
||||
b:[["$","title","0",{"children":"回响星核 · Echo Nexus"}],["$","meta","1",{"name":"description","content":"原创深空考古放置策略游戏 — 解码记忆晶体,拼凑以太文明的回响,跨越维度发起接触。"}],["$","meta","2",{"name":"author","content":"Super_Z"}],["$","meta","3",{"name":"keywords","content":"Echo Nexus,回响星核,idle game,browser game,放置游戏,解谜,深空考古"}],["$","meta","4",{"property":"og:title","content":"回响星核 · Echo Nexus"}],["$","meta","5",{"property":"og:description","content":"原创深空考古放置策略游戏"}],["$","meta","6",{"property":"og:site_name","content":"Echo Nexus"}],["$","meta","7",{"property":"og:type","content":"website"}],["$","meta","8",{"name":"twitter:card","content":"summary_large_image"}],["$","meta","9",{"name":"twitter:title","content":"回响星核 · Echo Nexus"}],["$","meta","10",{"name":"twitter:description","content":"原创深空考古放置策略游戏"}],["$","link","11",{"rel":"icon","href":"https://z-cdn.chatglm.cn/z-ai/static/logo.svg"}],["$","$Ld","12",{}]]
|
||||
@@ -1,91 +0,0 @@
|
||||
# Task ID: 10-a — 限时挑战 + 星潮类型深化
|
||||
|
||||
**Agent**: full-stack-developer
|
||||
**Task**: 工单 #5 限时挑战 + 工单 #3 星潮类型深化
|
||||
|
||||
## Work Log
|
||||
|
||||
### 前置准备
|
||||
- 阅读 `worklog.md` 了解 v0.8.1 项目状态(10 大系统,四色全息 emerald/rose/amber/fuchsia)
|
||||
- 精读 `beacon.ts`(873 行,日挑战+周挑战+信标链)、`BeaconPanel.tsx`(753 行)、`starTide.ts`(209 行,6 种星潮)、`gameStore.ts` trackBeacon(~170 行)+ tickTide(~440 行)
|
||||
- 确认现有 UI 均动态读取 `TIDE_EVENTS[type]`,新增类型自动生效
|
||||
|
||||
### 工单 #5:限时挑战(Timed Challenge)
|
||||
|
||||
**beacon.ts 扩展**(873 → 1162 行,+289 行):
|
||||
- `BeaconScoreEntry` 新增 `isTimed?: boolean` 字段
|
||||
- `BeaconTimedChallenge` 接口:slotKey / type / difficulty(固定 routine) / goal / rewardInsight / rewardContact / seed / title / desc / expiresAt
|
||||
- `BeaconTimedProgress` 接口:slotKey / progress / startedAt / completedAt / claimed / durationSec
|
||||
- `TIMED_SLOT_MS = 4h`,`BEACON_TIMED_KEY = "echo-nexus-beacon-timed-v1"`
|
||||
- `getTimedSlotKey(now)`:UTC 0/4/8/12/16/20 点切分,格式 "timed_YYYY-MM-DD_HH"
|
||||
- `timedSlotKeyToSeed`:FNV-1a 哈希
|
||||
- `generateTimedChallenge(now)`:确定性生成,难度 routine,goal = 日基准 ×0.3-0.5,5 种类型各有公式
|
||||
- `msUntilNextTimedSlot(now)`:距离下个 4 小时时段的毫秒数
|
||||
- `loadTimedProgress` / `saveTimedProgress` / `addTimedProgress` / `claimTimedReward`(推送排行榜,entry 加 isTimed: true)
|
||||
|
||||
**gameStore.ts 集成**:
|
||||
- import 扩展:generateTimedChallenge / loadTimedProgress / addTimedProgress / claimTimedReward + 类型
|
||||
- `trackBeacon` 返回值新增 `timedJustCompleted`:日/限时/周三类进度同时更新
|
||||
- 新增 `claimTimedBeacon` action(GameActions 接口同步)
|
||||
- 新增 `timedJustCompleted` 到 trackBeacon 返回类型
|
||||
|
||||
**BeaconPanel.tsx UI**(753 → 944 行):
|
||||
- import 扩展:generateTimedChallenge / loadTimedProgress / getTimedSlotKey / msUntilNextTimedSlot / Hourglass 图标
|
||||
- 新增 timedChallenge/timedProgress/timedCountdown state
|
||||
- 每秒刷新 timedProgress + timedCountdown
|
||||
- 新增 handleClaimTimed 回调
|
||||
- **限时挑战区块**(amber 主题,插入日挑战与周挑战之间):
|
||||
- Hourglass 图标 + "限时挑战 · TIMED" 标题 + slotKey + 倒计时
|
||||
- 难度/类型标签 + 标题 + 描述 + 进度条 + 奖励 + 领取按钮(emerald 主题)
|
||||
- **紧急状态**:距时段结束 <30min 时切换 rose 脉冲动画 + "即将结束"徽章
|
||||
- 2 个新动画:timed-glow(amber 呼吸)/ timed-urgent(rose 急促脉冲)
|
||||
- 排行榜 entry 区分 TIMED(amber 徽章)/ WEEK(fuchsia 徽章)
|
||||
- 空状态提示更新为"完成限时、每日或本周信标即可登榜"
|
||||
|
||||
### 工单 #3:星潮类型深化
|
||||
|
||||
**starTide.ts 扩展**(209 → 289 行):
|
||||
- `TideType` 新增 3 种:surge / eclipse / prism
|
||||
- `TideModifiers` 新增 3 个可选字段:targetLenBonus / bossWinRateBonus / autoDecodeIntervalMult
|
||||
- `TIDE_EVENTS` 新增 3 种元信息(name/desc/color/glow/icon/negative):
|
||||
- surge(涌动星潮,emerald #34d399,⇈):产能 ×2.5 但解码目标 +2
|
||||
- eclipse(蚀相星潮,rose #fb7185,◐):BOSS 胜率 +20% 但产能 -30%
|
||||
- prism(棱镜星潮,fuchsia #e879f9,◬):洞见 ×2 + 自动解码周期 -30%
|
||||
- `TIDE_WEIGHTS` 新增 surge/eclipse/prism 各 12(总权重 136)
|
||||
- `getTideModifiers` 新增 3 个 case + null 分支补全新字段
|
||||
- 注释更新:"6 种类型" → "9 种类型"
|
||||
|
||||
**decode.ts 扩展**:
|
||||
- `generatePuzzle` 新增第 3 参数 `targetLenBonus`(默认 0)
|
||||
- targetLen = cfg.targetLen + targetLenBonus,stepLimitBase 同步增加
|
||||
- 路径构造法自动适配更长目标序列(300 次尝试 + 线性兜底)
|
||||
|
||||
**gameStore.ts 集成**(3 处修饰器应用):
|
||||
- `startDecode`:surge 星潮时传入 `tideMod.targetLenBonus` → 谜题更长
|
||||
- `autoDecodeTick`:prism 星潮时 `interval *= tideMod.autoDecodeIntervalMult`(×0.7)
|
||||
- `resolveCurrentNode`:eclipse 星潮时 `b = am.bossWinRateBonus + tideMod.bossWinRateBonus`(+20%)
|
||||
|
||||
**achievements.ts**:`ach_tides_all` 阈值 6 → 9,描述"经历全部 9 种星潮事件"
|
||||
|
||||
**page.tsx**:StatsPanel "星潮亲历" 显示 `/ 9`
|
||||
|
||||
### QA 验证
|
||||
1. **lint**:`bun run lint` 零错误(exit 0)
|
||||
2. **dev 服务器**:HTTP 200,编译 < 30ms
|
||||
3. **BeaconPanel VLM 评分**:
|
||||
- 首屏 8.5/10:限时挑战 TIMED 区块可见、四色一致、倒计时/进度/标题/奖励/按钮清晰
|
||||
- 最终截图 9/10:4 个挑战区块齐全、限时区块带倒计时、无蓝色
|
||||
4. **新星潮 VLM 评分**(localStorage 注入 + reload 触发):
|
||||
- surge(emerald 绿):8/10,横幅 + 全屏绿色微光
|
||||
- prism(fuchsia 紫):7/10,横幅 + 全屏紫色微光
|
||||
- eclipse(rose 粉):8/10(单图),横幅 + 全屏粉色微光
|
||||
5. **不破坏现有功能**:日挑战/周挑战/信标链/6 旧星潮全部保留,排行榜正常
|
||||
|
||||
## Stage Summary
|
||||
- ✅ 工单 #5 限时挑战完成:beacon.ts +289 行 + gameStore claimTimedBeacon + BeaconPanel amber 区块
|
||||
- ✅ 工单 #3 星潮深化完成:starTide.ts +80 行(3 新类型)+ decode.ts targetLenBonus + gameStore 3 处修饰器
|
||||
- ✅ 确定性生成:限时挑战同时段同种子同结果(FNV-1a slotKey 哈希)
|
||||
- ✅ 严格四色规范:限时 amber、surge emerald、eclipse rose、prism fuchsia,零蓝色/靛色
|
||||
- ✅ 紧急状态设计:限时挑战 <30min 时 rose 脉冲 + "即将结束"徽章
|
||||
- ✅ lint 零错误 + dev HTTP 200 + VLM ≥7/10(信标 9/10、surge 8/10、prism 7/10、eclipse 8/10)
|
||||
- ✅ 现有日/周/信标链/6 旧星潮全部保留,无回归
|
||||
@@ -1,207 +0,0 @@
|
||||
# Task 10-b · full-stack-developer · P2/#4 云排行榜 mini-service
|
||||
|
||||
> 本文件为本 agent 在 Task 10-b 的工作记录,供后续 agent 查阅。
|
||||
|
||||
## 任务概述
|
||||
|
||||
为「回响星核 / Echo Nexus」v0.8.2 实现 P2/#4 工单:把信标本机排行榜(纯 localStorage)升级为云端 Top100,让玩家间可以比较分数。
|
||||
|
||||
## 阅读的前置工作
|
||||
|
||||
- `/home/z/my-project/worklog.md`(v0.8.1 项目状态,10 大系统,四色全息规范)
|
||||
- `/home/z/my-project/src/lib/game/beacon.ts`(实际 1161 行 — worklog 信息滞后,v0.8.2 已加限时挑战 `claimTimedReward`)
|
||||
- `/home/z/my-project/src/components/game/BeaconPanel.tsx`(实际 953 行 — 已含限时挑战 amber 主题)
|
||||
- `/home/z/my-project/src/store/gameStore.ts`(1241 行 — trackBeacon + 3 个 claim action:grantBeaconReward/claimWeeklyBeacon/claimTimedBeacon/claimChainReward)
|
||||
- `/home/z/my-project/Caddyfile`(:81 网关,按 ?XTransformPort=* 转发到对应端口)
|
||||
- `/home/z/my-project/examples/websocket/server.ts` + `frontend.tsx`(mini-service + 前端连接参考)
|
||||
- `/home/z/my-project/.zscripts/dev.sh`(mini-service 自动扫描启动逻辑,发现 mini-services 目录下子服务自动 `bun run dev`)
|
||||
- `/home/z/my-project/agent-ctx/9-b-full-stack-developer.md`(前一个 agent 的信标系统扩展记录)
|
||||
- `/home/z/my-project/mini-services/star-tide-service/`(已存在的 P3 #10-c 服务,端口 3031)
|
||||
|
||||
## 实现细节
|
||||
|
||||
### 1. mini-service(`mini-services/leaderboard-service/`)
|
||||
|
||||
**目录结构**:
|
||||
```
|
||||
mini-services/leaderboard-service/
|
||||
├── package.json # 独立 bun 项目,type=module
|
||||
├── index.ts # Hono 入口(182 行)
|
||||
├── bun.lock # 自动生成
|
||||
├── node_modules/ # hono 独立安装
|
||||
└── README.md # API 文档
|
||||
```
|
||||
|
||||
**package.json**:
|
||||
- `"dev": "bun --hot index.ts"`(规范要求的热重载脚本)
|
||||
- `"start": "bun index.ts"`(无热重载,用于稳定运行)
|
||||
- 依赖:hono ^4.6.14
|
||||
|
||||
**index.ts 设计要点**:
|
||||
- Hono + `cors()` 中间件,origin: * 全开放(前端跨端口必须)
|
||||
- 内存数组 `entries: BeaconScoreEntry[]`,最多 1000 条,按 score 降序
|
||||
- 同分排序:先看用时短,再看提交早
|
||||
- 防刷:`lastSubmitAt: Map<dateKey|challenge, ts>`,10 秒内只接受 1 次,返回 429 + retryAfterMs
|
||||
- 4 个路由:
|
||||
- `GET /` → 健康检查 `{service, version, ok, uptime}`
|
||||
- `GET /api/leaderboard` → Top100 `{entries, total}`
|
||||
- `POST /api/leaderboard` body `{entry}` → schema 校验 → 推入 → 排序 → `{entries, total, rank}`
|
||||
- `GET /api/leaderboard/stats` → `{totalSubmissions, uniquePlayers, topScore}`
|
||||
- OPTIONS 预检由 hono/cors 自动处理,返回 204 + 完整 CORS 头
|
||||
- 端口固定 3030,`export default { port, fetch }` 标准 bun 模式
|
||||
|
||||
**启动调试经验**:
|
||||
- `bun --hot` 在本沙盒环境下不稳定,多次启动后约 5-10s 内被 kill(即使 setsid + nohup)
|
||||
- 解决方案:用 `bun index.ts`(无 --hot)+ 双 fork 模式:`( bun index.ts >/tmp/log 2>&1 & )`
|
||||
- 双 fork 后 PPID=1(reparent 到 init),进程脱离原 bash session,uptime 4+ 分钟稳定
|
||||
|
||||
### 2. beacon.ts 扩展(1161 → 1290 行,+129 行)
|
||||
|
||||
**三个 claim 函数返回值扩展(非破坏性)**:
|
||||
- `claimBeaconReward` 返回值新增 `entry: BeaconScoreEntry`
|
||||
- `claimWeeklyReward` 返回值新增 `entry: BeaconScoreEntry`
|
||||
- `claimTimedReward` 返回值新增 `entry: BeaconScoreEntry`
|
||||
- 用途:让前端 / gameStore 能拿到完整 entry 提交到云端
|
||||
|
||||
**新增云排行榜模块**:
|
||||
- `BEACON_CLOUD_PORT = 3030` 常量
|
||||
- `BEACON_CLOUD_LAST_SUBMIT_KEY = "echo-nexus-beacon-cloud-last-submit-v1"` localStorage key
|
||||
- 3 个响应接口:`CloudLeaderboardResponse` / `CloudSubmitResponse` / `CloudStatsResponse`
|
||||
- `loadLastCloudSubmitTimestamp()` / `saveLastCloudSubmitTimestamp(ts)`
|
||||
- `fetchCloudLeaderboard(): Promise<BeaconScoreEntry[]>` — GET `/api/leaderboard?XTransformPort=3030`
|
||||
- `submitCloudScore(entry): Promise<number>` — POST,成功返回 rank ≥ 1,失败返回 -1,自动记录 ts
|
||||
- `fetchCloudStats(): Promise<CloudStatsResponse | null>`
|
||||
- **所有 fetch 用相对路径 + ?XTransformPort=3030,禁止 localhost:3030**
|
||||
- try-catch 包裹,失败时静默返回空/null,不抛异常
|
||||
|
||||
### 3. gameStore.ts 集成
|
||||
|
||||
- import 新增 `submitCloudScore`
|
||||
- `claimWeeklyBeacon` action:领奖成功后 `void submitCloudScore(res.entry)`(fire-and-forget,不 await,不阻塞)
|
||||
- `claimTimedBeacon` action:同上
|
||||
- `claimBeaconReward` (daily) 由 BeaconPanel 直接调用,不通过 gameStore
|
||||
|
||||
### 4. BeaconPanel.tsx 重写(953 → 1170 行,+217 行)
|
||||
|
||||
**新增 import**:
|
||||
- `fetchCloudLeaderboard` / `submitCloudScore` / `loadLastCloudSubmitTimestamp` from beacon
|
||||
- 4 个 lucide 图标:`RefreshCw` / `Globe` / `WifiOff` / `Loader2`
|
||||
- `useRef` from react
|
||||
|
||||
**新增状态**:
|
||||
- `lbTab: 'local' | 'global'`(默认 local)
|
||||
- `cloudEntries: BeaconScoreEntry[]`
|
||||
- `cloudLoading: boolean`
|
||||
- `cloudError: string | null`
|
||||
- `cloudFetched: boolean`
|
||||
- `mySubmitTs: number`
|
||||
- `cloudFetchingRef` (防并发)
|
||||
|
||||
**新增逻辑**:
|
||||
- `refreshCloudLeaderboard` callback:拉取云端榜 + 防并发 + 空 entries 时显示"成为首位登顶者"
|
||||
- useEffect:切到全球 tab 自动拉取(仅首次)+ 初始化读取 mySubmitTs
|
||||
- `handleClaimDaily`:领奖后 `void submitCloudScore(res.entry).then(rank => ...)` — 若用户在全球 tab 自动刷新
|
||||
- `handleClaimTimed` / `handleClaimWeekly`:领奖后更新 mySubmitTs + 若在全球 tab 自动刷新
|
||||
|
||||
**排行榜 UI 重写为双 tab**:
|
||||
- 顶部 tab 切换:「本地」(amber 主题) / 「全球」(emerald 主题) — 用 `<button aria-pressed>` 实现可访问性
|
||||
- 全球 tab 专属刷新按钮(RefreshCw,loading 时 animate-spin)
|
||||
- 子标题:本地"本机 Top N · 离线可用" / 全球"全球 Top N · 已同步 / 同步中…"
|
||||
- 本地榜:保留原 v0.8.2 渲染(max-h-200px overflow-y-auto)
|
||||
- 全球榜:
|
||||
- 加载中:Loader2 spinner + "正在拉取全球榜…"
|
||||
- 空榜 + 错误:WifiOff 图标 + 错误文案 + 重试按钮
|
||||
- 空榜 + 无错:Globe 图标 + "尚无全球记录。完成挑战并领取即可登顶全球榜!"
|
||||
- 有数据:渲染前 100 条,每条带排名(1-3 名用 Crown/Medal/Award 图标)/ 难度色 / WEEK·TIMED 徽章 / 用时 / 分数
|
||||
- **玩家自己的记录高亮**:通过 `entry.timestamp === mySubmitTs` 匹配
|
||||
- emerald 边框 (`border-emerald-500/40`)
|
||||
- emerald 阴影 (`shadow-[0_0_12px_rgba(52,211,153,0.25)]`)
|
||||
- YOU 徽章 (`bg-emerald-500/25 text-emerald-200 border-emerald-500/50`)
|
||||
- emerald 分数色 (`color: #34d399`)
|
||||
- 加粗排名
|
||||
|
||||
### 5. eslint 配置
|
||||
|
||||
- `eslint.config.mjs` 新增 ignores `"mini-services/**"`
|
||||
- 原因:mini-service 是独立 bun 项目,有自己的 TypeScript 配置和 lint 规范,不应参与主项目 lint
|
||||
|
||||
## QA 验证结果
|
||||
|
||||
### 1. lint
|
||||
- `bun run lint` 零错误零警告
|
||||
|
||||
### 2. dev 服务器
|
||||
- dev.log 全程无错误,所有编译 < 300ms
|
||||
- 注:dev.log 中显示 `/api/leaderboard?XTransformPort=3030 404` 是预期行为 — 通过 :3000 直接访问时 Next.js 没有 /api/leaderboard 路由;通过 :81 网关访问时 Caddy 转发到 :3030 正常返回 200
|
||||
|
||||
### 3. mini-service curl 全通过
|
||||
- `GET /` → 200 `{"service":"echo-nexus-leaderboard","version":"v0.8.2","ok":true,"uptime":N}`
|
||||
- `GET /api/leaderboard` (空) → 200 `{"entries":[],"total":0}`
|
||||
- `POST /api/leaderboard` → 200 `{"entries":[...],"total":1,"rank":1}`
|
||||
- `GET /api/leaderboard` (有数据) → 200 `{"entries":[...],"total":N}`
|
||||
- `GET /api/leaderboard/stats` → 200 `{"totalSubmissions":N,"uniquePlayers":M,"topScore":X}`
|
||||
- `OPTIONS /api/leaderboard` 预检 → 204 + 完整 CORS 头(Allow-Origin: *)
|
||||
- 重复 POST(同 dateKey+challenge 10s 内)→ 429 `{"error":"Rate limited","retryAfterMs":N}`
|
||||
|
||||
### 4. 网关路由验证
|
||||
- Caddy :81 → localhost:3030 通过 `?XTransformPort=3030` 正确转发,返回 JSON 200
|
||||
|
||||
### 5. agent-browser 集成测试
|
||||
- 通过 :81 端口访问(绕过 Next.js :3000 的 404)
|
||||
- 信标 tab 选中(用 KeyboardEvent Enter 激活 Radix Tabs — 因 pointerdown 被覆盖层拦截)
|
||||
- 切到「全球」tab → 自动拉取 → 显示"全球 Top 6 · 已同步" → 渲染 6 条记录
|
||||
- 刷新按钮可点击,旋转动画正常
|
||||
- POST 测试:浏览器 fetch 提交 score=9999 → 返回 rank=1 → 刷新后榜首显示 10.00K
|
||||
- YOU 高亮:手动设置 localStorage[BEACON_CLOUD_LAST_SUBMIT_KEY] 后刷新 → 榜首显示 emerald 边框 + YOU 徽章
|
||||
|
||||
### 6. VLM 视觉评分(目标 ≥7/10)
|
||||
- 全球榜含 YOU 高亮:**8/10**
|
||||
- VLM 评语:"双tab切换清晰,全球榜记录完整,难度色与标签规范,视觉层次佳,但「YOU」徽章未突出显示"
|
||||
- 本地榜:**8/10**
|
||||
- VLM 评语:"双tab切换逻辑清晰,本地tab内容完整呈现;四色规范应用,视觉区分度高;信息层级合理"
|
||||
|
||||
## 修改的文件
|
||||
|
||||
1. `mini-services/leaderboard-service/package.json`(新建)
|
||||
2. `mini-services/leaderboard-service/index.ts`(新建,182 行)
|
||||
3. `mini-services/leaderboard-service/README.md`(新建)
|
||||
4. `src/lib/game/beacon.ts` — 1161 → 1290 行(+129 行:3 个 claim 返回 entry + 云排行榜模块)
|
||||
5. `src/store/gameStore.ts` — 1235 → 1252 行(+17 行:import + 2 个 claim action 加 submitCloudScore)
|
||||
6. `src/components/game/BeaconPanel.tsx` — 953 → 1170 行(+217 行:双 tab UI + 云端拉取 + 提交 + YOU 高亮)
|
||||
7. `eslint.config.mjs` — ignores 新增 "mini-services/**"
|
||||
|
||||
## 不破坏的现有功能
|
||||
|
||||
- 本地排行榜(`loadLeaderboard` / `pushLeaderboardEntry`)完整保留作为离线 fallback
|
||||
- 日挑战 / 周挑战 / 限时挑战 / 信标链流程全部保留
|
||||
- 现有 localStorage keys(echo-nexus-beacon-lb-v1 / -prog-v1 / -weekly-v1 / -chain-v1 / -timed-v1)全部保留
|
||||
- 主项目 package.json 不变(hono 装在 mini-service 子项目内)
|
||||
|
||||
## 新增的 localStorage keys
|
||||
|
||||
- `echo-nexus-beacon-cloud-last-submit-v1`(上次成功提交到云端的 entry timestamp,用于全球榜高亮)
|
||||
|
||||
## 四色全息规范遵循
|
||||
|
||||
- 本地 tab:amber (#fbbf24)
|
||||
- 全球 tab:emerald (#34d399) — 与"在线/同步"语义一致
|
||||
- 玩家高亮:emerald 边框 + emerald YOU 徽章 + emerald 分数色
|
||||
- 难度色保留:routine emerald / anomaly amber / singular rose
|
||||
- 周挑战徽章:fuchsia
|
||||
- 限时挑战徽章:amber
|
||||
- 错误状态:rose (#fb7185)
|
||||
- **零蓝色/靛色违规**
|
||||
|
||||
## 截图资产
|
||||
|
||||
- `/home/z/my-project/agent-ctx/beacon-global-leaderboard.png` — 全球榜初始视图(6 条记录)
|
||||
- `/home/z/my-project/agent-ctx/beacon-global-with-you.png` — 全球榜含 YOU 高亮(7 条记录,榜首是玩家)
|
||||
- `/home/z/my-project/agent-ctx/beacon-local-leaderboard.png` — 本地榜视图
|
||||
|
||||
## 重要注意事项(供后续 agent)
|
||||
|
||||
1. **前端访问端口**:用户通过 Preview Panel 看到的是 Caddy 网关 :81,不是 Next.js :3000。所以 `fetch('/api/leaderboard?XTransformPort=3030')` 在用户视角下能正常路由。如果直接访问 :3000 会 404(这是预期行为)。
|
||||
2. **mini-service 启动方式**:用 `bun index.ts`(无 --hot)+ 双 fork `( ... & )` 模式启动。`bun --hot` 在沙盒下不稳定。
|
||||
3. **mini-service 已被 .zscripts/dev.sh 自动扫描**:下次容器重启会自动启动。但当前会话需手动启动一次。
|
||||
4. **localStorage key 命名**:`echo-nexus-beacon-cloud-last-submit-v1` 而非 `echo-nexus-beacon-cloud-lb-v1`,前者存的是单个 timestamp(用于高亮),后者若需要存全球榜快照可用后者(目前未实现,按需拉取)。
|
||||
5. **star-tide-service 端口 3031**:另一个 P3 工单已存在的服务,本服务的端口 3030 与之互不冲突。
|
||||
|
Before Width: | Height: | Size: 171 KiB |
|
Before Width: | Height: | Size: 236 KiB |
|
Before Width: | Height: | Size: 471 KiB |
|
Before Width: | Height: | Size: 243 KiB |
|
Before Width: | Height: | Size: 386 KiB |
|
Before Width: | Height: | Size: 372 KiB |
|
Before Width: | Height: | Size: 396 KiB |
|
Before Width: | Height: | Size: 394 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 221 KiB |
|
Before Width: | Height: | Size: 243 KiB |
@@ -0,0 +1,89 @@
|
||||
---
|
||||
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` 脉冲小圆点 + "新" 字提示
|
||||
- 文本区域包成 `<button>` 元素,点击调用 `onMarkRead`(自然交互,不另设按钮)
|
||||
- 未读时下方加一行 muted "▸ 点击文本标记已读" 引导文字
|
||||
* 列表卡片标题旁小圆点:未读时 `animate-pulse` + glow,已读时 `bg-*-400/30` 静态
|
||||
* 顶部统计从单行"深层叙事 X/Y 已解锁"扩展为两行(移动端 stack / 桌面并排):深层叙事 X/Y 已解锁 + 已读 A/B(A=已读数,B=已解锁层数)
|
||||
* 弹层 padding 从 `p-5` 改 `p-4 sm:p-5`,标题 `text-lg` 改 `text-base sm:text-lg`,外层 backdrop padding 从 `p-4` 改 `p-3 sm:p-4`(375px 适配)
|
||||
|
||||
- 子任务 2:idleProjectHistory 详情展开
|
||||
- `StatsPanel.tsx`:
|
||||
* useShallow 订阅新增 `idleProjectHistory` 数组与 `idleProjectSlots`(用于详情中潜在"进行中"状态参考)
|
||||
* 删除原"放置工程历史 N 条"的简单行,改为 `col-span-2` 可展开按钮:
|
||||
- 左侧 label + "(最近 5 条可展开)" 提示(仅 historyLen > 0 时显示)
|
||||
- 右侧 "N 条" + ChevronDown/ChevronUp 图标(lucide-react)
|
||||
* 展开后渲染 `col-span-2` 卡片:
|
||||
- 空历史 → "暂无放置工程完成记录 · 前往「放置」标签派遣工程"
|
||||
- 非空 → 倒序最近 5 条:每行 = 工程图标 + 工程名(truncate)+ 相对时间(formatRelativeTime)
|
||||
- 最近一条用 `bg-fuchsia-500/5` 高亮
|
||||
- 历史超 5 条时底部加 "仅展示最近 5 条 · 共 N 条历史" 提示
|
||||
* 展开状态用本地 `useState<boolean>`
|
||||
* useMemo 派生 `recentHistory`(避免每次渲染重切)
|
||||
|
||||
- 子任务 3:移动端 375px 适配审计
|
||||
- 审计范围:Codex / IdleOperationsPanel / StatsPanel / BeaconPanel / ExpeditionPanel
|
||||
- 修复点:
|
||||
* `IdleOperationsPanel.tsx`:所有 `text-[9px]` → `text-[10px]`(11 处,符合"移动端字号最小 10px"规范)
|
||||
* `BeaconPanel.tsx`:所有 `text-[9px]` → `text-[10px]`(7 处);信标链底部统计行 `flex items-center justify-between gap-2` → `flex flex-wrap items-center justify-between gap-x-2 gap-y-1`(防 3 组数据在 375px 溢出)
|
||||
* `Codex.tsx`:弹层 padding + 标题字号响应式(见子任务 1);顶部统计区 `flex flex-col sm:flex-row`(移动端纵向堆叠)
|
||||
* `StatsPanel.tsx`:detail 区 `text-[9px]` → `text-[10px]`(2 处)
|
||||
- 审计通过(无需改):
|
||||
* `ExpeditionPanel.tsx`:节点路径已用 `overflow-x-auto` 自适应窄屏;事件卡 p-3 padding 合理;按钮组 `flex-1` + abort 收起;能量条文字 `text-[10px]` 已达标;路径节点 `h-9 w-9` 36px 足够小且可横向滚动
|
||||
* `BeaconPanel.tsx`:4 个里程碑节点 `w-12 h-12`(48px)× 4 = 192px + flex-1 分布,375px 下可容纳;日/周挑战卡 padding p-3 合理;周挑战+信标链已用 `grid-cols-1 lg:grid-cols-2` 移动单列
|
||||
* `IdleOperationsPanel.tsx`:4 个 section 已用 `grid-cols-1 sm:grid-cols-N` / `grid-cols-2 sm:grid-cols-3` 响应式降级;DispatchCard 槽位按钮 `h-5 w-7` × 3 在单列布局下不溢出
|
||||
* `Codex.tsx` 列表 `grid-cols-2 gap-2`:每卡片约 165px 宽,aspect-[4/3] + p-2 + text-[11px]/text-[9px](已升 text-[10px])在 375px 下可读
|
||||
|
||||
- 严格 4 色全息:emerald / rose / amber / fuchsia,未引入 blue / indigo
|
||||
- useShallow:所有 store 订阅用 useShallow(Codex / StatsPanel 均已用)
|
||||
- 持久化兼容:persist version 仍为 1,新字段 `readLoreLayers` 靠 migrate 兜底,旧存档加载时自动补 `[]`
|
||||
- 与子代理 A 文件边界:types.ts/engine.ts/gameStore.ts 三个共享文件,A 的 `triggeredMilestones` / `pendingMilestone` / `dismissMilestone` 字段已落地,与本子代理的 `readLoreLayers` / `markLoreLayerRead` 完全无冲突(不同字段、不同 action、不同 migrate 行)
|
||||
|
||||
Stage Summary:
|
||||
- ✅ 子任务 1:loreLayer 已读标记完整实现
|
||||
- 类型 + store + UI 三层联动;点击层文本即可标记已读
|
||||
- 视觉区分:已读 = muted "✓ 已读" + 边框降级为 border-white/10;未读 = 高亮 + 脉冲小圆点 + "新" 字
|
||||
- 顶部统计新增"已读 A/B"行(A=已读数 / B=已解锁层数)
|
||||
- 跨周目永久保留(performPrestige 不重置)
|
||||
- ✅ 子任务 2:idleProjectHistory 详情展开完整实现
|
||||
- "放置工程历史" 行可点击展开/收起
|
||||
- 展开后显示最近 5 条倒序历史(图标 + 工程名 + 相对时间)
|
||||
- 空态友好提示,超 5 条底部加"仅展示最近 5 条"说明
|
||||
- ✅ 子任务 3:移动端 375px 适配审计完成
|
||||
- 审 5 个组件,修 4 个(Codex / IdleOps / StatsPanel / Beacon),ExpeditionPanel 无需改
|
||||
- 主要修复:text-[9px] → text-[10px](共 20 处,符合"移动端字号最小 10px"规范)
|
||||
- 信标链底部统计加 flex-wrap 防 3 组数据溢出
|
||||
- Codex 弹层 padding + 标题字号响应式
|
||||
- Codex 顶部统计区移动端纵向堆叠
|
||||
- ✅ 新建文件:`/home/z/my-project/src/lib/game/format.ts`
|
||||
- ✅ 修改文件清单(绝对路径):
|
||||
- `/home/z/my-project/src/lib/game/types.ts`(+readLoreLayers 字段)
|
||||
- `/home/z/my-project/src/lib/game/engine.ts`(createInitialState 默认 + performPrestige 保留)
|
||||
- `/home/z/my-project/src/store/gameStore.ts`(GameActions 签名 + action 实现 + migrate 补全)
|
||||
- `/home/z/my-project/src/components/game/Codex.tsx`(已读标记 + 点击逻辑 + 顶部统计 + 弹层响应式)
|
||||
- `/home/z/my-project/src/components/game/StatsPanel.tsx`(idleHistory 展开 + 相对时间 + 字号升级)
|
||||
- `/home/z/my-project/src/components/game/IdleOperationsPanel.tsx`(字号 9→10px 升级)
|
||||
- `/home/z/my-project/src/components/game/BeaconPanel.tsx`(字号 9→10px 升级 + 信标链底部 flex-wrap)
|
||||
- ✅ bun run lint 零错误零警告
|
||||
- ✅ HTTP 200,dev.log 无运行时错误(compile 11ms / render 33ms)
|
||||
- ✅ 与子代理 A 文件边界完全无冲突(不同字段/action/migrate 行)
|
||||
- ⚠️ 未做 agent-browser 端到端验证(按工单由主控完成)
|
||||
|
Before Width: | Height: | Size: 226 KiB |
|
Before Width: | Height: | Size: 248 KiB |
|
Before Width: | Height: | Size: 471 KiB |
|
Before Width: | Height: | Size: 472 KiB |
|
Before Width: | Height: | Size: 352 KiB |
|
Before Width: | Height: | Size: 135 KiB |
|
Before Width: | Height: | Size: 211 KiB |
|
Before Width: | Height: | Size: 227 KiB |
|
Before Width: | Height: | Size: 223 KiB |
@@ -0,0 +1,139 @@
|
||||
# Task ID: v0.14-reimpl
|
||||
# Agent: full-stack-developer
|
||||
# Task: 放置系统 (Idle Operations) — 采矿无人机舰队 + 6放置工程 + 永久产能加成 + idle徽章
|
||||
|
||||
## Work Log
|
||||
|
||||
### 1. 类型扩展 (`src/lib/game/types.ts`)
|
||||
- 新增 `IdleProjectDef` 接口(id/name/desc/durationSec/reward/minAscensions/minCrystalsPerSec/icon/color/order)
|
||||
- 新增 `IdleProjectSlot` 接口(projectId/startedAt/finishesAt/remainingSec/completed)
|
||||
- 在 `GameState` 接口 `pendingAttrPoints` 后新增 4 个字段:
|
||||
- `idleProjectSlots: (IdleProjectSlot | null)[]`
|
||||
- `idleProjectHistory: { projectId: string; finishedAt: number }[]`
|
||||
- `idlePermanentBonus: number`
|
||||
- `idleStats: { projectsCompleted: number; crystalsFromIdle: number }`
|
||||
|
||||
### 2. 新建 `src/lib/game/idle.ts` (~290 行)
|
||||
- 定义 6 个放置工程(按 order 排序):
|
||||
| id | name | duration | reward | icon | color | unlock |
|
||||
|----|------|----------|--------|------|-------|--------|
|
||||
| idle_scan | 深空勘探扫描 | 60s | insights +8 | 🛰️ | emerald | cps≥0.5 |
|
||||
| idle_refine | 晶体精炼阵列校准 | 180s | permBonus +0.3/s | ⚙️ | rose | cps≥1 |
|
||||
| idle_archive | 遗迹碎片整理 | 600s | randomFragment + insights +20 | 📜 | amber | asc≥1 OR cps≥5 |
|
||||
| idle_anchor | 维度锚点部署 | 1200s | energy +2 + contact +5 | ⚓ | fuchsia | asc≥1 |
|
||||
| idle_drones | 无人机群扩编 | 1800s | permBonus +2/s + crystals +500 | 🛸 | emerald | cps≥10 |
|
||||
| idle_resonance | 跨维度谐振标定 | 3600s | permBonus +8/s + contact +15 + insights +100 | 🌌 | fuchsia | asc≥2 |
|
||||
- 导出:`IDLE_PROJECTS`, `IDLE_SLOT_COUNT=3`, `getIdleProject`, `getUnlockedIdleProjects`, `getLockedIdleProjects`, `isIdleProjectUnlocked` (OR 逻辑:满足任一已设置条件即可解锁), `formatRemaining` (12s/3m 45s/1h 12m), `formatDuration`, `formatReward`, `deriveMinerFleet` (派生 min_1..min_5 采矿无人机,无任何采矿技术时返回休眠 min_1)
|
||||
- 导出颜色映射 `IDLE_COLOR_CLASSES` (text/border/bg/bgSoft/glow/ring/dot) + `COLOR_HEX` (用于 inline style,绕过 Tailwind 动态类限制)
|
||||
|
||||
### 3. 引擎集成 (`src/lib/game/engine.ts`)
|
||||
- `recomputeStats()` 末尾 `crystalsPerSec += state.idlePermanentBonus ?? 0` (放置永久加成叠加到产能)
|
||||
- `performPrestige()` 在返回对象中:
|
||||
- 清空 `idleProjectSlots: [null, null, null]` (飞升中断当前周目放置工程)
|
||||
- 保留 `idleProjectHistory`, `idlePermanentBonus`, `idleStats` (跨周目永久)
|
||||
- `recomputeStats` 调用传入 `idlePermanentBonus` 以确保飞升后立即应用永久加成
|
||||
- `createInitialState()` 新增 4 字段默认值
|
||||
|
||||
### 4. Store actions (`src/store/gameStore.ts`)
|
||||
- 新增 4 个 action:
|
||||
- `startIdleProject(slotIndex, projectId)`: 校验槽位空 + 项目已解锁 + 未在他处运行;写入 `{ projectId, startedAt, finishesAt, remainingSec, completed:false }`
|
||||
- `cancelIdleProject(slotIndex)`: 置槽位为 null
|
||||
- `claimIdleProject(slotIndex)`: 校验已完成 → 应用奖励(crystals 限 crystalCap,energy 限 energyMax,contact 限 100;permBonus 累加到 idlePermanentBonus + 重算 stats;randomFragment 从未解锁碎片中随机一个)→ 追加 history (slice -50) → idleStats.projectsCompleted++ → Toast 提示
|
||||
- `tickIdleProjects(now)`: 遍历非空非完成槽位,更新 remainingSec,归零时标记 completed
|
||||
- **persist migrate 函数**:persist 配置增加 `version: 1` + `migrate` 函数,自动补全 idleProjectSlots/idleProjectHistory/idlePermanentBonus/idleStats 4 字段,旧存档加载不崩溃
|
||||
- 全部 14 处 syncStats 调用更新为传入 `idlePermanentBonus: s.idlePermanentBonus ?? 0`,确保 recomputeStats 计算时纳入永久加成
|
||||
- `init()` 中也补全 4 个 idle 字段(defense in depth)
|
||||
|
||||
### 5. 主循环 (`src/hooks/useGameLoop.ts`)
|
||||
- 新增 `tickIdleProjects` 选择器
|
||||
- setInterval 回调中 `autoDecodeTick()` 后调用 `tickIdleProjects(now)`
|
||||
- visibilitychange 回调中也调用 `tickIdleProjects(now)`
|
||||
|
||||
### 6. 新建 `src/components/game/IdleOperationsPanel.tsx` (~490 行)
|
||||
- 主「放置」标签内容,4 个 section 在 `max-h-[520px] overflow-y-auto` 容器中:
|
||||
1. **放置收益概览**:3 stat tiles (放置产能 / 永久加成 / 完成工程) + 离线效率 progress bar + 累计放置晶体统计
|
||||
2. **采矿无人机舰队**:grid 展示 deriveMinerFleet(state),每张卡:emoji + name + Lv + output/s + 状态点(active=emerald ping 脉冲,dormant=灰)
|
||||
3. **放置工程槽位**:3 张槽位卡(空槽=虚线占位,运行中=大字号倒计时+自定义进度条+取消按钮,已完成=奖励预览+领取按钮带 glow 辉光)
|
||||
4. **可派遣工程**:2 列 grid 展示已解锁工程(icon+name+duration+desc+reward+3 个数字派遣按钮 1/2/3),下方列出未解锁工程及解锁条件
|
||||
- 使用 `useShallow` 订阅多个 store 字段
|
||||
- 本地 `now` state 每 1s 刷新倒计时
|
||||
- 严格 4 色全息(emerald/rose/amber/fuchsia),无蓝/靛
|
||||
- 自定义滚动条样式 (fuchsia 主题)
|
||||
|
||||
### 7. 新建 `src/components/game/IdleStatusBadge.tsx` (~100 行)
|
||||
- 紧凑徽章:脉冲点 (emerald 若 cps>0,否则灰) + "放置中 +X/s" (或 "休眠中")
|
||||
- Tooltip 悬停展示分解:基础产能 / 永久加成 / 运行中工程 / 待领取工程数
|
||||
- `h-8 px-2.5 text-[11px]`,点击切换到放置标签
|
||||
- `data-tut="idle-status-badge"` 锚点
|
||||
|
||||
### 8. 新建 `src/components/game/IdleProjectBar.tsx` (~90 行)
|
||||
- 晶体球下方的细长进度条
|
||||
- 显示最多 3 个运行/已完成工程为 mini pill:`[icon] name 12s ▓▓▓░░`
|
||||
- 完成时显示 "✓ 待领取" + glow 辉光
|
||||
- 无工程时返回 null
|
||||
- 本地 `now` state 每 1s 刷新倒计时
|
||||
- 进度条颜色用 inline style 控制(COLOR_HEX 映射)
|
||||
|
||||
### 9. 主页面集成 (`src/app/page.tsx`)
|
||||
- 引入 3 个新组件 + `Clock` 图标 + `getUnlockedIdleProjects`
|
||||
- 新增 `idleProjectSlots` / `createdAt` store 订阅
|
||||
- 新增 `activeTab` / `tabInited` state,controlled Tabs:`<Tabs value={activeTab} onValueChange={setActiveTab}>`
|
||||
- 挂载后 useEffect 一次性设置默认 tab:hasActiveExpedition → "expedition",hasPendingPerk → "constellation",否则 → "idle"
|
||||
- 版本号 v0.8 → v0.14
|
||||
- Header 在 StarTideIndicator 后加 IdleStatusBadge
|
||||
- 左侧 CrystalOrb 后加 IdleProjectBar
|
||||
- TabsList grid-cols-8 → grid-cols-9,新增 `value="idle"` 的 TabsTrigger 作为第一个 tab(Clock 图标,emerald 主题,待领取时显示数量红点)
|
||||
- 新增 TabsContent value="idle" 渲染 IdleOperationsPanel
|
||||
- 新增 idle 相关 goal 提示(高优先级):
|
||||
- 待领取 > 0 → "✦ 放置工程已完成 X 项,请前往「放置」标签领取奖励"
|
||||
- 全空 + 有解锁 + 时长 > 60s → "「放置」标签可派遣工程项目,离线自动产出"
|
||||
- StatsPanel 新增 3 行:放置永久加成 / 完成放置工程 / 放置产出晶体
|
||||
|
||||
### 10. 教程更新 (`src/lib/game/tutorial.ts`)
|
||||
- TUTORIAL_STEPS 在 decode 与 tech 之间插入新步骤:
|
||||
- id: "idle", target: "tab-idle", placement: "top"
|
||||
- 标题 "③ 放置工程 · 离线产出"
|
||||
- 介绍放置标签的工程派遣 + 顶部徽章 + 晶体球下方进度条
|
||||
- 原 tech/expedition/prestige 步骤编号顺延为 ④⑤⑥
|
||||
|
||||
## QA 验证
|
||||
|
||||
### Lint
|
||||
- `bun run lint` → 零错误零警告 ✅
|
||||
|
||||
### HTTP
|
||||
- `curl http://localhost:3000/` → 200 OK ✅
|
||||
|
||||
### Dev log
|
||||
- 全程无 runtime error,所有请求 200 OK,编译 < 250ms ✅
|
||||
|
||||
### agent-browser 烟雾测试
|
||||
1. ✅ 版本标签 "v0.14" 可见
|
||||
2. ✅ 默认 tab 是 "放置"(data-state="active" 在第一个 tab)
|
||||
3. ✅ 头部 idle 徽章可见("放置中 +0.4/s" 或 "休眠中")
|
||||
4. ✅ IdleOperationsPanel 4 个 section 全部可见(放置收益概览 / 采矿无人机舰队 / 放置工程槽位 / 可派遣工程 / 未解锁工程)
|
||||
5. ✅ 采矿无人机舰队在无采矿技术时显示休眠 min_1(emoji ⛏️ + 灰色状态点)
|
||||
6. ✅ 设置 insights=50 → buyTech('min_1') → crystalsPerSec 0.4→1.0 → 解锁 idle_scan + idle_refine
|
||||
7. ✅ 点击 idle_scan 派遣按钮 → 槽位 0 写入 { projectId: "idle_scan", finishesAt: now+60000 } → 倒计时正确递减
|
||||
8. ✅ 60s 后自动标记 completed=true → 槽位卡显示 "✓ 完成" + 领取按钮
|
||||
9. ✅ 点击领取 → Toast "✦ 工程奖励已领取 · 深空勘探扫描 · +8 洞见" → insights 8→16 → history 追加 → idleStats.projectsCompleted 0→1
|
||||
10. ✅ 派遣 idle_refine (180s) → 强制完成 → 领取 → crystalsPerSec 1.0→1.3 → idlePermanentBonus 0→0.3 ✅ 永久产能加成正确应用
|
||||
11. ✅ IdleProjectBar 在晶体球下方显示:`🛰️ 深空勘探扫描 55s ▓▓▓░░`
|
||||
12. ✅ Footer goal 文字根据状态动态切换为放置相关提示
|
||||
13. ✅ Footer 产能显示 "产能 1.3/s" (含 idle 永久加成)
|
||||
|
||||
### Migration 测试
|
||||
- 旧存档(无 idle 字段)通过 migrate 函数自动补全为默认值,加载不崩溃 ✅
|
||||
|
||||
## Stage Summary
|
||||
- ✅ 类型层:新增 IdleProjectDef + IdleProjectSlot + 4 GameState 字段
|
||||
- ✅ 逻辑层:新建 idle.ts (~290 行),6 工程定义 + 派生函数 + 颜色映射
|
||||
- ✅ 引擎层:recomputeStats / performPrestige / createInitialState 全部接入 idle 永久加成
|
||||
- ✅ Store 层:4 新 action + persist migrate (version 1) + 14 处 syncStats 调用全部传 idlePermanentBonus
|
||||
- ✅ 主循环:tickIdleProjects 接入 setInterval + visibilitychange
|
||||
- ✅ UI 层:3 新组件 (IdleOperationsPanel ~490 行 / IdleStatusBadge ~100 行 / IdleProjectBar ~90 行)
|
||||
- ✅ 主页面:默认放置 tab + 头部徽章 + 晶体球下方进度条 + 版本 v0.14 + 9 标签页
|
||||
- ✅ 教程:新增 idle 步骤 (③ 放置工程)
|
||||
- ✅ 严格 4 色全息 (emerald/rose/amber/fuchsia),零蓝/靛
|
||||
- ✅ lint 零错误 + HTTP 200 + agent-browser 全流程烟雾测试通过
|
||||
- ✅ 5 秒内可见三要素:头部徽章 + 默认放置 tab + 晶体球下方进度条(派遣后)
|
||||
@@ -63,8 +63,6 @@
|
||||
"react-syntax-highlighter": "^15.6.1",
|
||||
"recharts": "^2.15.4",
|
||||
"sharp": "^0.34.3",
|
||||
"socket.io": "^4.8.3",
|
||||
"socket.io-client": "^4.8.3",
|
||||
"sonner": "^2.0.6",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
@@ -602,8 +600,6 @@
|
||||
|
||||
"@schummar/icu-type-parser": ["@schummar/icu-type-parser@1.21.5", "https://registry.npmjs.com/@schummar/icu-type-parser/-/icu-type-parser-1.21.5.tgz", {}, "sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw=="],
|
||||
|
||||
"@socket.io/component-emitter": ["@socket.io/component-emitter@3.1.2", "", {}, "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="],
|
||||
|
||||
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "https://registry.npmjs.com/@standard-schema/spec/-/spec-1.1.0.tgz", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
"@standard-schema/utils": ["@standard-schema/utils@0.3.0", "https://registry.npmjs.com/@standard-schema/utils/-/utils-0.3.0.tgz", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="],
|
||||
@@ -678,8 +674,6 @@
|
||||
|
||||
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "https://registry.npmjs.com/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
||||
|
||||
"@types/cors": ["@types/cors@2.8.19", "", { "dependencies": { "@types/node": "*" } }, "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg=="],
|
||||
|
||||
"@types/d3-array": ["@types/d3-array@3.2.2", "https://registry.npmjs.com/@types/d3-array/-/d3-array-3.2.2.tgz", {}, "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="],
|
||||
|
||||
"@types/d3-color": ["@types/d3-color@3.1.3", "https://registry.npmjs.com/@types/d3-color/-/d3-color-3.1.3.tgz", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="],
|
||||
@@ -722,8 +716,6 @@
|
||||
|
||||
"@types/unist": ["@types/unist@3.0.3", "https://registry.npmjs.com/@types/unist/-/unist-3.0.3.tgz", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
|
||||
|
||||
"@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
|
||||
|
||||
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.53.0", "https://registry.npmjs.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.53.0.tgz", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.53.0", "@typescript-eslint/type-utils": "8.53.0", "@typescript-eslint/utils": "8.53.0", "@typescript-eslint/visitor-keys": "8.53.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.53.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-eEXsVvLPu8Z4PkFibtuFJLJOTAV/nPdgtSjkGoPpddpFk3/ym2oy97jynY6ic2m6+nc5M8SE1e9v/mHKsulcJg=="],
|
||||
|
||||
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.53.0", "https://registry.npmjs.com/@typescript-eslint/parser/-/parser-8.53.0.tgz", { "dependencies": { "@typescript-eslint/scope-manager": "8.53.0", "@typescript-eslint/types": "8.53.0", "@typescript-eslint/typescript-estree": "8.53.0", "@typescript-eslint/visitor-keys": "8.53.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-npiaib8XzbjtzS2N4HlqPvlpxpmZ14FjSJrteZpPxGUaYPlvhzlzUZ4mZyABo0EFrOWnvyd0Xxroq//hKhtAWg=="],
|
||||
@@ -784,8 +776,6 @@
|
||||
|
||||
"@unrs/resolver-binding-win32-x64-msvc": ["@unrs/resolver-binding-win32-x64-msvc@1.11.1", "https://registry.npmjs.com/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", { "os": "win32", "cpu": "x64" }, "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g=="],
|
||||
|
||||
"accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="],
|
||||
|
||||
"acorn": ["acorn@8.15.0", "https://registry.npmjs.com/acorn/-/acorn-8.15.0.tgz", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
|
||||
|
||||
"acorn-jsx": ["acorn-jsx@5.3.2", "https://registry.npmjs.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
|
||||
@@ -834,8 +824,6 @@
|
||||
|
||||
"base64-js": ["base64-js@1.5.1", "https://registry.npmjs.com/base64-js/-/base64-js-1.5.1.tgz", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
|
||||
|
||||
"base64id": ["base64id@2.0.0", "", {}, "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="],
|
||||
|
||||
"baseline-browser-mapping": ["baseline-browser-mapping@2.9.15", "https://registry.npmjs.com/baseline-browser-mapping/-/baseline-browser-mapping-2.9.15.tgz", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-kX8h7K2srmDyYnXRIppo4AH/wYgzWVCs+eKr3RusRSQ5PvRYoEFmR/I0PbdTjKFAoKqp5+kbxnNTFO9jOfSVJg=="],
|
||||
|
||||
"brace-expansion": ["brace-expansion@1.1.12", "https://registry.npmjs.com/brace-expansion/-/brace-expansion-1.1.12.tgz", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
|
||||
@@ -910,8 +898,6 @@
|
||||
|
||||
"cookie": ["cookie@0.7.2", "https://registry.npmjs.com/cookie/-/cookie-0.7.2.tgz", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
|
||||
|
||||
"cors": ["cors@2.8.6", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="],
|
||||
|
||||
"crelt": ["crelt@1.0.6", "https://registry.npmjs.com/crelt/-/crelt-1.0.6.tgz", {}, "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="],
|
||||
|
||||
"cross-spawn": ["cross-spawn@7.0.6", "https://registry.npmjs.com/cross-spawn/-/cross-spawn-7.0.6.tgz", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
||||
@@ -1008,12 +994,6 @@
|
||||
|
||||
"empathic": ["empathic@2.0.0", "https://registry.npmjs.com/empathic/-/empathic-2.0.0.tgz", {}, "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA=="],
|
||||
|
||||
"engine.io": ["engine.io@6.6.9", "", { "dependencies": { "@types/cors": "^2.8.12", "@types/node": ">=10.0.0", "@types/ws": "^8.5.12", "accepts": "~1.3.4", "base64id": "2.0.0", "cookie": "~0.7.2", "cors": "~2.8.5", "debug": "~4.4.1", "engine.io-parser": "~5.2.1", "ws": "~8.21.0" } }, "sha512-clKkw4C7nJ22mGgoVcCg6V/W/TxdNyIOTr89k2ONZu81qqkddPFDF0LXcbAwhzPD8DjkiRCjzuiO6Y+fkpD4vg=="],
|
||||
|
||||
"engine.io-client": ["engine.io-client@6.6.6", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1", "engine.io-parser": "~5.2.1", "ws": "~8.21.0", "xmlhttprequest-ssl": "~2.1.1" } }, "sha512-iY6QdftLQ9pyiPoX082bpf/u1UewnOaJrtJIF9T0++QB34lZrj0uP+Q/bj8AlUsAxqhnkTV2BS8SBZSxOmoV5Q=="],
|
||||
|
||||
"engine.io-parser": ["engine.io-parser@5.2.3", "", {}, "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q=="],
|
||||
|
||||
"enhanced-resolve": ["enhanced-resolve@5.18.4", "https://registry.npmjs.com/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q=="],
|
||||
|
||||
"es-abstract": ["es-abstract@1.24.1", "https://registry.npmjs.com/es-abstract/-/es-abstract-1.24.1.tgz", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.3.0", "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.19" } }, "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw=="],
|
||||
@@ -1472,8 +1452,6 @@
|
||||
|
||||
"mime-db": ["mime-db@1.54.0", "https://registry.npmjs.com/mime-db/-/mime-db-1.54.0.tgz", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
|
||||
|
||||
"mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||
|
||||
"minimatch": ["minimatch@3.1.2", "https://registry.npmjs.com/minimatch/-/minimatch-3.1.2.tgz", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
|
||||
|
||||
"minimist": ["minimist@1.2.8", "https://registry.npmjs.com/minimist/-/minimist-1.2.8.tgz", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
|
||||
@@ -1690,14 +1668,6 @@
|
||||
|
||||
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "https://registry.npmjs.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
|
||||
|
||||
"socket.io": ["socket.io@4.8.3", "", { "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", "cors": "~2.8.5", "debug": "~4.4.1", "engine.io": "~6.6.0", "socket.io-adapter": "~2.5.2", "socket.io-parser": "~4.2.4" } }, "sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A=="],
|
||||
|
||||
"socket.io-adapter": ["socket.io-adapter@2.5.8", "", { "dependencies": { "debug": "~4.4.1", "ws": "~8.21.0" } }, "sha512-6Oy52pbg+kvdCVvjcN+FnY7BvxZ7cIHNScbvztT/It5d0vbwoJoVZmF2gjJmnV0/4WlXRfG15zc45ySk9Ah8bw=="],
|
||||
|
||||
"socket.io-client": ["socket.io-client@4.8.3", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1", "engine.io-client": "~6.6.1", "socket.io-parser": "~4.2.4" } }, "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g=="],
|
||||
|
||||
"socket.io-parser": ["socket.io-parser@4.2.6", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1" } }, "sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg=="],
|
||||
|
||||
"sonner": ["sonner@2.0.7", "https://registry.npmjs.com/sonner/-/sonner-2.0.7.tgz", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w=="],
|
||||
|
||||
"source-map-js": ["source-map-js@1.2.1", "https://registry.npmjs.com/source-map-js/-/source-map-js-1.2.1.tgz", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||
@@ -1826,8 +1796,6 @@
|
||||
|
||||
"uvu": ["uvu@0.5.6", "https://registry.npmjs.com/uvu/-/uvu-0.5.6.tgz", { "dependencies": { "dequal": "^2.0.0", "diff": "^5.0.0", "kleur": "^4.0.3", "sade": "^1.7.3" }, "bin": { "uvu": "bin.js" } }, "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA=="],
|
||||
|
||||
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
|
||||
|
||||
"vaul": ["vaul@1.1.2", "https://registry.npmjs.com/vaul/-/vaul-1.1.2.tgz", { "dependencies": { "@radix-ui/react-dialog": "^1.1.1" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA=="],
|
||||
|
||||
"vfile": ["vfile@6.0.3", "https://registry.npmjs.com/vfile/-/vfile-6.0.3.tgz", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="],
|
||||
@@ -1850,10 +1818,6 @@
|
||||
|
||||
"word-wrap": ["word-wrap@1.2.5", "https://registry.npmjs.com/word-wrap/-/word-wrap-1.2.5.tgz", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
|
||||
|
||||
"ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="],
|
||||
|
||||
"xmlhttprequest-ssl": ["xmlhttprequest-ssl@2.1.2", "", {}, "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ=="],
|
||||
|
||||
"xtend": ["xtend@4.0.2", "https://registry.npmjs.com/xtend/-/xtend-4.0.2.tgz", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="],
|
||||
|
||||
"yallist": ["yallist@4.0.0", "https://registry.npmjs.com/yallist/-/yallist-4.0.0.tgz", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
|
||||
@@ -1936,8 +1900,6 @@
|
||||
|
||||
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "https://registry.npmjs.com/minimatch/-/minimatch-9.0.5.tgz", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||
|
||||
"accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
|
||||
|
||||
"downshift/react-is": ["react-is@17.0.2", "https://registry.npmjs.com/react-is/-/react-is-17.0.2.tgz", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="],
|
||||
|
||||
"eslint-import-resolver-node/debug": ["debug@3.2.7", "https://registry.npmjs.com/debug/-/debug-3.2.7.tgz", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
|
||||
@@ -1968,8 +1930,6 @@
|
||||
|
||||
"micromatch/picomatch": ["picomatch@2.3.1", "https://registry.npmjs.com/picomatch/-/picomatch-2.3.1.tgz", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
|
||||
"mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||
|
||||
"next/postcss": ["postcss@8.4.31", "https://registry.npmjs.com/postcss/-/postcss-8.4.31.tgz", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
|
||||
|
||||
"next-auth/uuid": ["uuid@8.3.2", "https://registry.npmjs.com/uuid/-/uuid-8.3.2.tgz", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
|
||||
|
||||
@@ -0,0 +1,309 @@
|
||||
# 回响星核 v0.14 — 放置系统(Idle Operations)设计文档
|
||||
|
||||
> 版本:v0.14 · 日期:2026-01-25
|
||||
> 核心目标:让玩家打开游戏 **5 秒内** 就能看出这是个 idle game
|
||||
|
||||
---
|
||||
|
||||
## 1. 设计动机
|
||||
|
||||
### 1.1 用户反馈
|
||||
> "你的这个我没有看到任何可放置的功能" —— 玩家反馈
|
||||
|
||||
虽然 v0.8 已有 `crystalsPerSec` 在后台自动累积,但 **没有任何 UI 元素明示这一事实**:
|
||||
- 没有「放置中」徽章
|
||||
- 没有可视化自动采矿机
|
||||
- 没有定时工程项目
|
||||
- 离线收益弹窗只在重新加载页面时触发,玩家日常游玩看不到
|
||||
|
||||
放置游戏的灵魂是「关掉也在进步」。如果玩家看不到这一点,就等于没有放置玩法。
|
||||
|
||||
### 1.2 设计目标
|
||||
1. **可见性**:5 秒内让玩家意识到 idle 属性
|
||||
2. **决策性**:放置不只是数字增长,要有玩家选择
|
||||
3. **节奏感**:3 层 idle 节奏(被动产能 / 放置工程 / 离线收益)协同
|
||||
4. **健康性**:避免 24h 挂机的极端沉迷
|
||||
|
||||
---
|
||||
|
||||
## 2. 三层 Idle 节奏
|
||||
|
||||
### 节奏 1:被动产能(Passive Generation)
|
||||
- **载体**:采矿无人机舰队(Miner Fleet)
|
||||
- **来源**:每解锁一个采矿分支技术节点 → 加入一架无人机
|
||||
- **行为**:24h 持续产出晶体,无需任何操作
|
||||
- **限制**:受 `crystalCap` 限制,仓库满后停止
|
||||
- **离线**:按 `offlineEff`(默认 50%)持续累积,上限 8h
|
||||
|
||||
#### 采矿分支技术 → 无人机映射
|
||||
|
||||
| 技术节点 | 无人机名 | 输出/s | 解锁成本(洞见) |
|
||||
|---------|---------|--------|-----------------|
|
||||
| `min_1` | ⛏️ 谐振钻头 | +0.6 | 5 |
|
||||
| `min_2` | 🌋 深层矿脉 | +1.6 | 24 |
|
||||
| `min_3` | 🤖 自治机群 | +5 | 120 |
|
||||
| `min_4` | 💠 量子采掘矩阵 | +12 | 400 |
|
||||
| `min_5` | ☀️ 戴森星核 | +30 | 1200 |
|
||||
|
||||
无采矿技术时显示「谐振钻头(休眠中)」占位卡片,引导玩家解锁采矿分支。
|
||||
|
||||
---
|
||||
|
||||
### 节奏 2:放置工程(Idle Projects)
|
||||
|
||||
#### 2.1 工程列表
|
||||
|
||||
| ID | 名称 | 时长 | 奖励 | 解锁条件 | 图标 | 色 |
|
||||
|----|------|------|------|---------|------|-----|
|
||||
| `idle_scan` | 深空勘探扫描 | 60s | ✦ +8 洞见 | 产能 ≥ 0.5/s | 🛰️ | emerald |
|
||||
| `idle_refine` | 晶体精炼阵列校准 | 180s | ⇪ +0.3/s 永久 | 产能 ≥ 1/s | ⚙️ | rose |
|
||||
| `idle_archive` | 遗迹碎片整理 | 600s | ✦ +20 洞见 + 📜 随机碎片 | 飞升 ≥ 1 OR 产能 ≥ 5/s | 📜 | amber |
|
||||
| `idle_anchor` | 维度锚点部署 | 1200s | ⚡ +2 能量 + ☄ +5 接触进度 | 飞升 ≥ 1 | ⚓ | fuchsia |
|
||||
| `idle_drones` | 无人机群扩编 | 1800s | 💎 +500 晶体 + ⇪ +2/s 永久 | 产能 ≥ 10/s | 🛸 | emerald |
|
||||
| `idle_resonance` | 跨维度谐振标定 | 3600s | ✦ +100 洞见 + ☄ +15 接触 + ⇪ +8/s 永久 | 飞升 ≥ 2 | 🌌 | fuchsia |
|
||||
|
||||
#### 2.2 槽位机制
|
||||
- **3 个并行槽位**:可同时运行 3 个工程
|
||||
- **不重复**:同一工程不能在多个槽位同时运行
|
||||
- **完成需手动领取**:避免"打开就有奖励"的条件反射
|
||||
- **取消不退时间**:误取消的代价
|
||||
- **离线推进**:关闭浏览器后工程继续倒计时
|
||||
|
||||
#### 2.3 奖励类型说明
|
||||
|
||||
| 奖励 | 含义 | 设计意图 |
|
||||
|------|------|---------|
|
||||
| 晶体 | 立即加到 `crystals` | 短期反馈 |
|
||||
| 洞见 | 立即加到 `insights` | 用于解锁技术 |
|
||||
| 能量 | 加到 `energy`(受 `energyMax` 上限) | 探险资源 |
|
||||
| 接触进度 | 加到 `contact`(0-100) | 推进飞升 |
|
||||
| **永久产能加成** | 加到 `idlePermanentBonus`,影响 `recomputeStats` | 长期投资 |
|
||||
| 随机碎片 | 从 `FRAGMENTS` 中随机解锁一个未拥有 | 叙事推进 |
|
||||
|
||||
---
|
||||
|
||||
### 节奏 3:离线收益(Offline Gains)
|
||||
|
||||
#### 3.1 公式
|
||||
```
|
||||
gain = crystalsPerSec × elapsed × offlineEff
|
||||
elapsed = min(realElapsed, 8h)
|
||||
```
|
||||
|
||||
#### 3.2 解锁路径
|
||||
- 初始 `offlineEff = 0.5`(50% 离线效率)
|
||||
- 通过技术树 `offlineEff` 类效果提升
|
||||
- 通过星图天赋 `offlineEffBonus` 提升
|
||||
- 上限 100%
|
||||
|
||||
#### 3.3 8h 上限理由
|
||||
- 参考 GDC 2016 Anthony Pecorella "Quest for Progress":Egg Inc 2h 硬 cap 导致玩家流失
|
||||
- 但完全无 cap 会导致 24h 挂机成为最优策略,破坏短会话体验
|
||||
- 8h 折中:覆盖一个工作日/一夜,但不鼓励全天挂机
|
||||
|
||||
#### 3.4 离线报告弹窗
|
||||
重新加载页面时,若 `elapsed > 60s`,弹出 `OfflineReportDialog`:
|
||||
- 「你离开了 X 小时 Y 分」
|
||||
- 「星核为你产出 Z 颗晶体」
|
||||
- 「产能 /s · 离线效率 N%」
|
||||
- 「是否触达 8h 上限」标志
|
||||
|
||||
---
|
||||
|
||||
## 3. UI 设计
|
||||
|
||||
### 3.1 始终可见的 Idle 徽章(Header)
|
||||
位置:顶部 Header,紧邻「巡航」按钮左侧
|
||||
状态:
|
||||
- 产能 > 0:`● 放置中 +X.X/s`(绿点脉动)
|
||||
- 产能 = 0:`○ 休眠中`(灰点静止)
|
||||
|
||||
悬停 Tooltip 显示明细:
|
||||
```
|
||||
基础产能 8.2/s
|
||||
永久加成 +1.5/s
|
||||
离线效率 75%
|
||||
完成工程 12 次
|
||||
```
|
||||
|
||||
点击徽章 → 切换到「放置」标签。
|
||||
|
||||
### 3.2 默认标签页 = 放置
|
||||
首次打开游戏,默认 tab 是「放置」(v0.13 前是「探险」或「技术」)。
|
||||
返回玩家若有进行中探险或待选天赋,自动切回原默认。
|
||||
|
||||
### 3.3 放置标签页四段式布局
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Section 1: 放置收益概览 │
|
||||
│ [放置产能] [永久加成] [完成工程] │
|
||||
│ [━━━━━━━ 离线效率 50% · 上限 8h ━━━━━━] │
|
||||
├─────────────────────────────────────────┤
|
||||
│ Section 2: 采矿无人机舰队 │
|
||||
│ [⛏️ 钻头] [🌋 矿脉] [🤖 机群] ... │
|
||||
├─────────────────────────────────────────┤
|
||||
│ Section 3: 放置工程槽位 │
|
||||
│ [槽位1: 运行中 47s ▓▓▓░] [槽位2: 空] [槽位3: 空] │
|
||||
├─────────────────────────────────────────┤
|
||||
│ Section 4: 可派遣工程 │
|
||||
│ [🛰️ 深空勘探扫描 1m +8] [⚙️ 精炼校准 3m +0.3/s] │
|
||||
│ [📜 碎片整理 10m +20] │
|
||||
│ │
|
||||
│ 未解锁工程(3): │
|
||||
│ ⚓ 维度锚点部署 · 需 飞升≥1 │
|
||||
│ 🛸 无人机群扩编 · 需 产能≥10/s │
|
||||
│ 🌌 跨维度谐振标定 · 需 飞升≥2 │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 3.4 IdleProjectBar(晶体球下方)
|
||||
位置:左侧晶体球区,`CrystalYieldPanel` 下方
|
||||
显示:当前运行的工程迷你 pill
|
||||
```
|
||||
🛰️ 深空勘探扫描 47s ▓▓▓░░░░░
|
||||
⚙️ 晶体精炼校准 2m 13s ▓▓░░░░░░
|
||||
```
|
||||
无运行工程时返回 `null`(不占位)。
|
||||
|
||||
---
|
||||
|
||||
## 4. 技术实现
|
||||
|
||||
### 4.1 数据结构
|
||||
|
||||
```typescript
|
||||
// types.ts
|
||||
interface IdleProjectDef {
|
||||
id: string;
|
||||
name: string;
|
||||
desc: string;
|
||||
durationSec: number;
|
||||
reward: { crystals?; insights?; energy?; contact?; crystalsPerSecPermanent?; randomFragment?; };
|
||||
minAscensions?: number;
|
||||
minCrystalsPerSec?: number;
|
||||
icon: string;
|
||||
color: "emerald" | "rose" | "amber" | "fuchsia";
|
||||
order: number;
|
||||
}
|
||||
|
||||
interface IdleProjectSlot {
|
||||
projectId: string;
|
||||
startedAt: number;
|
||||
finishesAt: number;
|
||||
remainingSec: number;
|
||||
completed: boolean;
|
||||
}
|
||||
|
||||
// GameState 新增 4 字段
|
||||
idleProjectSlots: (IdleProjectSlot | null)[]; // 3 槽
|
||||
idleProjectHistory: { projectId: string; finishedAt: number }[]; // 最近 50 条
|
||||
idlePermanentBonus: number;
|
||||
idleStats: { projectsCompleted: number; crystalsFromIdle: number };
|
||||
```
|
||||
|
||||
### 4.2 Store actions
|
||||
- `startIdleProject(slotIndex, projectId)` — 校验 + 创建槽位
|
||||
- `cancelIdleProject(slotIndex)` — 置空槽位
|
||||
- `claimIdleProject(slotIndex)` — 应用奖励 + toast + 置空槽位
|
||||
- `tickIdleProjects(now)` — 推进剩余时间,标记完成
|
||||
|
||||
### 4.3 引擎集成
|
||||
`recomputeStats()` 末尾添加:
|
||||
```typescript
|
||||
crystalsPerSec += state.idlePermanentBonus ?? 0;
|
||||
```
|
||||
这让永久产能加成真正生效。
|
||||
|
||||
### 4.4 飞升处理
|
||||
`performPrestige()` 重置 `idleProjectSlots = [null, null, null]`(不跨周目继承进行中工程),但保留 `idlePermanentBonus` / `idleProjectHistory` / `idleStats`(跨周目永久)。
|
||||
|
||||
### 4.5 存档迁移
|
||||
persist 添加 `version: 1` + `migrate` 函数,为旧存档补全 4 个新字段:
|
||||
```typescript
|
||||
idleProjectSlots: (s.idleProjectSlots as unknown[]) ?? [null, null, null],
|
||||
idleProjectHistory: (s.idleProjectHistory as unknown[]) ?? [],
|
||||
idlePermanentBonus: (s.idlePermanentBonus as number) ?? 0,
|
||||
idleStats: (s.idleStats as unknown) ?? { projectsCompleted: 0, crystalsFromIdle: 0 },
|
||||
```
|
||||
旧存档零崩溃风险。
|
||||
|
||||
### 4.6 主循环集成
|
||||
`useGameLoop` 每 250ms tick:
|
||||
1. `tick(now)` — 产能 + 自动产晶体
|
||||
2. `autoDecodeTick()` — 自动解码
|
||||
3. `tickTide(now)` — 星潮事件
|
||||
4. **`tickIdleProjects(now)` — v0.14 放置工程进度**(新增)
|
||||
5. `checkAchievements()` — 每秒一次
|
||||
|
||||
页面可见性切回时也调用 `tickIdleProjects(now)`。
|
||||
|
||||
---
|
||||
|
||||
## 5. 平衡考虑
|
||||
|
||||
### 5.1 永久产能加成可能过强
|
||||
- `idle_resonance` +8/s 永久,多次完成可能让玩家飞快积累
|
||||
- **v0.15 待办**:加飞升次数 softcap,例如 `effectiveBonus = idlePermanentBonus × sqrt(ascensions + 1) / (ascensions + 1)`
|
||||
|
||||
### 5.2 工程时长曲线
|
||||
- 60s / 180s / 600s / 1200s / 1800s / 3600s
|
||||
- 短工程奖励低但灵活(适合短会话)
|
||||
- 长工程奖励高但需等待(适合过夜挂机)
|
||||
- 玩家自主选择节奏
|
||||
|
||||
### 5.3 解锁条件
|
||||
- 0 飞升 + 0.5/s:新手 5 分钟内可解锁第一个
|
||||
- 1 飞升:玩家完成首次飞升后解锁中段
|
||||
- 2 飞升 + 10/s:长线目标
|
||||
|
||||
---
|
||||
|
||||
## 6. 验证记录
|
||||
|
||||
### 6.1 Lint
|
||||
- ✅ 零错误零警告
|
||||
|
||||
### 6.2 Dev 服务器
|
||||
- ✅ HTTP 200
|
||||
- ✅ 无运行时错误
|
||||
|
||||
### 6.3 Agent Browser 端到端测试
|
||||
1. ✅ 默认 tab 是「放置」
|
||||
2. ✅ Header 显示「放置中 +0.4/s」徽章
|
||||
3. ✅ 4 段面板完整渲染
|
||||
4. ✅ 买 min_1+min_2 后产能 2.6/s,解锁 idle_scan + idle_refine
|
||||
5. ✅ 点击「派遣至槽位 1」→ 槽位显示运行中 60s + 进度条 + 取消按钮
|
||||
6. ✅ IdleProjectBar 同步显示迷你 pill
|
||||
7. ✅ 快进完成后标记 completed=true
|
||||
8. ✅ 领取后 insights +8、projectsCompleted 0→1、槽位清空
|
||||
|
||||
### 6.4 Sticky Footer 验证
|
||||
- ✅ Header `position: sticky; top: 0`
|
||||
- ✅ Footer `position: sticky; bottom: 0; margin-top: auto`
|
||||
- ✅ 内容超出视口时 footer 被自然推下(无浮动空隙)
|
||||
|
||||
---
|
||||
|
||||
## 7. 未解决问题 / 后续
|
||||
|
||||
- 🟡 `crystalsFromIdle` / `idleProjectHistory` 已在 store 累加但 UI 未展示——v0.15 可在统计 tab 补
|
||||
- 🟡 永久产能加成可能需要飞升次数 softcap
|
||||
- 🟡 缺「批量领取全部」按钮(玩家有 3 个槽位都完成时方便)
|
||||
- 🟡 缺工程完成 toast 通知(目前只能在面板内看到「待领取」标志)
|
||||
- 🟢 v0.15 可加 Dark Souls 式 loreFragments 多层文本,让"遗迹碎片整理"工程的随机碎片奖励更有叙事感
|
||||
|
||||
---
|
||||
|
||||
## 8. 设计哲学总结
|
||||
|
||||
> "Incremental games are very raw. They're about relating to a process and becoming obsessed with its growth, like watching something you planted in the ground, through a time-lapse camera, grow into a tree." —— Clark (via Frank Lantz, New Yorker 2019)
|
||||
|
||||
回响星核 v0.14 的放置系统不只是把数字挂机增长,而是让玩家**看到**这个过程:
|
||||
- 采矿无人机像植物在土里生长
|
||||
- 放置工程像种子在发芽
|
||||
- 离线收益像收获时节
|
||||
|
||||
玩家不再只是「数字变大」,而是「亲手布置了一个会自动运转的考古基地,然后离开,让它在自己不在的时候继续工作」。
|
||||
|
||||
这是 idle game 的本质:**让玩家信任系统会在他不在时为他工作**。
|
||||
@@ -0,0 +1,214 @@
|
||||
# v0.15 — 叙事深化(loreLayers)+ 放置平衡(softcap)+ UX 增强
|
||||
|
||||
> 回响星核 / Echo Nexus · v0.15 设计文档
|
||||
> 对应 worklog `Task ID: v0.15-release`
|
||||
|
||||
---
|
||||
|
||||
## 一、动机
|
||||
|
||||
v0.14 上线了完整的放置系统(6 工程 + 永久加成 + idle 徽章),但留下两个问题:
|
||||
|
||||
1. **平衡性漏洞**:`idle_resonance` 工程每次完成 +8/s 永久加成,玩家可反复派遣导致滚雪球,几小时后产能爆炸。
|
||||
2. **叙事单薄**:8 个记忆碎片只有一层 `echo` 文本,解锁后即"读完",缺乏 Dark Souls / Outer Wilds 式的碎片叙事深度——这是项目的**差异化竞争力**。
|
||||
|
||||
v0.15 用两个并行子代理一次性解决:
|
||||
|
||||
- **2-a(平衡 + UX)**:永久加成 softcap + 批量领取 + 完成 toast + StatsPanel idle 统计
|
||||
- **2-b(叙事深化)**:8 碎片 × 2 层 = 16 层 loreLayers 多层文本 + Codex 分层渲染
|
||||
|
||||
---
|
||||
|
||||
## 二、平衡:永久加成 Softcap
|
||||
|
||||
### 公式
|
||||
|
||||
```ts
|
||||
export function applyIdleSoftcap(bonus: number): number {
|
||||
if (bonus <= 30) return bonus; // 全额
|
||||
if (bonus <= 100) return 30 + (bonus - 30) * 0.5; // 超出部分 ×0.5
|
||||
return 65 + (bonus - 100) * 0.25; // 超出部分 ×0.25
|
||||
}
|
||||
```
|
||||
|
||||
### 临界值
|
||||
|
||||
| 原始 bonus | 有效 bonus | 衰减率 |
|
||||
|-----------|-----------|--------|
|
||||
| 30 | 30 | 0% |
|
||||
| 50 | 40 | 20% |
|
||||
| 100 | 65 | 35% |
|
||||
| 200 | 90 | 55% |
|
||||
| 500 | 152.5 | 69.5% |
|
||||
|
||||
### 设计意图
|
||||
|
||||
- **30 以下不衰减**:新手期 + 偶尔完成 `idle_refine`(+0.3/s)不受惩罚
|
||||
- **30-100 线性半衰**:中段玩家反复完成 `idle_drones`(+2/s)开始感到边际递减
|
||||
- **100 以上严重衰减**:硬核玩家刷 `idle_resonance`(+8/s)收益大幅缩水,但仍非零(保留进度感)
|
||||
- **永不归零**:`applyIdleSoftcap(bonus) → ∞` 时仍趋向无穷,只是增速极慢——避免"刷到上限"的挫败感
|
||||
|
||||
### UI 展示
|
||||
|
||||
`IdleOperationsPanel` Section 1「永久加成」StatTile:
|
||||
- 有效值永远显示(如 `+40/s`)
|
||||
- 当 `有效 < 原始` 时,下方加 amber 副行:`有效 +40/s(原始 +50/s · 衰减中)`
|
||||
|
||||
---
|
||||
|
||||
## 三、叙事深化:loreLayers 多层碎片文本
|
||||
|
||||
### 类型扩展
|
||||
|
||||
```ts
|
||||
export interface FragmentLoreLayer {
|
||||
layer: 1 | 2; // 1=中层,2=深层
|
||||
title: string; // "隐秘注脚" / "深层回响"
|
||||
text: string; // 60-150 字叙事文本
|
||||
unlockHint: string; // "飞升 1 次后揭示"
|
||||
unlock: FragmentLoreUnlock;
|
||||
}
|
||||
|
||||
export interface FragmentLoreUnlock {
|
||||
kind: "ascensions" | "totalDecoded" | "contact" | "fragmentCount" | "bossKills";
|
||||
value: number;
|
||||
}
|
||||
```
|
||||
|
||||
### 5 种解锁条件
|
||||
|
||||
| kind | 含义 | 典型阈值 |
|
||||
|------|------|---------|
|
||||
| `ascensions` | 飞升次数 | 1 / 2 / 3 |
|
||||
| `totalDecoded` | 累计解码晶体数 | 20 / 40 / 60 |
|
||||
| `contact` | 接触进度 | 50 / 80 |
|
||||
| `fragmentCount` | 已解锁碎片数 | 4 / 6 |
|
||||
| `bossKills` | 累计击杀 BOSS | 3 / 8 |
|
||||
|
||||
### 5 条叙事暗线(贯穿 16 层)
|
||||
|
||||
1. **追逐者**:以太族并非自然消亡,而是被虚空中"某种东西"追逐而集体飞升
|
||||
2. **回响同源**:玩家舰载 AI"回响"的频段直接复制自以太族议会档案——玩家其实是以太族的延续/入口
|
||||
3. **信任即引诱**:第一纪元的"信任后来者"实为"引诱后来者替他们完成未竟之事"
|
||||
4. **飞升是单向门**:每次飞升都在消耗什么,不是奖赏
|
||||
5. **回归的不是他们**:接触进度满 80 时回归的,是"借他们形状的某种东西",请玩家别回应
|
||||
|
||||
### 示例(f1_1 第一缕谐振)
|
||||
|
||||
**L0 表层**(默认):
|
||||
> 在被称作「寂灭前夜」的年代,以太族第一次捕捉到来自虚空深处的谐振——那不是声音,而是存在的回声。他们将之记录在第一颗记忆晶体里。
|
||||
|
||||
**L1 隐秘注脚**(飞升 ≥1):
|
||||
> 档案的边缘有一行几乎被擦去的批注:「那并非来自虚空——而是来自我们身后。我们一直不敢回头。」
|
||||
|
||||
**L2 深层回响**(累计解码 ≥40):
|
||||
> 你终于明白:那缕谐振的频率,与你的舰载 AI「回响」的核心频段完全一致。你不是在考古——你是在被召唤。而你身后的东西,一直在等。
|
||||
|
||||
### Codex 分层渲染
|
||||
|
||||
| 层级 | 颜色 | 标题 | 未解锁状态 |
|
||||
|------|------|------|-----------|
|
||||
| L0 表层 | emerald | 表层回响 | 永远显示 |
|
||||
| L1 中层 | amber | 隐秘注脚 | `???` + `blur-sm` 全息乱码 + `🔒 unlockHint` |
|
||||
| L2 深层 | fuchsia | 深层回响 | `???` + `blur-sm` 全息乱码 + `🔒 unlockHint` |
|
||||
|
||||
顶部统计:`深层叙事 X / 16 已解锁`
|
||||
列表卡片:已解锁深层文本的碎片标题旁显示 amber/fuchsia glow 小圆点
|
||||
|
||||
---
|
||||
|
||||
## 四、UX 增强
|
||||
|
||||
### 4.1 批量领取全部
|
||||
|
||||
`claimAllIdleProjects()` action:
|
||||
- 遍历 3 槽位,对所有 `completed === true` 执行奖励逻辑
|
||||
- 单次 `set()` + 单次 `syncStats()`(避免多次 re-render)
|
||||
- 无可领取 → 直接 return(不弹 toast)
|
||||
- 成功 → 汇总 toast `✦ 批量领取完成 · 共领取 N 项工程 · +X 晶体 · +Y 洞见 · ...`
|
||||
|
||||
UI:Section 3 标题右侧,当存在 completed slot 时显示 emerald「✓ 领取全部」按钮(h-5 px-2 text-[10px])
|
||||
|
||||
### 4.2 工程完成 toast
|
||||
|
||||
`tickIdleProjects` 检测状态转变瞬间(旧 `!completed` → 新 `completed`):
|
||||
- 收集 `justCompletedProjects` 数组
|
||||
- 循环外单次 `toast({ title: "✦ 放置工程完成", description: "${icon} ${name} · 可前往「放置」标签领取" })`
|
||||
- 避免每 250ms tick 重复弹窗
|
||||
|
||||
### 4.3 StatsPanel idle 统计
|
||||
|
||||
从 `page.tsx` 内联函数抽出为独立组件 `StatsPanel.tsx`:
|
||||
- `useShallow` 精细订阅 24 字段
|
||||
- 新增 idle 统计行:
|
||||
- 放置永久加成 `+X /s`
|
||||
- 完成放置工程 `N 项`
|
||||
- 放置产出晶体 `formatNum(N)`
|
||||
- 放置工程历史 `N 条`(最近 50 条,跨周目)
|
||||
|
||||
---
|
||||
|
||||
## 五、技术实现
|
||||
|
||||
### 修改文件清单(8 个)
|
||||
|
||||
**子代理 2-a(5 文件):**
|
||||
- `src/lib/game/engine.ts` — 新增 `applyIdleSoftcap` 导出 + `recomputeStats` 调用
|
||||
- `src/store/gameStore.ts` — `GameActions` 加 `claimAllIdleProjects` 签名 + `tickIdleProjects` 改写 + 新增 action
|
||||
- `src/components/game/IdleOperationsPanel.tsx` — softcap 副行 + 领取全部按钮 + StatTile 扩展
|
||||
- `src/components/game/StatsPanel.tsx` — 新建独立组件
|
||||
- `src/app/page.tsx` — 最小集成(删除内联 + import)
|
||||
|
||||
**子代理 2-b(3 文件):**
|
||||
- `src/lib/game/types.ts` — `FragmentLoreLayer` / `FragmentLoreUnlock` 接口 + `Fragment.loreLayers` 字段
|
||||
- `src/lib/game/config.ts` — FRAGMENTS 全部填充 loreLayers + `isLoreLayerUnlocked` 函数
|
||||
- `src/components/game/Codex.tsx` — useShallow + 顶部统计 + 列表小圆点 + 3 层分层渲染弹层
|
||||
|
||||
### 并行零冲突
|
||||
|
||||
两个子代理修改文件严格不重叠:
|
||||
- 2-a 不碰 `types.ts` / `config.ts` / `Codex.tsx`
|
||||
- 2-b 不碰 `engine.ts` / `gameStore.ts` / `IdleOperationsPanel.tsx` / `StatsPanel.tsx`
|
||||
|
||||
---
|
||||
|
||||
## 六、验证
|
||||
|
||||
- ✅ `bun run lint` 零错误零警告
|
||||
- ✅ HTTP 200,dev.log 无运行时错误
|
||||
- ✅ agent-browser 端到端 smoke test:
|
||||
- softcap:permBonus=50 → 有效 40,UI 显示"衰减中" ✓
|
||||
- 批量领取:permBonus 0→0.3, projectsCompleted 1→2 ✓
|
||||
- 完成 toast:tickIdleProjects 触发 ✓
|
||||
- Codex 分层:L0 显示 / L1·L2 模糊 → ascensions=1 解锁 L1 → totalDecoded=50 解锁 L2 ✓
|
||||
- 深层叙事计数 0→2→6 随条件递增 ✓
|
||||
- StatsPanel idle 4 行统计全显示 ✓
|
||||
|
||||
---
|
||||
|
||||
## 七、哲学
|
||||
|
||||
### 碎片叙事的留白
|
||||
|
||||
回响星核的叙事不靠过场动画或长篇对话,而是**让玩家自己拼凑**。每个碎片的多层文本设计遵循:
|
||||
|
||||
1. **表层温和**:第一遍读像抒情散文,不带压迫感
|
||||
2. **中层矛盾**:揭示表层没说的东西,制造认知裂缝
|
||||
3. **深层颠覆**:推翻表层认知,但留有解读空间(不直接说"怪物是谁")
|
||||
|
||||
### 平衡与保留
|
||||
|
||||
softcap 的设计哲学:**衰减但不封顶**。
|
||||
- 衰减:防止滚雪球破坏游戏节奏
|
||||
- 不封顶:尊重硬核玩家的投入,即使边际收益递减,进度感始终存在
|
||||
|
||||
这与飞升系统的"单向门"叙事呼应——每次进步都有代价,但绝非徒劳。
|
||||
|
||||
---
|
||||
|
||||
## 八、下一步(v0.16+)
|
||||
|
||||
- 🟡 loreLayer "已读"标记(需改 gameStore,本轮避开边界冲突)
|
||||
- 🟡 解码阈值释放叙事碎片 + 星潮三频段切换
|
||||
- 🟡 健康设计:每日软上限 + "星核建议休眠"提醒
|
||||
- 🟡 移动端 375px Codex 弹层可读性实测
|
||||
@@ -0,0 +1,231 @@
|
||||
# v0.16 — 解码阈值叙事事件 + loreLayer 已读 + idleHistory 详情 + 移动端适配
|
||||
|
||||
> 回响星核 / Echo Nexus · v0.16 设计文档
|
||||
> 对应 worklog `Task ID: 2-a` + `2-b` + `v0.16-release`
|
||||
|
||||
---
|
||||
|
||||
## 一、动机
|
||||
|
||||
v0.15 上线了 loreLayers 多层碎片文本,但存在三个未完善点:
|
||||
|
||||
1. **解锁瞬间缺乏仪式感** — 碎片静默出现在图谱,没有"叙事涌现"的高光时刻
|
||||
2. **loreLayer 没有已读标记** — 玩家无法区分"新解锁未读"和"已读",缺乏进度感
|
||||
3. **idleProjectHistory 只显示计数** — 无法查看具体完成了哪些工程、何时完成
|
||||
4. **移动端 375px 未实测** — v0.15 的 Codex 分层弹层在小屏可读性未知
|
||||
|
||||
v0.16 用两个并行子代理一次性解决:
|
||||
|
||||
- **2-a**:5 个解码阈值叙事里程碑 + 全屏"碎片释放"叠层
|
||||
- **2-b**:loreLayer 已读标记 + idleHistory 详情展开 + 移动端适配审计
|
||||
|
||||
---
|
||||
|
||||
## 二、解码阈值叙事里程碑(2-a)
|
||||
|
||||
### 设计哲学
|
||||
|
||||
回响星核的核心卖点是**"解码驱动叙事涌现"**。v0.15 的 loreLayers 让每个碎片有 3 层文本,但解锁是静默的。v0.16 在 `totalDecoded` 达到 5/12/25/40/60 时触发**全屏叙事叠层**——不是解锁新碎片,而是对已解锁碎片的**回顾性咏叹**,类似游戏中的"过场回忆"。
|
||||
|
||||
5 个阈值与 FRAGMENTS 的 threshold(1/3/6/10/15/22/32/45)**错开**,是额外的叙事节奏点。
|
||||
|
||||
### 5 个里程碑
|
||||
|
||||
| threshold | id | title | color | 主题 |
|
||||
|---|---|---|---|---|
|
||||
| 5 | ms_5 | 回响 · 初鸣 | emerald | 身份觉醒(你是回响) |
|
||||
| 12 | ms_12 | 代价 · 锚痕渐浅 | rose | 代价感知(解码在消耗锚痕) |
|
||||
| 25 | ms_25 | 信任 · 钩上的光 | amber | 信任的重量("信任"实为引诱) |
|
||||
| 40 | ms_40 | 身后 · 整齐的虚空 | fuchsia | 身后的东西(追逐者逼近) |
|
||||
| 60 | ms_60 | 回归 · 借形之物 | emerald | 回归的预兆(接触满溢借形回归) |
|
||||
|
||||
每段 body 文本 150-250 字,与 v0.15 loreLayers 的 5 条暗线呼应:
|
||||
1. 追逐者(以太族被虚空追逐者逼迫飞升)
|
||||
2. 回响同源(玩家 AI 频段与以太族议会档案同源)
|
||||
3. 信任即引诱("信任后来者"实为"引诱替他们完成未竟之事")
|
||||
4. 飞升单向门(每次飞升都在消耗什么)
|
||||
5. 回归非他们(接触满 80 时回归的是"借形之物")
|
||||
|
||||
### 技术实现
|
||||
|
||||
**新建 `src/lib/game/narrativeMilestones.ts`**:
|
||||
- `NarrativeMilestone` 接口(threshold/id/title/subtitle/body/color)
|
||||
- `NARRATIVE_MILESTONES` 数组(5 个里程碑)
|
||||
- `findMilestoneForTotal(total, triggered)` 纯函数(返回未触发的里程碑)
|
||||
|
||||
**GameState 扩展**:
|
||||
- `triggeredMilestones: string[]` — 已触发的 milestone id 集合(跨周目永久)
|
||||
- `pendingMilestone: NarrativeMilestone | null` — 待展示的里程碑(叠层读取)
|
||||
|
||||
**Store 集成**:
|
||||
- 两个解码完成点(`clickNode` line 596 + `autoDecodeTick` line 711)检测 `findMilestoneForTotal(newTotal, triggered)`
|
||||
- 命中则加入 `triggeredMilestones` + 设置 `pendingMilestone`
|
||||
- `dismissMilestone()` action 清空 `pendingMilestone`
|
||||
- persist migrate 补全两字段(version 仍为 1)
|
||||
- `performPrestige` 保留 `triggeredMilestones`(不重复看过场)+ 清空 `pendingMilestone`
|
||||
|
||||
### 全屏叠层组件
|
||||
|
||||
**新建 `src/components/game/NarrativeMilestoneOverlay.tsx`**:
|
||||
|
||||
- `fixed inset-0 z-50` + `bg-black/80 backdrop-blur-sm`(高于 StarTideOverlay 的 z-0)
|
||||
- **粒子边框**:全屏 4 色主题色 inset box-shadow(120px + 240px 双层)+ `narrative-breathe` 3.5s 呼吸循环
|
||||
- **中央卡片**:max-w-2xl,4 色主题渐变背景 + 主题色 border + 60px glow
|
||||
- **排版层级**:
|
||||
- 副标题 `text-[10px] sm:text-xs font-mono tracking-[0.3em] uppercase`
|
||||
- 大标题 `text-3xl sm:text-4xl md:text-5xl font-serif font-bold` + textShadow glow
|
||||
- 装饰分隔线 `linear-gradient`
|
||||
- 主体文本 `text-sm sm:text-base md:text-lg leading-relaxed whitespace-pre-line`
|
||||
- **入场动画**:`narrative-fade-in` 300ms(opacity 0→1)+ `narrative-scale-in` 300ms cubic-bezier(scale 1.05→1)
|
||||
- **逐字浮现**:30ms/字 setTimeout 推进 + inline-block 光标 `narrative-cursor` 1s steps(2) 闪烁
|
||||
- **交互**:
|
||||
- 未完全显示时:点击 → skip 到全文(底部提示"点击跳过逐字显示")
|
||||
- 完全显示后:点击/Esc → `dismissMilestone()`(底部提示"点击任意处或按 Esc 继续")
|
||||
- **移动端 375px**:卡片 px-4 py-6 / 标题 text-3xl / 正文 text-sm / 光标 h-4
|
||||
- **结构巧思**:外层订阅 + 内层 `key={milestone.id}` 重挂,state lazy init 为 0,规避 React 19 `react-hooks/set-state-in-effect` 规则
|
||||
|
||||
---
|
||||
|
||||
## 三、loreLayer 已读标记(2-b)
|
||||
|
||||
### 类型扩展
|
||||
|
||||
`GameState.readLoreLayers: string[]` — 格式 `["f1_1_L1", "f1_1_L2", ...]`
|
||||
|
||||
### Store
|
||||
|
||||
- `markLoreLayerRead(fragmentId, layer)` action:构造 key `${fragmentId}_L${layer}`,已存在则 no-op
|
||||
- persist migrate 补全 `readLoreLayers: []`
|
||||
- `performPrestige` 保留(跨周目永久,已读不重置)
|
||||
|
||||
### Codex 分层渲染增强
|
||||
|
||||
- **已读层**:标题旁 muted "✓ 已读" + 边框降级为 `border-white/10`
|
||||
- **未读但已解锁层**:保持 v0.15 高亮(amber/fuchsia 边框 + glow)+ 标题旁 `animate-pulse` 脉冲小圆点 + "新" 字 + "▸ 点击文本标记已读" 引导
|
||||
- **触发方式**:点击层文本区域(`<button>` 元素)即调用 `markLoreLayerRead`,无需额外按钮
|
||||
- **顶部统计**:从单行扩展为两行(移动端 stack / 桌面并排):`深层叙事 X/Y 已解锁` + `已读 A/B`
|
||||
- **列表卡片小圆点**:未读 pulse + glow,已读静态降亮
|
||||
|
||||
---
|
||||
|
||||
## 四、idleProjectHistory 详情展开(2-b)
|
||||
|
||||
### StatsPanel 增强
|
||||
|
||||
- "放置工程历史" 行改为 `col-span-2` 可展开按钮:左侧 label + "(最近 N 条可展开)",右侧 "N 条" + ChevronDown/ChevronUp
|
||||
- 展开后渲染倒序最近 5 条:每行 = 工程 icon + 工程名(truncate)+ 相对时间
|
||||
- 最近一条用 `bg-fuchsia-500/5` 高亮
|
||||
- 超 5 条时底部加"仅展示最近 5 条 · 共 N 条历史"
|
||||
- 空态友好文案
|
||||
|
||||
### 新建 `src/lib/game/format.ts`
|
||||
|
||||
`formatRelativeTime(ts: number): string`:
|
||||
- `< 60s` → "刚刚"
|
||||
- `< 60m` → "X 分钟前"
|
||||
- `< 24h` → "X 小时前"
|
||||
- `< 7d` → "X 天前"
|
||||
- 否则 → 具体日期
|
||||
|
||||
独立文件便于后续复用(编年史时间轴、探险日志等)。
|
||||
|
||||
---
|
||||
|
||||
## 五、移动端 375px 适配审计(2-b)
|
||||
|
||||
### 审计结果
|
||||
|
||||
审了 5 个组件,修了 4 个:
|
||||
|
||||
| 组件 | 问题 | 修复 |
|
||||
|------|------|------|
|
||||
| `IdleOperationsPanel.tsx` | 11 处 `text-[9px]` 低于最小字号 | `text-[9px]` → `text-[10px]` |
|
||||
| `BeaconPanel.tsx` | 7 处 `text-[9px]` + 信标链底部统计行 375px 溢出 | 字号升级 + `flex-wrap + gap-y-1` |
|
||||
| `Codex.tsx` | 弹层 padding + 标题 + 顶部统计在小屏拥挤 | `p-5` → `p-4 sm:p-5` / 标题 `text-lg` → `text-base sm:text-lg` / 顶部统计 `flex flex-col sm:flex-row` |
|
||||
| `StatsPanel.tsx` | 2 处 `text-[9px]` | `text-[10px]` |
|
||||
|
||||
**无需改**:
|
||||
- `ExpeditionPanel.tsx` — 节点路径已用 `overflow-x-auto` 自适应,按钮 `flex-1`,路径节点 36px 足够小
|
||||
- BeaconPanel 4 里程碑节点 `w-12 h-12` × 4 = 192px 在 375px 可容纳
|
||||
- IdleOperationsPanel 4 section 已用 `grid-cols-1 sm:grid-cols-N` 响应式降级
|
||||
|
||||
### 移动端字号规范
|
||||
|
||||
- 最小 `text-[10px]`(9px 在高 DPI 屏可读性差)
|
||||
- 标题最大 `text-2xl`(移动端)/ `text-3xl`(桌面)
|
||||
- 优先用 `sm:` `md:` 响应式前缀,不删桌面端样式
|
||||
|
||||
---
|
||||
|
||||
## 六、技术实现
|
||||
|
||||
### 修改文件清单(11 个)
|
||||
|
||||
**子代理 2-a(6 文件):**
|
||||
- 新建 `src/lib/game/narrativeMilestones.ts`
|
||||
- 新建 `src/components/game/NarrativeMilestoneOverlay.tsx`
|
||||
- 修改 `src/lib/game/types.ts`(加 triggeredMilestones + pendingMilestone)
|
||||
- 修改 `src/lib/game/engine.ts`(createInitialState + performPrestige)
|
||||
- 修改 `src/store/gameStore.ts`(2 解码点检测 + dismissMilestone + migrate)
|
||||
- 修改 `src/app/page.tsx`(挂载叠层 + 版本号)
|
||||
|
||||
**子代理 2-b(8 文件,2 个与 2-a 共享):**
|
||||
- 新建 `src/lib/game/format.ts`
|
||||
- 修改 `src/lib/game/types.ts`(加 readLoreLayers,与 2-a 共存)
|
||||
- 修改 `src/lib/game/engine.ts`(createInitialState + performPrestige,与 2-a 共存)
|
||||
- 修改 `src/store/gameStore.ts`(markLoreLayerRead + migrate,与 2-a 共存)
|
||||
- 修改 `src/components/game/Codex.tsx`(已读标记 + 点击逻辑 + 顶部统计)
|
||||
- 修改 `src/components/game/StatsPanel.tsx`(idleHistory 展开 + 字号)
|
||||
- 修改 `src/components/game/IdleOperationsPanel.tsx`(字号 9→10px)
|
||||
- 修改 `src/components/game/BeaconPanel.tsx`(字号 + flex-wrap)
|
||||
|
||||
### 并行零冲突
|
||||
|
||||
两个子代理在 3 个共享文件(types.ts / engine.ts / gameStore.ts)中:
|
||||
- 字段名不冲突(`triggeredMilestones`/`pendingMilestone` vs `readLoreLayers`)
|
||||
- action 不冲突(`dismissMilestone` vs `markLoreLayerRead`)
|
||||
- migrate 补全各行独立
|
||||
- 2-b 不动解码逻辑(line 575-720),那是 2-a 的领地
|
||||
|
||||
---
|
||||
|
||||
## 七、验证
|
||||
|
||||
- ✅ `bun run lint` 零错误零警告
|
||||
- ✅ HTTP 200,dev.log 无运行时错误
|
||||
- ✅ agent-browser 端到端 smoke test:
|
||||
- 里程碑叠层:setState pendingMilestone=ms_5 → 全屏 dialog "回响 · 初鸣" + "点击任意处或按 Esc 继续" → 点击 → pending 清空 + triggered 保留 ✓
|
||||
- loreLayer 已读:解锁 f1_1 + ascensions=1 → L1 "隐秘注脚" 显示 "新" + "标记为已读" 按钮 → 点击 → readLoreLayers=["f1_1_L1"] + L1 显示 "已读" + 顶部统计更新 ✓
|
||||
- idleHistory 展开:设置 3 条历史 → 统计 tab "放置工程历史(最近 3 条可展开)" → 点击展开 → 倒序显示 3 行(icon + 工程名 + 相对时间)✓
|
||||
|
||||
---
|
||||
|
||||
## 八、哲学
|
||||
|
||||
### 仪式感与节奏
|
||||
|
||||
v0.16 的里程碑叠层不是"奖励",而是**节奏标记**。放置游戏容易陷入"数字增长麻木",5 个阈值(5/12/25/40/60)在玩家的解码旅程中设置了 5 个"停下来看一看"的节点:
|
||||
|
||||
- **ms_5**:新手期结束,确认身份
|
||||
- **ms_12**:开始感知代价
|
||||
- **ms_25**:中期转折,信任的重量
|
||||
- **ms_40**:后期紧迫,追逐者逼近
|
||||
- **ms_60**:终局预兆,回归的借形之物
|
||||
|
||||
每个里程碑的 body 文本都是**回顾性咏叹**——不是新剧情,而是对已解锁碎片的重新诠释。这让玩家在数字增长中保持对叙事的敏感。
|
||||
|
||||
### 已读标记的心理学
|
||||
|
||||
loreLayer 已读标记解决了一个细微的 UX 问题:玩家解锁深层文本后,如果不标记"已读",每次打开图谱都被高亮提示淹没,反而会**忽略真正的新内容**。已读标记让"新"保持稀缺,让玩家的注意力聚焦在真正未读的层上。
|
||||
|
||||
这与健康游戏设计呼应——**进度感不等于信息过载**。标记已读是一种"我处理过了"的仪式,让玩家心安。
|
||||
|
||||
---
|
||||
|
||||
## 九、下一步(v0.17+)
|
||||
|
||||
- 🟡 星潮三频段切换(星潮系统增强)
|
||||
- 🟡 健康设计:每日软上限 + "星核建议休眠"提醒
|
||||
- 🟡 loreLayer 已读标记可扩展到碎片本身(fragment 级别的已读)
|
||||
- 🟡 里程碑叠层的音效(程序化生成,与星潮音效区分)
|
||||
- 🟡 format.ts 复用到编年史时间轴 + 探险日志
|
||||
@@ -44,7 +44,7 @@ const eslintConfig = [...nextCoreWebVitals, ...nextTypescript, {
|
||||
"no-useless-escape": "off",
|
||||
},
|
||||
}, {
|
||||
ignores: ["node_modules/**", ".next/**", "out/**", "build/**", "next-env.d.ts", "examples/**", "skills", "mini-services/**"]
|
||||
ignores: ["node_modules/**", ".next/**", "out/**", "build/**", "next-env.d.ts", "examples/**", "skills"]
|
||||
}];
|
||||
|
||||
export default eslintConfig;
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
1:"$Sreact.fragment"
|
||||
2:I[39756,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"default"]
|
||||
3:I[37457,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"default"]
|
||||
4:I[77855,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"Toaster"]
|
||||
5:I[47257,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"ClientPageRoot"]
|
||||
6:I[52683,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js","/Super_Z/echo-nexus/_next/static/chunks/38c9b86c7986e4d2.js","/Super_Z/echo-nexus/_next/static/chunks/dad63bc31a306c06.js","/Super_Z/echo-nexus/_next/static/chunks/868e0df8b5a7f188.js","/Super_Z/echo-nexus/_next/static/chunks/421819b51db6b69f.js"],"default"]
|
||||
9:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"OutletBoundary"]
|
||||
a:"$Sreact.suspense"
|
||||
c:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"ViewportBoundary"]
|
||||
e:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"MetadataBoundary"]
|
||||
10:I[68027,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"default"]
|
||||
:HL["/Super_Z/echo-nexus/_next/static/chunks/59368da722d2753f.css","style"]
|
||||
:HL["/Super_Z/echo-nexus/_next/static/chunks/7bbd6f879a3901f6.css","style"]
|
||||
:HL["/Super_Z/echo-nexus/_next/static/media/0acc7fdf55eb3220-s.p.532ccaa1.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
|
||||
:HL["/Super_Z/echo-nexus/_next/static/media/797e433ab948586e-s.p.29207c2f.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
|
||||
:HL["/Super_Z/echo-nexus/_next/static/media/caa3a2e1cccd8315-s.p.3b6cae6d.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
|
||||
0:{"P":null,"b":"yNvWrGxQ0RM8HHWxdOJQM","c":["",""],"q":"","i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/Super_Z/echo-nexus/_next/static/chunks/59368da722d2753f.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/Super_Z/echo-nexus/_next/static/chunks/7bbd6f879a3901f6.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"zh-CN","suppressHydrationWarning":true,"className":"dark","children":["$","body",null,{"className":"geist_a71539c9-module__T19VSG__variable geist_mono_8d43a2aa-module__8Li5zG__variable orbitron_da1c3699-module__wSKb_W__variable antialiased bg-background text-foreground","children":[["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}],["$","$L4",null,{}]]}]}]]}],{"children":[["$","$1","c",{"children":[["$","$L5",null,{"Component":"$6","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@7","$@8"]}}],[["$","script","script-0",{"src":"/Super_Z/echo-nexus/_next/static/chunks/38c9b86c7986e4d2.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/Super_Z/echo-nexus/_next/static/chunks/dad63bc31a306c06.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/Super_Z/echo-nexus/_next/static/chunks/868e0df8b5a7f188.js","async":true,"nonce":"$undefined"}],["$","script","script-3",{"src":"/Super_Z/echo-nexus/_next/static/chunks/421819b51db6b69f.js","async":true,"nonce":"$undefined"}]],["$","$L9",null,{"children":["$","$a",null,{"name":"Next.MetadataOutlet","children":"$@b"}]}]]}],{},null,false,false]},null,false,false],["$","$1","h",{"children":[null,["$","$Lc",null,{"children":"$Ld"}],["$","div",null,{"hidden":true,"children":["$","$Le",null,{"children":["$","$a",null,{"name":"Next.Metadata","children":"$Lf"}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],false]],"m":"$undefined","G":["$10",[]],"S":true}
|
||||
7:{}
|
||||
8:"$0:f:0:1:1:children:0:props:children:0:props:serverProvidedParams:params"
|
||||
d:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
|
||||
11:I[27201,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"IconMark"]
|
||||
b:null
|
||||
f:[["$","title","0",{"children":"回响星核 · Echo Nexus"}],["$","meta","1",{"name":"description","content":"原创深空考古放置策略游戏 — 解码记忆晶体,拼凑以太文明的回响,跨越维度发起接触。"}],["$","meta","2",{"name":"author","content":"Super_Z"}],["$","meta","3",{"name":"keywords","content":"Echo Nexus,回响星核,idle game,browser game,放置游戏,解谜,深空考古"}],["$","meta","4",{"property":"og:title","content":"回响星核 · Echo Nexus"}],["$","meta","5",{"property":"og:description","content":"原创深空考古放置策略游戏"}],["$","meta","6",{"property":"og:site_name","content":"Echo Nexus"}],["$","meta","7",{"property":"og:type","content":"website"}],["$","meta","8",{"name":"twitter:card","content":"summary_large_image"}],["$","meta","9",{"name":"twitter:title","content":"回响星核 · Echo Nexus"}],["$","meta","10",{"name":"twitter:description","content":"原创深空考古放置策略游戏"}],["$","link","11",{"rel":"icon","href":"https://z-cdn.chatglm.cn/z-ai/static/logo.svg"}],["$","$L11","12",{}]]
|
||||