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:
@@ -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 }
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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('开局')
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user