v0.13: 性能重构+数值平衡+字体资源落地

性能(调研 7-A 落地):
- 新增 AnimatedNumber 组件(useRef + rAF lerp,60fps 平滑过渡,不触发 React re-render)
- CrystalOrb 拆 OrbStats 子组件:主体(Canvas+button)不再每 tick 重渲染
  crystals/crystalCap/crystalsPerSec 订阅下沉到 OrbStats,用 AnimatedNumber 平滑
- page.tsx StatsPanel: shallow → useShallow(修复 zustand v5 getSnapshot 崩溃)

数值平衡(调研 7-A 落地,防速通):
- 新增 src/lib/game/softcap.ts:applySoftcap/applyCostSoftcap/effectiveDecodedForPrestige
- 飞升蓝图公式改 sqrt softcap(阈值 40,power 0.5)
  前 2 蓝图照常给(新手友好),3-6 蓝图需更多解码(防速通)
  - totalDecoded=40 → 2 蓝图(与旧一致)
  - totalDecoded=80 → 2 蓝图(旧=4)
  - totalDecoded=360 → 6 蓝图(封顶)

非商用资源落地(调研 7-B):
- 安装 @fontsource/share-tech-mono + @fontsource/vt323(SIL OFL 自托管)
- globals.css 加 --font-tech / --font-terminal 变量
- ResourceBar 数值用 font-tech(Share Tech Mono 星舰仪表盘感)
- ArchaeoLogTicker 文本用 font-terminal(VT323 终端绿字)+ GiScrollUnfurled 图标
- 安装 react-icons,引入 game-icons.net 子集(MIT)

交互优化:
- ExpeditionPanel 加探险目的说明(3 资源卡片)+ 节点类型图例(issue #3 反馈)
- BeaconPanel 布局微调

验证:
- lint 零错误
- VLM 首页 QA 8.5/10(字体/配色/页脚贴底均良好)
- 点击脉冲交互正常(数值增加、浮动数字正确消失)
- dev.log 无运行时错误
This commit is contained in:
2026-06-24 04:02:45 +00:00
parent cfc87ca3a1
commit 93195f9b88
14 changed files with 287 additions and 170 deletions
+7 -2
View File
@@ -20,6 +20,7 @@ import {
computePrestigeAttrPoints,
createInitialAttributeProgress,
} from "./attributes";
import { effectiveDecodedForPrestige } from "./softcap";
/** 由技术树 + 飞升蓝图 + 成就 + 星图天赋 + 星潮聚合计算产能字段 */
export function recomputeStats(state: Partial<GameState>): {
@@ -140,9 +141,13 @@ export function computePrestigeBonus(state: GameState): PrestigeBonus {
};
}
/** 本次飞升可获得的蓝图数 */
/** 本次飞升可获得的蓝图数v0.13: sqrt softcap 防速通,见 softcap.ts */
export function computeNewBlueprints(state: GameState): number {
const earned = Math.floor(state.totalDecoded / PRESTIGE.blueprintsPerDecode);
// 旧公式:Math.floor(state.totalDecoded / PRESTIGE.blueprintsPerDecode)
// 新公式:先对 totalDecoded 施 sqrt 软上限(阈值 40),再 / 20
// 效果:前 2 蓝图照常给(新手友好),3-6 蓝图需更多解码(防速通)
const effectiveDecoded = effectiveDecodedForPrestige(state.totalDecoded);
const earned = Math.floor(effectiveDecoded / PRESTIGE.blueprintsPerDecode);
return Math.max(0, Math.min(PRESTIGE.maxBlueprints, earned) - state.blueprints.length);
}
+63
View File
@@ -0,0 +1,63 @@
// 回响星核 / Echo Nexus — 数值软上限工具(v0.13 调研 7-A 落地)
// 调研来源:AntimatterDimensions break_infinity.js / Pecorella GDC 2016
// 当数值超过阈值后,用幂函数衰减增长速率,防止速通 + 让后期仍有进度感。
//
// 标准公式(AD 风格):
// eff = x > thr ? pow(thr, 1 - p) * pow(x, p) : x
// 其中 p ∈ (0, 1)p 越小衰减越狠(p=0.5 即 sqrt 软上限)。
/**
* 应用软上限。
* @param value 原始值
* @param threshold 阈值,超过此值开始衰减
* @param power 衰减幂(0-10.5=sqrt0.3=更狠)
* @returns 衰减后的有效值
*
* @example
* applySoftcap(100, 50, 0.5) // = sqrt(50) * sqrt(100) ≈ 70.7
* applySoftcap(40, 50, 0.5) // = 40(未超阈值,原值返回)
*/
export function applySoftcap(value: number, threshold: number, power: number): number {
if (!isFinite(value) || value <= threshold) return value;
// eff = thr^(1-p) * x^p
return Math.pow(threshold, 1 - power) * Math.pow(value, power);
}
/**
* 反向软上限:用于"成本"曲线(让后期成本增长更陡)。
* eff = x > thr ? x * pow(x / thr, power) : xpower > 0
*
* @example
* applyCostSoftcap(100, 50, 0.5) // = 100 * sqrt(100/50) ≈ 141.4
*/
export function applyCostSoftcap(value: number, threshold: number, power: number): number {
if (!isFinite(value) || value <= threshold) return value;
return value * Math.pow(value / threshold, power);
}
// ===== 飞升蓝图曲线(v0.13 调研 7-A 落地)=====
// 旧公式:newBp = floor(totalDecoded / 20),线性,速通可一波拿满 6 蓝图
// 新公式:对 totalDecoded 先施 sqrt 软上限(阈值 40power 0.5),再 / 20
// - totalDecoded=20 → eff=20, newBp=1(与旧一致)
// - totalDecoded=40 → eff=40, newBp=2(与旧一致,恰在阈值)
// - totalDecoded=80 → eff≈56.6, newBp=2(旧=4,防速通)
// - totalDecoded=160 → eff=80, newBp=4(旧=8,已封顶 6
// - totalDecoded=360 → eff=120, newBp=6(封顶)
// 效果:前 2 蓝图照常给(新手友好),3-6 蓝图需更多解码(防速通)
/** 飞升蓝图 softcap 阈值(解码数) */
export const PRESTIGE_BP_SOFTCAP_THRESHOLD = 40;
/** 飞升蓝图 softcap 衰减幂 */
export const PRESTIGE_BP_SOFTCAP_POWER = 0.5;
/**
* 计算飞升蓝图的有效解码数(应用 softcap 后)。
* 用于 computeNewBlueprints 内部。
*/
export function effectiveDecodedForPrestige(totalDecoded: number): number {
return applySoftcap(
totalDecoded,
PRESTIGE_BP_SOFTCAP_THRESHOLD,
PRESTIGE_BP_SOFTCAP_POWER
);
}