v0.9 工单修复部署

This commit is contained in:
2026-06-24 00:41:29 +00:00
commit 73720d8306
279 changed files with 39708 additions and 0 deletions
Executable
+1
View File
@@ -0,0 +1 @@
DATABASE_URL=file:/home/z/my-project/db/custom.db
Executable
+150
View File
@@ -0,0 +1,150 @@
# ---> Node
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# vitepress build output
**/.vitepress/dist
# vitepress cache directory
**/.vitepress/cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# Echo Nexus 本地文件
local-*
.claude
.z-ai-config
dev.log
dev.out.log
*.tsbuildinfo
next-env.d.ts
.vercel
skills/
qa-*.png
View File
+122
View File
@@ -0,0 +1,122 @@
#!/bin/bash
# 将 stderr 重定向到 stdout,避免 execute_command 因为 stderr 输出而报错
exec 2>&1
set -e
# 获取脚本所在目录(.zscripts 目录,即 workspace-agent/.zscripts
# 使用 $0 获取脚本路径(兼容 sh 和 bash)
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# Next.js 项目路径
NEXTJS_PROJECT_DIR="/home/z/my-project"
# 检查 Next.js 项目目录是否存在
if [ ! -d "$NEXTJS_PROJECT_DIR" ]; then
echo "❌ 错误: Next.js 项目目录不存在: $NEXTJS_PROJECT_DIR"
exit 1
fi
echo "🚀 开始构建 Next.js 应用和 mini-services..."
echo "📁 Next.js 项目路径: $NEXTJS_PROJECT_DIR"
# 切换到 Next.js 项目目录
cd "$NEXTJS_PROJECT_DIR" || exit 1
# 设置环境变量
export NEXT_TELEMETRY_DISABLED=1
BUILD_DIR="/tmp/build_fullstack_$BUILD_ID"
echo "📁 清理并创建构建目录: $BUILD_DIR"
mkdir -p "$BUILD_DIR"
# 安装依赖
echo "📦 安装依赖..."
bun install
# 构建 Next.js 应用
echo "🔨 构建 Next.js 应用..."
bun run build
# 构建 mini-services
# 检查 Next.js 项目目录下是否有 mini-services 目录
if [ -d "$NEXTJS_PROJECT_DIR/mini-services" ]; then
echo "🔨 构建 mini-services..."
# 使用 workspace-agent 目录下的 mini-services 脚本
sh "$SCRIPT_DIR/mini-services-install.sh"
sh "$SCRIPT_DIR/mini-services-build.sh"
# 复制 mini-services-start.sh 到 mini-services-dist 目录
echo " - 复制 mini-services-start.sh 到 $BUILD_DIR"
cp "$SCRIPT_DIR/mini-services-start.sh" "$BUILD_DIR/mini-services-start.sh"
chmod +x "$BUILD_DIR/mini-services-start.sh"
else
echo "️ mini-services 目录不存在,跳过"
fi
# 将所有构建产物复制到临时构建目录
echo "📦 收集构建产物到 $BUILD_DIR..."
# 复制 Next.js standalone 构建输出
if [ -d ".next/standalone" ]; then
echo " - 复制 .next/standalone"
cp -r .next/standalone "$BUILD_DIR/next-service-dist/"
fi
# 复制 Next.js 静态文件
if [ -d ".next/static" ]; then
echo " - 复制 .next/static"
mkdir -p "$BUILD_DIR/next-service-dist/.next"
cp -r .next/static "$BUILD_DIR/next-service-dist/.next/"
fi
# 复制 public 目录
if [ -d "public" ]; then
echo " - 复制 public"
cp -r public "$BUILD_DIR/next-service-dist/"
fi
# 将测试环境数据库复制到构建产物中,生产环境直接使用这份数据库
if [ -f "./db/custom.db" ]; then
echo "🗄️ 复制测试环境数据库到构建产物..."
mkdir -p "$BUILD_DIR/db"
cp -r ./db/. "$BUILD_DIR/db/"
echo "🗄️ 同步构建产物中的数据库结构..."
DATABASE_URL="file:$BUILD_DIR/db/custom.db" bun run db:push
echo "✅ 构建产物数据库已准备完成"
ls -lah "$BUILD_DIR/db"
else
echo "❌ 未找到测试环境数据库文件 ./db/custom.db,无法继续构建生产包"
exit 1
fi
# 复制 Caddyfile(如果存在)
if [ -f "Caddyfile" ]; then
echo " - 复制 Caddyfile"
cp Caddyfile "$BUILD_DIR/"
else
echo "️ Caddyfile 不存在,跳过"
fi
# 复制 start.sh 脚本
echo " - 复制 start.sh 到 $BUILD_DIR"
cp "$SCRIPT_DIR/start.sh" "$BUILD_DIR/start.sh"
chmod +x "$BUILD_DIR/start.sh"
# 打包到 $BUILD_DIR.tar.gz
PACKAGE_FILE="${BUILD_DIR}.tar.gz"
echo ""
echo "📦 打包构建产物到 $PACKAGE_FILE..."
cd "$BUILD_DIR" || exit 1
tar -czf "$PACKAGE_FILE" .
cd - > /dev/null || exit 1
# # 清理临时目录
# rm -rf "$BUILD_DIR"
echo ""
echo "✅ 构建完成!所有产物已打包到 $PACKAGE_FILE"
echo "📊 打包文件大小:"
ls -lh "$PACKAGE_FILE"
+1
View File
@@ -0,0 +1 @@
12512
+154
View File
@@ -0,0 +1,154 @@
#!/bin/bash
set -euo pipefail
# 获取脚本所在目录(.zscripts)
# 使用 $0 获取脚本路径(与 build.sh 保持一致)
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
log_step_start() {
local step_name="$1"
echo "=========================================="
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Starting: $step_name"
echo "=========================================="
export STEP_START_TIME
STEP_START_TIME=$(date +%s)
}
log_step_end() {
local step_name="${1:-Unknown step}"
local end_time
end_time=$(date +%s)
local duration=$((end_time - STEP_START_TIME))
echo "=========================================="
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Completed: $step_name"
echo "[LOG] Step: $step_name | Duration: ${duration}s"
echo "=========================================="
echo ""
}
start_mini_services() {
local mini_services_dir="$PROJECT_DIR/mini-services"
local started_count=0
log_step_start "Starting mini-services"
if [ ! -d "$mini_services_dir" ]; then
echo "Mini-services directory not found, skipping..."
log_step_end "Starting mini-services"
return 0
fi
echo "Found mini-services directory, scanning for sub-services..."
for service_dir in "$mini_services_dir"/*; do
if [ ! -d "$service_dir" ]; then
continue
fi
local service_name
service_name=$(basename "$service_dir")
echo "Checking service: $service_name"
if [ ! -f "$service_dir/package.json" ]; then
echo "[$service_name] No package.json found, skipping..."
continue
fi
if ! grep -q '"dev"' "$service_dir/package.json"; then
echo "[$service_name] No dev script found, skipping..."
continue
fi
echo "Starting $service_name in background..."
(
cd "$service_dir"
echo "[$service_name] Installing dependencies..."
bun install
echo "[$service_name] Running bun run dev..."
exec bun run dev
) >"$PROJECT_DIR/.zscripts/mini-service-${service_name}.log" 2>&1 &
local service_pid=$!
echo "[$service_name] Started in background (PID: $service_pid)"
echo "[$service_name] Log: $PROJECT_DIR/.zscripts/mini-service-${service_name}.log"
disown "$service_pid" 2>/dev/null || true
started_count=$((started_count + 1))
done
echo "Mini-services startup completed. Started $started_count service(s)."
log_step_end "Starting mini-services"
}
wait_for_service() {
local host="$1"
local port="$2"
local service_name="$3"
local max_attempts="${4:-60}"
local attempt=1
echo "Waiting for $service_name to be ready on $host:$port..."
while [ "$attempt" -le "$max_attempts" ]; do
if curl -s --connect-timeout 2 --max-time 5 "http://$host:$port" >/dev/null 2>&1; then
echo "$service_name is ready!"
return 0
fi
echo "Attempt $attempt/$max_attempts: $service_name not ready yet, waiting..."
sleep 1
attempt=$((attempt + 1))
done
echo "ERROR: $service_name failed to start within $max_attempts seconds"
return 1
}
cleanup() {
if [ -n "${DEV_PID:-}" ] && kill -0 "$DEV_PID" >/dev/null 2>&1; then
echo "Stopping Next.js dev server (PID: $DEV_PID)..."
kill "$DEV_PID" >/dev/null 2>&1 || true
fi
}
trap cleanup EXIT INT TERM
cd "$PROJECT_DIR"
if ! command -v bun >/dev/null 2>&1; then
echo "ERROR: bun is not installed or not in PATH"
exit 1
fi
log_step_start "bun install"
echo "[BUN] Installing dependencies..."
bun install
log_step_end "bun install"
log_step_start "bun run db:push"
echo "[BUN] Setting up database..."
bun run db:push
log_step_end "bun run db:push"
log_step_start "Starting Next.js dev server"
echo "[BUN] Starting development server..."
bun run dev &
DEV_PID=$!
log_step_end "Starting Next.js dev server"
log_step_start "Waiting for Next.js dev server"
wait_for_service "localhost" "3000" "Next.js dev server"
log_step_end "Waiting for Next.js dev server"
log_step_start "Health check"
echo "[BUN] Performing health check..."
curl -fsS localhost:3000 >/dev/null
echo "[BUN] Health check passed"
log_step_end "Health check"
start_mini_services
echo "Next.js dev server is running in background (PID: $DEV_PID)."
echo "Use 'kill $DEV_PID' to stop it."
disown "$DEV_PID" 2>/dev/null || true
unset DEV_PID
+78
View File
@@ -0,0 +1,78 @@
#!/bin/bash
# 配置项
ROOT_DIR="/home/z/my-project/mini-services"
DIST_DIR="/tmp/build_fullstack_$BUILD_ID/mini-services-dist"
main() {
echo "🚀 开始批量构建..."
# 检查 rootdir 是否存在
if [ ! -d "$ROOT_DIR" ]; then
echo "️ 目录 $ROOT_DIR 不存在,跳过构建"
return
fi
# 创建输出目录(如果不存在)
mkdir -p "$DIST_DIR"
# 统计变量
success_count=0
fail_count=0
# 遍历 mini-services 目录下的所有文件夹
for dir in "$ROOT_DIR"/*; do
# 检查是否是目录且包含 package.json
if [ -d "$dir" ] && [ -f "$dir/package.json" ]; then
project_name=$(basename "$dir")
# 智能查找入口文件 (按优先级查找)
entry_path=""
for entry in "src/index.ts" "index.ts" "src/index.js" "index.js"; do
if [ -f "$dir/$entry" ]; then
entry_path="$dir/$entry"
break
fi
done
if [ -z "$entry_path" ]; then
echo "⚠️ 跳过 $project_name: 未找到入口文件 (index.ts/js)"
continue
fi
echo ""
echo "📦 正在构建: $project_name..."
# 使用 bun build CLI 构建
output_file="$DIST_DIR/mini-service-$project_name.js"
if bun build "$entry_path" \
--outfile "$output_file" \
--target bun \
--minify; then
echo "$project_name 构建成功 -> $output_file"
success_count=$((success_count + 1))
else
echo "$project_name 构建失败"
fail_count=$((fail_count + 1))
fi
fi
done
if [ -f ./.zscripts/mini-services-start.sh ]; then
cp ./.zscripts/mini-services-start.sh "$DIST_DIR/mini-services-start.sh"
chmod +x "$DIST_DIR/mini-services-start.sh"
fi
echo ""
echo "🎉 所有任务完成!"
if [ $success_count -gt 0 ] || [ $fail_count -gt 0 ]; then
echo "✅ 成功: $success_count"
if [ $fail_count -gt 0 ]; then
echo "❌ 失败: $fail_count"
fi
fi
}
main
+65
View File
@@ -0,0 +1,65 @@
#!/bin/bash
# 配置项
ROOT_DIR="/home/z/my-project/mini-services"
main() {
echo "🚀 开始批量安装依赖..."
# 检查 rootdir 是否存在
if [ ! -d "$ROOT_DIR" ]; then
echo "️ 目录 $ROOT_DIR 不存在,跳过安装"
return
fi
# 统计变量
success_count=0
fail_count=0
failed_projects=""
# 遍历 mini-services 目录下的所有文件夹
for dir in "$ROOT_DIR"/*; do
# 检查是否是目录且包含 package.json
if [ -d "$dir" ] && [ -f "$dir/package.json" ]; then
project_name=$(basename "$dir")
echo ""
echo "📦 正在安装依赖: $project_name..."
# 进入项目目录并执行 bun install
if (cd "$dir" && bun install); then
echo "$project_name 依赖安装成功"
success_count=$((success_count + 1))
else
echo "$project_name 依赖安装失败"
fail_count=$((fail_count + 1))
if [ -z "$failed_projects" ]; then
failed_projects="$project_name"
else
failed_projects="$failed_projects $project_name"
fi
fi
fi
done
# 汇总结果
echo ""
echo "=================================================="
if [ $success_count -gt 0 ] || [ $fail_count -gt 0 ]; then
echo "🎉 安装完成!"
echo "✅ 成功: $success_count"
if [ $fail_count -gt 0 ]; then
echo "❌ 失败: $fail_count"
echo ""
echo "失败的项目:"
for project in $failed_projects; do
echo " - $project"
done
fi
else
echo "️ 未找到任何包含 package.json 的项目"
fi
echo "=================================================="
}
main
+123
View File
@@ -0,0 +1,123 @@
#!/bin/sh
# 配置项
DIST_DIR="./mini-services-dist"
# 存储所有子进程的 PID
pids=""
# 清理函数:优雅关闭所有服务
cleanup() {
echo ""
echo "🛑 正在关闭所有服务..."
# 发送 SIGTERM 信号给所有子进程
for pid in $pids; do
if kill -0 "$pid" 2>/dev/null; then
service_name=$(ps -p "$pid" -o comm= 2>/dev/null || echo "unknown")
echo " 关闭进程 $pid ($service_name)..."
kill -TERM "$pid" 2>/dev/null
fi
done
# 等待所有进程退出(最多等待 5 秒)
sleep 1
for pid in $pids; do
if kill -0 "$pid" 2>/dev/null; then
# 如果还在运行,等待最多 4 秒
timeout=4
while [ $timeout -gt 0 ] && kill -0 "$pid" 2>/dev/null; do
sleep 1
timeout=$((timeout - 1))
done
# 如果仍然在运行,强制关闭
if kill -0 "$pid" 2>/dev/null; then
echo " 强制关闭进程 $pid..."
kill -KILL "$pid" 2>/dev/null
fi
fi
done
echo "✅ 所有服务已关闭"
}
main() {
echo "🚀 开始启动所有 mini services..."
# 检查 dist 目录是否存在
if [ ! -d "$DIST_DIR" ]; then
echo "️ 目录 $DIST_DIR 不存在"
return
fi
# 查找所有 mini-service-*.js 文件
service_files=""
for file in "$DIST_DIR"/mini-service-*.js; do
if [ -f "$file" ]; then
if [ -z "$service_files" ]; then
service_files="$file"
else
service_files="$service_files $file"
fi
fi
done
# 计算服务文件数量
service_count=0
for file in $service_files; do
service_count=$((service_count + 1))
done
if [ $service_count -eq 0 ]; then
echo "️ 未找到任何 mini service 文件"
return
fi
echo "📦 找到 $service_count 个服务,开始启动..."
echo ""
# 启动每个服务
for file in $service_files; do
service_name=$(basename "$file" .js | sed 's/mini-service-//')
echo "▶️ 启动服务: $service_name..."
# 使用 bun 运行服务(后台运行)
bun "$file" &
pid=$!
if [ -z "$pids" ]; then
pids="$pid"
else
pids="$pids $pid"
fi
# 等待一小段时间检查进程是否成功启动
sleep 0.5
if ! kill -0 "$pid" 2>/dev/null; then
echo "$service_name 启动失败"
# 从字符串中移除失败的 PID
pids=$(echo "$pids" | sed "s/\b$pid\b//" | sed 's/ */ /g' | sed 's/^ *//' | sed 's/ *$//')
else
echo "$service_name 已启动 (PID: $pid)"
fi
done
# 计算运行中的服务数量
running_count=0
for pid in $pids; do
if kill -0 "$pid" 2>/dev/null; then
running_count=$((running_count + 1))
fi
done
echo ""
echo "🎉 所有服务已启动!共 $running_count 个服务正在运行"
echo ""
echo "💡 按 Ctrl+C 停止所有服务"
echo ""
# 等待所有后台进程
wait
}
main
+135
View File
@@ -0,0 +1,135 @@
#!/bin/sh
set -e
# 获取脚本所在目录
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
BUILD_DIR="$SCRIPT_DIR"
# 存储所有子进程的 PID
pids=""
# 清理函数:优雅关闭所有服务
cleanup() {
echo ""
echo "🛑 正在关闭所有服务..."
# 发送 SIGTERM 信号给所有子进程
for pid in $pids; do
if kill -0 "$pid" 2>/dev/null; then
service_name=$(ps -p "$pid" -o comm= 2>/dev/null || echo "unknown")
echo " 关闭进程 $pid ($service_name)..."
kill -TERM "$pid" 2>/dev/null
fi
done
# 等待所有进程退出(最多等待 5 秒)
sleep 1
for pid in $pids; do
if kill -0 "$pid" 2>/dev/null; then
# 如果还在运行,等待最多 4 秒
timeout=4
while [ $timeout -gt 0 ] && kill -0 "$pid" 2>/dev/null; do
sleep 1
timeout=$((timeout - 1))
done
# 如果仍然在运行,强制关闭
if kill -0 "$pid" 2>/dev/null; then
echo " 强制关闭进程 $pid..."
kill -KILL "$pid" 2>/dev/null
fi
fi
done
echo "✅ 所有服务已关闭"
exit 0
}
echo "🚀 开始启动所有服务..."
echo ""
# 切换到构建目录
cd "$BUILD_DIR" || exit 1
ls -lah
DEFAULT_PACKAGED_DB_PATH="/app/db/custom.db"
DEFAULT_PACKAGED_DATABASE_URL="file:$DEFAULT_PACKAGED_DB_PATH"
# 启动 Next.js 服务器
if [ -f "./next-service-dist/server.js" ]; then
echo "🚀 启动 Next.js 服务器..."
cd next-service-dist/ || exit 1
# 设置环境变量
export NODE_ENV=production
export PORT="${PORT:-3000}"
export HOSTNAME="${HOSTNAME:-0.0.0.0}"
export DATABASE_URL="${DATABASE_URL:-$DEFAULT_PACKAGED_DATABASE_URL}"
if [ "$DATABASE_URL" = "$DEFAULT_PACKAGED_DATABASE_URL" ]; then
if [ ! -f "$DEFAULT_PACKAGED_DB_PATH" ]; then
echo "❌ 未找到打包后的数据库文件 $DEFAULT_PACKAGED_DB_PATH"
echo " 为避免生产环境启动到空数据库,启动已终止"
exit 1
fi
echo "🗄️ 当前使用打包数据库: $DEFAULT_PACKAGED_DB_PATH"
else
echo "🗄️ 当前使用外部指定数据库: $DATABASE_URL"
fi
# 后台启动 Next.js
bun server.js &
NEXT_PID=$!
pids="$NEXT_PID"
# 等待一小段时间检查进程是否成功启动
sleep 1
if ! kill -0 "$NEXT_PID" 2>/dev/null; then
echo "❌ Next.js 服务器启动失败"
exit 1
else
echo "✅ Next.js 服务器已启动 (PID: $NEXT_PID, Port: $PORT)"
fi
cd ../
else
echo "⚠️ 未找到 Next.js 服务器文件: ./next-service-dist/server.js"
fi
# 启动 mini-services
if [ -f "./mini-services-start.sh" ]; then
echo "🚀 启动 mini-services..."
# 运行启动脚本(从根目录运行,脚本内部会处理 mini-services-dist 目录)
sh ./mini-services-start.sh &
MINI_PID=$!
pids="$pids $MINI_PID"
# 等待一小段时间检查进程是否成功启动
sleep 1
if ! kill -0 "$MINI_PID" 2>/dev/null; then
echo "⚠️ mini-services 可能启动失败,但继续运行..."
else
echo "✅ mini-services 已启动 (PID: $MINI_PID)"
fi
elif [ -d "./mini-services-dist" ]; then
echo "⚠️ 未找到 mini-services 启动脚本,但目录存在"
else
echo "️ mini-services 目录不存在,跳过"
fi
# 启动 Caddy(如果存在 Caddyfile
echo "🚀 启动 Caddy..."
# Caddy 作为前台进程运行(主进程)
echo "✅ Caddy 已启动(前台运行)"
echo ""
echo "🎉 所有服务已启动!"
echo ""
echo "💡 按 Ctrl+C 停止所有服务"
echo ""
# Caddy 作为主进程运行
exec caddy run --config Caddyfile --adapter caddyfile
+1
View File
File diff suppressed because one or more lines are too long
+1
View File
File diff suppressed because one or more lines are too long
Executable
+23
View File
@@ -0,0 +1,23 @@
:81 {
@transform_port_query {
query XTransformPort=*
}
handle @transform_port_query {
reverse_proxy localhost:{query.XTransformPort} {
header_up Host {host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
header_up X-Real-IP {remote_host}
}
}
handle {
reverse_proxy localhost:3000 {
header_up Host {host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
header_up X-Real-IP {remote_host}
}
}
}
Executable
+130
View File
@@ -0,0 +1,130 @@
# 回响星核 / Echo Nexus
> 原创深空考古放置策略游戏 · Original deep-space archaeology idle strategy game
你是一名深空考古指挥官,驾驶自治勘探舰回收已「飞升」的以太文明遗留的记忆晶体,解码碎片拼凑出他们的故事,并最终跨越维度发起「接触」。
---
## 🎮 在线游玩
**👉 [https://gitea-pages.atdunbg.xyz/Super_Z/echo-nexus/](https://gitea-pages.atdunbg.xyz/Super_Z/echo-nexus/)**
无需安装,打开即玩。支持桌面(鼠标 + 键盘)与移动端(触控)。
---
## 项目状态
- **版本****v0.7** — CrystalOrb Canvas 粒子系统 + 四维角色属性
- **栈**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/)
- **部署**Gitea Pages(静态导出,`BUILD_EXPORT=true next build``out/` 推送 `gh-pages` 分支)
---
## 核心特色
- **解码共振谜题**:把放置游戏的「等待」变成有节奏的主动小谜题(路径构造法生成器,100% 可解)
- **碎片涌现叙事**:故事从解码中自然浮现,5 纪元循环命名 + 模板化 lore,不打断玩法
- **深空全息美学**:晶体 + Canvas 粒子 + 共振波纹的差异化视觉(严格 emerald/rose/amber/fuchsia 四色全息色谱,零蓝色/靛色)
- **飞升多周目**:终局「接触」后保留蓝图开启新周目,星图天文台 18 个天赋跨周目永久生效
- **实时巡航玩法**Canvas 2D 飞船操控 + 8 种实体 + 3 层视差星空,打破纯放置节奏
- **角色 RPG 深度**:四维属性(探索力/智慧/勇气/灵感)影响所有游戏系统
---
## 游戏系统(10 大系统)
| # | 系统 | 简介 |
|---|------|------|
| 1 | 放置采矿 | 中央晶体球 Canvas 粒子系统 + 主动脉冲 + 连击加成 |
| 2 | 谐振解码 | 原创路径谜题,100% 可解率 |
| 3 | 技术树 | 三层科技解锁,提升产能/解码/探险 |
| 4 | 遗迹探险 | 肉鸽系统,6 种节点 + 程序化路径 + BOSS |
| 5 | 星潮事件 | 6 种动态事件,全屏叠层 + 程序化音效 |
| 6 | 星图天文台 | 飞升后 3 选 1 天赋 draft,18 个跨周目天赋 |
| 7 | 回响编年史 | 跨周目叙事时间轴,5 纪元循环命名 |
| 8 | 深空信标 | 每日挑战 + 本地排行榜 Top20 |
| 9 | 深空巡航 | Canvas 2D 实时玩法,飞船操控/陨石躲避/星门通关 |
| 10 | 角色属性 | 四维属性(探索力/智慧/勇气/灵感),飞升获得属性点 |
---
## 开发路线
| 版本 | 核心内容 | 状态 |
|------|---------|------|
| v0.1 | 放置 + 解码 + 技术树 + 图谱 + 飞升 | ✅ |
| v0.1.1 | 解码可解性修复(路径构造法) | ✅ |
| v0.2 | 遗迹探险肉鸽系统 | ✅ |
| v0.2.1 | 程序化音频 + 14 项成就 | ✅ |
| v0.3 | 星潮事件系统 | ✅ |
| v0.3.1 | 星图天文台元进程 | ✅ |
| v0.4 | 回响编年史系统 | ✅ |
| v0.5 | 深空信标系统 | ✅ |
| v0.5.1 | Gitea Pages 静态部署 + 新手教程 | ✅ |
| v0.5.2 | 离线收益报告 + 标签页 UI 重设计 | ✅ |
| v0.6 | 深空巡航 Canvas 2D 实时玩法 | ✅ |
| **v0.7** | **CrystalOrb Canvas 粒子系统 + 四维角色属性** | ✅ |
| v0.8 | 巡航玩法增强 / BOSS 关卡 / 道具掉落 | 🔜 |
| v0.9 | 云存档 + 云排行榜 | 🔜 |
| v1.0 | 全 5 纪元手写叙事 + 多人同步星潮 | 🔜 |
---
## 文档
- [`docs/01-调研报告.md`](docs/01-调研报告.md) — 游戏市场调研
- [`docs/02-游戏设计文档.md`](docs/02-游戏设计文档.md) — GDD 策划设计
- [`docs/03-技术架构.md`](docs/03-技术架构.md) — 技术架构
- [`docs/04-解码系统修复-v0.1.1.md`](docs/04-解码系统修复-v0.1.1.md) — v0.1.1 解码修复
- [`docs/05-遗迹探险系统-v0.2.md`](docs/05-遗迹探险系统-v0.2.md) — v0.2 探险系统
- [`docs/06-音频与成就系统-v0.2.1.md`](docs/06-音频与成就系统-v0.2.1.md) — v0.2.1 音频与成就
- [`docs/07-星潮事件系统-v0.3.md`](docs/07-星潮事件系统-v0.3.md) — v0.3 星潮
- [`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 信标
---
## 本地开发
```bash
# 安装依赖
bun install
# 启动 dev 服务器(http://localhost:3000
bun run dev
# 静态导出(用于 Gitea Pages 部署)
BUILD_EXPORT=true bun run build:static
# 产物在 out/ 目录,basePath 为 /Super_Z/echo-nexus
# 代码检查
bun run lint
```
---
## 部署到 Gitea Pages
```bash
# 1. 静态导出
BUILD_EXPORT=true bun run build:static
# 2. 推送到 gh-pages 分支
cd out
git init && git checkout -b gh-pages
git add -A
git commit -m "deploy: v0.7"
git remote add origin https://git.atdunbg.xyz/Super_Z/echo-nexus.git
git push -f origin gh-pages
```
Gitea Pages 会自动从 `gh-pages` 分支提供静态网站服务。
---
© Super_Z · 持续开发中
+9
View File
@@ -0,0 +1,9 @@
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/a239accb5eb992e9.js","/Super_Z/echo-nexus/_next/static/chunks/385ae85fdd4cea24.js","/Super_Z/echo-nexus/_next/static/chunks/9049994ef69d11f1.js"],"default"]
6:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"OutletBoundary"]
7:"$Sreact.suspense"
0:{"buildId":"CnCCjeOHNwL-4l2fXOrVx","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/9049994ef69d11f1.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
+22
View File
@@ -0,0 +1,22 @@
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/a239accb5eb992e9.js","/Super_Z/echo-nexus/_next/static/chunks/385ae85fdd4cea24.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/a239accb5eb992e9.js","/Super_Z/echo-nexus/_next/static/chunks/385ae85fdd4cea24.js","/Super_Z/echo-nexus/_next/static/chunks/9049994ef69d11f1.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/34d933785a17edf3.css","style"]
:HL["/Super_Z/echo-nexus/_next/static/chunks/e2282f292208e0c9.css","style"]
: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":"CnCCjeOHNwL-4l2fXOrVx","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/34d933785a17edf3.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/Super_Z/echo-nexus/_next/static/chunks/e2282f292208e0c9.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/Super_Z/echo-nexus/_next/static/chunks/a239accb5eb992e9.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/Super_Z/echo-nexus/_next/static/chunks/385ae85fdd4cea24.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 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/9049994ef69d11f1.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",{}]]
+6
View File
@@ -0,0 +1,6 @@
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":"CnCCjeOHNwL-4l2fXOrVx","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}
+7
View File
@@ -0,0 +1,7 @@
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/a239accb5eb992e9.js","/Super_Z/echo-nexus/_next/static/chunks/385ae85fdd4cea24.js"],"Toaster"]
:HL["/Super_Z/echo-nexus/_next/static/chunks/34d933785a17edf3.css","style"]
:HL["/Super_Z/echo-nexus/_next/static/chunks/e2282f292208e0c9.css","style"]
0:{"buildId":"CnCCjeOHNwL-4l2fXOrVx","rsc":["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/Super_Z/echo-nexus/_next/static/chunks/34d933785a17edf3.css","precedence":"next"}],["$","link","1",{"rel":"stylesheet","href":"/Super_Z/echo-nexus/_next/static/chunks/e2282f292208e0c9.css","precedence":"next"}],["$","script","script-0",{"src":"/Super_Z/echo-nexus/_next/static/chunks/a239accb5eb992e9.js","async":true}],["$","script","script-1",{"src":"/Super_Z/echo-nexus/_next/static/chunks/385ae85fdd4cea24.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 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}
+5
View File
@@ -0,0 +1,5 @@
:HL["/Super_Z/echo-nexus/_next/static/chunks/34d933785a17edf3.css","style"]
:HL["/Super_Z/echo-nexus/_next/static/chunks/e2282f292208e0c9.css","style"]
: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":"CnCCjeOHNwL-4l2fXOrVx","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}
@@ -0,0 +1,11 @@
self.__BUILD_MANIFEST = {
"__rewrites": {
"afterFiles": [],
"beforeFiles": [],
"fallback": []
},
"sortedPages": [
"/_app",
"/_error"
]
};self.__BUILD_MANIFEST_CB && self.__BUILD_MANIFEST_CB()
@@ -0,0 +1 @@
[]
@@ -0,0 +1 @@
self.__SSG_MANIFEST=new Set([]);self.__SSG_MANIFEST_CB&&self.__SSG_MANIFEST_CB()
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
View File
@@ -0,0 +1,2 @@
@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"}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+16
View File
@@ -0,0 +1,16 @@
1:"$Sreact.fragment"
2:I[39756,["/Super_Z/echo-nexus/_next/static/chunks/a239accb5eb992e9.js","/Super_Z/echo-nexus/_next/static/chunks/385ae85fdd4cea24.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"default"]
3:I[37457,["/Super_Z/echo-nexus/_next/static/chunks/a239accb5eb992e9.js","/Super_Z/echo-nexus/_next/static/chunks/385ae85fdd4cea24.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"default"]
4:I[77855,["/Super_Z/echo-nexus/_next/static/chunks/a239accb5eb992e9.js","/Super_Z/echo-nexus/_next/static/chunks/385ae85fdd4cea24.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"Toaster"]
5:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/a239accb5eb992e9.js","/Super_Z/echo-nexus/_next/static/chunks/385ae85fdd4cea24.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"OutletBoundary"]
6:"$Sreact.suspense"
8:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/a239accb5eb992e9.js","/Super_Z/echo-nexus/_next/static/chunks/385ae85fdd4cea24.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"ViewportBoundary"]
a:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/a239accb5eb992e9.js","/Super_Z/echo-nexus/_next/static/chunks/385ae85fdd4cea24.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"MetadataBoundary"]
c:I[68027,["/Super_Z/echo-nexus/_next/static/chunks/a239accb5eb992e9.js","/Super_Z/echo-nexus/_next/static/chunks/385ae85fdd4cea24.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"default"]
:HL["/Super_Z/echo-nexus/_next/static/chunks/34d933785a17edf3.css","style"]
:HL["/Super_Z/echo-nexus/_next/static/chunks/e2282f292208e0c9.css","style"]
0:{"P":null,"b":"CnCCjeOHNwL-4l2fXOrVx","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/34d933785a17edf3.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/Super_Z/echo-nexus/_next/static/chunks/e2282f292208e0c9.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/Super_Z/echo-nexus/_next/static/chunks/a239accb5eb992e9.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/Super_Z/echo-nexus/_next/static/chunks/385ae85fdd4cea24.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.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 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/a239accb5eb992e9.js","/Super_Z/echo-nexus/_next/static/chunks/385ae85fdd4cea24.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.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",{}]]
+6
View File
@@ -0,0 +1,6 @@
1:"$Sreact.fragment"
2:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/a239accb5eb992e9.js","/Super_Z/echo-nexus/_next/static/chunks/385ae85fdd4cea24.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"ViewportBoundary"]
3:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/a239accb5eb992e9.js","/Super_Z/echo-nexus/_next/static/chunks/385ae85fdd4cea24.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"MetadataBoundary"]
4:"$Sreact.suspense"
5:I[27201,["/Super_Z/echo-nexus/_next/static/chunks/a239accb5eb992e9.js","/Super_Z/echo-nexus/_next/static/chunks/385ae85fdd4cea24.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"IconMark"]
0:{"buildId":"CnCCjeOHNwL-4l2fXOrVx","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}
+7
View File
@@ -0,0 +1,7 @@
1:"$Sreact.fragment"
2:I[39756,["/Super_Z/echo-nexus/_next/static/chunks/a239accb5eb992e9.js","/Super_Z/echo-nexus/_next/static/chunks/385ae85fdd4cea24.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"default"]
3:I[37457,["/Super_Z/echo-nexus/_next/static/chunks/a239accb5eb992e9.js","/Super_Z/echo-nexus/_next/static/chunks/385ae85fdd4cea24.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"default"]
4:I[77855,["/Super_Z/echo-nexus/_next/static/chunks/a239accb5eb992e9.js","/Super_Z/echo-nexus/_next/static/chunks/385ae85fdd4cea24.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"Toaster"]
:HL["/Super_Z/echo-nexus/_next/static/chunks/34d933785a17edf3.css","style"]
:HL["/Super_Z/echo-nexus/_next/static/chunks/e2282f292208e0c9.css","style"]
0:{"buildId":"CnCCjeOHNwL-4l2fXOrVx","rsc":["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/Super_Z/echo-nexus/_next/static/chunks/34d933785a17edf3.css","precedence":"next"}],["$","link","1",{"rel":"stylesheet","href":"/Super_Z/echo-nexus/_next/static/chunks/e2282f292208e0c9.css","precedence":"next"}],["$","script","script-0",{"src":"/Super_Z/echo-nexus/_next/static/chunks/a239accb5eb992e9.js","async":true}],["$","script","script-1",{"src":"/Super_Z/echo-nexus/_next/static/chunks/385ae85fdd4cea24.js","async":true}],["$","script","script-2",{"src":"/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.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 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}
@@ -0,0 +1,5 @@
1:"$Sreact.fragment"
2:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/a239accb5eb992e9.js","/Super_Z/echo-nexus/_next/static/chunks/385ae85fdd4cea24.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"OutletBoundary"]
3:"$Sreact.suspense"
0:{"buildId":"CnCCjeOHNwL-4l2fXOrVx","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
+4
View File
@@ -0,0 +1,4 @@
1:"$Sreact.fragment"
2:I[39756,["/Super_Z/echo-nexus/_next/static/chunks/a239accb5eb992e9.js","/Super_Z/echo-nexus/_next/static/chunks/385ae85fdd4cea24.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"default"]
3:I[37457,["/Super_Z/echo-nexus/_next/static/chunks/a239accb5eb992e9.js","/Super_Z/echo-nexus/_next/static/chunks/385ae85fdd4cea24.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"default"]
0:{"buildId":"CnCCjeOHNwL-4l2fXOrVx","rsc":["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","template":["$","$L3",null,{}]}]]}],"loading":null,"isPartial":false}
+3
View File
@@ -0,0 +1,3 @@
:HL["/Super_Z/echo-nexus/_next/static/chunks/34d933785a17edf3.css","style"]
:HL["/Super_Z/echo-nexus/_next/static/chunks/e2282f292208e0c9.css","style"]
0:{"buildId":"CnCCjeOHNwL-4l2fXOrVx","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}
File diff suppressed because one or more lines are too long
+16
View File
@@ -0,0 +1,16 @@
1:"$Sreact.fragment"
2:I[39756,["/Super_Z/echo-nexus/_next/static/chunks/a239accb5eb992e9.js","/Super_Z/echo-nexus/_next/static/chunks/385ae85fdd4cea24.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"default"]
3:I[37457,["/Super_Z/echo-nexus/_next/static/chunks/a239accb5eb992e9.js","/Super_Z/echo-nexus/_next/static/chunks/385ae85fdd4cea24.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"default"]
4:I[77855,["/Super_Z/echo-nexus/_next/static/chunks/a239accb5eb992e9.js","/Super_Z/echo-nexus/_next/static/chunks/385ae85fdd4cea24.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"Toaster"]
5:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/a239accb5eb992e9.js","/Super_Z/echo-nexus/_next/static/chunks/385ae85fdd4cea24.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"OutletBoundary"]
6:"$Sreact.suspense"
8:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/a239accb5eb992e9.js","/Super_Z/echo-nexus/_next/static/chunks/385ae85fdd4cea24.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"ViewportBoundary"]
a:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/a239accb5eb992e9.js","/Super_Z/echo-nexus/_next/static/chunks/385ae85fdd4cea24.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"MetadataBoundary"]
c:I[68027,["/Super_Z/echo-nexus/_next/static/chunks/a239accb5eb992e9.js","/Super_Z/echo-nexus/_next/static/chunks/385ae85fdd4cea24.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"default"]
:HL["/Super_Z/echo-nexus/_next/static/chunks/34d933785a17edf3.css","style"]
:HL["/Super_Z/echo-nexus/_next/static/chunks/e2282f292208e0c9.css","style"]
0:{"P":null,"b":"CnCCjeOHNwL-4l2fXOrVx","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/34d933785a17edf3.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/Super_Z/echo-nexus/_next/static/chunks/e2282f292208e0c9.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/Super_Z/echo-nexus/_next/static/chunks/a239accb5eb992e9.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/Super_Z/echo-nexus/_next/static/chunks/385ae85fdd4cea24.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.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 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/a239accb5eb992e9.js","/Super_Z/echo-nexus/_next/static/chunks/385ae85fdd4cea24.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.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",{}]]
+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.5,5 种类型各有公式
- `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

+52
View File
@@ -0,0 +1,52 @@
# Task 6: 开发 Canvas 2D 深空巡航实时玩法
## Agent: full-stack-developer
## 完成状态: ✅ 全部完成
## 创建的文件
1. `/home/z/my-project/src/lib/game/cruise.ts` — 逻辑层(~520 行纯 TS
2. `/home/z/my-project/src/components/game/CruiseMode.tsx` — Canvas 2D 渲染层(~830 行)
## 修改的文件
1. `/home/z/my-project/src/app/page.tsx` — 新增巡航入口按钮 + CruiseMode 渲染
## 关键实现细节
### cruise.ts 逻辑层
- **类型系统**CruiseEntity 联合类型(8 种实体:ship/asteroid/storm/crystal/insight/beacon/stargate/particle),完整 CruiseState/Phase/RunResult/Stats/Input
- **关卡生成**mulberry32 + FNV-1a 种子化 RNG;陨石(8+level×2)、风暴(1+level/2)、晶体(5+level)、洞见(2+level/3)、信标(1+level/4)、星门;时长 60-90s
- **物理**:飞船 8 方向加速度+摩擦+限速+边界反弹;陨石碰撞扣盾+击退+600ms 无敌;风暴持续 DPS;收集物吸引半径 90px 自动吸入
- **奖励**crystals=碎片×(8+lvl×2)、insights=洞见×(2+lvl×0.5)、contact=信标×(1.5+lvl×0.3);通关×1.5、失败×0.5
- **localStorage**:独立 key `echo-nexus-cruise-v1`,记录最高分/通关数/最高关卡/累计奖励/最近 20 局
### CruiseMode.tsx 渲染层
- **全屏 Canvas**fixed inset-0 z-50DPR cap 2resize 监听
- **3 层视差星空**:远/中/近 100/60/30 星,独立漂移+飞船速度视差+闪烁
- **实体辉光绘制**:全部使用 ctx.shadowBlur;飞船三角形+尾焰渐变;陨石不规则多边形;风暴 5 层云团+闪电;晶体菱形+脉冲环;洞见球体+漩涡;信标光束+旋转环;星门 4 层旋涡+吸入粒子
- **粒子系统**:尾焰/收集/碰撞/烟花,上限 200
- **屏幕震动**:受击 280ms 衰减抖动
- **低护盾警告**shield<30% 屏幕边缘 rose 脉冲边框
- **HUD**HTML 叠层(非 canvas),glass+backdrop-blur;护盾/能量/分数/用时/收集计数;80ms 节流更新
- **控制**:桌面 WASD/方向键;移动端 floating 虚拟摇杆;P 暂停;Esc 退出;失焦自动暂停
- **奖励同步**:结算时 grantCruiseReward + recordRunrewardGrantedRef 防重
### page.tsx 集成
- header 按钮区新增「巡航」按钮(Navigation 图标,amber 主题)
- 新增 cruiseOpen state
- 组件末尾渲染 {cruiseOpen && <CruiseMode onClose={...} />}
- 未破坏现有 7 标签页和其他功能
## QA 验证结果
- ✅ lint 零错误
- ✅ 巡航按钮出现在顶部 header
- ✅ 点击进入全屏 Canvas,ready 界面完整
- ✅ WASD 控制飞船移动,按 W 直冲星门通关
- ✅ 收集 2 晶体 → 奖励 +30(2×10×1.5=30 计算正确)
- ✅ localStorage 正确记录 highScore/totalWins/bestLevel
- ✅ 奖励同步到 gameStore
- ✅ 退出返回主界面,7 标签页完好
- ✅ 移动端 viewport 测试通过
## 色彩遵循
emerald(#34d399) / rose(#fb7185) / amber(#fbbf24) / fuchsia(#e879f9) 四色全息色谱,零蓝色/靛色
+103
View File
@@ -0,0 +1,103 @@
# Task 7: 角色属性系统(探索力/智慧/勇气/灵感)
## Agent: full-stack-developer
## 完成状态: ✅ 全部完成
## 创建的文件
1. `/home/z/my-project/src/lib/game/attributes.ts` — 属性逻辑层(~330 行)
2. `/home/z/my-project/src/components/game/AttributesPanel.tsx` — 属性 UI 面板(~330 行)
## 修改的文件
1. `/home/z/my-project/src/lib/game/types.ts` — GameState 新增 attributes/attributeProgress/pendingAttrPoints
2. `/home/z/my-project/src/lib/game/config.ts` — INITIAL_STATE 补全新字段默认值
3. `/home/z/my-project/src/lib/game/engine.ts` — recomputeStats 聚合属性加成;performPrestige 发放属性点
4. `/home/z/my-project/src/store/gameStore.ts` — 新增 allocateAttribute/gainAttributeExp actionpulse/clickNode/autoDecodeTick/resolveCurrentNode/grantCruiseReward/tickTide/init 接入属性逻辑;旧存档兼容
5. `/home/z/my-project/src/lib/game/achievements.ts` — 新增 2 项属性成就
6. `/home/z/my-project/src/app/page.tsx` — 新增第 8 个「角色」标签页 + grid-cols-7→8 + 红点提示 + 统计面板新增属性行 + 版本号 v0.6→v0.7
## 关键实现细节
### attributes.ts 逻辑层
- **类型系统**AttributeKey4 个键)/ CharacterAttributes0-100 数值)/ AttributeProgressEntryexp + level
- **加成公式**
- 0-50 线性区:每点 +0.5% 加成(0..50 → 0..25%
- 50-100 递减区:每点 +0.2% 加成(50..100 → 25..35%
- 超过 100 仍按 100 计算加成(软上限)
- **getAttributeBonus(attr)** 单属性加成百分比
- **getAllBonuses(attrs)** 返回 12 个修饰器:探险力倍率/巡航速度/解码步数/洞见倍率/自动解码周期/探险生命/BOSS 胜率/巡航护盾/接触率/星潮触发/脉冲连击/产能加成
- **levelUpCheck(progress)** 自动跨多级升级,安全上限 200 次循环
- **expRequiredForLevel(level) = 10 × level**(最低 10
- **computePrestigeAttrPoints(ascensions) = ascensions × 2 + 1**(飞升前次数计算)
- **migrateAttributes(state)** 旧存档兼容:补全缺失字段、夹紧越界值、同步 level 与 attributes
### engine.ts 改造
- recomputeStats 末尾追加 `getAllBonuses(state.attributes ?? {})` 聚合:
- `crystalsPerSec *= am.crystalsPerSecMult`
- `insightMult += am.insightMultAdd`
- `contactRateMult *= am.contactRateMult`
- `decodeStepsBonus += am.decodeStepsBonus`
- performPrestige
- 保留 attributes 数值(跨周目永久)
- 清空 attributeProgress(新周目重新累积经验)
- pendingAttrPoints += computePrestigeAttrPoints(ascensions) = ascensions × 2 + 1
- createInitialState:每次返回全新 attributes/attributeProgress 对象,避免引用共享
### gameStore.ts 集成(核心)
- **pulse**:连击 ≥3 给灵感经验(expGain = 1 + floor(combo/2));脉冲威力乘以灵感连击加成
- **clickNode** 完成:给智慧经验(expGain = puzzle.tier × 2
- **autoDecodeTick**:自动解码也给智慧经验 +1;自动解码周期受智慧 am.autoDecodeIntervalMult 影响
- **startExpedition**:探险力乘 am.expeditionPowerMult;探险生命加 am.expeditionHpBonus
- **resolveCurrentNode**
- BOSS 节点用包装 RNG 提升 +am.bossWinRateBonus 胜率(单次 rng 调用,B% 概率返回 0,其余返回 r-B 保持均匀分布)
- 战斗胜利给勇气+探索力经验(中途战斗 +2/+1,BOSS 击破 +8/+6,探险胜利 +4
- **grantCruiseReward**:按总奖励量缩放给探索力+勇气经验(expBase = max(2, totalReward/30)
- **tickTide**:灵感 am.tideTriggerBonus 缩短星潮间隙(gap × (1 - bonus)),上限 30%
- **doPrestige**performPrestige 后用 next.attributes 重算 stats
- **allocateAttribute(attr, points=1)**:分配属性点,同步 attributeProgress[attr].level
- **gainAttributeExp(attr, amount)**:通用经验获取(自动升级)
- **init()**:调用 migrateAttributes 补全旧存档字段,并传 attributes 到 syncStats
- 所有 syncStats 调用点(10+ 处)都补充 `attributes: ...` 参数
### AttributesPanel.tsx UI
- 四维属性卡片网格(小屏 2×2,大屏 1×4):
- 图标(Compass/Brain/Swords/Sparkles+ 中文名 + 英文名 + Lv.{value} badge
- 数值 /100 + 加成百分比
- 经验进度条(gradient + glow+ "递减区"标记
- 加成影响列表(3 条)
- 「+分配」按钮(pendingAttrPoints > 0 时可点,hover scale 105
- 顶部:标题 + 待分配点数 badgependingAttrPoints > 0 时 echo-pending-pulse 动画)
- 底部:总等级 + 总加成概览 + 12 个修饰器明细行
- 配色:4 色全息(emerald/fuchsia/amber/rose),辉光边框 + 顶角光晕装饰
- 完全响应式(2 列 → 4 列),overflow-y-auto + 自定义 scrollbar
### page.tsx 集成
- 新增 `User` 图标导入
- 新增 `pendingAttrPoints` store selector
- TabsList: grid-cols-7 → grid-cols-8
- 新增第 8 个 TabsTrigger「角色」(value="attributes"),主题色用 emerald→fuchsia→rose 渐变
- pendingAttrPoints > 0 时显示 rose 红点
- 新增 TabsContent 渲染 AttributesPanel
- StatsPanel 新增 5 行属性行(探索力/智慧/勇气/灵感/待分配属性点)
- 版本号 v0.6 → v0.7
### achievements.ts 新增
- `ach_attr_total_50`(四维觉醒):四维属性总和 ≥ 50 → 产能+6%/洞见+6%
- `ach_attr_max_100`(维度精通):任一属性 ≥ 100 → 产能+12%/洞见+10%
## QA 验证结果
- ✅ `bun run lint` 零错误
- ✅ dev 服务器 HTTP 200
- ✅ 编译 < 250ms177ms 实测)
- ✅ 7 标签页 + 巡航按钮完好保留,新增第 8 个「角色」标签页
- ✅ data-tut 锚点(tab-expedition/tab-tech/prestige-btn/crystal-orb/decode-panel)保留
- ✅ 色彩规范:严格 emerald/fuchsia/amber/rose 四色全息,零蓝色/靛色
- ✅ 旧存档兼容:migrateAttributes 补全 attributes/attributeProgress/pendingAttrPoints 字段
- ✅ 飞升后 pendingAttrPoints = ascensions × 2 + 1(飞升前次数)
- ✅ pulse/clickNode/resolveCurrentNode/grantCruiseReward/autoDecodeTick 均接入属性经验获取
## 注意事项
- BOSS 胜率 RNG 包装:仅在 boss 节点生效,单次 rng 调用保持均匀分布;+am.bossWinRateBonus 上限 +30%
- 灵感星潮触发:通过缩短 gap 间接提升触发频率(上限 30%)
- 属性加成与现有所有系统(技术树/蓝图/成就/星图/星潮)叠加,不冲突
- migrateAttributes 同步 attributeProgress[attr].level 与 attributes[attr] 数值,避免漂移
+141
View File
@@ -0,0 +1,141 @@
# Task 9-b · full-stack-developer · 信标系统扩展(周挑战 + 信标链)
> 本文件为本 agent 在 Task 9-b 的工作记录,供后续 agent 查阅。
## 任务概述
为「回响星核 / Echo Nexus」v0.8 扩展深空信标系统,新增两大功能:
1. **周挑战(Weekly Challenge** — 每周一 UTC 0 点刷新,目标更大、奖励更好,与日挑战并行
2. **信标链(Beacon Chain** — 连续完成日挑战形成"链",达成里程碑(3/7/14/30 天)领取递增大奖,含 grace 续命机制
## 阅读的前置工作
- `/home/z/my-project/worklog.md`(v0.8 项目状态,10 大系统,四色全息规范)
- `/home/z/my-project/src/lib/game/beacon.ts`(v0.5 原版 358 行:每日挑战 + 本地排行榜)
- `/home/z/my-project/src/components/game/BeaconPanel.tsx`(原版 301 行)
- `/home/z/my-project/src/store/gameStore.ts` 第 173 行 `trackBeacon` 函数 + 7 处调用点(pulse/clickNode/autoDecodeTick/resolveCurrentNode 等)
## 实现细节
### 1. beacon.ts 扩展(358 → 873 行)
**周挑战(WEEKLY CHALLENGEsection**
- `BeaconWeeklyChallenge` 接口 + `BeaconWeeklyProgress` 接口
- `getWeekKey(now)`ISO 8601 周键(YYYY-Www,周一为起点,含首个周四的周为第一周)
- `weekKeyToSeed`FNV-1a 哈希
- `generateWeeklyChallenge(now)`:基于 weekKey 种子确定性生成
- 难度加权:anomaly 60% / singular 40%
- mult = 3 + floor(rng() * 3) → 3-5 倍
- goal 范围:decode 18-75 / expedition 3-15 / pulse 60-245 / boss 3-12 / insight 120-595
- `loadWeeklyProgress` / `saveWeeklyProgress` / `addWeeklyProgress` / `claimWeeklyReward`
- `msUntilNextWeek(now)`
- `BEACON_WEEKLY_KEY = "echo-nexus-beacon-weekly-v1"`
**信标链(BEACON CHAINsection**
- `BeaconChainState` 接口(lastCompletedDateKey / currentStreak / longestStreak / totalCompletions / graceUsed / milestonesClaimed
- `BEACON_CHAIN_KEY = "echo-nexus-beacon-chain-v1"`
- `BEACON_CHAIN_MILESTONES = [3, 7, 14, 30] as const`
- `BEACON_CHAIN_REWARDS`4 个里程碑
- 3 天:+50 洞见 / +5 接触 / "三日谐振"
- 7 天:+120 洞见 / +12 接触 / "七日回响"
- 14 天:+280 洞见 / +28 接触 / "半月星潮"
- 30 天:+680 洞见 / +68 接触 / "满月飞升"
- `loadChainState` / `saveChainState`(每次返回新对象避免引用共享 bug
- `recordChainCompletion(dateKey)` 核心逻辑:
- 同日重复完成 → 忽略
- 次日 → streak++
- 隔一天 miss 且 graceUsed<1 → 续命 streak++ graceUsed++
- 其他 → 断链 streak=1 graceUsed=0
- 返回 `{ state, newMilestones }`
- `claimChainMilestone(milestone)` / `getNextMilestone(streak)` / `getChainProgress(streak)`
- `dateKeyToTimestamp` / `dateKeyDiffDays` 工具
`BeaconScoreEntry` 新增可选 `isWeekly?: boolean` 字段,向后兼容。
### 2. gameStore.ts 集成
- import 扩展:新增 generateWeeklyChallenge / loadWeeklyProgress / loadChainState / addWeeklyProgress / recordChainCompletion / claimWeeklyReward / claimChainMilestone / getTodayKey + 类型
- `trackBeacon(type, delta)` 返回值从 `boolean` 升级为 `{ dailyJustCompleted, weeklyJustCompleted, newChainMilestones }`
- 同时更新日挑战进度 + 周挑战进度
- 日挑战刚完成时调用 `recordChainCompletion(getTodayKey())`
- 所有 7 处调用点原本忽略返回值,向后兼容
- 新增 action `claimWeeklyBeacon()`:调用 claimWeeklyReward → 发放奖励到 state
- 新增 action `claimChainReward(milestone)`:前置校验 → claimChainMilestone → 发放奖励
- GameActions 接口同步扩展
### 3. BeaconPanel.tsx 重写(301 → 638 行)
- 头部 + 每日挑战卡片(v0.5 保留)+ 难度色按钮主题
- **周挑战区块**fuchsia 主题):标题 + weekKey + 倒计时 + 卡片(标签/标题/描述/进度/奖励/领取按钮 emerald+ weekly-glow 动画
- **信标链区块**amber→rose 渐变):
- 标题"信标链 · CHAIN" + 大字号 streakchain-streak-text 流动渐变动画)
- 今日完成状态徽章
- 4 个里程碑节点(w-12 h-12 圆形):
- claimed: emerald 实心 + ✓
- reachable: rose 脉冲动画 + "领取"按钮
- inProgress (next milestone): amber 半亮
- 未到达: muted 灰
- 节点间连线:背景灰 + 已达成部分 amber→rose 渐变 + 辉光,基于 prev→next 插值定位
- 进度条 + 底部统计(最长链/累计完成/续命状态)
- 桌面端 lg:grid-cols-2 让周挑战 + 信标链并排,移动端单列
- 排行榜区分日/周:周挑战 entry 显示 "WEEK" 徽章 + fuchsia 高亮背景
- 4 个新 CSS 动画:weekly-glow / chain-milestone-pulse / chain-streak-flux
## QA 验证结果
### 1. lint
- `bun run lint` 零错误(每次修改后均验证)
### 2. dev 服务器
- dev.log 全程无错误,所有编译 < 300msHTTP 200
### 3. 信标链逻辑测试(bun 直接运行 TS,5 个场景全 PASS)
1. ✅ 昨日 streak=1 → 今日完成 → streak=2normal increment
2. ✅ 同日重复完成 → 忽略
3. ✅ 明日完成 → streak=3,无需 grace
4. ✅ 隔一天 misslastCompleted + 2 天)→ streak=4graceUsed=1(续命触发)
5. ✅ 再次 missgrace 已用)→ 断链 streak=1graceUsed=0
### 4. UI 集成测试(agent-browser
- localStorage 模拟设置 chain statelastCompletedDateKey=yesterday, currentStreak=1)→ reload → UI 正确显示 streak=1
- 设置 streak=3 + milestonesClaimed=[] → milestone 3 节点显示 rose 脉冲动画 + "领取"按钮
- 点击"领取"→ milestonesClaimed=[3] + Toast "✦ 三日谐振 已领取 +50 洞见 · +5.0 接触" + 按钮消失 ✅
### 5. VLM 视觉评分(目标 ≥7/10)
- 首屏截图:**8/10**(四色一致、布局合理、信标链清晰)
- 里程碑可领取状态:**8/10**(amber→rose 渐变醒目、里程碑层次分明、fuchsia 主题清晰)
- 重置后干净状态:**8/10**(WEEKLY 字体对比度可优化,但整体可读性强)
## 修改的文件
1. `src/lib/game/beacon.ts` — 358 → 873 行(+515 行)
2. `src/store/gameStore.ts` — trackBeacon 升级 + 2 个新 action+~80 行)
3. `src/components/game/BeaconPanel.tsx` — 301 → 638 行(+337 行,重写)
## 不破坏的现有功能
- 日挑战进度追踪与领奖流程
- 本地排行榜 Top20
- 现有 5 种挑战类型 + 3 档难度
- 现有 localStorage keysecho-nexus-beacon-lb-v1 / echo-nexus-beacon-prog-v1
## 新增的 localStorage keys
- `echo-nexus-beacon-weekly-v1`(周挑战进度)
- `echo-nexus-beacon-chain-v1`(信标链状态)
## 四色全息规范遵循
- 周挑战主题:**fuchsia** (#e879f9)
- 信标链主题:**amber → rose** 渐变 (#fbbf24#fb7185)
- 领取按钮:**emerald** (#34d399)
- 难度色:routine emerald / anomaly amber / singular rose
- **零蓝色/靛色违规**
## 截图资产
- `/home/z/my-project/agent-ctx/beacon-panel-v0.8.png` — 初次进入信标页
- `/home/z/my-project/agent-ctx/beacon-chain-streak1.png` — chain streak=1 状态
- `/home/z/my-project/agent-ctx/beacon-chain-milestone3.png` — chain streak=3 milestone 可领取
- `/home/z/my-project/agent-ctx/beacon-milestone3-claimable.png` — milestone 3 领取前
- `/home/z/my-project/agent-ctx/beacon-final-fresh.png` — 重置后干净状态
Binary file not shown.

After

Width:  |  Height:  |  Size: 299 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 380 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 386 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: 383 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 387 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

+2005
View File
File diff suppressed because it is too large Load Diff
+21
View File
@@ -0,0 +1,21 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/app/globals.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}
Executable
BIN
View File
Binary file not shown.
+95
View File
@@ -0,0 +1,95 @@
# 游戏市场调研报告
> 调研日期:2025 年(持续更新)
> 调研者:Super_Z
> 目的:为原创游戏《回响星核 / Echo Nexus》的设计提供数据支撑,避免落入「1:1 复刻」陷阱。
---
## 一、市场大盘(数据时效性已核实,多源交叉验证)
### 1.1 浏览器 / HTML5 游戏市场
- **市场规模**2025 年浏览器游戏市场约 **78 亿 ~ 118 亿美元**(不同机构口径略有差异),预计 2033-2034 年达到 **92 亿 ~ 163 亿美元**CAGR 约 3.5%。
- 来源:Research and Markets78.1 亿→80.1 亿)、Dataintelo118 亿→163 亿)、Business Research Insights。
- **HTML5 游戏细分**2024 年约 53.2 亿美元,2033 年预计 92.2 亿美元,CAGR 约 7.7%LinkedIn / Metastat)。
- **供给爆发**2025 年有 **15,000+** 款 HTML5 新作上线,同比增长 **2.7 倍**Business Research Insights)。
- **技术趋势**WebAssembly 让浏览器游戏性能逼近原生;WebGL/WebGPU 渲染能力持续提升。
**结论**:浏览器游戏是增长赛道,**可及性高、潜在受众巨大(尤其国际市场)**,但供给端竞争激烈,**必须靠差异化创新突围**。
### 1.2 独立游戏品类格局(Steam 视角)
- **2025 年叙事游戏**成为 Steam 达到 1000 评价数量最多的品类(#1),超越传统品类(HowToMarketAGame 年度报告)。
- **新一代独立原生品类**:开放世界生存、卡牌构筑(deckbuilder)、Roguelike 持续走强(SuperJump)。
- **红海/饱和品类**(Reddit 独开者共识,建议避开正面对撞):Roguelike Deckbuilder、视觉小说、本地合作、4X。
- **2025 独立代表作**Hades II、Deltarune、Ways of Alchemy、Cluckmech Oasis 等。
**结论**:纯复刻热门品类(如又一款肉鸽卡牌)会被淹没;**叙事 + 创新机制组合**是突破口。
### 1.3 放置 / 增量(Idle / Incremental)品类
- **极强留存**:Idle 类天然契合浏览器场景,代表作 Cookie Clicker、Universal Paperclips、Melvor Idle 长盛不衰。
- **2025 热度持续**GameSpot、Bits N' Pixels 年度榜单均大量收录 idle/clicker 游戏。
- **典型玩法**:资源自动累积 + 主动操作 + 解锁/重置(prestige)循环。
- **优势**:开发成本可控、留存曲线平缓、适合持续迭代。
### 1.4 玩家留存关键洞察
- **核心循环(core loop**是留存的根基;D1/D7/D30 是关键基准。
- **渐进式功能发现**:让玩家逐步解锁新机制,避免一次性信息过载。
- **社交连接 + 流畅运营**:长期留存的两大支柱。
- **动机分层**:成就型、探索型、社交型玩家需要差异化设计。
---
## 二、竞品分析与差异化机会
| 维度 | 主流放置游戏 | 主流叙事游戏 | 主流肉鸽 | 《回响星核》定位 |
|---|---|---|---|---|
| 核心 | 数值增长 | 剧情推进 | 战斗随机 | **解码谜题 + 放置 + 探险** |
| 叙事 | 弱/无 | 强但线性 | 弱 | **碎片化叙事,由玩法涌现** |
| 美术 | 简笔画/像素 | 插画 | 像素 | **全息晶体 + 深空粒子美学** |
| 留存钩子 | prestige | 章节 | 随机性 | **「接触」终局 + 多周目** |
| 单局 | 长 | 中 | 短 | **可长可短(放置 + 主动)** |
**差异化结论**
1. 没有任何一款主流游戏同时是「深空考古 + 记忆解码谜题 + 放置 + 肉鸽探险 + 碎片叙事」。
2. 叙事由解码行为**涌现**而非过场灌输,符合 2025 叙事品类崛起趋势。
3. 放置核心保证留存,解码谜题提供主动乐趣,探险提供随机性重玩价值。
---
## 三、技术选型调研结论
- **框架**Next.js 16App Router+ TypeScript —— 本项目既有栈,SSR/路由/API 一体化,适合 web 游戏。
- **渲染**2D 场景用 **HTML5 Canvas**(星空/粒子/晶体动画),UI 用 **React + shadcn/ui**。3D 暂不需要,控制复杂度。
- **状态**Zustand(客户端)+ localStorage 持久化;后续可接 API 同步存档。
- **后端**Next.js API Routes + Prisma(SQLite),承载存档同步、全局统计、(后续)排行榜。
- **实时**:socket.io 小服务,用于全局「星潮」事件(后续阶段)。
**结论**:在既有 Next.js 栈内即可实现,无需引入重框架,性能与可维护性平衡良好。
---
## 四、设计原则(源自调研)
1. **原创优先**:不做任何现有游戏的 1:1 复刻,机制组合与世界观均自创。
2. **核心循环扎实**:放置产出 → 解码消耗 → 解锁成长 → 探险扩张 → 终局「接触」。
3. **叙事涌现**:故事从解码碎片中拼出,不打断玩法。
4. **渐进披露**:功能随进度解锁,符合留存最佳实践。
5. **视觉差异化**:全息晶体 + 深空粒子,第一眼即与同类区分。
6. **可持续迭代**:模块化架构,便于后续通过工单需求持续扩展。
---
## 五、数据来源清单(真实性/时效性核验)
1. Business Research Insights — Browser Games Market Report
2. Research and Markets — Browser Games Market 2026
3. Dataintelo — Browser Game Market Research 2034
4. Metastat Insights — HTML5 Games Market 2030
5. LinkedIn (行业分析) — Casual HTML5 Games 2024-2033
6. HowToMarketAGame — 2025 Steam 品类年度报告
7. SuperJump — State of Indie Games 2025
8. GameSpot / Bits N' Pixels — 2025 Idle 游戏榜单
9. GameDesignSkills / GameDeveloper — 留存策略 17 条
10. Reddit r/incremental_games、r/gamedev 社区共识
所有数据均为 2024-2026 年发布,时效性满足设计决策需要。
+141
View File
@@ -0,0 +1,141 @@
# 《回响星核 / Echo Nexus》游戏设计文档 (GDD)
> 版本:v0.1(首发概念版)
> 作者:Super_Z
> 状态:设计已定稿,进入实现阶段
---
## 一、一句话概念
**你是一名深空考古指挥官,驾驶自治勘探舰回收已「飞升」的以太文明遗留的记忆晶体,解码碎片拼凑出他们的故事,并最终跨越维度发起「接触」。**
## 二、游戏定位
- **品类**:深空考古放置策略 + 记忆解码谜题 + 肉鸽探险 + 涌现叙事
- **平台**:现代浏览器(桌面优先,移动适配)
- **单局时长**:可长可短(放置 30 秒也有产出;主动游玩 10-30 分钟一轮探险)
- **目标留存**D1 > 40%D7 > 15%idle 品类基准之上)
- **差异化**:业界首个「解码谜题驱动叙事涌现」的放置游戏
## 三、世界观
数万年前,银河系存在一个高度发达的文明——**以太族(Aetherians)**。他们没有灭亡,而是集体「飞升」到了更高维度。临走前,他们将文明的知识与记忆封存在遍布星系的**记忆晶体(Memory Crystals)**中,作为留给后来者的「面包屑」——只待某个文明能解读足够多的晶体,便有资格发起「接触」。
玩家扮演人类深空考古局的指挥官,舰载 AI「回响(Echo)」辅助你勘探遗迹、回收晶体、解码记忆。
## 四、核心循环
```
┌─────────────────────────────────────────────┐
▼ │
[自治无人机采矿] ──产出──> [记忆晶体库存] │
│ │
[解码谜题] <── 主动操作 │
│ │
┌──────────────┼──────────────┐ │
▼ ▼ ▼ │
[记忆碎片] [技术洞见] [剧情片段] │
(叙事资源) (解锁升级) (拼凑故事) │
│ │ │ │
└──────────────┴──────────────┘ │
│ │
[发起遗迹探险] ─肉鸽─> 新晶体类型
│ │
[「接触」进度条 ↑] │
│ │
────── 达成 100% ───── 终局 / 新周目
```
**核心循环一句话**:采矿→解码→成长→探险→填满接触条→新周目。
## 五、核心系统
### 5.1 记忆晶体采集(Idle 核心)
- 自治无人机以 **晶体/秒** 速率自动产出记忆晶体。
- 玩家可主动「脉冲扫描」(点击/按键)获得瞬时增量 + 连击加成。
- 离线时按 50% 效率持续产出(最多累积 8 小时)。
### 5.2 解码系统(主动玩法核心 ⭐)
每颗晶体需经「解码」才能释放其中内容。解码是一个**原创的图案共振谜题**:
- 屏幕呈现一个由发光节点构成的**共振阵列**(类似星图)。
- 玩家需在限定步数内,按特定**共振顺序**点亮节点,使能量流闭合形成回路。
- 成功 → 释放记忆碎片 + 技术洞见 + 剧情片段。
- 不同晶体等级 / 类型 → 阵列规模与共振规则不同(渐进难度)。
- **设计意图**:把「解码」从被动等待变成有节奏的主动小谜题,避免放置游戏「只挂机不动手」的枯燥。
### 5.3 技术树(成长系统)
四个分支,由「技术洞见」解锁:
- **采矿**:提升晶体/秒、仓库上限、离线效率。
- **解码**:降低解码步数消耗、解锁阵列提示、自动解码低级晶体。
- **探险**:提升探险成功率、降低风险、增加稀有晶体掉落。
- **叙事**:解锁更深记忆层、揭示隐藏剧情、提升「接触」进度转化率。
### 5.4 遗迹探险(Roguelike 层)
- 消耗「能量」向随机生成的遗迹节点图派出探险队。
- 每个节点有事件(战斗/解谜/宝藏/抉择),路径分支。
- 失败损失探险队,成功带回稀有晶体 + 大量洞见 + 独有剧情。
- 单次探险 10-30 分钟,提供短期目标与随机性重玩价值。
### 5.5 碎片叙事(叙事层)
- 解码与探险产出的「记忆碎片」自动归档到**记忆图谱(Memory Codex**。
- 图谱以星图形式呈现,碎片拼合后揭示以太族编年史(5 个纪元)。
- 关键碎片触发**「回响回放」**——一段沉浸式全息短叙(文字 + 视觉),但**绝不打断玩法**(可随时关闭)。
### 5.6 「接触」终局与多周目
- 全部行为累积「接触进度」。
- 达 100% → 触发终局「接触」事件(戏剧性叙事高潮)。
- 之后可选择**飞升(Prestige)**进入新周目:保留叙事图谱与技术蓝图的一部分,重置数值,开启更高难度与隐藏内容。
## 六、美术与音频方向
- **视觉**:深空黑紫底色 + 全息青/品虹光晶体 + 流动星尘粒子。整体「冷峻神秘 + 科技仪式感」。
- **字体**:标题用未来感无衬线,正文清晰可读。
- **动效**:晶体生长、解码共振波纹、粒子涌动、飞升白光。
- **音频**:环境氛围 pad + 解码成功清脆音 + 关键叙事轻旋律(后续阶段)。
## 七、UI 信息架构
```
顶栏:晶体库存 | 接触进度 | 能量 | 设置
左侧:主视图(Canvas 星空 + 晶体集群 + 解码阵列)
右侧:标签面板
- 采矿 (Mining)
- 解码 (Decode) ⭐默认
- 技术 (Tech)
- 探险 (Expedition)
- 记忆图谱 (Codex)
底栏:当前目标提示 | 在线/离线收益 | 版本号
```
## 八、首版(v0.1MVP 范围
为快速验证核心乐趣,首版聚焦:
- [x] 放置采矿(晶体/秒 + 主动脉冲)
- [x] 解码共振谜题(3 种阵列规模)
- [x] 技术树 4 分支 × 3 级 = 12 个升级
- [x] 记忆图谱(首纪元 8 个碎片)
- [x] 接触进度 + 飞升(Prestige
- [x] localStorage 存档 + 离线收益
- [x] 精美 Canvas 星空背景 + 晶体动画
- [x] 响应式布局(桌面/移动)
**v0.1 暂不做**:遗迹探险肉鸽、socket 全局事件、音频、多语言切换、账号云存档(留待后续版本/工单驱动)。
## 九、后续路线图
- **v0.2**:遗迹探险肉鸽系统
- **v0.3**:音频系统 + 全局「星潮」socket 事件
- **v0.4**:账号云存档 + 排行榜
- **v0.5**:多周目隐藏内容 + 全 5 纪元叙事
- **持续**:根据工单需求迭代
## 十、设计风险与对策
| 风险 | 对策 |
|---|---|
| 解码谜题单调 | 多种阵列规则 + 自动解码低级晶体 |
| 放置枯燥 | 主动脉冲 + 连击 + 探险提供短期目标 |
| 叙事打断玩法 | 所有叙事可跳过,图谱自愿浏览 |
| 数值膨胀 | 飞升重置 + 指数曲线校准 |
| 移动端操作 | 解码阵列适配触控,UI 自适应 |
+109
View File
@@ -0,0 +1,109 @@
# 技术架构设计
## 一、技术栈
| 层 | 技术 | 说明 |
|---|---|---|
| 框架 | Next.js 16 (App Router) | 既有栈,SSR + API 一体 |
| 语言 | TypeScript 5 | 全量类型 |
| 样式 | Tailwind CSS 4 + shadcn/ui | 既有 UI 组件库 |
| 渲染 | HTML5 Canvas 2D | 星空/粒子/晶体/解码阵列 |
| 客户端状态 | Zustand + persist | 游戏状态 + localStorage 持久化 |
| 服务端状态 | TanStack Query(按需) | 存档同步、统计 |
| 后端 | Next.js API Routes | 存档同步、全局统计 |
| 数据库 | Prisma + SQLite | 存档、统计(v0.4+ |
| 实时 | socket.io mini-service | 全局「星潮」事件(v0.3+) |
## 二、目录结构
```
src/
app/
page.tsx # 游戏主入口(唯一用户可见路由)
layout.tsx
api/
save/route.ts # 存档同步(v0.4+
stats/route.ts # 全局统计
components/
game/ # 游戏专属组件
StarfieldCanvas.tsx # 星空背景
CrystalCluster.tsx # 晶体集群
DecodeArray.tsx # 解码共振阵列 ⭐
ResourceBar.tsx # 顶栏资源
MiningPanel.tsx
TechTree.tsx
Codex.tsx # 记忆图谱
PrestigeDialog.tsx
ui/ # shadcn/ui 既有
lib/
game/ # 游戏逻辑(纯函数,可测)
config.ts # 数值配置
types.ts # 类型
engine.ts # 核心 tick 引擎
decode.ts # 解码谜题逻辑
tech.ts # 技术树
codex.ts # 叙事数据
prestige.ts # 飞升
db.ts # Prisma
store/
gameStore.ts # Zustand 游戏状态
hooks/
useGameLoop.ts # 主循环
use-toast.ts # 既有
```
## 三、状态模型(Zustand
```ts
interface GameState {
// 资源
crystals: number; // 记忆晶体(主货币)
insights: number; // 技术洞见
fragments: Record<FragmentId, boolean>; // 已获碎片
contact: number; // 接触进度 0-100
// 产能
crystalsPerSec: number; // 由技术树计算
pulsePower: number; // 主动脉冲产出
// 技术
tech: Record<TechId, number>; // 各技术等级
// 飞升
ascensions: number;
blueprints: string[]; // 保留的蓝图
// 解码
currentCrystal: Crystal | null;
// 元
lastTick: number;
prestige: PrestigeBonus;
}
```
## 四、核心循环引擎
- 客户端 `setInterval` 每 100ms tick 一次。
- 每次 tick`crystals += crystalsPerSec * dt`
- 离线收益:加载时计算 `now - lastTick`,按 50% 效率补发(上限 8h)。
- 状态自动持久化到 localStorageZustand persist,节流 1s)。
## 五、解码谜题实现
- 阵列 = 节点图(邻接表),每节点有 `resonance` 值。
- 共振规则:玩家依序点击节点,相邻点击须满足 `resonance` 差值 ≤ 阈值,且最终形成闭合回路。
- 步数限制:规模 3×3=9 节点 / 4×4=16 / 5×5=25,步数上限分别为 6/10/15。
- 成功 → 结算奖励 + 动画;失败 → 晶体不损耗,可重试。
## 六、Canvas 渲染策略
- 单一全屏 Canvas 作为背景层(星空 + 星尘 + 远景星云)。
- 晶体集群与解码阵列用**独立 Canvas 或 SVG**叠加,便于交互。
- requestAnimationFrame 驱动,设备像素比适配,低性能设备降帧。
## 七、可扩展性
- 数值配置集中在 `config.ts`,便于平衡调整与工单需求响应。
- 叙事数据集中在 `codex.ts`,新增碎片只需追加数据。
- 技术树/解码规则/探险地图均数据驱动,支持后续模块化扩展。
+57
View File
@@ -0,0 +1,57 @@
# 解码系统修复说明 (v0.1.1)
## 问题
用户反馈「解码大量的无法通过」。经分析,根因是 `generatePuzzle` **没有保证生成的局面有解**:随机填色后可能根本不存在一条满足「按目标顺序、四邻接、不重复」的路径,导致大量死局。
## 修复方案
### 1. 路径构造法生成器(核心修复)
重写 `generatePuzzle`,采用**路径构造法**
1. 先随机生成目标序列 target。
2. 在网格上**主动构造一条合法路径**:随机起点,每步走向一个未使用的四邻接节点,长度 = target.length。
3. 把路径上第 i 个节点颜色**强制设为 target[i]**。
4. 其余非路径节点随机填色(60% 偏向目标色,提升多解性)。
这样**至少存在一条解**(即构造的路径),玩家不会遇到「怎么都过不了」的死局。
### 2. 精确可解性检测 `isSolvable`
新增 DFS 函数,从当前路径末端出发,精确判断是否存在一条按 target 剩余顺序、四邻接、不重复的完成路径。用于:
- 玩家点击后检测「此路不通」,提示撤销。
- `isStuck` 复用此判定。
### 3. 撤销功能 `undoStep`
新增 store action,支持单步回退(恢复节点 used 状态)。走错时无需整个重排。
### 4. 卡死提示横幅
当局面不可解时,顶部显示「谐振受阻,此路已无解」横幅 + 快捷撤销按钮。
### 5. 可行起点高亮(体验增强)
选起点阶段,用 `canStartFrom` 检测每个目标色节点「选了之后是否仍可解」:
- **可行起点**:强光圈 + 2px 边框 + 快脉冲,引导玩家。
- **死起点**:淡化(opacity 0.5),避免玩家踩坑。
### 6. 旧存档兼容
`init` 时检测存档中的活跃谜题,若不可解(旧算法生成)则自动放回晶体队列,避免老玩家卡死。
## 验证结果
### 算法验证(1000 次/档位)
- Tier 1: 1000/1000 可解 (100.0%)
- Tier 2: 1000/1000 可解 (100.0%)
- Tier 3: 1000/1000 可解 (100.0%)
- 总计: 3000/3000 (100.00%)
### 模拟游玩(带可解性筛选的贪心玩家)
- 500 局: 100% 解出,平均撤销 0 次
### agent-browser 端到端验证
- T1 (3×3): 通过 ✓
- T2 (4×4): 通过 ✓
- 死路检测: 触发「谐振受阻」横幅 ✓
- 撤销: 单步回退正常 ✓
- 可行起点高亮: 准确标记(4 个玫色节点中仅 1 个为可行起点,与手算一致)✓
## 影响文件
- `src/lib/game/decode.ts` — 重写生成器 + 新增 isSolvable/canStartFrom
- `src/store/gameStore.ts` — clickNode 可解性检测 + undoStep + init 兼容
- `src/components/game/DecodeArray.tsx` — 撤销按钮 + 卡死横幅 + 可行起点高亮
+107
View File
@@ -0,0 +1,107 @@
# v0.2 遗迹探险肉鸽系统
> 版本:v0.2.0
> 日期:2025-06-23
> 作者:Super_Z
## 概述
实现路线图中的 v0.2 旗舰功能——**遗迹探险肉鸽系统**。玩家消耗能量派出探险队,进入程序化生成的遗迹节点路径,沿途遭遇战斗、宝藏、抉择、解谜与休整事件,击败终点 BOSS 获取丰厚奖励。
## 新增系统
### 1. 能量系统
- 新增「探险能量」资源,初始 3 点,上限 5 点。
- 每 45 秒自动恢复 1 点能量(tick 驱动,离线也恢复)。
- 每次探险消耗 1 点能量。
### 2. 探险力与生命
- **探险力**:由基础值 10 + 探险分支技术加成 + 飞升蓝图加成 + 飞升周目加成计算。
- **探险生命**:基础 100 + 技术/飞升加成。归零则探险失败结束。
- **战斗胜率**`clamp(power / (power + difficulty × 8), 0.25, 0.95)`
### 3. 节点路径
- 每次探险生成 5-7 个节点的线性路径。
- 末位固定为 BOSS 节点。
- 首节点强制为友好类型(宝藏/休整),保证开局体验。
- 6 种节点类型:
| 类型 | 图标 | 颜色 | 机制 |
|---|---|---|---|
| 战斗 combat | 剑 | 玫红 | 按胜率决定胜负,胜获奖、败扣血或全灭 |
| 宝藏 treasure | 宝石 | 琥珀 | 直接获得晶体+洞见 |
| 抉择 choice | 问号 | 青 | 55% 好结果(奖励)/45% 坏结果(扣血) |
| 解谜 puzzle | 拼图 | 紫 | 探险力越高越可能解出,成功获大奖 |
| 休整 rest | 心 | 翠 | 恢复生命 |
| BOSS boss | 骷髅 | 红 | 必定战斗,高难度,胜则大量奖励+探险胜利 |
### 4. 奖励机制
- **实时入账**:每个节点结算后奖励立即加入玩家资源。
- **晶体绕过仓库上限**:探险奖励晶体不受 idle 产能仓库上限限制(仅闲置产能受限)。
- **失败保留**:探险失败(HP 归零)仍保留已获得的全部奖励。
- **BOSS 胜利**:额外给予大量晶体、洞见、接触进度。
- **撤退**:玩家可随时主动撤退,保留已获奖励。
### 5. 探险日志
- 记录最近 30 条探险事件,含节点类型、结果描述、奖励明细。
- 飞升后保留日志。
## UI 设计
### 探险标签页(主页面第 4 个标签)
- **入口态**(无活跃探险):
- 能量条(含恢复倒计时)
- 出发按钮(消耗 1 能量)
- 探险说明文案
- 最近探险记录(最近 4 条)
- **进行态**(有活跃探险):
- 探险力 + 当前节点位置
- 生命条(HP/maxHP<30% 变红)
- 节点路径图(横向,已通过/当前/未知,含类型图标与颜色)
- 路径进度条
- 当前节点事件卡(类型标签、难度、胜率、标题、描述、结算结果)
- 操作按钮(探索节点 / 前进 / 撤退)
- 本次累计奖励
### 视觉细节
- 每种节点类型有专属颜色与图标
- 当前节点放大 1.25 倍 + 光晕
- 已通过节点显示绿色对勾
- 事件卡使用对应节点类型的渐变背景与边框光晕
- 能量条渐变(琥珀→黄)
- 生命条渐变(玫红→翠绿)
## 配套修复与增强
### 仓库满仓警告
- 晶体达上限 98% 时,底部状态栏变琥珀色 + 脉冲动画 + 警告文案。
- 引导玩家解码或升级仓库。
### 闲置产能上限优化
- 仓库满时不再自动产出晶体(而非产出后截断)。
- 探险奖励晶体可超出上限,不被浪费。
### 技术树探险分支增强
- 远征推进器:探险力 +3
- 遗迹图谱:探险力 +5,探险生命 +20
- 维度信标:探险力 +8,接触进度 +50%
## 验证结果
- ✅ 探险出发/节点结算/前进/BOSS/撤退全流程通过
- ✅ HP clamp 修复(休整不超上限)
- ✅ 探险奖励绕过仓库上限(101 晶体 > 50 cap
- ✅ 探险失败保留奖励
- ✅ 能量恢复计时
- ✅ 探险日志记录
- ✅ 探险标签红点提示(有能量/有进行中探险)
## 影响文件
- `src/lib/game/types.ts` — 新增探险类型定义
- `src/lib/game/expedition.ts`**新建**,探险逻辑(生成/结算/能量恢复)
- `src/lib/game/config.ts` — 初始状态增加探险字段,技术树描述更新
- `src/lib/game/engine.ts` — createInitialState/performPrestige 包含探险字段
- `src/store/gameStore.ts` — 探险 actions + 能量恢复 tick + HP clamp + 产能上限优化
- `src/components/game/ExpeditionPanel.tsx`**新建**,探险 UI
- `src/app/page.tsx` — 新增探险标签页 + 仓库满仓警告 + 能量红点
+161
View File
@@ -0,0 +1,161 @@
# 回响星核 v0.2.1 — 音频系统 + 成就系统 + 视觉打磨
> 版本:v0.2.1
> 日期:2026-06-23
> 范围:程序化音频引擎、14 项成就系统、脉冲粒子爆发、UI 打磨
---
## 一、程序化音频系统(Web Audio API,零资源文件)
### 设计目标
- 无需任何外部音频文件,全部由振荡器 + 噪声 + 包络合成
- 音色契合"深空考古 / 全息晶体"美学:谐振、水晶、低频脉冲
- 受 `soundOn` 开关控制,支持浏览器自动播放策略(首次用户交互后初始化 AudioContext
### 实现
- **文件**`src/lib/game/audio.ts`AudioEngine 单例 + 15 种音效)
- **Hook**`src/hooks/useAudio.ts``useAudioSync` 同步开关,`sfx()` 便捷播放)
### 音效清单
| 音效名 | 触发场景 | 合成方式 |
|--------|---------|---------|
| pulse | 主动脉冲 | 上升正弦短音 + 三角波泛音 |
| pulseCombo | 连击≥3 脉冲 | 更高基频 + 多泛音 |
| decodeClick | 解码点击节点 | 按颜色变调(C5/D5/E5/F5+ 泛音 |
| decodeFail | 解码点击错误 | 低频锯齿 + 噪声脉冲 |
| decodeSuccess | 解码成功 | 大三和弦琶音上扬 |
| fragmentUnlock | 记忆碎片浮现 | 空灵长音 + 扫频 |
| techBuy | 购买技术 | 双音确认 |
| expeditionStart | 探险出发 | 低频锯齿扫频 + 噪声(引擎启动感) |
| expeditionNode | 探险节点结算 | 三角波中频 |
| expeditionVictory | 探险胜利 | 上行凯旋和弦 |
| expeditionDefeat | 探险失败 | 下行低音 |
| prestige | 飞升 | 宏大扫频 + 和弦堆叠 |
| achievement | 成就解锁 | 亮丽四音琶音 |
| uiHover | 界面悬停 | 极轻正弦 |
| uiClick | 界面点击 | 轻确认 |
### 接入点
- `CrystalOrb`pulse / pulseCombo
- `DecodeArray`decodeClick(按颜色变调)/ decodeFail / decodeSuccess / fragmentUnlock
- `ExpeditionPanel`expeditionStart / expeditionNode / expeditionVictory / expeditionDefeat
- `TechTree`techBuy
- `PrestigeDialog`prestige
- `AchievementNotifier`achievement
- `SettingsDialog`:新增「试听」按钮(播放 decodeSuccess
---
## 二、成就系统(14 项,跨周目永久加成)
### 设计目标
- 给玩家长期目标与即时正反馈
- 成就奖励包含即时资源 + 永久产能/洞见百分比加成(跨周目保留,与蓝图叠加)
- 解锁时 Toast 通知 + 专属音效
### 实现
- **定义**`src/lib/game/achievements.ts`14 项成就 + `achievementBonuses` 聚合)
- **状态**`GameState.achievements: Record<string, boolean>`
- **检测**`gameStore.checkAchievements()` 每秒由主循环调用;页面可见性恢复时立即检测
- **通知**`AchievementNotifier` 组件消费 `_achievementQueue`,弹 Toast + 播音效
- **UI**`AchievementsPanel` 组件,作为第 4 个标签页
### 成就列表
| ID | 名称 | 条件 | 奖励 |
|----|------|------|------|
| ach_first_pulse | 初次触碰 | 发起第一次脉冲 | +5 晶体 |
| ach_first_decode | 谐振初鸣 | 解码第一颗晶体 | +3 洞见 |
| ach_decoded_10 | 回响解码者 | 累计解码 10 颗 | +15 洞见 · 产能 +5% |
| ach_decoded_25 | 记忆织匠 | 累计解码 25 颗 | +40 洞见 · 产能 +8% |
| ach_decoded_50 | 星核解密师 | 累计解码 50 颗 | +100 洞见 · 产能 +12% · 洞见 +10% |
| ach_tech_3 | 初窥门径 | 解锁 3 项技术 | +20 洞见 |
| ach_tech_all | 全谱精通 | 解锁全部 12 项技术 | 产能 +15% · 洞见 +15% |
| ach_frag_4 | 残篇拾遗 | 拼凑 4 段碎片 | +10 接触 · +25 洞见 |
| ach_frag_all | 回响全谱 | 拼凑全部 8 段碎片 | +25 接触 · 产能 +10% |
| ach_exp_1 | 初探遗迹 | 完成 1 次探险 | +10 洞见 |
| ach_exp_5 | 遗迹猎手 | 出发 5 次探险 | +30 洞见 · 产能 +5% |
| ach_prestige_1 | 初次接触 | 飞升 1 次 | 产能 +10% · 洞见 +10% |
| ach_prestige_3 | 维度行者 | 飞升 3 次 | 产能 +20% · 洞见 +20% |
| ach_warehouse | 满仓时刻 | 晶体达仓库上限 | +8 洞见 |
### 加成计算
`recomputeStats()` 现聚合三层数值来源:
1. 技术树节点效果
2. 飞升蓝图加成(每张 +5% 产能 / +3% 洞见 / +4% 接触)
3. **成就永久加成**(跨周目保留,飞升后仍生效)
### 旧存档兼容
- `init()` 补全缺失的 `achievements` 字段为 `{}`
- `partialize` 排除 `_achievementQueue`(运行时临时队列不持久化)
---
## 三、视觉打磨
### CrystalOrb 增强
- **粒子爆发**:每次脉冲生成 6–12 个粒子,沿径向发散(连击越高粒子越多)
- **脉冲扩散环**:点击时从晶体向外扩散的绿色光环
- **浮动数字变色**:连击≥3 紫色、≥5 琥珀色,带对应颜色光晕
- 新增 `echo-ring` / `echo-burst` 关键帧动画
### 标签栏
- 从 4 列扩展为 5 列(新增「成就」)
- 成就标签有未解锁时显示琥珀色脉冲点
- 标签字号微调以适配 5 列
### StatsPanel
- 新增「已解锁成就」「累计探险」「探险能量」三行
---
## 四、QA 验证
### agent-browser 自动化测试
- ✅ 页面加载 HTTP 200,无运行时错误
- ✅ 解码谜题:生成 → 求解(按目标色序点击相邻同色节点)→ 奖励入账(绕过仓库上限)
- ✅ 探险系统:出发 → 探索节点 → 获得奖励 → 前进 → 日志记录
- ✅ 成就面板:14 项渲染,3 项已解锁(初次触碰/谐振初鸣/满仓时刻),永久加成区域正确显示
- ✅ 统计面板:成就计数 3/14,探险次数,能量条
- ✅ 设置面板:音效试听按钮可点击,开关可用
### VLM 视觉验证
- 成就面板:深色背景 + 科技光效 + 锁/解锁状态分明,色彩对比鲜明
- 晶体集群:渐变晶核 + 旋转环 + 粒子爆发正常
### Lint
- `bun run lint` 零错误
---
## 五、文件变更清单
**新增**
- `src/lib/game/audio.ts` — 程序化音频引擎
- `src/hooks/useAudio.ts` — 音频 React 适配
- `src/lib/game/achievements.ts` — 成就定义与聚合
- `src/components/game/AchievementsPanel.tsx` — 成就面板
- `src/components/game/AchievementNotifier.tsx` — 成就 Toast 消费者
**修改**
- `src/lib/game/types.ts` — GameState 新增 `achievements` 字段
- `src/lib/game/config.ts` — INITIAL_STATE 新增 `achievements: {}`
- `src/lib/game/engine.ts` — recomputeStats 聚合成就加成;performPrestige/createInitialState 保留成就
- `src/store/gameStore.ts` — checkAchievements / consumeAchievementQueueinit/hardReset/partialize 兼容
- `src/hooks/useGameLoop.ts` — 每秒检测成就 + 可见性恢复检测
- `src/app/page.tsx` — 5 列标签 + 成就页 + AchievementNotifier + useAudioSync
- `src/components/game/CrystalOrb.tsx` — 粒子爆发 + 扩散环 + 浮动数字变色 + pulse 音效
- `src/components/game/DecodeArray.tsx` — decodeClick/fail/success/fragment 音效
- `src/components/game/ExpeditionPanel.tsx` — start/node/victory/defeat 音效
- `src/components/game/TechTree.tsx` — techBuy 音效
- `src/components/game/PrestigeDialog.tsx` — prestige 音效
- `src/components/game/SettingsDialog.tsx` — 音效试听按钮 + 文案更新
---
## 六、下一步规划
- **v0.3 剩余**:socket 全局「星潮」实时事件(需 mini-service
- **v0.4**:云存档 + 排行榜
- **v0.5**:全 5 纪元叙事扩展
- 持续关注 Gitea 工单
+113
View File
@@ -0,0 +1,113 @@
# 回响星核 v0.3 — 星潮事件系统(周期性全局玩法修饰)
> 版本:v0.3
> 日期:2026-06-23
> 范围:6 种星潮事件、周期触发、全屏视觉叠层、程序化音效、寂静期补偿机制
---
## 一、设计动机
回应 Gitea Issue #1「就一直连连看?」的玩法单一反馈。星潮事件为放置循环注入**动态变化**:深空中偶发的「星潮」会短暂改变物理法则,让玩家在 75 秒内体验产能翻倍、解码加成、探险增强等不同玩法侧重,打破单调感。
## 二、6 种星潮事件
| 类型 | 名称 | 效果 | 色调 | 性质 |
|------|------|------|------|------|
| crystal | 晶体潮 | 采矿产能 ×2 | 翠绿 | 正面 |
| resonance | 谐振风暴 | 解码奖励 ×1.6 | 玫红 | 正面 |
| ruins | 遗迹共振 | 探险力 +5、生命 +30 | 琥珀 | 正面 |
| void | 虚空低语 | 洞见获取 ×2 | 紫红 | 正面 |
| core | 星核悸动 | 接触率 ×3、脉冲威力 ×2 | 青色 | 正面 |
| silence | 寂静期 | 产能 ×0.5;结束时按时长补偿洞见 | 灰蓝 | 负面(有补偿) |
## 三、触发机制
- **首次星潮**:游戏开始 30 秒后触发(`TIDE_CONFIG.firstDelay`
- **常规间隙**:星潮结束后 40 秒触发下一次(`TIDE_CONFIG.gap`
- **持续时间**75 秒(`TIDE_CONFIG.duration`
- **权重抽取**:6 种事件按权重随机(正面事件权重高,寂静期权重最低 10%)
- **飞升重置**:飞升后清空当前星潮,重置计时,30 秒后重新开始
## 四、修饰器应用层
| 修饰器 | 应用点 |
|--------|--------|
| crystalsPerSecMult | `tick()` 即时乘(显示基础值,实际翻倍积累) |
| pulsePowerMult | `pulse()` 即时乘 |
| decodeRewardMult | `clickNode()` / `autoDecodeTick()` 结算时乘 |
| insightMultAdd | 进 `recomputeStats` 缓存(星潮起止时重算) |
| contactRateMult | 进 `recomputeStats` 缓存 |
| expeditionPowerBonus / hpBonus | `startExpedition()` 即时加 |
| silenceInsightPerSec | 星潮结束时 `computeSilenceCompensation` 结算 |
## 五、UI 表现
### 顶部指示器(`StarTideIndicator`
- 活跃时在 header 右侧显示彩色芯片:图标 + 名称 + 倒计时秒数
- 芯片背景为对应色半透明,带光晕,进度条随时间推进
### 全屏背景叠层(`StarTideOverlay`
- 顶部 + 底部对应色渐变色带
- 边缘 `inset box-shadow` 呼吸光效(3 秒周期)
- 活跃时整页笼罩对应色调氛围
### 通知(`StarTideNotifier`
- 星潮降临时 Toast + `tideStart` 音效(神秘扫频)
- 星潮结束时 Toast + `tideEnd` 音效(柔和消退)
- 寂静期结束时显示「虚空回赠 N 洞见」+ `techBuy` 音效
### Footer 联动
- 活跃时 footer 提示文字变为「✺ 星潮「晶体潮」进行中 · 深空晶体涌动…」
- footer 边框点亮对应色光晕
## 六、程序化音效
新增 2 种音效(`audio.ts`):
- `tideStart`330→660Hz 正弦扫频 + 440→880Hz 三角波 + 523/784Hz 长音(神秘降临感)
- `tideEnd`659→523→392Hz 下行三音(柔和消退)
## 七、状态与兼容
- `GameState` 新增 `activeTide: StarTide | null``lastTideEnd: number`
- 旧存档兼容:`init()` 补全字段为 `null/0`
- `partialize` 排除运行时队列 `_tideEvents`
- `performPrestige` 重置星潮(清空 activeTidelastTideEnd 设为 now
## 八、QA 验证
### agent-browser 自动化
- ✅ 强制触发星潮:`lastTideEnd=0` → 重载 → 30s 内自动触发,状态显示 `activeTide: crystal`
- ✅ 顶部指示器渲染:「✺ 晶体潮 64s」绿色芯片
- ✅ 背景叠层:VLM 确认绿色调背景光晕 + 顶部/底部色带 + 边缘呼吸光
- ✅ 寂静期补偿:设置 `type=silence, endsAt=now+3s` → 等待结束 → insights 从 50 增至 81(补偿 30
- ✅ Footer 联动:活跃时显示「✺ 星潮「晶体潮」进行中 · 深空晶体涌动…」
- ✅ 版本号 v0.3 显示
- ✅ lint 零错误,HTTP 200,无运行时错误
### VLM 视觉确认
- 指示器芯片:绿色文字「晶体潮 11s」
- 背景光晕:深绿色,顶部/底部色带 + 边缘呼吸光
- 视觉完成度:界面清晰、风格统一、细节良好
## 九、文件变更清单
**新增**
- `src/lib/game/starTide.ts` — 星潮定义、修饰器、触发逻辑
- `src/components/game/StarTide.tsx` — 指示器 + 叠层 + 通知三组件
**修改**
- `src/lib/game/types.ts` — GameState 新增 `activeTide` / `lastTideEnd`
- `src/lib/game/config.ts` — INITIAL_STATE 新增潮汐字段
- `src/lib/game/engine.ts``recomputeStats` 应用 insightMultAdd/contactRateMult`performPrestige`/`createInitialState` 处理潮汐
- `src/lib/game/audio.ts` — 新增 `tideStart` / `tideEnd` 音效
- `src/store/gameStore.ts``tickTide` / `consumeTideEvents`tick/pulse/clickNode/autoDecodeTick/startExpedition 应用修饰器;init/hardReset/partialize 兼容
- `src/hooks/useGameLoop.ts` — 主循环调用 `tickTide`,可见性恢复补检测
- `src/app/page.tsx` — 接入 StarTideOverlay/Indicator/NotifierFooter 联动;版本号 v0.3
## 十、下一步规划
- v0.3 已完成客户端星潮(原计划 socket 全局事件,现以单机版先行,后续可扩展为多人同步)
- v0.4:云存档 + 排行榜
- v0.5:全 5 纪元叙事扩展
- 持续关注 Gitea 工单
+171
View File
@@ -0,0 +1,171 @@
# v0.3.1 · 星图天文台(Constellation Observatory)元进程天赋系统
> 第二次回应 Issue #1「就一直连连看?」——为飞升循环加入 Slay the Spire 式 3 选 1 天赋 draft
> 跨周目永久生效,配合 Canvas 动态星图可视化,提供长期成长目标与策略选择。
## 一、设计动机
v0.3 星潮事件已为放置循环注入动态变化,但玩家仍缺长期 meta-progression 目标。本系统在飞升后触发:
- **策略选择**:3 选 1 的 draft 让每次飞升都有差异化构建方向
- **永久成长**:天赋跨周目保留,配合蓝图/成就形成三层加成
- **视觉反馈**:Canvas 动态星图,每解锁一个天赋就在对应星座点亮一颗星
- **目标驱动**:18 个天赋 + 2 个专属成就,给玩家清晰的中长期目标
## 二、系统架构
### 2.1 数据层(`src/lib/game/constellation.ts`
```typescript
// 6 大星座 × 3 颗星 = 18 个天赋
type ConstellationCategory =
| "mining" | "decoding" | "expedition"
| "contact" | "economy" | "cosmic";
interface ConstellationPerk {
id: string; // c_min_1, c_dec_2, ...
category: ConstellationCategory;
name: string;
desc: string;
order: 1 | 2 | 3; // 星座内位置
}
```
### 2.2 18 个天赋一览
| 类别 | #1 | #2 | #3 |
|------|-----|-----|-----|
| 翠·永动矿脉 | 永动钻头(产能+20%) | 深井网络(上限+50%) | 谐振熔炉(脉冲+50%) |
| 玫·光谱矩阵 | 光谱记忆(洞见+15%) | 步幅延展(步数+1) | 自动校准(自解-3s) |
| 琥·远征星图 | 维生护盾(生命+25%) | 信标矩阵(探险力+20%) | 能量共振(能量上限+1) |
| 紫·接触回响 | 接触共鸣(接触率+20%) | 飞升加速(门槛-10) | 蓝图回响(每蓝图+2%全属性) |
| 灰·虚空市场 | 离线缓存(离线+15%) | 晶体富集(T2/T3+8% | 星潮引导(间隙-10s |
| 金·宇宙回响 | 飞升礼包(+30晶体/+5洞见) | 二周目经验(解码+10%) | 全息共振(全产能+8%) |
### 2.3 修饰器聚合
`constellationBonuses(perks: string[]): ConstellationModifiers` 返回 **19 项修饰器**
`recomputeStats()` / `tickTide()` / `tick()` / `clickNode()` / `autoDecodeTick()` / `computeExpeditionPower()` / `computeExpeditionHp()` 等所有相关用点处应用。
### 2.4 状态扩展
```typescript
// types.ts
interface GameState {
// ... 既有字段
constellation: string[]; // 已解锁天赋 ID
pendingPerkChoices: string[] | null; // 飞升后待选 3 个
}
// config.ts INITIAL_STATE
constellation: [],
pendingPerkChoices: null,
```
### 2.5 飞升流程
```
performPrestige(state)
├─ 计算 newBp(蓝图数)
├─ rollPerkChoices(constellation) → 3 个不同类别的可选天赋
├─ 应用「飞升礼包」补偿(+30 晶体 / +5 洞见,若已解锁)
├─ 重置资源/技术/接触进度
└─ 保留:fragments / totalDecoded / blueprints / achievements / constellation
```
飞升后 `pendingPerkChoices` 不为空 → `ConstellationDialog` 自动弹出 → 玩家选 1 → `chooseConstellationPerk(perkId)` 写入 constellation 并清空 pending。
## 三、UI 组件
### 3.1 ConstellationPanel(星图标签内容)
**Canvas 动态渲染**requestAnimationFrame 60fps):
- 六边形布局 6 个星座中心点
- 每星座内 3 颗星(三角形排布:order 1 顶部,2/3 底部)
- **未解锁星**:暗灰小点 + 弱光晕
- **已解锁星**:类别色填充 + 强光晕 + 4 道十字光线 + 闪烁动画
- **同星座已解锁星之间**:连接线(类别色 60% 透明度)
- **星座 → 中心星核**:虚线(已解锁 ≥1 时显示,呼吸效果)
- **中心星核**:紫红色径向渐变,半径随时间正弦呼吸
- **Hover**:星点 14px 范围内显示 tooltip(天赋名/描述/类别/序号)
**底部信息区**
- 6 类别图例条(彩色圆点 + 名称 + 已解锁/总数)
- 已解锁天赋列表(彩色边框 + 名称 + 描述 + 类别标签)
- 空状态:虚线边框卡片 + 引导文案
### 3.2 ConstellationDialog(飞升后 draft 模态)
- 自动 open`pendingPerkChoices` 非空时
- 3 张卡片:左侧类别色条 + 圆形首字图标 + 天赋名 + 类别徽章 + 描述
- 选中动画:8 个粒子 ping + 卡片缩放 + 类别色发光
- 「重新抽取」按钮:rerollPerkChoices(依然保证 3 不同类别)
- 底部说明:「选择后永久保留,跨周目生效」
### 3.3 顶部「觉醒」按钮
`pendingPerkChoices` 存在时显示,fuchsia 边框 + animate-pulse,点击打开 ConstellationDialog。
## 四、平衡设计
### 4.1 单天赋强度
- 每个天赋加成约 +15%~+25% 单项,避免数值膨胀
- 「飞升礼包」给前期起飞资源(30 晶体 + 5 洞见)
- 「全息共振」给全局 +8%,作为终极通用天赋
### 4.2 跨周目叠加
- 18 个天赋全解锁 ≈ 总加成 200%~300%(与 6 蓝图、14 成就叠加后约 5x~8x)
- 配合飞升次数加成(每次 +15% 探险力、+10 生命),后期探险胜率显著提升
### 4.3 类别多样性
- `rollPerkChoices` 第一轮强制不同类别,避免 3 选全是同类
- 玩家可在 6 类别间自由构建:采矿流/解码流/探险流/速飞升流/经济流/全能流
## 五、QA 验证记录
| 测试项 | 结果 |
|--------|------|
| 星图标签页渲染 | ✅ 6 类别图例 + Canvas + 空状态 |
| 对话框自动弹出 | ✅ pendingPerkChoices 设置后立即开 |
| 3 卡片显示 | ✅ 来自 3 不同类别 |
| 点击天赋 | ✅ constellation 添加,crystalsPerSec 提升,pending 清空 |
| 重新抽取 | ✅ 3 张新卡片(依然不同类别) |
| 多天赋叠加 | ✅ c_min_1+c_con_1 → 产能 1.416 / 接触率 1.2 |
| 6 类别成就触发 | ✅ 六分星辉 Toast 弹出 |
| VLM 视觉确认 | ✅ 单星点亮(绿色永动矿脉) |
| lint | ✅ 零错误 |
| 编译 | ✅ < 200ms |
## 六、与既有系统的协同
```
飞升(Prestige)
┌─────────┼─────────┐
▼ ▼ ▼
蓝图+ 星图天赋+ 成就+
(5%/3%/4%) (各天赋) (各成就)
│ │ │
└─────────┴─────────┘
recomputeStats()
┌─────────┼─────────┐
▼ ▼ ▼
产能 解码奖励 探险力/生命
星潮修饰(即时乘)
实际游戏体验
```
## 七、后续扩展方向
- **天赋觉醒动画**:选择天赋时全屏闪光 + 星图缩放
- **天赋重置**:消耗稀有资源重置 constellation,重新选择
- **隐藏天赋**:达成特殊条件解锁(如「飞升 10 次自选 1 天赋」)
- **天赋树依赖**order 2 需要 order 1 解锁(增加构建深度)
- **socket 同步**:多人共享同一星图,协作解锁
+175
View File
@@ -0,0 +1,175 @@
# v0.4 回响编年史 · Chronicle of Echoes
> 跨周目叙事时间轴系统。每次飞升自动铭刻一条纪元条目,记录本周目的解码、探险、星潮、天赋觉醒与里程碑,形成永恒叙事流。朝 v0.5「全 5 纪元叙事」目标推进。
## 动机
- **回应 Issue #1**「就一直连连看?」的进一步反馈:星潮+星图解决了「玩法单一」,但仍缺**叙事深度**与**进度回顾**。
- 玩家完成多次飞升后无回顾入口,对「自己走过多远」缺乏感知,影响长线留存。
- v0.5 路线图为「全 5 纪元叙事」,编年史是其底层骨架——先把数据结构与展示框架立起来。
## 核心模块
### 数据模型(`src/lib/game/types.ts`
```ts
export interface ChronicleEntry {
ascensionNumber: number; // 第几次飞升(1-based
epochName: string; // 程序化生成的纪元名(5 纪元循环)
timestamp: number;
durationSec: number; // 本周目持续时间
summary: {
techsUnlocked: number;
constellationsTotal: number;
expeditionsCompleted: number;
bossKills: number;
starTidesEncountered: number;
blueprintsAfter: number;
crystalsDecoded: number;
crystalsDecodedThisRun: number;
};
perksThisAscension: string[]; // 本次飞升后选择的天赋
tidesThisRun: string[]; // 本周目新遇到的星潮 ID
lore: string; // 程序化生成的叙事文本
milestones: string[]; // 里程碑标签
}
export interface RunStartSnapshot {
timestamp: number;
techsUnlocked: number;
expeditionsCompleted: number;
bossKills: number;
starTidesEncountered: string[];
crystalsDecoded: number;
}
```
GameState 新增字段:
- `chronicle: ChronicleEntry[]` — 编年史条目列表(上限 50
- `runStart: RunStartSnapshot` — 当前周目起始快照(用于 delta 计算)
- `bossKills: number` — 累计 BOSS 击破数(跨周目)
- `starTidesEncountered: string[]` — 累计遇到的星潮 ID 集合(去重)
### 编年史逻辑(`src/lib/game/chronicle.ts`,新建)
- `EPOCH_NAMES` — 5 纪元 × 4 后缀 = 20 种纪元名组合(如「第一纪元 · 觉醒之晨」「第三纪元 · 虚空回望」)
- `generateEpochName(ascensionNumber, seed)` — 按 ascensionNumber 循环 5 纪元,seed 决定后缀
- `buildLore(entry)` — 模板化叙事生成:开场 → 核心活动 → 星潮段落 → 觉醒段落 → 里程碑 → 收尾
- `detectMilestones(state, deltas)` — 9 种里程碑检测(首次飞升/首杀 BOSS/单周目解码 10+/技术狂人/星潮亲历者/星图六分等)
- `computeRunDeltas(state)` — 基于 runStart 快照计算本周目 delta(持续时间/解码数/技术数/探险数/BOSS/新星潮)
- `buildChronicleEntry(state, perks)` — 飞升时构建一条 ChronicleEntry
- `createRunStartSnapshot(state)` — 飞升后创建新 runStart
- `migrateChronicleFields(state)` — 旧存档兼容:补全 chronicle=[]/runStart/bossKills=0/starTidesEncountered=[]
- `withPerks(entry, perks)` — 飞升后选天赋时回填 perksThisAscension,并重新生成 lore 包含天赋名
- `getPerkCategoryBreakdown(perks)` / `getCategoryName` / `getCategoryColor` — UI 辅助
### 引擎接入(`src/lib/game/engine.ts`
- `performPrestige` 在重置前:
1. `buildChronicleEntry(state, [])` 构建本周目条目(perks 暂为空,待玩家选择后回填)
2. 追加到 `chronicle` 数组(上限 50 条)
3. `createRunStartSnapshot(state)` 创建新周目的快照
- 保留跨周目字段:`chronicle`, `bossKills`, `starTidesEncountered`
- 重置 `runStart` 为新快照
### Store 接入(`src/store/gameStore.ts`
- `init()` — 调用 `migrateChronicleFields` 补全旧存档
- `tickTide` — 新星潮触发时,将 `tide_${type}` 追加到 `starTidesEncountered`(去重)
- `resolveCurrentNode` — BOSS 战胜利结算时,`bossKills++`
- `chooseConstellationPerk` — 选择天赋后,调用 `withPerks` 回填最近一条 chronicle entry 的 `perksThisAscension`,并重新生成 lore
### UI 组件(`src/components/game/ChronicleDialog.tsx`,新建)
- 顶部 BookOpen 图标按钮(带 badge 显示条目数)
- 全屏对话框,深紫黑渐变背景 + 顶部三色装饰条
- 顶部总览统计:飞升次数 / BOSS 击破 / 星潮亲历
- 垂直时间轴:
- 左侧渐变时间轴线
- 每条卡片左侧有发光圆形节点(5 纪元循环色:翠/玫/琥/紫/金),最新一条带 ping 动画
- 卡片含:#编号 / 纪元名(带 textShadow 光晕)/ 时间戳+持续时长 / 叙事文本(衬线字体)/ 6 项统计芯片 / 星潮徽章列表 / 天赋徽章 / 里程碑徽章
- 入场动画:fade-in + slide-up,错峰 80ms
- 空状态:根据 ascensions 是否 > 0 显示不同提示
- 打开时播放 `chronicleOpen` 翻页音效
### 音频(`src/lib/game/audio.ts`
- `chronicle` — 编年史铭刻音效:G3 基音 + C4/G4/C5/G5 多层泛音,深沉钟声感
- `chronicleOpen` — 翻页音效:A4→D5→A5 轻柔上升 + 短促气流
- 飞升成功时延迟 600ms 触发 `chronicle` sfx,与飞升音分层
### 成就(`src/lib/game/achievements.ts`
新增 6 项成就:
- `ach_chronicle_1` 「首部编年」— 完成首次飞升(+40 洞见 · 产能 +6%)
- `ach_chronicle_3` 「三纪元回响」— 完成 3 次飞升(产能 +10% · 洞见 +10%)
- `ach_chronicle_5` 「五纪元闭环」— 完成 5 次飞升(产能 +15% · 洞见 +15%)
- `ach_boss_1` 「首杀维度」— 击破首个 BOSS(+25 洞见 · 产能 +4%
- `ach_boss_5` 「维度猎手」— 累计击破 5 个 BOSS(产能 +10% · 洞见 +8%
- `ach_tides_all` 「星潮亲历者」— 经历全部 6 种星潮(产能 +8% · 洞见 +8%)
### 飞升对话框增强(`src/components/game/PrestigeDialog.tsx`
- 新增「编年史铭刻预告」卡片,提示本次飞升将记录入编年史
- **修复关键 BUG**:原 `disabled={newBp <= 0 || confirming}``confirming=true` 时按钮永久禁用,玩家无法完成飞升!现已改为 `disabled={confirming && !canReconfirm}` 并加 900ms 冷却窗口
- **修复 UX**:当 newBp=0(无可获蓝图)时不再永久禁用飞升按钮——v0.3.1 星图天赋 + v0.4 编年史已为飞升提供足够动机。按钮文字改为「飞升(无新蓝图)」
- 飞升成功 toast 增加「编年史已铭刻新纪元」提示
### 统计面板增强(`src/app/page.tsx`
新增 3 行统计:
- BOSS 击破次数
- 星潮亲历数(X/6
- 编年史条目数(X 纪元)
## QA 验证
### 修复的关键 BUG
- 飞升按钮 `disabled` 逻辑错误导致玩家永远无法飞升(`confirming` 变 true 后按钮被禁用)
- 验证:agent-browser 端到端测试,第一次点飞升 → 900ms 冷却 → 第二次点击成功触发,星图觉醒对话框弹出
### 编年史端到端测试
1. 设置 contact=100 + 资源,触发飞升 → ascensions 1→2, chronicle 0→1 ✓
2. 选择天赋「二周目经验」→ constellation 增加 c_cos_2, chronicle 末条 perksThisAscension=["c_cos_2"] ✓
3. 打开编年史对话框 → 显示 1 条卡片,含完整 lore、统计芯片、星潮徽章、天赋徽章、里程碑 ✓
4. 再次飞升 → ascensions 2→3, chronicle 1→2 ✓
5. 第二条卡片正确显示新纪元名「第三纪元 · 虚空回望」+ 正确的星潮名「星核悸动」+ 正确的 duration delta2分42秒) ✓
6. 成就解锁:`ach_chronicle_1`「首部编年」+ `ach_prestige_3`自动检测 ✓
7. 统计面板新增「BOSS 击破」「星潮亲历」「编年史条目」3 行 ✓
8. VLM 视觉评估:8/10,无瑕疵,时间轴清晰,纪元颜色区分明显
### 视觉细节
- 5 纪元循环色:翠 #34d399 / 玫 #fb7185 / 琥 #fbbf24 / 紫 #e879f9 / 金 #fcd34d
- 时间轴节点带 boxShadow 光晕(12px + 24px 双层)
- 最新条目带 animate-ping 脉冲
- 卡片入场动画:fade-in + slide-in-from-bottom-2,错峰 80ms
- 叙事文本使用 font-serif 衬线字体增加文学感
- 统计芯片按类别用对应颜色 tint
### 旧存档兼容
- `migrateChronicleFields` 自动补全 chronicle=[]/runStart/bossKills=0/starTidesEncountered=[]
- 玩家旧存档不丢失进度,新字段以默认值初始化
## 文件清单
新增:
- `src/lib/game/chronicle.ts` — 编年史核心逻辑
- `src/components/game/ChronicleDialog.tsx` — 编年史对话框 UI
- `docs/repo/docs/09-回响编年史系统-v0.4.md` — 本文档
修改:
- `src/lib/game/types.ts` — 新增 ChronicleEntry / RunStartSnapshot 类型 + GameState 字段
- `src/lib/game/config.ts` — INITIAL_STATE 新增 chronicle/runStart/bossKills/starTidesEncountered
- `src/lib/game/engine.ts` — performPrestige 构建编年史条目 + 重置 runStart
- `src/lib/game/achievements.ts` — 新增 6 项成就
- `src/lib/game/audio.ts` — 新增 chronicle / chronicleOpen 音效
- `src/store/gameStore.ts` — init 兼容 + tickTide 记录 + resolveCurrentNode BOSS 计数 + chooseConstellationPerk 回填
- `src/components/game/PrestigeDialog.tsx` — 修复 BUG + 编年史预告卡片 + 音效
- `src/app/page.tsx` — 头部编年史按钮 + 对话框渲染 + 统计面板新行 + 版本号 v0.4
## 已知限制 / 后续
- 编年史上限 50 条(超过自动丢弃最早的)
- v0.4 之前发生的飞升不会被回填(无数据可恢复)—— 空状态有对应提示
- 5 纪元叙事内容仍为模板化生成,v0.5 将加入手写剧情节点
- 编年史目前纯本地,v0.4 云存档+排行榜会将其纳入同步范围
+151
View File
@@ -0,0 +1,151 @@
# 深空信标系统 · v0.5(每日挑战 + 本地排行榜)
> 版本:v0.5 · 日期:2026-06-23
> 路线图对应:v0.5 排行榜(本地版先行)+ 每日挑战动机注入
## 动机
v0.4 编年史系统完成后,游戏已有 6 大玩法层(采矿/解码/技术树/图谱/飞升 + 探险 + 星图 + 星潮 + 编年史),但缺少"今日目标"型的短期动机。玩家进入游戏后没有明确的"今天该做什么"指引。
本版本新增**深空信标(Deep Space Beacon**系统:
- **每日挑战**:基于 UTC 日期种子的确定性挑战,全球同一天同一挑战,每天 0 点(UTC)刷新
- **本地排行榜**:记录历次挑战得分,Top 20,纯 localStorage 持久化,无需后端
- 给放置循环注入"今日目标",并为 v0.5 路线图的"云排行榜"奠定数据结构基础
## 核心设计
### 每日挑战(Daily Challenge
- **确定性生成**`generateDailyChallenge(now)` 基于 UTC 日期 keyYYYY-MM-DD)派生 FNV-1a 种子 → mulberry32 PRNG,保证同一天全球玩家看到同一挑战
- **5 种挑战类型**
| 类型 | 标签 | 目标范围 | 单位 |
|------|------|----------|------|
| `decode` | 解码协议 | 6-15 × 难度倍率 | 颗 |
| `expedition` | 远征指令 | 1-3 × 难度倍率 | 次 |
| `pulse` | 脉冲任务 | 20-49 × 难度倍率 | 次 |
| `boss` | 猎杀契约 | 1 × 难度倍率 | 处 |
| `insight` | 洞见采集 | 40-119 × 难度倍率 | 点 |
- **3 档难度**(加权抽取):
| 难度 | 标签 | 倍率 | 权重 | 颜色 |
|------|------|------|------|------|
| `routine` | 常规信标 | ×1.0 | 55% | 翠 |
| `anomaly` | 异常波动 | ×1.6 | 33% | 琥 |
| `singular` | 奇点回响 | ×2.4 | 12% | 玫 |
- **奖励**:完成时按类型 + 难度发放洞见 / 接触进度
- **倒计时**:显示距 UTC 次日 0 点的 HH:MM:SS 倒计时
### 得分计算
```
得分 = 完成度 × 1000 × 难度倍率 + 速度奖励
速度奖励 = max(0, 500 - 完成时长秒 × 0.5) // 仅完成时计算
```
例:常规难度 × 1.090 秒完成 → 1000 + (500 - 45) = 1455 分
### 进度追踪(独立 localStorage
- 进度独立存储于 `echo-nexus-beacon-prog-v1`**不污染 GameState**,避免触发存档迁移
- 在 gameStore 关键动作后调用 `trackBeacon(type, delta)`
- `pulse`:每次脉冲 +1
- `decode`:每次解码完成 +1(手动 + 自动解码均计)
- `expedition`:每次探险结束 +1(胜负都计)
- `boss`:每次 BOSS 胜利 +1
- `insight`:每次洞见获取按数值累加
- 挑战类型与今日挑战不匹配时 `trackBeacon` 直接 return,零开销
### 本地排行榜
- 存储于 `echo-nexus-beacon-lb-v1`Top 20,按得分降序
- 每条记录含:时间戳 / 日期 key / 挑战类型 / 难度 / 完成度 / 得分 / 用时
- UI:奖牌图标(1st 金 / 2nd 银 / 3rd 铜)+ 难度色点 + 类型名 + 用时 + 得分
- 今日记录高亮(fuchsia 边框)
## 文件变更
### 新增
- `src/lib/game/beacon.ts`~290 行):核心逻辑模块
- `generateDailyChallenge()` — 确定性挑战生成
- `computeBeaconScore()` — 得分计算
- `loadLeaderboard()` / `pushLeaderboardEntry()` — 排行榜 CRUD
- `loadDailyProgress()` / `saveDailyProgress()` / `addBeaconProgress()` — 进度 CRUD
- `claimBeaconReward()` — 领取奖励 + 推送排行榜
- `msUntilNextDay()` / `formatCountdown()` — 倒计时
- `src/components/game/BeaconPanel.tsx`~280 行):UI 面板
- 每日挑战卡片(难度色 + 脉冲环动画 + 进度条 + 领取按钮)
- 本地排行榜(Top 20 + 奖牌 + 今日高亮)
- 难度图例
- 倒计时
### 修改
- `src/store/gameStore.ts`
- 新增 `trackBeacon()` 模块级辅助函数
- 新增 `grantBeaconReward(insights, contact)` action
- 在 `pulse` / `clickNode`(完成) / `autoDecodeTick` / `resolveCurrentNode` 后接入 `trackBeacon`
- `src/app/page.tsx`
- 新增第 7 个标签页「信标」(grid-cols-7
- 新增 `beaconClaimable` 状态(2s 轮询,可领取时显示绿点)
- StatsPanel 新增「信标最高分」行
- 版本号 v0.4 → v0.5
- `src/lib/game/chronicle.ts`
- 导出 `buildLore()`(原私有)
- 新增 `regenerateLoreFromEntry(entry)` — 显示时重新生成 lore,修复历史条目中 `tide_ruins` 等原始键名
- `src/components/game/ChronicleDialog.tsx`:叙事文本改用 `regenerateLoreFromEntry(entry)` 替代 `entry.lore`
## BUG 修复
### 编年史历史条目显示原始 tide 键名
- **现象**:v0.4 之前创建的编年史条目 lore 中显示 `tide_ruins` 而非「遗迹共振」(最新条目已修复,旧条目未回填)
- **根因**:lore 文本在条目创建时一次性生成并存储,历史条目存储了修复前的错误文本
- **修复**:新增 `regenerateLoreFromEntry()`,在 ChronicleDialog 显示时从 entry 的结构化数据(tidesThisRun / perksThisAscension / milestones)重新生成 lore,保证历史与未来条目命名一致
- **验证**:第二纪元 lore 现正确显示「遗迹共振、晶体潮、虚空低语、谐振风暴」
## QA 验证(agent-browser + VLM
### 编年史 BUG 修复验证
- ✅ 第二纪元 lore 显示「遗迹共振、晶体潮、虚空低语、谐振风暴」(修复前显示 `tide_ruins` 等原始键)
- ✅ 第三纪元 lore 保持正确「星核悸动」
- ✅ 徽章显示正确
### 深空信标系统验证
- ✅ 第 7 个标签页「信标」渲染正确
- ✅ 每日挑战卡片:难度标签 + 类型标签 + 标题 + 描述 + 进度条 + 奖励 + 领取按钮
- ✅ 倒计时显示「08:47:12」并每秒更新
- ✅ 难度图例显示 3 档(×1 / ×1.6 / ×2.4
- ✅ 进度追踪:解码 1 颗晶体 → 洞见采集进度 +12(解码奖励的洞见值)
- ✅ 完成挑战后按钮变为「领取奖励」
- ✅ 领取奖励后:contact +5.113.47 → 18.57),按钮变「已领取」
- ✅ 排行榜生成条目:「洞见采集 ✓ 1m30s 1.46K」
- ✅ 统计面板新增「信标最高分」行
- ✅ 可领取时标签页显示绿点提示
- ✅ VLM 视觉评估:高美观度,颜色搭配佳,无重叠 bug
### 全系统回归验证
- ✅ 主视图(资源条 / 晶体球 / 解码面板)正常
- ✅ 探险全流程(出发 → 探索 → 前进 → BOSS → 结束 → 日志)正常
- ✅ 解码谜题生成 + 自动求解通过
- ✅ 星潮事件触发 + 指示器 + Footer 联动正常
- ✅ 成就解锁 Toast 正常
- ✅ 星图标签页(9/18 天赋,6 类别)正常
- ✅ 编年史对话框(2 条目,时间轴,里程碑)正常
- ✅ lint 零错误;HTTP 200;编译 < 250ms
## 路线图进展
- [x] v0.1 MVP:放置采矿 + 解码 + 技术树 + 图谱 + 飞升
- [x] v0.1.1:解码可解性修复
- [x] v0.2:遗迹探险肉鸽
- [x] v0.2.1:程序化音频 + 成就系统
- [x] v0.3:星潮事件系统
- [x] v0.3.1:星图天文台元进程
- [x] v0.4:回响编年史
- [x] **v0.5:深空信标(每日挑战 + 本地排行榜)** ← 本轮
- [ ] v0.5+:云存档 + 云排行榜(后端同步)
- [ ] v0.5+:全 5 纪元手写叙事节点
- [ ] 后续:socket 多人同步星潮
## 下一阶段优先
1. 云存档 + 云排行榜(需后端 API,可将本地 localStorage 同步到服务端)
2. 全 5 纪元手写叙事节点(替换模板化 lore)
3. socket 多人同步星潮(全局事件,所有在线玩家同时经历)
Submodule
+1
Submodule docs/repo added at a02b6191c5
+1
View File
@@ -0,0 +1 @@
Here are all the generated files.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Some files were not shown because too many files have changed in this diff Show More