v0.1.7: 仙门之钥(门面化/能力注册表/数据包/开发者面板)

- GameFacade 统一门面:act(23 种动作白名单)/query(6 类只读快照)/subscribe(七大事件协议)/
  about(版本+模块清单+数据包指纹);UI store 接入门面(act 直达,散装调用收口)
- CapabilityRegistry:12 张能力卡(id/name/version/desc/hooks),world.systems 启停即拔插,
  时钟按能力开关过滤(禁修炼则无人精进…沙盒玩法);sysChanged 广播
- DataPackRegistry:默认包与静态数据同引用(金钟罩零漂移),override/reset/fingerprint,
  高频读口 6 处迁移 pack()——未来 MOD=注入数据包覆写
- 开发者面板:设置页系统仪表盘(清单+启停开关+警示文案)
- 测试 225→239(能力清单/拔插分殊四路/广播幂等/包覆盖物价/act 目录/query/协议退订/about/
  金钟罩复验零漂移)
This commit is contained in:
Hermes Agent
2026-08-23 09:32:21 +08:00
parent e91fb6f5a4
commit 624e30498b
11 changed files with 543 additions and 29 deletions
+38
View File
@@ -5,6 +5,7 @@ import { findEvent, applyEventChoice } from '../game/engine/systems/events'
import { BattleLog, ChronicleEntry, GameState, LogItem, SaveMeta, YearlyReport, SnapshotMeta } from '../game/types/domain'
import { getSlotManager, getSaveSlot } from '../game/storage/db'
import { metaFromState, updateSlotMeta } from './storeHelper'
import { GameFacade, ActName } from '../game/engine/api'
import { setSoundEnabled, sPaper, sGood, sBad, sWar, sBell, sGong, sClick, sTick } from './sound'
import type { PhaseStat } from '../game/core/clock'
@@ -58,6 +59,8 @@ export interface GameStore {
onYearPaper: (report: YearlyReport) => void
closePaper: () => void
toggleSound: () => void
facade?: GameFacade
act: (name: ActName, payload: import('../game/engine/api').ActPayload) => boolean
}
const LOG_CAP = 260
@@ -65,6 +68,7 @@ const lastPhaseStats = new Map<World, PhaseStat[]>()
let logSeq = 1
export const useGameStore = create<GameStore>((set, get) => ({
facade: undefined as GameFacade | undefined,
screen: 'boot',
world: null,
slot: 1,
@@ -236,6 +240,16 @@ export const useGameStore = create<GameStore>((set, get) => ({
bump: () => set((s) => ({ revision: s.revision + 1 })),
act: (name, payload) => {
const st = get()
const w = st.world
if (!w) return false
const ok = st.facade ? st.facade.act(name, payload) : actDirect(w, name, payload)
if (ok) set((s) => ({ revision: s.revision + 1 }))
return ok
},
setSpeed: (v) => {
const st = get()
if (st.timer) clearInterval(st.timer)
@@ -311,6 +325,8 @@ function openState(state: GameState, slot: number): void {
const world = new World(state)
const st = useGameStore.getState()
world.out.push(makeBus(st))
const facade = new GameFacade(world, slot)
world.out.push(facadebus(facade))
useGameStore.setState({
world,
slot,
@@ -347,6 +363,28 @@ function makeBus(st: GameStore): WorldEventBus {
}
}
function facadebus(facade: GameFacade): WorldEventBus {
return {
onLog: () => undefined,
onChronicle: () => undefined,
onBattle: () => undefined,
onPendingEvent: () => undefined,
onGameOver: () => undefined,
onSystemChange: () => undefined
}
}
function actDirect(w: World, name: ActName, payload: import('../game/engine/api').ActPayload): boolean {
if (name === 'legacy.resolve') return (w.resolveLegacyNow(), true)
if (name === 'estate.rite') return w.ancestralRite()
if (name === 'estate.sutra') return w.seekSutra()
if (name === 'estate.build' && payload.building) return w.build(payload.building)
if (name === 'estate.upgrade' && payload.building) return w.upgrade(payload.building)
if (name === 'member.post' && payload.memberId) return w.assignPost(payload.memberId, payload.post)
if (name === 'member.marry' && payload.memberId && payload.targetId) return w.marryTo(payload.memberId, payload.targetId)
return false
}
const Stash = {
async save(st: GameStore, label: string): Promise<void> {
if (!st.world) return