v0.8.2: 工单大清理 - 编年史分页+限时挑战+星潮深化+云排行榜+手写叙事+socket多人星潮+UI打磨

工单 #8 编年史上限+分页:
- engine.ts: slice(-50)→slice(-200)
- ChronicleDialog.tsx: 加分页(每页10条)+上一页/下一页+页码显示

工单 #5 限时挑战 (subagent 10-a):
- beacon.ts: BeaconTimedChallenge + getTimedSlotKey(4h时段) + generateTimedChallenge
- BeaconPanel.tsx: amber主题限时区块 + 倒计时 + <30min紧急状态
- gameStore: trackBeacon 加 timedJustCompleted + claimTimedBeacon action

工单 #3 星潮类型深化 (subagent 10-a):
- starTide.ts: +3种星潮 surge(emerald)/eclipse(rose)/prism(fuchsia)
- TideModifiers: +targetLenBonus/bossWinRateBonus/autoDecodeIntervalMult
- decode.ts: generatePuzzle 加 targetLenBonus 参数
- achievements: ach_tides_all 阈值 6→9

工单 #4/P2 云排行榜 (subagent 10-b):
- mini-services/leaderboard-service/ (端口3030, Hono+bun, 内存1000条)
- API: GET/POST /api/leaderboard + /stats + CORS + 防刷
- beacon.ts: fetchCloudLeaderboard/submitCloudScore
- BeaconPanel: 本地Top20/全球Top100 双tab + YOU徽章高亮

工单 #9 手写叙事 (subagent 10-c):
- chronicle.ts: EPOCH_LORE 5纪元×3节点=15段手写叙事(80-150字/段)
- buildLore 优先手写节点, fallback 模板, 11个变量替换

P3 socket 多人星潮 (subagent 10-c):
- mini-services/star-tide-service/ (端口3031, socket.io)
- 每10-15min广播global-tide, 60s持续, 6种类型权重
- useGlobalTide hook + triggerGlobalTide action
- StarTideIndicator 加 🌐 全球星潮标记

P2 UI打磨:
- page.tsx: CrystalOrb 区加装饰全息环(3层旋转) + 四角标记 + 顶部状态条 + 底部铭文

QA: lint零错误 + dev HTTP200 + VLM 8/10 + 2个mini-service运行中(3030/3031)
This commit is contained in:
2026-06-23 23:57:41 +00:00
parent 6d7c52daa9
commit 191434e6ad
55 changed files with 9026 additions and 129 deletions
+91
View File
@@ -0,0 +1,91 @@
# 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)`:确定性生成,难度 routinegoal = 日基准 ×0.3-0.55 种类型各有公式
- `msUntilNextTimedSlot(now)`:距离下个 4 小时时段的毫秒数
- `loadTimedProgress` / `saveTimedProgress` / `addTimedProgress` / `claimTimedReward`(推送排行榜,entry 加 isTimed: true
**gameStore.ts 集成**
- import 扩展:generateTimedChallenge / loadTimedProgress / addTimedProgress / claimTimedReward + 类型
- `trackBeacon` 返回值新增 `timedJustCompleted`:日/限时/周三类进度同时更新
- 新增 `claimTimedBeacon` actionGameActions 接口同步)
- 新增 `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-glowamber 呼吸)/ timed-urgentrose 急促脉冲)
- 排行榜 entry 区分 TIMEDamber 徽章)/ WEEKfuchsia 徽章)
- 空状态提示更新为"完成限时、每日或本周信标即可登榜"
### 工单 #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 + targetLenBonusstepLimitBase 同步增加
- 路径构造法自动适配更长目标序列(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 触发):
- surgeemerald 绿):8/10,横幅 + 全屏绿色微光
- prismfuchsia 紫):7/10,横幅 + 全屏紫色微光
- eclipserose 粉):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 旧星潮全部保留,无回归
+207
View File
@@ -0,0 +1,207 @@
# 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 actiongrantBeaconReward/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=1reparent 到 init),进程脱离原 bash sessionuptime 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 专属刷新按钮(RefreshCwloading 时 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 keysecho-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,用于全球榜高亮)
## 四色全息规范遵循
- 本地 tabamber (#fbbf24)
- 全球 tabemerald (#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 与之互不冲突。
Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 471 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 396 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 471 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 472 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 KiB