v0.1.6: 时轮与造化(GameClock/RngHub/时节/年轴/保护)
- 统一时轮 GameClock:七 phase 注册制(production/aging/cultivation/missions/ events/diplomacy/epilogue) + 年首三钩子(婚配→岁贡→族簿);advanceMonth 化作薄壳; 新增系统只注册不改本体;alive 守卫、随机序、族簿时序均与重构前逐一等价 - 金钟罩:3 枚 seed × 560 月指纹常驻(重构前后已验证同哈希;时节属有意变更后重固化) - 统一随机 RngHub:开局播种(crypto UV)、音效白噪独立流、引擎 rng 带 nextCount 审计; 全工程拔除逻辑侧 Math.random - 时节系统:春夏秋冬乘子(春田+10%/夏修+5%/秋市+5%/冬闭+8%)入生产与修炼 - 崩溃保护:advance 异常自动存『出险前』档 + 停速提示回档 - 性能预算:dev-only 单 tick>60ms 报最慢 phase - 横轴年表:十年一格大事/生殇聚合 + 春秋原年轴视图 - 测试 197→225(时钟/收集器/四季/年轴/效果全矩阵/死局链/残留防御)
This commit is contained in:
@@ -4,6 +4,7 @@ import { computeLegacy, resolveLegacy } from '../../game/core/legacy'
|
||||
import { resolveLegacy as doesIt } from '../../game/core/legacy'
|
||||
import { ResolveModal } from '../components/ResolveModal'
|
||||
import { buildBiography } from '../../game/core/biography'
|
||||
import { yearAxis, decadeLabel } from '../../game/core/yearaxis'
|
||||
|
||||
void doesIt
|
||||
|
||||
@@ -12,7 +13,7 @@ export default function LegacyPanel() {
|
||||
const revision = useGameStore((s) => s.revision)
|
||||
void revision
|
||||
const [showResolve, setShowResolve] = useState(false)
|
||||
const [view, setView] = useState<'dims' | 'chronicle' | 'report' | 'scroll'>('dims')
|
||||
const [view, setView] = useState<'dims' | 'chronicle' | 'report' | 'scroll' | 'axis'>('dims')
|
||||
if (!world) return null
|
||||
const w = world
|
||||
const s = w.state
|
||||
@@ -54,6 +55,7 @@ export default function LegacyPanel() {
|
||||
<button className="btn" onClick={() => setView('report')}>年报</button>
|
||||
<button className="btn" onClick={() => setView('chronicle')}>年表</button>
|
||||
<button className="btn" onClick={() => setView('scroll')}>长卷</button>
|
||||
<button className="btn" onClick={() => setView('axis')}>年轴</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -110,6 +112,8 @@ export default function LegacyPanel() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{view === 'axis' && <AxisView />}
|
||||
|
||||
{view === 'scroll' && <ScrollScroll />}
|
||||
|
||||
{view === 'chronicle' && (
|
||||
@@ -133,6 +137,32 @@ export default function LegacyPanel() {
|
||||
)
|
||||
}
|
||||
|
||||
function AxisView() {
|
||||
const world = useGameStore((s) => s.world)
|
||||
if (!world) return null
|
||||
const cells = yearAxis(world.state)
|
||||
return (
|
||||
<div className="card">
|
||||
<div className="card-title">横轴年表(十年一格)</div>
|
||||
{cells.map((c) => (
|
||||
<div key={c.from} className="axis-cell">
|
||||
<div className="axis-decade">{decadeLabel(c.from, c.to)}</div>
|
||||
<div className="axis-events">
|
||||
<span className="tag good-t-bg">生 {c.births}</span>
|
||||
<span className="tag bad-t-bg">殇 {c.deaths}</span>
|
||||
{c.events.slice(0, 6).map((e, i) => (
|
||||
<span key={i} className={`tag ${e.kind === '战' || e.kind === '渡劫' ? 'bad-t-bg' : ''}`}>
|
||||
{e.kind}·{e.year}年 {e.label}
|
||||
</span>
|
||||
))}
|
||||
{c.events.length > 6 && <span className="dim2">…还有{c.events.length - 6}件</span>}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ScrollScroll() {
|
||||
const world = useGameStore((s) => s.world)
|
||||
if (!world) return null
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useGameStore } from '../store'
|
||||
import { SURNAME_POOL } from '../../game/core/names'
|
||||
import { RngHub } from '../../game/core/rng'
|
||||
|
||||
export default function NewGame() {
|
||||
const go = useGameStore((s) => s.go)
|
||||
@@ -12,10 +13,7 @@ export default function NewGame() {
|
||||
const [slot, setSlot] = useState(1)
|
||||
const [randoming, setRandoming] = useState(false)
|
||||
|
||||
const seed = useMemo(
|
||||
() => `${Date.now()}-${Math.floor(Math.random() * 1e9)}-${Math.floor(Math.random() * 1e9)}`,
|
||||
[randoming]
|
||||
)
|
||||
const seed = useMemo(() => RngHub.rollSeed(), [randoming])
|
||||
|
||||
const rollNames = () => {
|
||||
setRandoming((r) => !r)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// 合成音效:鼓点·钟鸣·纸韵(Web Audio,零素材依赖)
|
||||
import { RngHub } from '../game/core/rng'
|
||||
let ctx: AudioContext | null = null
|
||||
let enabled = true
|
||||
|
||||
@@ -49,7 +50,7 @@ function noise(dur: number, gain: number, when = 0, freq = 3200): void {
|
||||
const len = Math.floor(a.sampleRate * dur)
|
||||
const buf = a.createBuffer(1, len, a.sampleRate)
|
||||
const data = buf.getChannelData(0)
|
||||
for (let i = 0; i < len; i++) data[i] = (Math.random() * 2 - 1) * (1 - i / len)
|
||||
for (let i = 0; i < len; i++) data[i] = (RngHub.audioNoise01() * 2 - 1) * (1 - i / len)
|
||||
const src = a.createBufferSource()
|
||||
src.buffer = buf
|
||||
const f = a.createBiquadFilter()
|
||||
|
||||
@@ -6,6 +6,7 @@ import { BattleLog, ChronicleEntry, GameState, LogItem, SaveMeta, YearlyReport,
|
||||
import { getSlotManager, getSaveSlot } from '../game/storage/db'
|
||||
import { metaFromState, updateSlotMeta } from './storeHelper'
|
||||
import { setSoundEnabled, sPaper, sGood, sBad, sWar, sBell, sGong, sClick, sTick } from './sound'
|
||||
import type { PhaseStat } from '../game/core/clock'
|
||||
|
||||
export type Screen = 'boot' | 'newgame' | 'game'
|
||||
export type PanelId = 'family' | 'genealogy' | 'territory' | 'market' | 'diplomacy' | 'expedition' | 'chronicle' | 'legacy' | 'settings'
|
||||
@@ -60,6 +61,7 @@ export interface GameStore {
|
||||
}
|
||||
|
||||
const LOG_CAP = 260
|
||||
const lastPhaseStats = new Map<World, PhaseStat[]>()
|
||||
let logSeq = 1
|
||||
|
||||
export const useGameStore = create<GameStore>((set, get) => ({
|
||||
@@ -147,13 +149,29 @@ export const useGameStore = create<GameStore>((set, get) => ({
|
||||
const w = st.world
|
||||
if (!w || st.pendingEventId || w.state.gameOver) return
|
||||
try {
|
||||
const t0 = performance.now()
|
||||
w.advanceMonth()
|
||||
const elapsed = performance.now() - t0
|
||||
if (import.meta.env?.DEV && elapsed > 60) {
|
||||
// 性能预算:单 tick 超 60ms 提示最慢 phase
|
||||
const stats = w.clock.stepMonthly(w)
|
||||
void stats
|
||||
const slowest = lastPhaseStats.get(w)?.sort((a, b) => b.ms - a.ms)[0]
|
||||
if (slowest) console.warn('[perf] tick 超预算', elapsed.toFixed(1) + 'ms', '最慢:', slowest.phase, slowest.ms.toFixed(1) + 'ms')
|
||||
}
|
||||
w.syncRng()
|
||||
sTick()
|
||||
set((s) => ({ revision: s.revision + 1 }))
|
||||
await Stash.save(st, '自动')
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
// 出险保护:先落一档"出险前"现场再提示回档
|
||||
console.error('[advance-crash]', e)
|
||||
try {
|
||||
await Stash.save(st, '出险前')
|
||||
} catch {
|
||||
// 保存失败时不再叠加错误
|
||||
}
|
||||
set({ toast: '时日流转出现异常,已保存出险现场,可回档重来。', speed: 0 })
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user