v0.9 工单修复部署
@@ -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
|
||||
@@ -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"
|
||||
@@ -0,0 +1 @@
|
||||
12512
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 · 持续开发中
|
||||
@@ -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
|
||||
@@ -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",{}]]
|
||||
@@ -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}
|
||||
@@ -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}
|
||||
@@ -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()
|
||||
@@ -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"}
|
||||
@@ -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",{}]]
|
||||
@@ -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}
|
||||
@@ -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
|
||||
@@ -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}
|
||||
@@ -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}
|
||||
@@ -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",{}]]
|
||||
@@ -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)`:确定性生成,难度 routine,goal = 日基准 ×0.3-0.5,5 种类型各有公式
|
||||
- `msUntilNextTimedSlot(now)`:距离下个 4 小时时段的毫秒数
|
||||
- `loadTimedProgress` / `saveTimedProgress` / `addTimedProgress` / `claimTimedReward`(推送排行榜,entry 加 isTimed: true)
|
||||
|
||||
**gameStore.ts 集成**:
|
||||
- import 扩展:generateTimedChallenge / loadTimedProgress / addTimedProgress / claimTimedReward + 类型
|
||||
- `trackBeacon` 返回值新增 `timedJustCompleted`:日/限时/周三类进度同时更新
|
||||
- 新增 `claimTimedBeacon` action(GameActions 接口同步)
|
||||
- 新增 `timedJustCompleted` 到 trackBeacon 返回类型
|
||||
|
||||
**BeaconPanel.tsx UI**(753 → 944 行):
|
||||
- import 扩展:generateTimedChallenge / loadTimedProgress / getTimedSlotKey / msUntilNextTimedSlot / Hourglass 图标
|
||||
- 新增 timedChallenge/timedProgress/timedCountdown state
|
||||
- 每秒刷新 timedProgress + timedCountdown
|
||||
- 新增 handleClaimTimed 回调
|
||||
- **限时挑战区块**(amber 主题,插入日挑战与周挑战之间):
|
||||
- Hourglass 图标 + "限时挑战 · TIMED" 标题 + slotKey + 倒计时
|
||||
- 难度/类型标签 + 标题 + 描述 + 进度条 + 奖励 + 领取按钮(emerald 主题)
|
||||
- **紧急状态**:距时段结束 <30min 时切换 rose 脉冲动画 + "即将结束"徽章
|
||||
- 2 个新动画:timed-glow(amber 呼吸)/ timed-urgent(rose 急促脉冲)
|
||||
- 排行榜 entry 区分 TIMED(amber 徽章)/ WEEK(fuchsia 徽章)
|
||||
- 空状态提示更新为"完成限时、每日或本周信标即可登榜"
|
||||
|
||||
### 工单 #3:星潮类型深化
|
||||
|
||||
**starTide.ts 扩展**(209 → 289 行):
|
||||
- `TideType` 新增 3 种:surge / eclipse / prism
|
||||
- `TideModifiers` 新增 3 个可选字段:targetLenBonus / bossWinRateBonus / autoDecodeIntervalMult
|
||||
- `TIDE_EVENTS` 新增 3 种元信息(name/desc/color/glow/icon/negative):
|
||||
- surge(涌动星潮,emerald #34d399,⇈):产能 ×2.5 但解码目标 +2
|
||||
- eclipse(蚀相星潮,rose #fb7185,◐):BOSS 胜率 +20% 但产能 -30%
|
||||
- prism(棱镜星潮,fuchsia #e879f9,◬):洞见 ×2 + 自动解码周期 -30%
|
||||
- `TIDE_WEIGHTS` 新增 surge/eclipse/prism 各 12(总权重 136)
|
||||
- `getTideModifiers` 新增 3 个 case + null 分支补全新字段
|
||||
- 注释更新:"6 种类型" → "9 种类型"
|
||||
|
||||
**decode.ts 扩展**:
|
||||
- `generatePuzzle` 新增第 3 参数 `targetLenBonus`(默认 0)
|
||||
- targetLen = cfg.targetLen + targetLenBonus,stepLimitBase 同步增加
|
||||
- 路径构造法自动适配更长目标序列(300 次尝试 + 线性兜底)
|
||||
|
||||
**gameStore.ts 集成**(3 处修饰器应用):
|
||||
- `startDecode`:surge 星潮时传入 `tideMod.targetLenBonus` → 谜题更长
|
||||
- `autoDecodeTick`:prism 星潮时 `interval *= tideMod.autoDecodeIntervalMult`(×0.7)
|
||||
- `resolveCurrentNode`:eclipse 星潮时 `b = am.bossWinRateBonus + tideMod.bossWinRateBonus`(+20%)
|
||||
|
||||
**achievements.ts**:`ach_tides_all` 阈值 6 → 9,描述"经历全部 9 种星潮事件"
|
||||
|
||||
**page.tsx**:StatsPanel "星潮亲历" 显示 `/ 9`
|
||||
|
||||
### QA 验证
|
||||
1. **lint**:`bun run lint` 零错误(exit 0)
|
||||
2. **dev 服务器**:HTTP 200,编译 < 30ms
|
||||
3. **BeaconPanel VLM 评分**:
|
||||
- 首屏 8.5/10:限时挑战 TIMED 区块可见、四色一致、倒计时/进度/标题/奖励/按钮清晰
|
||||
- 最终截图 9/10:4 个挑战区块齐全、限时区块带倒计时、无蓝色
|
||||
4. **新星潮 VLM 评分**(localStorage 注入 + reload 触发):
|
||||
- surge(emerald 绿):8/10,横幅 + 全屏绿色微光
|
||||
- prism(fuchsia 紫):7/10,横幅 + 全屏紫色微光
|
||||
- eclipse(rose 粉):8/10(单图),横幅 + 全屏粉色微光
|
||||
5. **不破坏现有功能**:日挑战/周挑战/信标链/6 旧星潮全部保留,排行榜正常
|
||||
|
||||
## Stage Summary
|
||||
- ✅ 工单 #5 限时挑战完成:beacon.ts +289 行 + gameStore claimTimedBeacon + BeaconPanel amber 区块
|
||||
- ✅ 工单 #3 星潮深化完成:starTide.ts +80 行(3 新类型)+ decode.ts targetLenBonus + gameStore 3 处修饰器
|
||||
- ✅ 确定性生成:限时挑战同时段同种子同结果(FNV-1a slotKey 哈希)
|
||||
- ✅ 严格四色规范:限时 amber、surge emerald、eclipse rose、prism fuchsia,零蓝色/靛色
|
||||
- ✅ 紧急状态设计:限时挑战 <30min 时 rose 脉冲 + "即将结束"徽章
|
||||
- ✅ lint 零错误 + dev HTTP 200 + VLM ≥7/10(信标 9/10、surge 8/10、prism 7/10、eclipse 8/10)
|
||||
- ✅ 现有日/周/信标链/6 旧星潮全部保留,无回归
|
||||
@@ -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 action:grantBeaconReward/claimWeeklyBeacon/claimTimedBeacon/claimChainReward)
|
||||
- `/home/z/my-project/Caddyfile`(:81 网关,按 ?XTransformPort=* 转发到对应端口)
|
||||
- `/home/z/my-project/examples/websocket/server.ts` + `frontend.tsx`(mini-service + 前端连接参考)
|
||||
- `/home/z/my-project/.zscripts/dev.sh`(mini-service 自动扫描启动逻辑,发现 mini-services 目录下子服务自动 `bun run dev`)
|
||||
- `/home/z/my-project/agent-ctx/9-b-full-stack-developer.md`(前一个 agent 的信标系统扩展记录)
|
||||
- `/home/z/my-project/mini-services/star-tide-service/`(已存在的 P3 #10-c 服务,端口 3031)
|
||||
|
||||
## 实现细节
|
||||
|
||||
### 1. mini-service(`mini-services/leaderboard-service/`)
|
||||
|
||||
**目录结构**:
|
||||
```
|
||||
mini-services/leaderboard-service/
|
||||
├── package.json # 独立 bun 项目,type=module
|
||||
├── index.ts # Hono 入口(182 行)
|
||||
├── bun.lock # 自动生成
|
||||
├── node_modules/ # hono 独立安装
|
||||
└── README.md # API 文档
|
||||
```
|
||||
|
||||
**package.json**:
|
||||
- `"dev": "bun --hot index.ts"`(规范要求的热重载脚本)
|
||||
- `"start": "bun index.ts"`(无热重载,用于稳定运行)
|
||||
- 依赖:hono ^4.6.14
|
||||
|
||||
**index.ts 设计要点**:
|
||||
- Hono + `cors()` 中间件,origin: * 全开放(前端跨端口必须)
|
||||
- 内存数组 `entries: BeaconScoreEntry[]`,最多 1000 条,按 score 降序
|
||||
- 同分排序:先看用时短,再看提交早
|
||||
- 防刷:`lastSubmitAt: Map<dateKey|challenge, ts>`,10 秒内只接受 1 次,返回 429 + retryAfterMs
|
||||
- 4 个路由:
|
||||
- `GET /` → 健康检查 `{service, version, ok, uptime}`
|
||||
- `GET /api/leaderboard` → Top100 `{entries, total}`
|
||||
- `POST /api/leaderboard` body `{entry}` → schema 校验 → 推入 → 排序 → `{entries, total, rank}`
|
||||
- `GET /api/leaderboard/stats` → `{totalSubmissions, uniquePlayers, topScore}`
|
||||
- OPTIONS 预检由 hono/cors 自动处理,返回 204 + 完整 CORS 头
|
||||
- 端口固定 3030,`export default { port, fetch }` 标准 bun 模式
|
||||
|
||||
**启动调试经验**:
|
||||
- `bun --hot` 在本沙盒环境下不稳定,多次启动后约 5-10s 内被 kill(即使 setsid + nohup)
|
||||
- 解决方案:用 `bun index.ts`(无 --hot)+ 双 fork 模式:`( bun index.ts >/tmp/log 2>&1 & )`
|
||||
- 双 fork 后 PPID=1(reparent 到 init),进程脱离原 bash session,uptime 4+ 分钟稳定
|
||||
|
||||
### 2. beacon.ts 扩展(1161 → 1290 行,+129 行)
|
||||
|
||||
**三个 claim 函数返回值扩展(非破坏性)**:
|
||||
- `claimBeaconReward` 返回值新增 `entry: BeaconScoreEntry`
|
||||
- `claimWeeklyReward` 返回值新增 `entry: BeaconScoreEntry`
|
||||
- `claimTimedReward` 返回值新增 `entry: BeaconScoreEntry`
|
||||
- 用途:让前端 / gameStore 能拿到完整 entry 提交到云端
|
||||
|
||||
**新增云排行榜模块**:
|
||||
- `BEACON_CLOUD_PORT = 3030` 常量
|
||||
- `BEACON_CLOUD_LAST_SUBMIT_KEY = "echo-nexus-beacon-cloud-last-submit-v1"` localStorage key
|
||||
- 3 个响应接口:`CloudLeaderboardResponse` / `CloudSubmitResponse` / `CloudStatsResponse`
|
||||
- `loadLastCloudSubmitTimestamp()` / `saveLastCloudSubmitTimestamp(ts)`
|
||||
- `fetchCloudLeaderboard(): Promise<BeaconScoreEntry[]>` — GET `/api/leaderboard?XTransformPort=3030`
|
||||
- `submitCloudScore(entry): Promise<number>` — POST,成功返回 rank ≥ 1,失败返回 -1,自动记录 ts
|
||||
- `fetchCloudStats(): Promise<CloudStatsResponse | null>`
|
||||
- **所有 fetch 用相对路径 + ?XTransformPort=3030,禁止 localhost:3030**
|
||||
- try-catch 包裹,失败时静默返回空/null,不抛异常
|
||||
|
||||
### 3. gameStore.ts 集成
|
||||
|
||||
- import 新增 `submitCloudScore`
|
||||
- `claimWeeklyBeacon` action:领奖成功后 `void submitCloudScore(res.entry)`(fire-and-forget,不 await,不阻塞)
|
||||
- `claimTimedBeacon` action:同上
|
||||
- `claimBeaconReward` (daily) 由 BeaconPanel 直接调用,不通过 gameStore
|
||||
|
||||
### 4. BeaconPanel.tsx 重写(953 → 1170 行,+217 行)
|
||||
|
||||
**新增 import**:
|
||||
- `fetchCloudLeaderboard` / `submitCloudScore` / `loadLastCloudSubmitTimestamp` from beacon
|
||||
- 4 个 lucide 图标:`RefreshCw` / `Globe` / `WifiOff` / `Loader2`
|
||||
- `useRef` from react
|
||||
|
||||
**新增状态**:
|
||||
- `lbTab: 'local' | 'global'`(默认 local)
|
||||
- `cloudEntries: BeaconScoreEntry[]`
|
||||
- `cloudLoading: boolean`
|
||||
- `cloudError: string | null`
|
||||
- `cloudFetched: boolean`
|
||||
- `mySubmitTs: number`
|
||||
- `cloudFetchingRef` (防并发)
|
||||
|
||||
**新增逻辑**:
|
||||
- `refreshCloudLeaderboard` callback:拉取云端榜 + 防并发 + 空 entries 时显示"成为首位登顶者"
|
||||
- useEffect:切到全球 tab 自动拉取(仅首次)+ 初始化读取 mySubmitTs
|
||||
- `handleClaimDaily`:领奖后 `void submitCloudScore(res.entry).then(rank => ...)` — 若用户在全球 tab 自动刷新
|
||||
- `handleClaimTimed` / `handleClaimWeekly`:领奖后更新 mySubmitTs + 若在全球 tab 自动刷新
|
||||
|
||||
**排行榜 UI 重写为双 tab**:
|
||||
- 顶部 tab 切换:「本地」(amber 主题) / 「全球」(emerald 主题) — 用 `<button aria-pressed>` 实现可访问性
|
||||
- 全球 tab 专属刷新按钮(RefreshCw,loading 时 animate-spin)
|
||||
- 子标题:本地"本机 Top N · 离线可用" / 全球"全球 Top N · 已同步 / 同步中…"
|
||||
- 本地榜:保留原 v0.8.2 渲染(max-h-200px overflow-y-auto)
|
||||
- 全球榜:
|
||||
- 加载中:Loader2 spinner + "正在拉取全球榜…"
|
||||
- 空榜 + 错误:WifiOff 图标 + 错误文案 + 重试按钮
|
||||
- 空榜 + 无错:Globe 图标 + "尚无全球记录。完成挑战并领取即可登顶全球榜!"
|
||||
- 有数据:渲染前 100 条,每条带排名(1-3 名用 Crown/Medal/Award 图标)/ 难度色 / WEEK·TIMED 徽章 / 用时 / 分数
|
||||
- **玩家自己的记录高亮**:通过 `entry.timestamp === mySubmitTs` 匹配
|
||||
- emerald 边框 (`border-emerald-500/40`)
|
||||
- emerald 阴影 (`shadow-[0_0_12px_rgba(52,211,153,0.25)]`)
|
||||
- YOU 徽章 (`bg-emerald-500/25 text-emerald-200 border-emerald-500/50`)
|
||||
- emerald 分数色 (`color: #34d399`)
|
||||
- 加粗排名
|
||||
|
||||
### 5. eslint 配置
|
||||
|
||||
- `eslint.config.mjs` 新增 ignores `"mini-services/**"`
|
||||
- 原因:mini-service 是独立 bun 项目,有自己的 TypeScript 配置和 lint 规范,不应参与主项目 lint
|
||||
|
||||
## QA 验证结果
|
||||
|
||||
### 1. lint
|
||||
- `bun run lint` 零错误零警告
|
||||
|
||||
### 2. dev 服务器
|
||||
- dev.log 全程无错误,所有编译 < 300ms
|
||||
- 注:dev.log 中显示 `/api/leaderboard?XTransformPort=3030 404` 是预期行为 — 通过 :3000 直接访问时 Next.js 没有 /api/leaderboard 路由;通过 :81 网关访问时 Caddy 转发到 :3030 正常返回 200
|
||||
|
||||
### 3. mini-service curl 全通过
|
||||
- `GET /` → 200 `{"service":"echo-nexus-leaderboard","version":"v0.8.2","ok":true,"uptime":N}`
|
||||
- `GET /api/leaderboard` (空) → 200 `{"entries":[],"total":0}`
|
||||
- `POST /api/leaderboard` → 200 `{"entries":[...],"total":1,"rank":1}`
|
||||
- `GET /api/leaderboard` (有数据) → 200 `{"entries":[...],"total":N}`
|
||||
- `GET /api/leaderboard/stats` → 200 `{"totalSubmissions":N,"uniquePlayers":M,"topScore":X}`
|
||||
- `OPTIONS /api/leaderboard` 预检 → 204 + 完整 CORS 头(Allow-Origin: *)
|
||||
- 重复 POST(同 dateKey+challenge 10s 内)→ 429 `{"error":"Rate limited","retryAfterMs":N}`
|
||||
|
||||
### 4. 网关路由验证
|
||||
- Caddy :81 → localhost:3030 通过 `?XTransformPort=3030` 正确转发,返回 JSON 200
|
||||
|
||||
### 5. agent-browser 集成测试
|
||||
- 通过 :81 端口访问(绕过 Next.js :3000 的 404)
|
||||
- 信标 tab 选中(用 KeyboardEvent Enter 激活 Radix Tabs — 因 pointerdown 被覆盖层拦截)
|
||||
- 切到「全球」tab → 自动拉取 → 显示"全球 Top 6 · 已同步" → 渲染 6 条记录
|
||||
- 刷新按钮可点击,旋转动画正常
|
||||
- POST 测试:浏览器 fetch 提交 score=9999 → 返回 rank=1 → 刷新后榜首显示 10.00K
|
||||
- YOU 高亮:手动设置 localStorage[BEACON_CLOUD_LAST_SUBMIT_KEY] 后刷新 → 榜首显示 emerald 边框 + YOU 徽章
|
||||
|
||||
### 6. VLM 视觉评分(目标 ≥7/10)
|
||||
- 全球榜含 YOU 高亮:**8/10**
|
||||
- VLM 评语:"双tab切换清晰,全球榜记录完整,难度色与标签规范,视觉层次佳,但「YOU」徽章未突出显示"
|
||||
- 本地榜:**8/10**
|
||||
- VLM 评语:"双tab切换逻辑清晰,本地tab内容完整呈现;四色规范应用,视觉区分度高;信息层级合理"
|
||||
|
||||
## 修改的文件
|
||||
|
||||
1. `mini-services/leaderboard-service/package.json`(新建)
|
||||
2. `mini-services/leaderboard-service/index.ts`(新建,182 行)
|
||||
3. `mini-services/leaderboard-service/README.md`(新建)
|
||||
4. `src/lib/game/beacon.ts` — 1161 → 1290 行(+129 行:3 个 claim 返回 entry + 云排行榜模块)
|
||||
5. `src/store/gameStore.ts` — 1235 → 1252 行(+17 行:import + 2 个 claim action 加 submitCloudScore)
|
||||
6. `src/components/game/BeaconPanel.tsx` — 953 → 1170 行(+217 行:双 tab UI + 云端拉取 + 提交 + YOU 高亮)
|
||||
7. `eslint.config.mjs` — ignores 新增 "mini-services/**"
|
||||
|
||||
## 不破坏的现有功能
|
||||
|
||||
- 本地排行榜(`loadLeaderboard` / `pushLeaderboardEntry`)完整保留作为离线 fallback
|
||||
- 日挑战 / 周挑战 / 限时挑战 / 信标链流程全部保留
|
||||
- 现有 localStorage keys(echo-nexus-beacon-lb-v1 / -prog-v1 / -weekly-v1 / -chain-v1 / -timed-v1)全部保留
|
||||
- 主项目 package.json 不变(hono 装在 mini-service 子项目内)
|
||||
|
||||
## 新增的 localStorage keys
|
||||
|
||||
- `echo-nexus-beacon-cloud-last-submit-v1`(上次成功提交到云端的 entry timestamp,用于全球榜高亮)
|
||||
|
||||
## 四色全息规范遵循
|
||||
|
||||
- 本地 tab:amber (#fbbf24)
|
||||
- 全球 tab:emerald (#34d399) — 与"在线/同步"语义一致
|
||||
- 玩家高亮:emerald 边框 + emerald YOU 徽章 + emerald 分数色
|
||||
- 难度色保留:routine emerald / anomaly amber / singular rose
|
||||
- 周挑战徽章:fuchsia
|
||||
- 限时挑战徽章:amber
|
||||
- 错误状态:rose (#fb7185)
|
||||
- **零蓝色/靛色违规**
|
||||
|
||||
## 截图资产
|
||||
|
||||
- `/home/z/my-project/agent-ctx/beacon-global-leaderboard.png` — 全球榜初始视图(6 条记录)
|
||||
- `/home/z/my-project/agent-ctx/beacon-global-with-you.png` — 全球榜含 YOU 高亮(7 条记录,榜首是玩家)
|
||||
- `/home/z/my-project/agent-ctx/beacon-local-leaderboard.png` — 本地榜视图
|
||||
|
||||
## 重要注意事项(供后续 agent)
|
||||
|
||||
1. **前端访问端口**:用户通过 Preview Panel 看到的是 Caddy 网关 :81,不是 Next.js :3000。所以 `fetch('/api/leaderboard?XTransformPort=3030')` 在用户视角下能正常路由。如果直接访问 :3000 会 404(这是预期行为)。
|
||||
2. **mini-service 启动方式**:用 `bun index.ts`(无 --hot)+ 双 fork `( ... & )` 模式启动。`bun --hot` 在沙盒下不稳定。
|
||||
3. **mini-service 已被 .zscripts/dev.sh 自动扫描**:下次容器重启会自动启动。但当前会话需手动启动一次。
|
||||
4. **localStorage key 命名**:`echo-nexus-beacon-cloud-last-submit-v1` 而非 `echo-nexus-beacon-cloud-lb-v1`,前者存的是单个 timestamp(用于高亮),后者若需要存全球榜快照可用后者(目前未实现,按需拉取)。
|
||||
5. **star-tide-service 端口 3031**:另一个 P3 工单已存在的服务,本服务的端口 3030 与之互不冲突。
|
||||
|
After Width: | Height: | Size: 171 KiB |
|
After Width: | Height: | Size: 236 KiB |
|
After Width: | Height: | Size: 471 KiB |
|
After Width: | Height: | Size: 243 KiB |
|
After Width: | Height: | Size: 386 KiB |
|
After Width: | Height: | Size: 372 KiB |
|
After Width: | Height: | Size: 396 KiB |
|
After Width: | Height: | Size: 394 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 221 KiB |
|
After Width: | Height: | Size: 243 KiB |
@@ -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-50,DPR cap 2,resize 监听
|
||||
- **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 + recordRun,rewardGrantedRef 防重
|
||||
|
||||
### 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) 四色全息色谱,零蓝色/靛色
|
||||
@@ -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 action;pulse/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 逻辑层
|
||||
- **类型系统**:AttributeKey(4 个键)/ CharacterAttributes(0-100 数值)/ AttributeProgressEntry(exp + 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)
|
||||
- 顶部:标题 + 待分配点数 badge(pendingAttrPoints > 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
|
||||
- ✅ 编译 < 250ms(177ms 实测)
|
||||
- ✅ 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] 数值,避免漂移
|
||||
@@ -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 CHALLENGE)section**:
|
||||
- `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 CHAIN)section**:
|
||||
- `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" + 大字号 streak(chain-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 全程无错误,所有编译 < 300ms,HTTP 200
|
||||
|
||||
### 3. 信标链逻辑测试(bun 直接运行 TS,5 个场景全 PASS)
|
||||
1. ✅ 昨日 streak=1 → 今日完成 → streak=2(normal increment)
|
||||
2. ✅ 同日重复完成 → 忽略
|
||||
3. ✅ 明日完成 → streak=3,无需 grace
|
||||
4. ✅ 隔一天 miss(lastCompleted + 2 天)→ streak=4,graceUsed=1(续命触发)
|
||||
5. ✅ 再次 miss(grace 已用)→ 断链 streak=1,graceUsed=0
|
||||
|
||||
### 4. UI 集成测试(agent-browser)
|
||||
- localStorage 模拟设置 chain state(lastCompletedDateKey=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 keys(echo-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` — 重置后干净状态
|
||||
|
After Width: | Height: | Size: 299 KiB |
|
After Width: | Height: | Size: 380 KiB |
|
After Width: | Height: | Size: 386 KiB |
|
After Width: | Height: | Size: 226 KiB |
|
After Width: | Height: | Size: 248 KiB |
|
After Width: | Height: | Size: 471 KiB |
|
After Width: | Height: | Size: 472 KiB |
|
After Width: | Height: | Size: 383 KiB |
|
After Width: | Height: | Size: 352 KiB |
|
After Width: | Height: | Size: 135 KiB |
|
After Width: | Height: | Size: 387 KiB |
|
After Width: | Height: | Size: 211 KiB |
|
After Width: | Height: | Size: 227 KiB |
|
After Width: | Height: | Size: 223 KiB |
@@ -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"
|
||||
}
|
||||
@@ -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 Markets(78.1 亿→80.1 亿)、Dataintelo(118 亿→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 16(App 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 年发布,时效性满足设计决策需要。
|
||||
@@ -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.1)MVP 范围
|
||||
|
||||
为快速验证核心乐趣,首版聚焦:
|
||||
- [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 自适应 |
|
||||
@@ -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)。
|
||||
- 状态自动持久化到 localStorage(Zustand 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`,新增碎片只需追加数据。
|
||||
- 技术树/解码规则/探险地图均数据驱动,支持后续模块化扩展。
|
||||
@@ -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` — 撤销按钮 + 卡死横幅 + 可行起点高亮
|
||||
@@ -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` — 新增探险标签页 + 仓库满仓警告 + 能量红点
|
||||
@@ -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 / consumeAchievementQueue;init/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 工单
|
||||
@@ -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` 重置星潮(清空 activeTide,lastTideEnd 设为 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/Notifier;Footer 联动;版本号 v0.3
|
||||
|
||||
## 十、下一步规划
|
||||
|
||||
- v0.3 已完成客户端星潮(原计划 socket 全局事件,现以单机版先行,后续可扩展为多人同步)
|
||||
- v0.4:云存档 + 排行榜
|
||||
- v0.5:全 5 纪元叙事扩展
|
||||
- 持续关注 Gitea 工单
|
||||
@@ -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 同步**:多人共享同一星图,协作解锁
|
||||
@@ -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 delta(2分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 云存档+排行榜会将其纳入同步范围
|
||||
@@ -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 日期 key(YYYY-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.0,90 秒完成 → 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.1(13.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 多人同步星潮(全局事件,所有在线玩家同时经历)
|
||||
@@ -0,0 +1 @@
|
||||
Here are all the generated files.
|
||||
|
After Width: | Height: | Size: 174 KiB |