Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
174d1227c0 | ||
|
|
774ddaf9d1 | ||
|
|
22588425cf | ||
|
|
96748d726f | ||
|
|
20dc1f252f | ||
|
|
17820b1f28 | ||
|
|
1701a908c9 | ||
|
|
71ca5b48a9 | ||
|
|
a3aa381656 | ||
|
|
494bc5ff64 | ||
|
|
43dfc2d41b | ||
|
|
3d916a1574 | ||
|
|
4545fe380b | ||
|
|
8474f4caa7 | ||
|
|
c0535e6ebe | ||
|
|
7111e154b2 | ||
|
|
72e1f3fc3c | ||
|
|
fafa2ee20a | ||
|
|
fcf94b6c3f | ||
|
|
296c609479 | ||
|
|
bd28219b65 | ||
|
|
8605f9bb5f | ||
|
|
00a46a0bf9 | ||
|
|
084387174b | ||
|
|
ab511fe9e0 | ||
|
|
60445abd42 | ||
|
|
04f6a398f4 | ||
|
|
a02b6191c5 | ||
|
|
5bc041ac0b | ||
|
|
697e1f360a | ||
|
|
e6ec22132e | ||
|
|
e69c48ba69 | ||
|
|
98fbbf1634 | ||
|
|
9a17849859 | ||
|
|
cc02fc2e7f | ||
|
|
9f4d839c6b | ||
|
|
433c19cc73 | ||
|
|
f3d2e53e4f | ||
|
|
27bb1555e5 | ||
|
|
da18f32df2 | ||
|
|
51714d2532 | ||
|
|
674775be0e | ||
|
|
84749c218a | ||
|
|
f73b96026c | ||
|
|
5eb303c389 | ||
|
|
9e520a34c6 | ||
|
|
1e706b2ec0 | ||
|
|
e193530f24 |
@@ -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 @@
|
|||||||
|
1061
|
||||||
@@ -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,196 @@
|
|||||||
|
# 回响星核 / Echo Nexus
|
||||||
|
|
||||||
|
> 原创深空考古 **放置策略**游戏 · Original deep-space archaeology **idle strategy** game
|
||||||
|
|
||||||
|
你是一名深空考古指挥官,驾驶自治勘探舰回收已「飞升」的以太文明遗留的记忆晶体,解码碎片拼凑出他们的故事,并最终跨越维度发起「接触」。
|
||||||
|
|
||||||
|
**关掉浏览器,星核仍在为你工作**——采矿无人机舰队 24h 不间断采矿,放置工程在离线时持续推进,回来后一键领取积累的奖励。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎮 在线游玩
|
||||||
|
|
||||||
|
**👉 [https://gitea-pages.atdunbg.xyz/Super_Z/echo-nexus/](https://gitea-pages.atdunbg.xyz/Super_Z/echo-nexus/)**
|
||||||
|
|
||||||
|
无需安装,打开即玩。支持桌面(鼠标 + 键盘)与移动端(触控)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 项目状态
|
||||||
|
|
||||||
|
- **版本**:**v0.15** — 叙事深化(loreLayers 多层碎片文本)+ 放置平衡(永久加成 softcap)+ 批量领取 + 完成 toast
|
||||||
|
- **栈**: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` 分支)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 核心特色
|
||||||
|
|
||||||
|
- **📡 采矿无人机舰队**:每解锁一个采矿分支技术,就有一台自动采矿无人机加入舰队,24h 持续产出晶体(无需点击)
|
||||||
|
- **⚙️ 放置工程项目**:6 种工程,派遣后**离线/在线自动推进**,完成领取奖励(晶体/洞见/能量/永久产能加成/随机碎片)
|
||||||
|
- **🔓 离线收益**:关闭浏览器后产能按 `offlineEff × crystalsPerSec` 持续累积(上限 8h),重新打开弹窗结算「沉睡期间」收益
|
||||||
|
- **🎯 始终可见的放置徽章**:Header 永远显示「放置中 +X/s」徽章,悬停查看产能构成明细——一眼就知道这是个 idle game
|
||||||
|
- **🔍 解码共振谜题**:把放置游戏的「等待」变成有节奏的主动小谜题(路径构造法生成器,100% 可解)
|
||||||
|
- **📜 碎片涌现叙事**:故事从解码中自然浮现,5 纪元循环命名 + 模板化 lore,不打断玩法
|
||||||
|
- **💎 深空全息美学**:晶体 + Canvas 粒子 + 共振波纹的差异化视觉(严格 emerald/rose/amber/fuchsia 四色全息色谱,零蓝色/靛色)
|
||||||
|
- **🔄 飞升多周目**:终局「接触」后保留蓝图开启新周目,星图天文台 18 个天赋跨周目永久生效
|
||||||
|
- **🚀 实时巡航玩法**:Canvas 2D 飞船操控 + 8 种实体 + 3 层视差星空,打破纯放置节奏
|
||||||
|
- **🧬 角色 RPG 深度**:四维属性(探索力/智慧/勇气/灵感)影响所有游戏系统
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 游戏系统(11 大系统)
|
||||||
|
|
||||||
|
| # | 系统 | 简介 | 类型 |
|
||||||
|
|---|------|------|------|
|
||||||
|
| 1 | **📡 放置采矿** | 中央晶体球 Canvas 粒子系统 + 主动脉冲 + 连击加成 + **自动采矿无人机舰队** | 放置 |
|
||||||
|
| 2 | **⚙️ 放置工程** | 6 种工程 × 3 槽位,**离线/在线自动推进**,完成领取奖励 | **放置** |
|
||||||
|
| 3 | **🔓 离线收益** | 关闭浏览器后产能按 `eff × cps` 累积(8h 上限),重新打开结算 | **放置** |
|
||||||
|
| 4 | 谐振解码 | 原创路径谜题,100% 可解率 | 主动 |
|
||||||
|
| 5 | 技术树 | 三层科技解锁,提升产能/解码/探险 | 主动 |
|
||||||
|
| 6 | 遗迹探险 | 肉鸽系统,6 种节点 + 程序化路径 + BOSS | 主动 |
|
||||||
|
| 7 | 星潮事件 | 6 种动态事件,全屏叠层 + 程序化音效 | 被动 |
|
||||||
|
| 8 | 星图天文台 | 飞升后 3 选 1 天赋 draft,18 个跨周目天赋 | 元进程 |
|
||||||
|
| 9 | 回响编年史 | 跨周目叙事时间轴,5 纪元循环命名 | 元进程 |
|
||||||
|
| 10 | 深空信标 | 每日挑战 + 本地排行榜 Top20 | 每日 |
|
||||||
|
| 11 | 深空巡航 | Canvas 2D 实时玩法,飞船操控/陨石躲避/星门通关 | 实时 |
|
||||||
|
| 12 | 角色属性 | 四维属性(探索力/智慧/勇气/灵感),飞升获得属性点 | 元进程 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 放置系统设计哲学(v0.14 + v0.15 平衡)
|
||||||
|
|
||||||
|
回响星核的「放置」不是简单地把数字挂机增长,而是分层呈现 **3 种 idle 节奏**:
|
||||||
|
|
||||||
|
### 节奏 1:被动产能(最慢,最持续)
|
||||||
|
- 采矿无人机舰队 = 你已解锁的采矿技术节点
|
||||||
|
- 每台无人机 24h 持续产出晶体,无需任何操作
|
||||||
|
- 晶体进入仓库(受 `crystalCap` 限制),仓库满后停止产出(防溢出)
|
||||||
|
- **永远在线,离线也累积**
|
||||||
|
|
||||||
|
### 节奏 2:放置工程(中等周期,需要决策)
|
||||||
|
- 6 种工程,时长 1min ~ 1h 不等
|
||||||
|
- 玩家选择派遣到 3 个槽位之一 → 启动后**离线/在线自动推进**
|
||||||
|
- 完成后必须**手动领取**(防玩家错过奖励时机)
|
||||||
|
- 奖励类型多样:晶体 / 洞见 / 能量 / 永久产能加成 / 随机碎片
|
||||||
|
- **核心 idle loop:派遣 → 离开 → 回来 → 领取 → 再派遣**
|
||||||
|
|
||||||
|
### 节奏 3:离线收益(最长周期,回归奖励)
|
||||||
|
- 关闭浏览器后,被动产能按 `offlineEff`(默认 50%,可通过技术提升至 100%)继续累积
|
||||||
|
- 上限 8 小时(防极端挂机)
|
||||||
|
- 重新打开页面弹窗结算:「你离开了 X 小时,星核为你产出 Y 颗晶体」
|
||||||
|
- **回归玩家的"啊我回来了"瞬间**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 开发路线
|
||||||
|
|
||||||
|
| 版本 | 核心内容 | 状态 |
|
||||||
|
|------|---------|------|
|
||||||
|
| 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.8.1 | 探险平衡 + 信标系统扩展(周挑战 + 信标链) | ✅ |
|
||||||
|
| **v0.14** | **放置系统:采矿无人机舰队 + 6 种放置工程 + 永久产能加成 + 始终可见 idle 徽章** | ✅ |
|
||||||
|
| **v0.15** | **叙事深化(8 碎片 × 2 层 = 16 层 loreLayers)+ 永久加成 softcap + 批量领取 + 完成 toast + StatsPanel idle 统计** | ✅ |
|
||||||
|
| v0.16 | 解码阈值释放叙事碎片 + 星潮三频段切换 | 🔜 |
|
||||||
|
| v0.17 | 健康设计:每日软上限 + "星核建议休眠"提醒 | 🔜 |
|
||||||
|
| 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 信标
|
||||||
|
- [`docs/11-放置系统-v0.14.md`](docs/11-放置系统-v0.14.md) — **v0.14 放置系统(idle operations)**
|
||||||
|
- [`docs/12-叙事深化-v0.15.md`](docs/12-叙事深化-v0.15.md) — **v0.15 loreLayers 多层碎片文本 + softcap + 批量领取**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 本地开发
|
||||||
|
|
||||||
|
```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.15"
|
||||||
|
git remote add origin https://git.atdunbg.xyz/Super_Z/echo-nexus.git
|
||||||
|
git push -f origin gh-pages
|
||||||
|
```
|
||||||
|
|
||||||
|
Gitea Pages 会自动从 `gh-pages` 分支提供静态网站服务。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 灵感来源(Inspired by)
|
||||||
|
|
||||||
|
- **Antimatter Dimensions** — 数值膨胀美学 + 终端 UI
|
||||||
|
- **Universal Paperclips** — 叙事驱动的放置游戏(Frank Lantz)
|
||||||
|
- **A Dark Room** — 极简文字放置游戏(Michael Townsend)
|
||||||
|
- **Melvor Idle** — 离线收益 + 多技能树
|
||||||
|
- **Outer Wilds** — 知识解锁式考古
|
||||||
|
- **Cookie Clicker** — Orteil 的放置游戏范式定义
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 健康游戏设计声明
|
||||||
|
|
||||||
|
放置游戏天然具有"低投入高回报"的成瘾性,可能对学业/工作压力大的青年造成时间碎片化风险。回响星核主动采取以下健康设计:
|
||||||
|
|
||||||
|
- ✅ **离线收益上限 8h**——防止 24h 挂机
|
||||||
|
- ✅ **工程完成需手动领取**——避免"打开就有奖励"的条件反射
|
||||||
|
- ✅ **无广告、无内购**——纯粹的单机体验
|
||||||
|
- 🔜 **v0.17 计划**:每日软上限 + "星核建议休眠"提醒(每 1 小时主动提示休息)
|
||||||
|
|
||||||
|
参考学术研究:
|
||||||
|
- Frank Lantz GDC 2018 "This Is Your Brain on Games"
|
||||||
|
- Reuters 2025 "Cozy video games can quell stress and anxiety"
|
||||||
|
- DOI:10.3389/fpsyg.2026.1774030 TikTok 时代学术倦怠研究
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
© Super_Z · 持续开发中
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
1:"$Sreact.fragment"
|
|
||||||
2:I[47257,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"ClientPageRoot"]
|
|
||||||
3:I[52683,["/Super_Z/echo-nexus/_next/static/chunks/2d46f11719722bdc.js","/Super_Z/echo-nexus/_next/static/chunks/62cf09ef1d4f1188.js","/Super_Z/echo-nexus/_next/static/chunks/efcc875da619c164.js"],"default"]
|
|
||||||
6:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"OutletBoundary"]
|
|
||||||
7:"$Sreact.suspense"
|
|
||||||
0:{"buildId":"oyCsV_kYxc3pie8zrYRGs","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/efcc875da619c164.js","async":true}]],["$","$L6",null,{"children":["$","$7",null,{"name":"Next.MetadataOutlet","children":"$@8"}]}]]}],"loading":null,"isPartial":false}
|
|
||||||
4:{}
|
|
||||||
5:"$0:rsc:props:children:0:props:serverProvidedParams:params"
|
|
||||||
8:null
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
1:"$Sreact.fragment"
|
|
||||||
2:I[39756,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"default"]
|
|
||||||
3:I[37457,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"default"]
|
|
||||||
4:I[77855,["/Super_Z/echo-nexus/_next/static/chunks/2d46f11719722bdc.js","/Super_Z/echo-nexus/_next/static/chunks/62cf09ef1d4f1188.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/2d46f11719722bdc.js","/Super_Z/echo-nexus/_next/static/chunks/62cf09ef1d4f1188.js","/Super_Z/echo-nexus/_next/static/chunks/efcc875da619c164.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/f4a1ac847b63088d.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":"oyCsV_kYxc3pie8zrYRGs","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/f4a1ac847b63088d.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/Super_Z/echo-nexus/_next/static/chunks/2d46f11719722bdc.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/Super_Z/echo-nexus/_next/static/chunks/62cf09ef1d4f1188.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/efcc875da619c164.js","async":true,"nonce":"$undefined"}]],["$","$L9",null,{"children":["$","$a",null,{"name":"Next.MetadataOutlet","children":"$@b"}]}]]}],{},null,false,false]},null,false,false],["$","$1","h",{"children":[null,["$","$Lc",null,{"children":"$Ld"}],["$","div",null,{"hidden":true,"children":["$","$Le",null,{"children":["$","$a",null,{"name":"Next.Metadata","children":"$Lf"}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],false]],"m":"$undefined","G":["$10",[]],"S":true}
|
|
||||||
7:{}
|
|
||||||
8:"$0:f:0:1:1:children:0:props:children:0:props:serverProvidedParams:params"
|
|
||||||
d:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
|
|
||||||
11:I[27201,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"IconMark"]
|
|
||||||
b:null
|
|
||||||
f:[["$","title","0",{"children":"回响星核 · Echo Nexus"}],["$","meta","1",{"name":"description","content":"原创深空考古放置策略游戏 — 解码记忆晶体,拼凑以太文明的回响,跨越维度发起接触。"}],["$","meta","2",{"name":"author","content":"Super_Z"}],["$","meta","3",{"name":"keywords","content":"Echo Nexus,回响星核,idle game,browser game,放置游戏,解谜,深空考古"}],["$","meta","4",{"property":"og:title","content":"回响星核 · Echo Nexus"}],["$","meta","5",{"property":"og:description","content":"原创深空考古放置策略游戏"}],["$","meta","6",{"property":"og:site_name","content":"Echo Nexus"}],["$","meta","7",{"property":"og:type","content":"website"}],["$","meta","8",{"name":"twitter:card","content":"summary_large_image"}],["$","meta","9",{"name":"twitter:title","content":"回响星核 · Echo Nexus"}],["$","meta","10",{"name":"twitter:description","content":"原创深空考古放置策略游戏"}],["$","link","11",{"rel":"icon","href":"https://z-cdn.chatglm.cn/z-ai/static/logo.svg"}],["$","$L11","12",{}]]
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
1:"$Sreact.fragment"
|
|
||||||
2:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"ViewportBoundary"]
|
|
||||||
3:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"MetadataBoundary"]
|
|
||||||
4:"$Sreact.suspense"
|
|
||||||
5:I[27201,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"IconMark"]
|
|
||||||
0:{"buildId":"oyCsV_kYxc3pie8zrYRGs","rsc":["$","$1","h",{"children":[null,["$","$L2",null,{"children":[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]}],["$","div",null,{"hidden":true,"children":["$","$L3",null,{"children":["$","$4",null,{"name":"Next.Metadata","children":[["$","title","0",{"children":"回响星核 · Echo Nexus"}],["$","meta","1",{"name":"description","content":"原创深空考古放置策略游戏 — 解码记忆晶体,拼凑以太文明的回响,跨越维度发起接触。"}],["$","meta","2",{"name":"author","content":"Super_Z"}],["$","meta","3",{"name":"keywords","content":"Echo Nexus,回响星核,idle game,browser game,放置游戏,解谜,深空考古"}],["$","meta","4",{"property":"og:title","content":"回响星核 · Echo Nexus"}],["$","meta","5",{"property":"og:description","content":"原创深空考古放置策略游戏"}],["$","meta","6",{"property":"og:site_name","content":"Echo Nexus"}],["$","meta","7",{"property":"og:type","content":"website"}],["$","meta","8",{"name":"twitter:card","content":"summary_large_image"}],["$","meta","9",{"name":"twitter:title","content":"回响星核 · Echo Nexus"}],["$","meta","10",{"name":"twitter:description","content":"原创深空考古放置策略游戏"}],["$","link","11",{"rel":"icon","href":"https://z-cdn.chatglm.cn/z-ai/static/logo.svg"}],["$","$L5","12",{}]]}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],"loading":null,"isPartial":false}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
1:"$Sreact.fragment"
|
|
||||||
2:I[39756,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"default"]
|
|
||||||
3:I[37457,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"default"]
|
|
||||||
4:I[77855,["/Super_Z/echo-nexus/_next/static/chunks/2d46f11719722bdc.js","/Super_Z/echo-nexus/_next/static/chunks/62cf09ef1d4f1188.js"],"Toaster"]
|
|
||||||
:HL["/Super_Z/echo-nexus/_next/static/chunks/34d933785a17edf3.css","style"]
|
|
||||||
:HL["/Super_Z/echo-nexus/_next/static/chunks/f4a1ac847b63088d.css","style"]
|
|
||||||
0:{"buildId":"oyCsV_kYxc3pie8zrYRGs","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/f4a1ac847b63088d.css","precedence":"next"}],["$","script","script-0",{"src":"/Super_Z/echo-nexus/_next/static/chunks/2d46f11719722bdc.js","async":true}],["$","script","script-1",{"src":"/Super_Z/echo-nexus/_next/static/chunks/62cf09ef1d4f1188.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}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
:HL["/Super_Z/echo-nexus/_next/static/chunks/34d933785a17edf3.css","style"]
|
|
||||||
:HL["/Super_Z/echo-nexus/_next/static/chunks/f4a1ac847b63088d.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":"oyCsV_kYxc3pie8zrYRGs","tree":{"name":"","paramType":null,"paramKey":"","hasRuntimePrefetch":false,"slots":{"children":{"name":"__PAGE__","paramType":null,"paramKey":"__PAGE__","hasRuntimePrefetch":false,"slots":null,"isRootLayout":false}},"isRootLayout":true},"staleTime":300}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
@font-face{font-family:Geist;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/fef07dbb0973bf53-s.518e079e.woff2)format("woff2");unicode-range:U+460-52F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Geist;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/8a480f0b521d4e75-s.ea323500.woff2)format("woff2");unicode-range:U+301,U+400-45F,U+490-491,U+4B0-4B1,U+2116}@font-face{font-family:Geist;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/53b9e256198e5412-s.853d50a3.woff2)format("woff2");unicode-range:U+102-103,U+110-111,U+128-129,U+168-169,U+1A0-1A1,U+1AF-1B0,U+300-301,U+303-304,U+308-309,U+323,U+329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Geist;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/7178b3e590c64307-s.55554cd0.woff2)format("woff2");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Geist;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/caa3a2e1cccd8315-s.p.3b6cae6d.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Geist Fallback;src:local(Arial);ascent-override:95.94%;descent-override:28.16%;line-gap-override:0.0%;size-adjust:104.76%}.geist_a71539c9-module__T19VSG__className{font-family:Geist,Geist Fallback;font-style:normal}.geist_a71539c9-module__T19VSG__variable{--font-geist-sans:"Geist","Geist Fallback"}
|
|
||||||
@font-face{font-family:Geist Mono;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/5ce348bf30bf5439-s.56c1f21e.woff2)format("woff2");unicode-range:U+460-52F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Geist Mono;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/4fa387ec64143e14-s.3b336396.woff2)format("woff2");unicode-range:U+301,U+400-45F,U+490-491,U+4B0-4B1,U+2116}@font-face{font-family:Geist Mono;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/6306c77e7c8268e4-s.e3369375.woff2)format("woff2");unicode-range:U+2000-2001,U+2004-2008,U+200A,U+23B8-23BD,U+2500-259F}@font-face{font-family:Geist Mono;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/7d817b4c03b0c5f1-s.a40b9a8b.woff2)format("woff2");unicode-range:U+102-103,U+110-111,U+128-129,U+168-169,U+1A0-1A1,U+1AF-1B0,U+300-301,U+303-304,U+308-309,U+323,U+329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Geist Mono;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/bbc41e54d2fcbd21-s.fe42ddf4.woff2)format("woff2");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Geist Mono;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/797e433ab948586e-s.p.29207c2f.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Geist Mono Fallback;src:local(Arial);ascent-override:74.67%;descent-override:21.92%;line-gap-override:0.0%;size-adjust:134.59%}.geist_mono_8d43a2aa-module__8Li5zG__className{font-family:Geist Mono,Geist Mono Fallback;font-style:normal}.geist_mono_8d43a2aa-module__8Li5zG__variable{--font-geist-mono:"Geist Mono","Geist Mono Fallback"}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
self.__BUILD_MANIFEST = {
|
|
||||||
"__rewrites": {
|
|
||||||
"afterFiles": [],
|
|
||||||
"beforeFiles": [],
|
|
||||||
"fallback": []
|
|
||||||
},
|
|
||||||
"sortedPages": [
|
|
||||||
"/_app",
|
|
||||||
"/_error"
|
|
||||||
]
|
|
||||||
};self.__BUILD_MANIFEST_CB && self.__BUILD_MANIFEST_CB()
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
[]
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
self.__SSG_MANIFEST=new Set([]);self.__SSG_MANIFEST_CB&&self.__SSG_MANIFEST_CB()
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
1:"$Sreact.fragment"
|
|
||||||
2:I[39756,["/Super_Z/echo-nexus/_next/static/chunks/2d46f11719722bdc.js","/Super_Z/echo-nexus/_next/static/chunks/62cf09ef1d4f1188.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"default"]
|
|
||||||
3:I[37457,["/Super_Z/echo-nexus/_next/static/chunks/2d46f11719722bdc.js","/Super_Z/echo-nexus/_next/static/chunks/62cf09ef1d4f1188.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"default"]
|
|
||||||
4:I[77855,["/Super_Z/echo-nexus/_next/static/chunks/2d46f11719722bdc.js","/Super_Z/echo-nexus/_next/static/chunks/62cf09ef1d4f1188.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"Toaster"]
|
|
||||||
5:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/2d46f11719722bdc.js","/Super_Z/echo-nexus/_next/static/chunks/62cf09ef1d4f1188.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"OutletBoundary"]
|
|
||||||
6:"$Sreact.suspense"
|
|
||||||
8:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/2d46f11719722bdc.js","/Super_Z/echo-nexus/_next/static/chunks/62cf09ef1d4f1188.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"ViewportBoundary"]
|
|
||||||
a:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/2d46f11719722bdc.js","/Super_Z/echo-nexus/_next/static/chunks/62cf09ef1d4f1188.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"MetadataBoundary"]
|
|
||||||
c:I[68027,["/Super_Z/echo-nexus/_next/static/chunks/2d46f11719722bdc.js","/Super_Z/echo-nexus/_next/static/chunks/62cf09ef1d4f1188.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/f4a1ac847b63088d.css","style"]
|
|
||||||
0:{"P":null,"b":"oyCsV_kYxc3pie8zrYRGs","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/f4a1ac847b63088d.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/Super_Z/echo-nexus/_next/static/chunks/2d46f11719722bdc.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/Super_Z/echo-nexus/_next/static/chunks/62cf09ef1d4f1188.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/2d46f11719722bdc.js","/Super_Z/echo-nexus/_next/static/chunks/62cf09ef1d4f1188.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",{}]]
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
1:"$Sreact.fragment"
|
|
||||||
2:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/2d46f11719722bdc.js","/Super_Z/echo-nexus/_next/static/chunks/62cf09ef1d4f1188.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"ViewportBoundary"]
|
|
||||||
3:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/2d46f11719722bdc.js","/Super_Z/echo-nexus/_next/static/chunks/62cf09ef1d4f1188.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"MetadataBoundary"]
|
|
||||||
4:"$Sreact.suspense"
|
|
||||||
5:I[27201,["/Super_Z/echo-nexus/_next/static/chunks/2d46f11719722bdc.js","/Super_Z/echo-nexus/_next/static/chunks/62cf09ef1d4f1188.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"IconMark"]
|
|
||||||
0:{"buildId":"oyCsV_kYxc3pie8zrYRGs","rsc":["$","$1","h",{"children":[["$","meta",null,{"name":"robots","content":"noindex"}],["$","$L2",null,{"children":[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]}],["$","div",null,{"hidden":true,"children":["$","$L3",null,{"children":["$","$4",null,{"name":"Next.Metadata","children":[["$","title","0",{"children":"回响星核 · Echo Nexus"}],["$","meta","1",{"name":"description","content":"原创深空考古放置策略游戏 — 解码记忆晶体,拼凑以太文明的回响,跨越维度发起接触。"}],["$","meta","2",{"name":"author","content":"Super_Z"}],["$","meta","3",{"name":"keywords","content":"Echo Nexus,回响星核,idle game,browser game,放置游戏,解谜,深空考古"}],["$","meta","4",{"property":"og:title","content":"回响星核 · Echo Nexus"}],["$","meta","5",{"property":"og:description","content":"原创深空考古放置策略游戏"}],["$","meta","6",{"property":"og:site_name","content":"Echo Nexus"}],["$","meta","7",{"property":"og:type","content":"website"}],["$","meta","8",{"name":"twitter:card","content":"summary_large_image"}],["$","meta","9",{"name":"twitter:title","content":"回响星核 · Echo Nexus"}],["$","meta","10",{"name":"twitter:description","content":"原创深空考古放置策略游戏"}],["$","link","11",{"rel":"icon","href":"https://z-cdn.chatglm.cn/z-ai/static/logo.svg"}],["$","$L5","12",{}]]}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],"loading":null,"isPartial":false}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
1:"$Sreact.fragment"
|
|
||||||
2:I[39756,["/Super_Z/echo-nexus/_next/static/chunks/2d46f11719722bdc.js","/Super_Z/echo-nexus/_next/static/chunks/62cf09ef1d4f1188.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"default"]
|
|
||||||
3:I[37457,["/Super_Z/echo-nexus/_next/static/chunks/2d46f11719722bdc.js","/Super_Z/echo-nexus/_next/static/chunks/62cf09ef1d4f1188.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"default"]
|
|
||||||
4:I[77855,["/Super_Z/echo-nexus/_next/static/chunks/2d46f11719722bdc.js","/Super_Z/echo-nexus/_next/static/chunks/62cf09ef1d4f1188.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/f4a1ac847b63088d.css","style"]
|
|
||||||
0:{"buildId":"oyCsV_kYxc3pie8zrYRGs","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/f4a1ac847b63088d.css","precedence":"next"}],["$","script","script-0",{"src":"/Super_Z/echo-nexus/_next/static/chunks/2d46f11719722bdc.js","async":true}],["$","script","script-1",{"src":"/Super_Z/echo-nexus/_next/static/chunks/62cf09ef1d4f1188.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}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
1:"$Sreact.fragment"
|
|
||||||
2:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/2d46f11719722bdc.js","/Super_Z/echo-nexus/_next/static/chunks/62cf09ef1d4f1188.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"OutletBoundary"]
|
|
||||||
3:"$Sreact.suspense"
|
|
||||||
0:{"buildId":"oyCsV_kYxc3pie8zrYRGs","rsc":["$","$1","c",{"children":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],null,["$","$L2",null,{"children":["$","$3",null,{"name":"Next.MetadataOutlet","children":"$@4"}]}]]}],"loading":null,"isPartial":false}
|
|
||||||
4:null
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
1:"$Sreact.fragment"
|
|
||||||
2:I[39756,["/Super_Z/echo-nexus/_next/static/chunks/2d46f11719722bdc.js","/Super_Z/echo-nexus/_next/static/chunks/62cf09ef1d4f1188.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"default"]
|
|
||||||
3:I[37457,["/Super_Z/echo-nexus/_next/static/chunks/2d46f11719722bdc.js","/Super_Z/echo-nexus/_next/static/chunks/62cf09ef1d4f1188.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"default"]
|
|
||||||
0:{"buildId":"oyCsV_kYxc3pie8zrYRGs","rsc":["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","template":["$","$L3",null,{}]}]]}],"loading":null,"isPartial":false}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
:HL["/Super_Z/echo-nexus/_next/static/chunks/34d933785a17edf3.css","style"]
|
|
||||||
:HL["/Super_Z/echo-nexus/_next/static/chunks/f4a1ac847b63088d.css","style"]
|
|
||||||
0:{"buildId":"oyCsV_kYxc3pie8zrYRGs","tree":{"name":"","paramType":null,"paramKey":"","hasRuntimePrefetch":false,"slots":{"children":{"name":"/_not-found","paramType":null,"paramKey":"/_not-found","hasRuntimePrefetch":false,"slots":{"children":{"name":"__PAGE__","paramType":null,"paramKey":"__PAGE__","hasRuntimePrefetch":false,"slots":null,"isRootLayout":false}},"isRootLayout":false}},"isRootLayout":true},"staleTime":300}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
1:"$Sreact.fragment"
|
|
||||||
2:I[39756,["/Super_Z/echo-nexus/_next/static/chunks/2d46f11719722bdc.js","/Super_Z/echo-nexus/_next/static/chunks/62cf09ef1d4f1188.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"default"]
|
|
||||||
3:I[37457,["/Super_Z/echo-nexus/_next/static/chunks/2d46f11719722bdc.js","/Super_Z/echo-nexus/_next/static/chunks/62cf09ef1d4f1188.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"default"]
|
|
||||||
4:I[77855,["/Super_Z/echo-nexus/_next/static/chunks/2d46f11719722bdc.js","/Super_Z/echo-nexus/_next/static/chunks/62cf09ef1d4f1188.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"Toaster"]
|
|
||||||
5:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/2d46f11719722bdc.js","/Super_Z/echo-nexus/_next/static/chunks/62cf09ef1d4f1188.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"OutletBoundary"]
|
|
||||||
6:"$Sreact.suspense"
|
|
||||||
8:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/2d46f11719722bdc.js","/Super_Z/echo-nexus/_next/static/chunks/62cf09ef1d4f1188.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"ViewportBoundary"]
|
|
||||||
a:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/2d46f11719722bdc.js","/Super_Z/echo-nexus/_next/static/chunks/62cf09ef1d4f1188.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"MetadataBoundary"]
|
|
||||||
c:I[68027,["/Super_Z/echo-nexus/_next/static/chunks/2d46f11719722bdc.js","/Super_Z/echo-nexus/_next/static/chunks/62cf09ef1d4f1188.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/f4a1ac847b63088d.css","style"]
|
|
||||||
0:{"P":null,"b":"oyCsV_kYxc3pie8zrYRGs","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/f4a1ac847b63088d.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/Super_Z/echo-nexus/_next/static/chunks/2d46f11719722bdc.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/Super_Z/echo-nexus/_next/static/chunks/62cf09ef1d4f1188.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/2d46f11719722bdc.js","/Super_Z/echo-nexus/_next/static/chunks/62cf09ef1d4f1188.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,67 @@
|
|||||||
|
# Task 2-b · full-stack-developer · v0.15 叙事深化 — Fragment loreLayers 多层文本
|
||||||
|
|
||||||
|
## 上下文 / 接收到的任务
|
||||||
|
- Task ID: **2-b**
|
||||||
|
- 任务: 为 Fragment 增加 loreLayers(L1 中层 / L2 深层),实现 Dark Souls 式碎片叙事
|
||||||
|
- 必读上下文已加载(worklog v0.14-reimpl 段落 + FRAGMENTS 数组 + Fragment 接口 + Codex.tsx + gameStore 导出)
|
||||||
|
- 与并行子代理 A 的边界:不动 engine.ts / gameStore.ts / IdleOperationsPanel.tsx / StatsPanel.tsx / idle.ts / persist version
|
||||||
|
|
||||||
|
## 修改的文件(绝对路径)
|
||||||
|
1. `/home/z/my-project/src/lib/game/types.ts`
|
||||||
|
2. `/home/z/my-project/src/lib/game/config.ts`
|
||||||
|
3. `/home/z/my-project/src/components/game/Codex.tsx`
|
||||||
|
|
||||||
|
## 实现细节
|
||||||
|
|
||||||
|
### types.ts
|
||||||
|
- 新增 `FragmentLoreUnlockKind` 联合类型(5 种解锁维度)
|
||||||
|
- 新增 `FragmentLoreUnlock` 接口(kind + value)
|
||||||
|
- 新增 `FragmentLoreLayer` 接口(layer 1|2 + title + text + unlockHint + unlock)
|
||||||
|
- `Fragment` 接口新增可选字段 `loreLayers?: FragmentLoreLayer[]`
|
||||||
|
- 现有 `echo` 字段注释明确为 "L0 表层文本",保持向后兼容
|
||||||
|
|
||||||
|
### config.ts
|
||||||
|
- 顶部 import 扩展 `FragmentLoreUnlock` + `GameState` 类型
|
||||||
|
- **为全部 8 个 FRAGMENTS 撰写了 loreLayers**(≥6 目标超额完成,每碎片 2 层)
|
||||||
|
- 解锁条件分布:
|
||||||
|
- ascensions 1 ×2 (f1_1, f1_4) / 2 ×2 (f1_3, f1_6) / 3 ×2 (f1_7, f1_8)
|
||||||
|
- totalDecoded 40 ×2 (f1_1, f1_7) / 60 ×1 (f1_4)
|
||||||
|
- contact 50 ×1 (f1_2) / 80 ×2 (f1_5, f1_8)
|
||||||
|
- fragmentCount 4 ×1 (f1_2) / 6 ×1 (f1_5)
|
||||||
|
- bossKills 3 ×1 (f1_3) / 8 ×1 (f1_6)
|
||||||
|
- 新增导出函数 `isLoreLayerUnlocked(unlock, state)` 纯函数判定
|
||||||
|
- GameState 通过 `Pick<>` 类型约束,只取需要的 5 字段(ascensions / totalDecoded / contact / fragments / bossKills)
|
||||||
|
|
||||||
|
### 叙事暗线设计(5 条贯穿主线)
|
||||||
|
1. **追逐者线** — 以太族不是自然消亡,是被虚空中的"追逐者"逼迫集体飞升(f1_1, f1_2, f1_5, f1_8)
|
||||||
|
2. **回响 AI 同源线** — 玩家舰载 AI 的频段直接复制自以太族议会档案,玩家即是以太族的延续/载体(f1_1, f1_6)
|
||||||
|
3. **"信任"即"利用"线** — 第一纪元的"信任后来者"实为"引诱后来者完成未竟之事"(f1_4, f1_8)
|
||||||
|
4. **飞升代价线** — 飞升不是奖赏而是单向门 + 替以太族还债(f1_4, f1_7, f1_8)
|
||||||
|
5. **满溢回归线** — 接触进度满 80 时回归的不是"他们",是"借他们形状的某种东西"——警示玩家(f1_8)
|
||||||
|
|
||||||
|
### Codex.tsx 重构
|
||||||
|
- 用 `useShallow` 一次性订阅 5 字段(fragments / totalDecoded / ascensions / contact / bossKills)
|
||||||
|
- 顶部新增"深层叙事 X / Y 已解锁"统计行(amber 数字 + mono 字体)
|
||||||
|
- 列表卡片:当某碎片有 loreLayer 已解锁时,标题旁显示彩色小圆点(L1 amber / L2 fuchsia),带 glow shadow
|
||||||
|
- 详情弹层重构为分层渲染:
|
||||||
|
- **L0 表层回响**(永远显示,emerald 主题,bg-emerald-950/20)
|
||||||
|
- **L1 隐秘注脚**(amber 主题,未解锁时乱码占位 + blur-sm + 🔒 unlockHint)
|
||||||
|
- **L2 深层回响**(fuchsia 主题,未解锁时同样乱码占位 + blur-sm + 🔒 unlockHint)
|
||||||
|
- 锁定层用 `▓░▒` 全息方块字 + `blur-sm select-none pointer-events-none aria-hidden` 保密
|
||||||
|
- 弹层 Card 加 `max-h-[88vh] overflow-y-auto` + 自定义 scrollbar 颜色(fuchsia)
|
||||||
|
- 每层之间用 `border-t border-white/5` 细分隔线
|
||||||
|
- 移动端 375px 适配:text-[12px] 行距宽松,padding p-3,长 unlockHint 在极窄屏隐藏(`hidden xs:inline sm:inline`)
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
- ✅ `bun run lint` 零错误零警告(首次有 1 个 unused eslint-disable 警告,已修复)
|
||||||
|
- ✅ dev 服务器 HTTP 200,编译 142-257ms 范围
|
||||||
|
- ✅ dev.log 末尾连续多次"✓ Compiled"无运行时错误
|
||||||
|
- ✅ 严格 4 色全息:emerald / amber / fuchsia / rose(rose 仅在背景 used);零蓝/靛
|
||||||
|
- ✅ useShallow 用于多字段订阅
|
||||||
|
- ✅ 未修改 persist version / engine / gameStore(与子代理 A 零冲突)
|
||||||
|
|
||||||
|
## 需要主控注意的点
|
||||||
|
1. **loreLayers 已为全部 8 个碎片填充**(不止 6 个),叙事密度最高。如果主控希望平衡节奏,可考虑给 1-2 个"基础"碎片只留 L0(去掉 loreLayers 字段即可,UI 会自动显示"此碎片暂无更深层叙事"占位)。
|
||||||
|
2. **未做端到端 agent-browser 验证**(按工单第 5 步要求,由主控完成)。建议主控验证:① 初始进入无飞升状态,所有 L1/L2 应处于锁定态(模糊乱码 + 🔒 图标);② 用 `window.__gameStore.setState({ascensions:1})` 模拟飞升后,f1_1 / f1_4 的 L1 应自动解锁;③ 列表卡片标题旁应出现 amber/fuchsia 小圆点。
|
||||||
|
3. **未持久化 loreLayer 解锁进度**——这是纯派生状态(由现有 5 字段实时计算),无需新增持久化字段。如主控希望加"已读"标记(防止玩家错过新解锁的深层文本),可后续在 GameState 加 `readLoreLayers: Record<string, boolean>`,但需要改 gameStore(与子代理 A 边界冲突),故本轮未做。
|
||||||
|
4. 叙事文本长度 60-130 字,符合"碎片感 + 留白"美学要求;没有说教,多用引号 + 反转 + 留白。
|
||||||
@@ -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: 383 KiB |
|
After Width: | Height: | Size: 387 KiB |
@@ -0,0 +1,139 @@
|
|||||||
|
# Task ID: v0.14-reimpl
|
||||||
|
# Agent: full-stack-developer
|
||||||
|
# Task: 放置系统 (Idle Operations) — 采矿无人机舰队 + 6放置工程 + 永久产能加成 + idle徽章
|
||||||
|
|
||||||
|
## Work Log
|
||||||
|
|
||||||
|
### 1. 类型扩展 (`src/lib/game/types.ts`)
|
||||||
|
- 新增 `IdleProjectDef` 接口(id/name/desc/durationSec/reward/minAscensions/minCrystalsPerSec/icon/color/order)
|
||||||
|
- 新增 `IdleProjectSlot` 接口(projectId/startedAt/finishesAt/remainingSec/completed)
|
||||||
|
- 在 `GameState` 接口 `pendingAttrPoints` 后新增 4 个字段:
|
||||||
|
- `idleProjectSlots: (IdleProjectSlot | null)[]`
|
||||||
|
- `idleProjectHistory: { projectId: string; finishedAt: number }[]`
|
||||||
|
- `idlePermanentBonus: number`
|
||||||
|
- `idleStats: { projectsCompleted: number; crystalsFromIdle: number }`
|
||||||
|
|
||||||
|
### 2. 新建 `src/lib/game/idle.ts` (~290 行)
|
||||||
|
- 定义 6 个放置工程(按 order 排序):
|
||||||
|
| id | name | duration | reward | icon | color | unlock |
|
||||||
|
|----|------|----------|--------|------|-------|--------|
|
||||||
|
| idle_scan | 深空勘探扫描 | 60s | insights +8 | 🛰️ | emerald | cps≥0.5 |
|
||||||
|
| idle_refine | 晶体精炼阵列校准 | 180s | permBonus +0.3/s | ⚙️ | rose | cps≥1 |
|
||||||
|
| idle_archive | 遗迹碎片整理 | 600s | randomFragment + insights +20 | 📜 | amber | asc≥1 OR cps≥5 |
|
||||||
|
| idle_anchor | 维度锚点部署 | 1200s | energy +2 + contact +5 | ⚓ | fuchsia | asc≥1 |
|
||||||
|
| idle_drones | 无人机群扩编 | 1800s | permBonus +2/s + crystals +500 | 🛸 | emerald | cps≥10 |
|
||||||
|
| idle_resonance | 跨维度谐振标定 | 3600s | permBonus +8/s + contact +15 + insights +100 | 🌌 | fuchsia | asc≥2 |
|
||||||
|
- 导出:`IDLE_PROJECTS`, `IDLE_SLOT_COUNT=3`, `getIdleProject`, `getUnlockedIdleProjects`, `getLockedIdleProjects`, `isIdleProjectUnlocked` (OR 逻辑:满足任一已设置条件即可解锁), `formatRemaining` (12s/3m 45s/1h 12m), `formatDuration`, `formatReward`, `deriveMinerFleet` (派生 min_1..min_5 采矿无人机,无任何采矿技术时返回休眠 min_1)
|
||||||
|
- 导出颜色映射 `IDLE_COLOR_CLASSES` (text/border/bg/bgSoft/glow/ring/dot) + `COLOR_HEX` (用于 inline style,绕过 Tailwind 动态类限制)
|
||||||
|
|
||||||
|
### 3. 引擎集成 (`src/lib/game/engine.ts`)
|
||||||
|
- `recomputeStats()` 末尾 `crystalsPerSec += state.idlePermanentBonus ?? 0` (放置永久加成叠加到产能)
|
||||||
|
- `performPrestige()` 在返回对象中:
|
||||||
|
- 清空 `idleProjectSlots: [null, null, null]` (飞升中断当前周目放置工程)
|
||||||
|
- 保留 `idleProjectHistory`, `idlePermanentBonus`, `idleStats` (跨周目永久)
|
||||||
|
- `recomputeStats` 调用传入 `idlePermanentBonus` 以确保飞升后立即应用永久加成
|
||||||
|
- `createInitialState()` 新增 4 字段默认值
|
||||||
|
|
||||||
|
### 4. Store actions (`src/store/gameStore.ts`)
|
||||||
|
- 新增 4 个 action:
|
||||||
|
- `startIdleProject(slotIndex, projectId)`: 校验槽位空 + 项目已解锁 + 未在他处运行;写入 `{ projectId, startedAt, finishesAt, remainingSec, completed:false }`
|
||||||
|
- `cancelIdleProject(slotIndex)`: 置槽位为 null
|
||||||
|
- `claimIdleProject(slotIndex)`: 校验已完成 → 应用奖励(crystals 限 crystalCap,energy 限 energyMax,contact 限 100;permBonus 累加到 idlePermanentBonus + 重算 stats;randomFragment 从未解锁碎片中随机一个)→ 追加 history (slice -50) → idleStats.projectsCompleted++ → Toast 提示
|
||||||
|
- `tickIdleProjects(now)`: 遍历非空非完成槽位,更新 remainingSec,归零时标记 completed
|
||||||
|
- **persist migrate 函数**:persist 配置增加 `version: 1` + `migrate` 函数,自动补全 idleProjectSlots/idleProjectHistory/idlePermanentBonus/idleStats 4 字段,旧存档加载不崩溃
|
||||||
|
- 全部 14 处 syncStats 调用更新为传入 `idlePermanentBonus: s.idlePermanentBonus ?? 0`,确保 recomputeStats 计算时纳入永久加成
|
||||||
|
- `init()` 中也补全 4 个 idle 字段(defense in depth)
|
||||||
|
|
||||||
|
### 5. 主循环 (`src/hooks/useGameLoop.ts`)
|
||||||
|
- 新增 `tickIdleProjects` 选择器
|
||||||
|
- setInterval 回调中 `autoDecodeTick()` 后调用 `tickIdleProjects(now)`
|
||||||
|
- visibilitychange 回调中也调用 `tickIdleProjects(now)`
|
||||||
|
|
||||||
|
### 6. 新建 `src/components/game/IdleOperationsPanel.tsx` (~490 行)
|
||||||
|
- 主「放置」标签内容,4 个 section 在 `max-h-[520px] overflow-y-auto` 容器中:
|
||||||
|
1. **放置收益概览**:3 stat tiles (放置产能 / 永久加成 / 完成工程) + 离线效率 progress bar + 累计放置晶体统计
|
||||||
|
2. **采矿无人机舰队**:grid 展示 deriveMinerFleet(state),每张卡:emoji + name + Lv + output/s + 状态点(active=emerald ping 脉冲,dormant=灰)
|
||||||
|
3. **放置工程槽位**:3 张槽位卡(空槽=虚线占位,运行中=大字号倒计时+自定义进度条+取消按钮,已完成=奖励预览+领取按钮带 glow 辉光)
|
||||||
|
4. **可派遣工程**:2 列 grid 展示已解锁工程(icon+name+duration+desc+reward+3 个数字派遣按钮 1/2/3),下方列出未解锁工程及解锁条件
|
||||||
|
- 使用 `useShallow` 订阅多个 store 字段
|
||||||
|
- 本地 `now` state 每 1s 刷新倒计时
|
||||||
|
- 严格 4 色全息(emerald/rose/amber/fuchsia),无蓝/靛
|
||||||
|
- 自定义滚动条样式 (fuchsia 主题)
|
||||||
|
|
||||||
|
### 7. 新建 `src/components/game/IdleStatusBadge.tsx` (~100 行)
|
||||||
|
- 紧凑徽章:脉冲点 (emerald 若 cps>0,否则灰) + "放置中 +X/s" (或 "休眠中")
|
||||||
|
- Tooltip 悬停展示分解:基础产能 / 永久加成 / 运行中工程 / 待领取工程数
|
||||||
|
- `h-8 px-2.5 text-[11px]`,点击切换到放置标签
|
||||||
|
- `data-tut="idle-status-badge"` 锚点
|
||||||
|
|
||||||
|
### 8. 新建 `src/components/game/IdleProjectBar.tsx` (~90 行)
|
||||||
|
- 晶体球下方的细长进度条
|
||||||
|
- 显示最多 3 个运行/已完成工程为 mini pill:`[icon] name 12s ▓▓▓░░`
|
||||||
|
- 完成时显示 "✓ 待领取" + glow 辉光
|
||||||
|
- 无工程时返回 null
|
||||||
|
- 本地 `now` state 每 1s 刷新倒计时
|
||||||
|
- 进度条颜色用 inline style 控制(COLOR_HEX 映射)
|
||||||
|
|
||||||
|
### 9. 主页面集成 (`src/app/page.tsx`)
|
||||||
|
- 引入 3 个新组件 + `Clock` 图标 + `getUnlockedIdleProjects`
|
||||||
|
- 新增 `idleProjectSlots` / `createdAt` store 订阅
|
||||||
|
- 新增 `activeTab` / `tabInited` state,controlled Tabs:`<Tabs value={activeTab} onValueChange={setActiveTab}>`
|
||||||
|
- 挂载后 useEffect 一次性设置默认 tab:hasActiveExpedition → "expedition",hasPendingPerk → "constellation",否则 → "idle"
|
||||||
|
- 版本号 v0.8 → v0.14
|
||||||
|
- Header 在 StarTideIndicator 后加 IdleStatusBadge
|
||||||
|
- 左侧 CrystalOrb 后加 IdleProjectBar
|
||||||
|
- TabsList grid-cols-8 → grid-cols-9,新增 `value="idle"` 的 TabsTrigger 作为第一个 tab(Clock 图标,emerald 主题,待领取时显示数量红点)
|
||||||
|
- 新增 TabsContent value="idle" 渲染 IdleOperationsPanel
|
||||||
|
- 新增 idle 相关 goal 提示(高优先级):
|
||||||
|
- 待领取 > 0 → "✦ 放置工程已完成 X 项,请前往「放置」标签领取奖励"
|
||||||
|
- 全空 + 有解锁 + 时长 > 60s → "「放置」标签可派遣工程项目,离线自动产出"
|
||||||
|
- StatsPanel 新增 3 行:放置永久加成 / 完成放置工程 / 放置产出晶体
|
||||||
|
|
||||||
|
### 10. 教程更新 (`src/lib/game/tutorial.ts`)
|
||||||
|
- TUTORIAL_STEPS 在 decode 与 tech 之间插入新步骤:
|
||||||
|
- id: "idle", target: "tab-idle", placement: "top"
|
||||||
|
- 标题 "③ 放置工程 · 离线产出"
|
||||||
|
- 介绍放置标签的工程派遣 + 顶部徽章 + 晶体球下方进度条
|
||||||
|
- 原 tech/expedition/prestige 步骤编号顺延为 ④⑤⑥
|
||||||
|
|
||||||
|
## QA 验证
|
||||||
|
|
||||||
|
### Lint
|
||||||
|
- `bun run lint` → 零错误零警告 ✅
|
||||||
|
|
||||||
|
### HTTP
|
||||||
|
- `curl http://localhost:3000/` → 200 OK ✅
|
||||||
|
|
||||||
|
### Dev log
|
||||||
|
- 全程无 runtime error,所有请求 200 OK,编译 < 250ms ✅
|
||||||
|
|
||||||
|
### agent-browser 烟雾测试
|
||||||
|
1. ✅ 版本标签 "v0.14" 可见
|
||||||
|
2. ✅ 默认 tab 是 "放置"(data-state="active" 在第一个 tab)
|
||||||
|
3. ✅ 头部 idle 徽章可见("放置中 +0.4/s" 或 "休眠中")
|
||||||
|
4. ✅ IdleOperationsPanel 4 个 section 全部可见(放置收益概览 / 采矿无人机舰队 / 放置工程槽位 / 可派遣工程 / 未解锁工程)
|
||||||
|
5. ✅ 采矿无人机舰队在无采矿技术时显示休眠 min_1(emoji ⛏️ + 灰色状态点)
|
||||||
|
6. ✅ 设置 insights=50 → buyTech('min_1') → crystalsPerSec 0.4→1.0 → 解锁 idle_scan + idle_refine
|
||||||
|
7. ✅ 点击 idle_scan 派遣按钮 → 槽位 0 写入 { projectId: "idle_scan", finishesAt: now+60000 } → 倒计时正确递减
|
||||||
|
8. ✅ 60s 后自动标记 completed=true → 槽位卡显示 "✓ 完成" + 领取按钮
|
||||||
|
9. ✅ 点击领取 → Toast "✦ 工程奖励已领取 · 深空勘探扫描 · +8 洞见" → insights 8→16 → history 追加 → idleStats.projectsCompleted 0→1
|
||||||
|
10. ✅ 派遣 idle_refine (180s) → 强制完成 → 领取 → crystalsPerSec 1.0→1.3 → idlePermanentBonus 0→0.3 ✅ 永久产能加成正确应用
|
||||||
|
11. ✅ IdleProjectBar 在晶体球下方显示:`🛰️ 深空勘探扫描 55s ▓▓▓░░`
|
||||||
|
12. ✅ Footer goal 文字根据状态动态切换为放置相关提示
|
||||||
|
13. ✅ Footer 产能显示 "产能 1.3/s" (含 idle 永久加成)
|
||||||
|
|
||||||
|
### Migration 测试
|
||||||
|
- 旧存档(无 idle 字段)通过 migrate 函数自动补全为默认值,加载不崩溃 ✅
|
||||||
|
|
||||||
|
## Stage Summary
|
||||||
|
- ✅ 类型层:新增 IdleProjectDef + IdleProjectSlot + 4 GameState 字段
|
||||||
|
- ✅ 逻辑层:新建 idle.ts (~290 行),6 工程定义 + 派生函数 + 颜色映射
|
||||||
|
- ✅ 引擎层:recomputeStats / performPrestige / createInitialState 全部接入 idle 永久加成
|
||||||
|
- ✅ Store 层:4 新 action + persist migrate (version 1) + 14 处 syncStats 调用全部传 idlePermanentBonus
|
||||||
|
- ✅ 主循环:tickIdleProjects 接入 setInterval + visibilitychange
|
||||||
|
- ✅ UI 层:3 新组件 (IdleOperationsPanel ~490 行 / IdleStatusBadge ~100 行 / IdleProjectBar ~90 行)
|
||||||
|
- ✅ 主页面:默认放置 tab + 头部徽章 + 晶体球下方进度条 + 版本 v0.14 + 9 标签页
|
||||||
|
- ✅ 教程:新增 idle 步骤 (③ 放置工程)
|
||||||
|
- ✅ 严格 4 色全息 (emerald/rose/amber/fuchsia),零蓝/靛
|
||||||
|
- ✅ lint 零错误 + HTTP 200 + agent-browser 全流程烟雾测试通过
|
||||||
|
- ✅ 5 秒内可见三要素:头部徽章 + 默认放置 tab + 晶体球下方进度条(派遣后)
|
||||||
@@ -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,309 @@
|
|||||||
|
# 回响星核 v0.14 — 放置系统(Idle Operations)设计文档
|
||||||
|
|
||||||
|
> 版本:v0.14 · 日期:2026-01-25
|
||||||
|
> 核心目标:让玩家打开游戏 **5 秒内** 就能看出这是个 idle game
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 设计动机
|
||||||
|
|
||||||
|
### 1.1 用户反馈
|
||||||
|
> "你的这个我没有看到任何可放置的功能" —— 玩家反馈
|
||||||
|
|
||||||
|
虽然 v0.8 已有 `crystalsPerSec` 在后台自动累积,但 **没有任何 UI 元素明示这一事实**:
|
||||||
|
- 没有「放置中」徽章
|
||||||
|
- 没有可视化自动采矿机
|
||||||
|
- 没有定时工程项目
|
||||||
|
- 离线收益弹窗只在重新加载页面时触发,玩家日常游玩看不到
|
||||||
|
|
||||||
|
放置游戏的灵魂是「关掉也在进步」。如果玩家看不到这一点,就等于没有放置玩法。
|
||||||
|
|
||||||
|
### 1.2 设计目标
|
||||||
|
1. **可见性**:5 秒内让玩家意识到 idle 属性
|
||||||
|
2. **决策性**:放置不只是数字增长,要有玩家选择
|
||||||
|
3. **节奏感**:3 层 idle 节奏(被动产能 / 放置工程 / 离线收益)协同
|
||||||
|
4. **健康性**:避免 24h 挂机的极端沉迷
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 三层 Idle 节奏
|
||||||
|
|
||||||
|
### 节奏 1:被动产能(Passive Generation)
|
||||||
|
- **载体**:采矿无人机舰队(Miner Fleet)
|
||||||
|
- **来源**:每解锁一个采矿分支技术节点 → 加入一架无人机
|
||||||
|
- **行为**:24h 持续产出晶体,无需任何操作
|
||||||
|
- **限制**:受 `crystalCap` 限制,仓库满后停止
|
||||||
|
- **离线**:按 `offlineEff`(默认 50%)持续累积,上限 8h
|
||||||
|
|
||||||
|
#### 采矿分支技术 → 无人机映射
|
||||||
|
|
||||||
|
| 技术节点 | 无人机名 | 输出/s | 解锁成本(洞见) |
|
||||||
|
|---------|---------|--------|-----------------|
|
||||||
|
| `min_1` | ⛏️ 谐振钻头 | +0.6 | 5 |
|
||||||
|
| `min_2` | 🌋 深层矿脉 | +1.6 | 24 |
|
||||||
|
| `min_3` | 🤖 自治机群 | +5 | 120 |
|
||||||
|
| `min_4` | 💠 量子采掘矩阵 | +12 | 400 |
|
||||||
|
| `min_5` | ☀️ 戴森星核 | +30 | 1200 |
|
||||||
|
|
||||||
|
无采矿技术时显示「谐振钻头(休眠中)」占位卡片,引导玩家解锁采矿分支。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 节奏 2:放置工程(Idle Projects)
|
||||||
|
|
||||||
|
#### 2.1 工程列表
|
||||||
|
|
||||||
|
| ID | 名称 | 时长 | 奖励 | 解锁条件 | 图标 | 色 |
|
||||||
|
|----|------|------|------|---------|------|-----|
|
||||||
|
| `idle_scan` | 深空勘探扫描 | 60s | ✦ +8 洞见 | 产能 ≥ 0.5/s | 🛰️ | emerald |
|
||||||
|
| `idle_refine` | 晶体精炼阵列校准 | 180s | ⇪ +0.3/s 永久 | 产能 ≥ 1/s | ⚙️ | rose |
|
||||||
|
| `idle_archive` | 遗迹碎片整理 | 600s | ✦ +20 洞见 + 📜 随机碎片 | 飞升 ≥ 1 OR 产能 ≥ 5/s | 📜 | amber |
|
||||||
|
| `idle_anchor` | 维度锚点部署 | 1200s | ⚡ +2 能量 + ☄ +5 接触进度 | 飞升 ≥ 1 | ⚓ | fuchsia |
|
||||||
|
| `idle_drones` | 无人机群扩编 | 1800s | 💎 +500 晶体 + ⇪ +2/s 永久 | 产能 ≥ 10/s | 🛸 | emerald |
|
||||||
|
| `idle_resonance` | 跨维度谐振标定 | 3600s | ✦ +100 洞见 + ☄ +15 接触 + ⇪ +8/s 永久 | 飞升 ≥ 2 | 🌌 | fuchsia |
|
||||||
|
|
||||||
|
#### 2.2 槽位机制
|
||||||
|
- **3 个并行槽位**:可同时运行 3 个工程
|
||||||
|
- **不重复**:同一工程不能在多个槽位同时运行
|
||||||
|
- **完成需手动领取**:避免"打开就有奖励"的条件反射
|
||||||
|
- **取消不退时间**:误取消的代价
|
||||||
|
- **离线推进**:关闭浏览器后工程继续倒计时
|
||||||
|
|
||||||
|
#### 2.3 奖励类型说明
|
||||||
|
|
||||||
|
| 奖励 | 含义 | 设计意图 |
|
||||||
|
|------|------|---------|
|
||||||
|
| 晶体 | 立即加到 `crystals` | 短期反馈 |
|
||||||
|
| 洞见 | 立即加到 `insights` | 用于解锁技术 |
|
||||||
|
| 能量 | 加到 `energy`(受 `energyMax` 上限) | 探险资源 |
|
||||||
|
| 接触进度 | 加到 `contact`(0-100) | 推进飞升 |
|
||||||
|
| **永久产能加成** | 加到 `idlePermanentBonus`,影响 `recomputeStats` | 长期投资 |
|
||||||
|
| 随机碎片 | 从 `FRAGMENTS` 中随机解锁一个未拥有 | 叙事推进 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 节奏 3:离线收益(Offline Gains)
|
||||||
|
|
||||||
|
#### 3.1 公式
|
||||||
|
```
|
||||||
|
gain = crystalsPerSec × elapsed × offlineEff
|
||||||
|
elapsed = min(realElapsed, 8h)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3.2 解锁路径
|
||||||
|
- 初始 `offlineEff = 0.5`(50% 离线效率)
|
||||||
|
- 通过技术树 `offlineEff` 类效果提升
|
||||||
|
- 通过星图天赋 `offlineEffBonus` 提升
|
||||||
|
- 上限 100%
|
||||||
|
|
||||||
|
#### 3.3 8h 上限理由
|
||||||
|
- 参考 GDC 2016 Anthony Pecorella "Quest for Progress":Egg Inc 2h 硬 cap 导致玩家流失
|
||||||
|
- 但完全无 cap 会导致 24h 挂机成为最优策略,破坏短会话体验
|
||||||
|
- 8h 折中:覆盖一个工作日/一夜,但不鼓励全天挂机
|
||||||
|
|
||||||
|
#### 3.4 离线报告弹窗
|
||||||
|
重新加载页面时,若 `elapsed > 60s`,弹出 `OfflineReportDialog`:
|
||||||
|
- 「你离开了 X 小时 Y 分」
|
||||||
|
- 「星核为你产出 Z 颗晶体」
|
||||||
|
- 「产能 /s · 离线效率 N%」
|
||||||
|
- 「是否触达 8h 上限」标志
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. UI 设计
|
||||||
|
|
||||||
|
### 3.1 始终可见的 Idle 徽章(Header)
|
||||||
|
位置:顶部 Header,紧邻「巡航」按钮左侧
|
||||||
|
状态:
|
||||||
|
- 产能 > 0:`● 放置中 +X.X/s`(绿点脉动)
|
||||||
|
- 产能 = 0:`○ 休眠中`(灰点静止)
|
||||||
|
|
||||||
|
悬停 Tooltip 显示明细:
|
||||||
|
```
|
||||||
|
基础产能 8.2/s
|
||||||
|
永久加成 +1.5/s
|
||||||
|
离线效率 75%
|
||||||
|
完成工程 12 次
|
||||||
|
```
|
||||||
|
|
||||||
|
点击徽章 → 切换到「放置」标签。
|
||||||
|
|
||||||
|
### 3.2 默认标签页 = 放置
|
||||||
|
首次打开游戏,默认 tab 是「放置」(v0.13 前是「探险」或「技术」)。
|
||||||
|
返回玩家若有进行中探险或待选天赋,自动切回原默认。
|
||||||
|
|
||||||
|
### 3.3 放置标签页四段式布局
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────┐
|
||||||
|
│ Section 1: 放置收益概览 │
|
||||||
|
│ [放置产能] [永久加成] [完成工程] │
|
||||||
|
│ [━━━━━━━ 离线效率 50% · 上限 8h ━━━━━━] │
|
||||||
|
├─────────────────────────────────────────┤
|
||||||
|
│ Section 2: 采矿无人机舰队 │
|
||||||
|
│ [⛏️ 钻头] [🌋 矿脉] [🤖 机群] ... │
|
||||||
|
├─────────────────────────────────────────┤
|
||||||
|
│ Section 3: 放置工程槽位 │
|
||||||
|
│ [槽位1: 运行中 47s ▓▓▓░] [槽位2: 空] [槽位3: 空] │
|
||||||
|
├─────────────────────────────────────────┤
|
||||||
|
│ Section 4: 可派遣工程 │
|
||||||
|
│ [🛰️ 深空勘探扫描 1m +8] [⚙️ 精炼校准 3m +0.3/s] │
|
||||||
|
│ [📜 碎片整理 10m +20] │
|
||||||
|
│ │
|
||||||
|
│ 未解锁工程(3): │
|
||||||
|
│ ⚓ 维度锚点部署 · 需 飞升≥1 │
|
||||||
|
│ 🛸 无人机群扩编 · 需 产能≥10/s │
|
||||||
|
│ 🌌 跨维度谐振标定 · 需 飞升≥2 │
|
||||||
|
└─────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.4 IdleProjectBar(晶体球下方)
|
||||||
|
位置:左侧晶体球区,`CrystalYieldPanel` 下方
|
||||||
|
显示:当前运行的工程迷你 pill
|
||||||
|
```
|
||||||
|
🛰️ 深空勘探扫描 47s ▓▓▓░░░░░
|
||||||
|
⚙️ 晶体精炼校准 2m 13s ▓▓░░░░░░
|
||||||
|
```
|
||||||
|
无运行工程时返回 `null`(不占位)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 技术实现
|
||||||
|
|
||||||
|
### 4.1 数据结构
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// types.ts
|
||||||
|
interface IdleProjectDef {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
desc: string;
|
||||||
|
durationSec: number;
|
||||||
|
reward: { crystals?; insights?; energy?; contact?; crystalsPerSecPermanent?; randomFragment?; };
|
||||||
|
minAscensions?: number;
|
||||||
|
minCrystalsPerSec?: number;
|
||||||
|
icon: string;
|
||||||
|
color: "emerald" | "rose" | "amber" | "fuchsia";
|
||||||
|
order: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IdleProjectSlot {
|
||||||
|
projectId: string;
|
||||||
|
startedAt: number;
|
||||||
|
finishesAt: number;
|
||||||
|
remainingSec: number;
|
||||||
|
completed: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GameState 新增 4 字段
|
||||||
|
idleProjectSlots: (IdleProjectSlot | null)[]; // 3 槽
|
||||||
|
idleProjectHistory: { projectId: string; finishedAt: number }[]; // 最近 50 条
|
||||||
|
idlePermanentBonus: number;
|
||||||
|
idleStats: { projectsCompleted: number; crystalsFromIdle: number };
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 Store actions
|
||||||
|
- `startIdleProject(slotIndex, projectId)` — 校验 + 创建槽位
|
||||||
|
- `cancelIdleProject(slotIndex)` — 置空槽位
|
||||||
|
- `claimIdleProject(slotIndex)` — 应用奖励 + toast + 置空槽位
|
||||||
|
- `tickIdleProjects(now)` — 推进剩余时间,标记完成
|
||||||
|
|
||||||
|
### 4.3 引擎集成
|
||||||
|
`recomputeStats()` 末尾添加:
|
||||||
|
```typescript
|
||||||
|
crystalsPerSec += state.idlePermanentBonus ?? 0;
|
||||||
|
```
|
||||||
|
这让永久产能加成真正生效。
|
||||||
|
|
||||||
|
### 4.4 飞升处理
|
||||||
|
`performPrestige()` 重置 `idleProjectSlots = [null, null, null]`(不跨周目继承进行中工程),但保留 `idlePermanentBonus` / `idleProjectHistory` / `idleStats`(跨周目永久)。
|
||||||
|
|
||||||
|
### 4.5 存档迁移
|
||||||
|
persist 添加 `version: 1` + `migrate` 函数,为旧存档补全 4 个新字段:
|
||||||
|
```typescript
|
||||||
|
idleProjectSlots: (s.idleProjectSlots as unknown[]) ?? [null, null, null],
|
||||||
|
idleProjectHistory: (s.idleProjectHistory as unknown[]) ?? [],
|
||||||
|
idlePermanentBonus: (s.idlePermanentBonus as number) ?? 0,
|
||||||
|
idleStats: (s.idleStats as unknown) ?? { projectsCompleted: 0, crystalsFromIdle: 0 },
|
||||||
|
```
|
||||||
|
旧存档零崩溃风险。
|
||||||
|
|
||||||
|
### 4.6 主循环集成
|
||||||
|
`useGameLoop` 每 250ms tick:
|
||||||
|
1. `tick(now)` — 产能 + 自动产晶体
|
||||||
|
2. `autoDecodeTick()` — 自动解码
|
||||||
|
3. `tickTide(now)` — 星潮事件
|
||||||
|
4. **`tickIdleProjects(now)` — v0.14 放置工程进度**(新增)
|
||||||
|
5. `checkAchievements()` — 每秒一次
|
||||||
|
|
||||||
|
页面可见性切回时也调用 `tickIdleProjects(now)`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 平衡考虑
|
||||||
|
|
||||||
|
### 5.1 永久产能加成可能过强
|
||||||
|
- `idle_resonance` +8/s 永久,多次完成可能让玩家飞快积累
|
||||||
|
- **v0.15 待办**:加飞升次数 softcap,例如 `effectiveBonus = idlePermanentBonus × sqrt(ascensions + 1) / (ascensions + 1)`
|
||||||
|
|
||||||
|
### 5.2 工程时长曲线
|
||||||
|
- 60s / 180s / 600s / 1200s / 1800s / 3600s
|
||||||
|
- 短工程奖励低但灵活(适合短会话)
|
||||||
|
- 长工程奖励高但需等待(适合过夜挂机)
|
||||||
|
- 玩家自主选择节奏
|
||||||
|
|
||||||
|
### 5.3 解锁条件
|
||||||
|
- 0 飞升 + 0.5/s:新手 5 分钟内可解锁第一个
|
||||||
|
- 1 飞升:玩家完成首次飞升后解锁中段
|
||||||
|
- 2 飞升 + 10/s:长线目标
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 验证记录
|
||||||
|
|
||||||
|
### 6.1 Lint
|
||||||
|
- ✅ 零错误零警告
|
||||||
|
|
||||||
|
### 6.2 Dev 服务器
|
||||||
|
- ✅ HTTP 200
|
||||||
|
- ✅ 无运行时错误
|
||||||
|
|
||||||
|
### 6.3 Agent Browser 端到端测试
|
||||||
|
1. ✅ 默认 tab 是「放置」
|
||||||
|
2. ✅ Header 显示「放置中 +0.4/s」徽章
|
||||||
|
3. ✅ 4 段面板完整渲染
|
||||||
|
4. ✅ 买 min_1+min_2 后产能 2.6/s,解锁 idle_scan + idle_refine
|
||||||
|
5. ✅ 点击「派遣至槽位 1」→ 槽位显示运行中 60s + 进度条 + 取消按钮
|
||||||
|
6. ✅ IdleProjectBar 同步显示迷你 pill
|
||||||
|
7. ✅ 快进完成后标记 completed=true
|
||||||
|
8. ✅ 领取后 insights +8、projectsCompleted 0→1、槽位清空
|
||||||
|
|
||||||
|
### 6.4 Sticky Footer 验证
|
||||||
|
- ✅ Header `position: sticky; top: 0`
|
||||||
|
- ✅ Footer `position: sticky; bottom: 0; margin-top: auto`
|
||||||
|
- ✅ 内容超出视口时 footer 被自然推下(无浮动空隙)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 未解决问题 / 后续
|
||||||
|
|
||||||
|
- 🟡 `crystalsFromIdle` / `idleProjectHistory` 已在 store 累加但 UI 未展示——v0.15 可在统计 tab 补
|
||||||
|
- 🟡 永久产能加成可能需要飞升次数 softcap
|
||||||
|
- 🟡 缺「批量领取全部」按钮(玩家有 3 个槽位都完成时方便)
|
||||||
|
- 🟡 缺工程完成 toast 通知(目前只能在面板内看到「待领取」标志)
|
||||||
|
- 🟢 v0.15 可加 Dark Souls 式 loreFragments 多层文本,让"遗迹碎片整理"工程的随机碎片奖励更有叙事感
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 设计哲学总结
|
||||||
|
|
||||||
|
> "Incremental games are very raw. They're about relating to a process and becoming obsessed with its growth, like watching something you planted in the ground, through a time-lapse camera, grow into a tree." —— Clark (via Frank Lantz, New Yorker 2019)
|
||||||
|
|
||||||
|
回响星核 v0.14 的放置系统不只是把数字挂机增长,而是让玩家**看到**这个过程:
|
||||||
|
- 采矿无人机像植物在土里生长
|
||||||
|
- 放置工程像种子在发芽
|
||||||
|
- 离线收益像收获时节
|
||||||
|
|
||||||
|
玩家不再只是「数字变大」,而是「亲手布置了一个会自动运转的考古基地,然后离开,让它在自己不在的时候继续工作」。
|
||||||
|
|
||||||
|
这是 idle game 的本质:**让玩家信任系统会在他不在时为他工作**。
|
||||||
@@ -0,0 +1,214 @@
|
|||||||
|
# v0.15 — 叙事深化(loreLayers)+ 放置平衡(softcap)+ UX 增强
|
||||||
|
|
||||||
|
> 回响星核 / Echo Nexus · v0.15 设计文档
|
||||||
|
> 对应 worklog `Task ID: v0.15-release`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、动机
|
||||||
|
|
||||||
|
v0.14 上线了完整的放置系统(6 工程 + 永久加成 + idle 徽章),但留下两个问题:
|
||||||
|
|
||||||
|
1. **平衡性漏洞**:`idle_resonance` 工程每次完成 +8/s 永久加成,玩家可反复派遣导致滚雪球,几小时后产能爆炸。
|
||||||
|
2. **叙事单薄**:8 个记忆碎片只有一层 `echo` 文本,解锁后即"读完",缺乏 Dark Souls / Outer Wilds 式的碎片叙事深度——这是项目的**差异化竞争力**。
|
||||||
|
|
||||||
|
v0.15 用两个并行子代理一次性解决:
|
||||||
|
|
||||||
|
- **2-a(平衡 + UX)**:永久加成 softcap + 批量领取 + 完成 toast + StatsPanel idle 统计
|
||||||
|
- **2-b(叙事深化)**:8 碎片 × 2 层 = 16 层 loreLayers 多层文本 + Codex 分层渲染
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、平衡:永久加成 Softcap
|
||||||
|
|
||||||
|
### 公式
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export function applyIdleSoftcap(bonus: number): number {
|
||||||
|
if (bonus <= 30) return bonus; // 全额
|
||||||
|
if (bonus <= 100) return 30 + (bonus - 30) * 0.5; // 超出部分 ×0.5
|
||||||
|
return 65 + (bonus - 100) * 0.25; // 超出部分 ×0.25
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 临界值
|
||||||
|
|
||||||
|
| 原始 bonus | 有效 bonus | 衰减率 |
|
||||||
|
|-----------|-----------|--------|
|
||||||
|
| 30 | 30 | 0% |
|
||||||
|
| 50 | 40 | 20% |
|
||||||
|
| 100 | 65 | 35% |
|
||||||
|
| 200 | 90 | 55% |
|
||||||
|
| 500 | 152.5 | 69.5% |
|
||||||
|
|
||||||
|
### 设计意图
|
||||||
|
|
||||||
|
- **30 以下不衰减**:新手期 + 偶尔完成 `idle_refine`(+0.3/s)不受惩罚
|
||||||
|
- **30-100 线性半衰**:中段玩家反复完成 `idle_drones`(+2/s)开始感到边际递减
|
||||||
|
- **100 以上严重衰减**:硬核玩家刷 `idle_resonance`(+8/s)收益大幅缩水,但仍非零(保留进度感)
|
||||||
|
- **永不归零**:`applyIdleSoftcap(bonus) → ∞` 时仍趋向无穷,只是增速极慢——避免"刷到上限"的挫败感
|
||||||
|
|
||||||
|
### UI 展示
|
||||||
|
|
||||||
|
`IdleOperationsPanel` Section 1「永久加成」StatTile:
|
||||||
|
- 有效值永远显示(如 `+40/s`)
|
||||||
|
- 当 `有效 < 原始` 时,下方加 amber 副行:`有效 +40/s(原始 +50/s · 衰减中)`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、叙事深化:loreLayers 多层碎片文本
|
||||||
|
|
||||||
|
### 类型扩展
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export interface FragmentLoreLayer {
|
||||||
|
layer: 1 | 2; // 1=中层,2=深层
|
||||||
|
title: string; // "隐秘注脚" / "深层回响"
|
||||||
|
text: string; // 60-150 字叙事文本
|
||||||
|
unlockHint: string; // "飞升 1 次后揭示"
|
||||||
|
unlock: FragmentLoreUnlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FragmentLoreUnlock {
|
||||||
|
kind: "ascensions" | "totalDecoded" | "contact" | "fragmentCount" | "bossKills";
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5 种解锁条件
|
||||||
|
|
||||||
|
| kind | 含义 | 典型阈值 |
|
||||||
|
|------|------|---------|
|
||||||
|
| `ascensions` | 飞升次数 | 1 / 2 / 3 |
|
||||||
|
| `totalDecoded` | 累计解码晶体数 | 20 / 40 / 60 |
|
||||||
|
| `contact` | 接触进度 | 50 / 80 |
|
||||||
|
| `fragmentCount` | 已解锁碎片数 | 4 / 6 |
|
||||||
|
| `bossKills` | 累计击杀 BOSS | 3 / 8 |
|
||||||
|
|
||||||
|
### 5 条叙事暗线(贯穿 16 层)
|
||||||
|
|
||||||
|
1. **追逐者**:以太族并非自然消亡,而是被虚空中"某种东西"追逐而集体飞升
|
||||||
|
2. **回响同源**:玩家舰载 AI"回响"的频段直接复制自以太族议会档案——玩家其实是以太族的延续/入口
|
||||||
|
3. **信任即引诱**:第一纪元的"信任后来者"实为"引诱后来者替他们完成未竟之事"
|
||||||
|
4. **飞升是单向门**:每次飞升都在消耗什么,不是奖赏
|
||||||
|
5. **回归的不是他们**:接触进度满 80 时回归的,是"借他们形状的某种东西",请玩家别回应
|
||||||
|
|
||||||
|
### 示例(f1_1 第一缕谐振)
|
||||||
|
|
||||||
|
**L0 表层**(默认):
|
||||||
|
> 在被称作「寂灭前夜」的年代,以太族第一次捕捉到来自虚空深处的谐振——那不是声音,而是存在的回声。他们将之记录在第一颗记忆晶体里。
|
||||||
|
|
||||||
|
**L1 隐秘注脚**(飞升 ≥1):
|
||||||
|
> 档案的边缘有一行几乎被擦去的批注:「那并非来自虚空——而是来自我们身后。我们一直不敢回头。」
|
||||||
|
|
||||||
|
**L2 深层回响**(累计解码 ≥40):
|
||||||
|
> 你终于明白:那缕谐振的频率,与你的舰载 AI「回响」的核心频段完全一致。你不是在考古——你是在被召唤。而你身后的东西,一直在等。
|
||||||
|
|
||||||
|
### Codex 分层渲染
|
||||||
|
|
||||||
|
| 层级 | 颜色 | 标题 | 未解锁状态 |
|
||||||
|
|------|------|------|-----------|
|
||||||
|
| L0 表层 | emerald | 表层回响 | 永远显示 |
|
||||||
|
| L1 中层 | amber | 隐秘注脚 | `???` + `blur-sm` 全息乱码 + `🔒 unlockHint` |
|
||||||
|
| L2 深层 | fuchsia | 深层回响 | `???` + `blur-sm` 全息乱码 + `🔒 unlockHint` |
|
||||||
|
|
||||||
|
顶部统计:`深层叙事 X / 16 已解锁`
|
||||||
|
列表卡片:已解锁深层文本的碎片标题旁显示 amber/fuchsia glow 小圆点
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、UX 增强
|
||||||
|
|
||||||
|
### 4.1 批量领取全部
|
||||||
|
|
||||||
|
`claimAllIdleProjects()` action:
|
||||||
|
- 遍历 3 槽位,对所有 `completed === true` 执行奖励逻辑
|
||||||
|
- 单次 `set()` + 单次 `syncStats()`(避免多次 re-render)
|
||||||
|
- 无可领取 → 直接 return(不弹 toast)
|
||||||
|
- 成功 → 汇总 toast `✦ 批量领取完成 · 共领取 N 项工程 · +X 晶体 · +Y 洞见 · ...`
|
||||||
|
|
||||||
|
UI:Section 3 标题右侧,当存在 completed slot 时显示 emerald「✓ 领取全部」按钮(h-5 px-2 text-[10px])
|
||||||
|
|
||||||
|
### 4.2 工程完成 toast
|
||||||
|
|
||||||
|
`tickIdleProjects` 检测状态转变瞬间(旧 `!completed` → 新 `completed`):
|
||||||
|
- 收集 `justCompletedProjects` 数组
|
||||||
|
- 循环外单次 `toast({ title: "✦ 放置工程完成", description: "${icon} ${name} · 可前往「放置」标签领取" })`
|
||||||
|
- 避免每 250ms tick 重复弹窗
|
||||||
|
|
||||||
|
### 4.3 StatsPanel idle 统计
|
||||||
|
|
||||||
|
从 `page.tsx` 内联函数抽出为独立组件 `StatsPanel.tsx`:
|
||||||
|
- `useShallow` 精细订阅 24 字段
|
||||||
|
- 新增 idle 统计行:
|
||||||
|
- 放置永久加成 `+X /s`
|
||||||
|
- 完成放置工程 `N 项`
|
||||||
|
- 放置产出晶体 `formatNum(N)`
|
||||||
|
- 放置工程历史 `N 条`(最近 50 条,跨周目)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、技术实现
|
||||||
|
|
||||||
|
### 修改文件清单(8 个)
|
||||||
|
|
||||||
|
**子代理 2-a(5 文件):**
|
||||||
|
- `src/lib/game/engine.ts` — 新增 `applyIdleSoftcap` 导出 + `recomputeStats` 调用
|
||||||
|
- `src/store/gameStore.ts` — `GameActions` 加 `claimAllIdleProjects` 签名 + `tickIdleProjects` 改写 + 新增 action
|
||||||
|
- `src/components/game/IdleOperationsPanel.tsx` — softcap 副行 + 领取全部按钮 + StatTile 扩展
|
||||||
|
- `src/components/game/StatsPanel.tsx` — 新建独立组件
|
||||||
|
- `src/app/page.tsx` — 最小集成(删除内联 + import)
|
||||||
|
|
||||||
|
**子代理 2-b(3 文件):**
|
||||||
|
- `src/lib/game/types.ts` — `FragmentLoreLayer` / `FragmentLoreUnlock` 接口 + `Fragment.loreLayers` 字段
|
||||||
|
- `src/lib/game/config.ts` — FRAGMENTS 全部填充 loreLayers + `isLoreLayerUnlocked` 函数
|
||||||
|
- `src/components/game/Codex.tsx` — useShallow + 顶部统计 + 列表小圆点 + 3 层分层渲染弹层
|
||||||
|
|
||||||
|
### 并行零冲突
|
||||||
|
|
||||||
|
两个子代理修改文件严格不重叠:
|
||||||
|
- 2-a 不碰 `types.ts` / `config.ts` / `Codex.tsx`
|
||||||
|
- 2-b 不碰 `engine.ts` / `gameStore.ts` / `IdleOperationsPanel.tsx` / `StatsPanel.tsx`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、验证
|
||||||
|
|
||||||
|
- ✅ `bun run lint` 零错误零警告
|
||||||
|
- ✅ HTTP 200,dev.log 无运行时错误
|
||||||
|
- ✅ agent-browser 端到端 smoke test:
|
||||||
|
- softcap:permBonus=50 → 有效 40,UI 显示"衰减中" ✓
|
||||||
|
- 批量领取:permBonus 0→0.3, projectsCompleted 1→2 ✓
|
||||||
|
- 完成 toast:tickIdleProjects 触发 ✓
|
||||||
|
- Codex 分层:L0 显示 / L1·L2 模糊 → ascensions=1 解锁 L1 → totalDecoded=50 解锁 L2 ✓
|
||||||
|
- 深层叙事计数 0→2→6 随条件递增 ✓
|
||||||
|
- StatsPanel idle 4 行统计全显示 ✓
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、哲学
|
||||||
|
|
||||||
|
### 碎片叙事的留白
|
||||||
|
|
||||||
|
回响星核的叙事不靠过场动画或长篇对话,而是**让玩家自己拼凑**。每个碎片的多层文本设计遵循:
|
||||||
|
|
||||||
|
1. **表层温和**:第一遍读像抒情散文,不带压迫感
|
||||||
|
2. **中层矛盾**:揭示表层没说的东西,制造认知裂缝
|
||||||
|
3. **深层颠覆**:推翻表层认知,但留有解读空间(不直接说"怪物是谁")
|
||||||
|
|
||||||
|
### 平衡与保留
|
||||||
|
|
||||||
|
softcap 的设计哲学:**衰减但不封顶**。
|
||||||
|
- 衰减:防止滚雪球破坏游戏节奏
|
||||||
|
- 不封顶:尊重硬核玩家的投入,即使边际收益递减,进度感始终存在
|
||||||
|
|
||||||
|
这与飞升系统的"单向门"叙事呼应——每次进步都有代价,但绝非徒劳。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 八、下一步(v0.16+)
|
||||||
|
|
||||||
|
- 🟡 loreLayer "已读"标记(需改 gameStore,本轮避开边界冲突)
|
||||||
|
- 🟡 解码阈值释放叙事碎片 + 星潮三频段切换
|
||||||
|
- 🟡 健康设计:每日软上限 + "星核建议休眠"提醒
|
||||||
|
- 🟡 移动端 375px Codex 弹层可读性实测
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Here are all the generated files.
|
||||||
|
After Width: | Height: | Size: 174 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 249 KiB |
|
After Width: | Height: | Size: 186 KiB |
|
After Width: | Height: | Size: 168 KiB |
|
After Width: | Height: | Size: 238 KiB |
|
After Width: | Height: | Size: 228 KiB |
|
After Width: | Height: | Size: 174 KiB |
|
After Width: | Height: | Size: 235 KiB |
|
After Width: | Height: | Size: 290 KiB |
|
After Width: | Height: | Size: 174 KiB |
|
After Width: | Height: | Size: 172 KiB |
|
After Width: | Height: | Size: 224 KiB |
|
After Width: | Height: | Size: 197 KiB |
|
After Width: | Height: | Size: 210 KiB |
|
After Width: | Height: | Size: 138 KiB |
|
After Width: | Height: | Size: 275 KiB |
|
After Width: | Height: | Size: 260 KiB |
|
After Width: | Height: | Size: 281 KiB |
|
After Width: | Height: | Size: 232 KiB |
|
After Width: | Height: | Size: 288 KiB |