feat(0.1.14-P4/P5): UI 单点 + 引擎级测试(979)+ 顶栏天下风云

- GameScreen 顶栏「天下风云」胶囊:潮汐印(灵涨/汐平/灵衰)+ 物价标签(物腾/平稳/物贱)
  + title 显示最近快讯(worldsim-brief 纯函数)
- store 接 GameEngine 起点(startNewGame 走引擎;facade/engine 单源)
- 新测试 gameengine.test.ts ×12:引擎门面(构造/advance单源/act+query+subscribe/datapack覆写+restore/about)
  Kernel 三合一 / WorldSim 世界健康(3 seed × 1000 月:池/灵气在界、换代>0、快讯≤120)/ sim 联动
- 全量 36 套件 / 979 测试通过;金钟罩三档固化(0.1.14 正式);typecheck 0 error
- AGENTS 补「引擎系统」章节;版本 0.1.14
This commit is contained in:
2026-08-23 13:33:39 +08:00
parent 5dd5d6b558
commit 26bd61fba0
7 changed files with 272 additions and 4 deletions
@@ -0,0 +1,13 @@
import { World } from '../runtime/World'
export function worldSimBrief(w: World): { tideLabel: string; priceLabel: string; latest: string } | null {
const ws = w.state.worldSim
if (!ws) return null
const tide = ws.tide ?? 0.5
const tideLabel = tide > 0.9 ? '灵涨' : tide < 0.7 ? '灵衰' : '汐平'
const pool = ws.marketPool ?? {}
const ratio = (pool['lingcao'] ?? 600) / 600
const priceLabel = ratio > 1.15 ? '物腾' : ratio < 0.85 ? '物贱' : '平稳'
const latest = ws.newsFeed?.[ws.newsFeed.length - 1]?.text ?? ''
return { tideLabel, priceLabel, latest }
}
+17
View File
@@ -16,6 +16,7 @@ import { fmt as fmtNum } from '../../game/engine/kernel/format'
import { seasonOf } from '../../game/data/season'
import { seasonTint, yearRing, tideOf } from '../../game/engine/kernel/timesense'
import landscapeUrl from '../../assets/gen/landscape-gold.svg'
import { worldSimBrief } from '../../game/engine/sim/worldsim-brief'
import { GuideStrip } from '../components/GuideStrip'
const TABS: { id: PanelId; label: string }[] = [
@@ -87,6 +88,7 @@ export default function GameScreen() {
</div>
))}
</div>
<WorldCondensed />
<button className="btn btn-sm" title="手动存点" onClick={() => void saveNow()}></button>
</div>
<UrgentBadges />
@@ -139,6 +141,21 @@ export default function GameScreen() {
)
}
function WorldCondensed() {
const revision = useGameStore((s) => s.revision)
void revision
const st = useGameStore.getState()
if (!st.world) return null
const brief = worldSimBrief(st.world)
if (!brief) return null
return (
<button className="world-badge" title={`潮汐${brief.tideLabel} · 物价${brief.priceLabel} · 快讯:${brief.latest ?? ''}`}>
<span className="seal">{brief.tideLabel}</span>
<span className="wb-price">{brief.priceLabel}</span>
</button>
)
}
function dispRes(id: string): number {
const st = useGameStore.getState()
const w = st.world
+7 -3
View File
@@ -1,5 +1,6 @@
import { create } from 'zustand'
import { World, WorldEventBus } from '../game/engine/runtime/World'
import { GameEngine } from '../game/engine/GameEngine'
import { EventDef } from '../game/data/events'
import { findEvent, applyEventChoice } from '../game/engine/runtime/Systems/events'
import { BattleLog, ChronicleEntry, GameState, LogItem, SaveMeta, YearlyReport, SnapshotMeta } from '../game/types/domain'
@@ -61,6 +62,7 @@ export interface GameStore {
closePaper: () => void
toggleSound: () => void
facade?: GameFacade
engine?: GameEngine
advancing: boolean
setNextToast: (msg: string) => void
act: (name: ActName, payload: import('../game/engine/runtime/ApiFacade').ActPayload) => boolean
@@ -88,6 +90,7 @@ function stopTimer(): void {
export const useGameStore = create<GameStore>((set, get) => ({
facade: undefined as GameFacade | undefined,
engine: undefined as GameEngine | undefined,
advancing: false,
setNextToast: (msg: string) => setNextToast(msg),
screen: 'boot',
@@ -144,11 +147,12 @@ export const useGameStore = create<GameStore>((set, get) => ({
go: (screen) => set({ screen }),
startNewGame: async (opts, slot) => {
const world = World.create(opts)
set({ world, slot, screen: 'game', panel: 'family', logFeed: [], battleView: undefined, pendingEventId: undefined, pendingEventDef: undefined, revision: 1, speed: 0, gameOverReason: undefined, paperReport: undefined, toast: undefined, selectedMemberId: undefined, selectedMissionDef: undefined })
const engine = new GameEngine({ seed: opts.seed })
const world = engine.world
set({ world, engine, slot, screen: 'game', panel: 'family', logFeed: [], battleView: undefined, pendingEventId: undefined, pendingEventDef: undefined, revision: 1, speed: 0, gameOverReason: undefined, paperReport: undefined, toast: undefined, selectedMemberId: undefined, selectedMissionDef: undefined })
const st = get()
st.world?.out.push(makeBus(st))
const facade = new GameFacade(world, slot)
const facade = st.facade ?? new GameFacade(world, slot)
set({ facade })
await st.saveNow('开局')
},
+22
View File
@@ -1799,3 +1799,25 @@ html[data-blade] .battle-lines {
max-height: 120px;
overflow-y: auto;
}
/* ---------- 天下风云徽章 ---------- */
.world-badge {
display: inline-flex;
align-items: center;
gap: 6px;
background: rgba(38, 29, 16, 0.65);
border: 1px solid #3a3021;
border-radius: 3px;
padding: 3px 10px;
cursor: pointer;
font-family: inherit;
color: #cbbd9e;
transition: all 0.15s;
}
.world-badge:hover {
border-color: var(--gold);
}
.world-badge .wb-price {
font-size: 0.8rem;
color: #a8c2da;
}