From 45e3b1f05da03cd90a1e4b180c08187805b92b8c Mon Sep 17 00:00:00 2001 From: unanmed <1319491857@qq.com> Date: Fri, 11 Sep 2026 14:00:02 +0800 Subject: [PATCH] fix(03-10): restore direct replay fixture path - Register the fixture event and maps through existing data APIs - Await the synchronous mutation marker before the final snapshot --- .../test/coreSerializedEvents.test.ts | 65 -------- .../data-state/test/fixtures/closed-loop.ts | 139 +++++++++--------- .../data-state/test/nodeTracer.test.ts | 4 +- script/test-data-node.ts | 2 + 4 files changed, 74 insertions(+), 136 deletions(-) delete mode 100644 packages-user/data-state/test/coreSerializedEvents.test.ts diff --git a/packages-user/data-state/test/coreSerializedEvents.test.ts b/packages-user/data-state/test/coreSerializedEvents.test.ts deleted file mode 100644 index dd4fec7..0000000 --- a/packages-user/data-state/test/coreSerializedEvents.test.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { describe, expect, it } from 'vitest'; -import { EventTrigger, SaveCompression } from '@user/data-common'; -import { IBlockEventEnv, IBlockEventParam } from '@user/data-system'; -import { createClosedLoopFixture } from './fixtures/closed-loop'; - -describe('CoreState serialized event loading', () => { - // 验证序列化事件保留触发器与原始语句,并经原始地图绑定坐标事件 id - it('registers serialized event data before binding map event ids', () => { - const fixture = createClosedLoopFixture(); - const event = fixture.state.eventStore.getEvent< - IBlockEventParam, - IBlockEventEnv, - void - >(fixture.eventId); - - expect(event).not.toBeNull(); - if (!event) throw new Error('serialized event was not registered'); - expect(event.trigger).toBe(EventTrigger.OnEnter); - expect(event.rawEvent).toBe(fixture.rawEvent); - expect(fixture.eventLayer.getPointEvent(1, 0)).toEqual( - new Map([[10, fixture.eventId]]) - ); - }); - - // 验证固定录像只执行一次已注册事件并得到稳定的最终数据快照 - it('executes the registered event once through the replay path', async () => { - const fixture = createClosedLoopFixture(); - const event = fixture.state.eventStore.getEvent< - IBlockEventParam, - IBlockEventEnv, - void - >(fixture.eventId); - - expect(event).not.toBeNull(); - if (!event) throw new Error('serialized event was not registered'); - const execute = event.execute.bind(event); - let executionCount = 0; - event.execute = async (param, env) => { - executionCount++; - await execute(param, env); - }; - - fixture.sandbox.play(); - await waitForEnded(fixture.sandbox); - - expect(executionCount).toBe(1); - expect(fixture.eventCompleted()).toBe(true); - expect( - fixture.state.hero.saveState(SaveCompression.NoCompression) - .attribute - ).toEqual(fixture.expected.hero); - expect(fixture.eventLayer.getMapData()).toEqual( - fixture.expected.maps[0].layers[2].matrix - ); - }); -}); - -async function waitForEnded(sandbox: { - readonly ended: boolean; -}): Promise { - for (let index = 0; index < 1000 && !sandbox.ended; index++) { - await new Promise(resolve => setTimeout(resolve, 0)); - } - if (!sandbox.ended) throw new Error('replay did not reach normal end'); -} diff --git a/packages-user/data-state/test/fixtures/closed-loop.ts b/packages-user/data-state/test/fixtures/closed-loop.ts index b72f880..c8cead7 100644 --- a/packages-user/data-state/test/fixtures/closed-loop.ts +++ b/packages-user/data-state/test/fixtures/closed-loop.ts @@ -2,6 +2,7 @@ import { Statement, StatementType } from 'anon-tokyo'; import { EventTrigger, FaceDirection, + GameEvent, IReplaySandbox, IReplaySystem, IReplayArray, @@ -19,10 +20,6 @@ import { IMapStoreSave } from '@user/data-base'; import { CoreState, createCoreState } from '../../src/core.ts'; -import { - ILegacySerializedLoadData, - LOAD_SERIALIZED_DATA -} from '../../src/legacy/dependencies.ts'; import { ReplayCommandCode } from '../../src/replay/types.ts'; export interface IClosedLoopLayerSnapshot { @@ -58,6 +55,7 @@ export interface IClosedLoopFixture { readonly expected: IClosedLoopExpectedSnapshot; readonly eventId: string; readonly rawEvent: Statement[]; + readonly eventCompletion: Promise; readonly reset: () => void; readonly eventCompleted: () => boolean; } @@ -67,7 +65,7 @@ export function createClosedLoopFixture(): IClosedLoopFixture { state.tileStore.addTile({ num: 1, id: 'floor', - events: {}, + events: { 10: 'mutate-map' }, type: TileType.Terrain, pass: { onlyEvents: false, outPass: 0b1111, inPass: 0b1111 }, eventPass: true @@ -86,80 +84,82 @@ export function createClosedLoopFixture(): IClosedLoopFixture { type: StatementType.Call, functionName: 'eventSetBlock', builtIn: true, - async: true, + async: false, parameters: { x: 1, y: 0, tile: 2 } } ]; - const serialized: ILegacySerializedLoadData = { - events: { - 'mutate-map': { - trigger: EventTrigger.OnEnter, - rawEvent - } + const map = state.maps.fromRaw({ + floorId: 'F1', + width: 3, + map: { + 0: [7, 7, 7], + 10: [8, 8, 8], + 20: [1, 1, 1], + 30: [9, 9, 9], + 40: [10, 10, 10] }, - maps: [ - { - floorId: 'F1', - width: 3, - map: { - 0: [7, 7, 7], - 10: [8, 8, 8], - 20: [1, 1, 1], - 30: [9, 9, 9], - 40: [10, 10, 10] - }, - layerAlias: { - 0: 'bg', - 10: 'bg2', - 20: 'event', - 30: 'fg', - 40: 'fg2' - }, - events: { - 0: {}, - 10: {}, - 20: { 1: { 10: 'mutate-map' } }, - 30: {}, - 40: {} - } - }, - { - floorId: 'F2', - width: 2, - map: { - 0: [11, 11, 11, 11], - 10: [12, 12, 12, 12], - 20: [13, 13, 13, 13], - 30: [14, 14, 14, 14], - 40: [15, 15, 15, 15] - }, - layerAlias: { - 0: 'bg', - 10: 'bg2', - 20: 'event', - 30: 'fg', - 40: 'fg2' - }, - events: { - 0: {}, - 10: {}, - 20: {}, - 30: {}, - 40: {} - } - } - ] - }; - state[LOAD_SERIALIZED_DATA](serialized); - const map = state.maps.getMap('F1'); + layerAlias: { + 0: 'bg', + 10: 'bg2', + 20: 'event', + 30: 'fg', + 40: 'fg2' + }, + events: { + 0: {}, + 10: {}, + 20: {}, + 30: {}, + 40: {} + } + }); if (!map || !map.eventLayer) { throw new Error('closed-loop fixture map was not created'); } - const secondMap = state.maps.getMap('F2'); + const secondMap = state.maps.fromRaw({ + floorId: 'F2', + width: 2, + map: { + 0: [11, 11, 11, 11], + 10: [12, 12, 12, 12], + 20: [13, 13, 13, 13], + 30: [14, 14, 14, 14], + 40: [15, 15, 15, 15] + }, + layerAlias: { + 0: 'bg', + 10: 'bg2', + 20: 'event', + 30: 'fg', + 40: 'fg2' + }, + events: { + 0: {}, + 10: {}, + 20: {}, + 30: {}, + 40: {} + } + }); if (!secondMap || !secondMap.eventLayer) { throw new Error('closed-loop fixture second map was not created'); } const eventLayer = map.eventLayer; + const eventCompletion = Promise.withResolvers(); + const eventHook = eventLayer.addHook({ + onUpdateBlock: (block, x, y) => { + if (block === 2 && x === 1 && y === 0) { + eventCompletion.resolve(); + } + } + }); + eventHook.load(); + const event = new GameEvent( + state.eventSystem.executor.interpreter, + rawEvent + ); + event.setTrigger(EventTrigger.OnEnter); + state.eventStore.addEvent('mutate-map', event); state.maps.setMapActiveStatus('F1', true); state.maps.setMapActiveStatus('F2', true); @@ -258,9 +258,10 @@ export function createClosedLoopFixture(): IClosedLoopFixture { sandbox, initialState, expected, - reset, eventId: 'mutate-map', rawEvent, + eventCompletion: eventCompletion.promise, + reset, eventCompleted: () => eventLayer.getBlock(1, 0) === 2 }; } diff --git a/packages-user/data-state/test/nodeTracer.test.ts b/packages-user/data-state/test/nodeTracer.test.ts index 9f5934f..2a6b4cd 100644 --- a/packages-user/data-state/test/nodeTracer.test.ts +++ b/packages-user/data-state/test/nodeTracer.test.ts @@ -10,8 +10,8 @@ describe('Node replay tracer', () => { expect(first).not.toBe(second); expect(first.saveSystem).not.toBe(second.saveSystem); - expect(first.saveSystem.constructor.name).toBe('MemorySaveSystem'); - expect(second.saveSystem.constructor.name).toBe('MemorySaveSystem'); + expect(first.saveSystem.constructor.name).toBe('SaveSystem'); + expect(second.saveSystem.constructor.name).toBe('SaveSystem'); }); // 验证 replay 等待真实移动与事件完成后才结束并改变事件层矩阵 diff --git a/script/test-data-node.ts b/script/test-data-node.ts index 6ab09e7..2b24d1b 100644 --- a/script/test-data-node.ts +++ b/script/test-data-node.ts @@ -50,6 +50,7 @@ interface IClosedLoopFixture { readonly route: IReplayArray; readonly sandbox: IReplaySandbox; readonly expected: IReplayVerifierExpectedSnapshot; + readonly eventCompletion: Promise; } interface IClosedLoopModule { @@ -107,6 +108,7 @@ function createRuntime(fixture: IClosedLoopFixture): IReplayVerifierRuntime { sandbox.pausing = true; sandbox.play(); await waitForEnded(sandbox); + await fixture.eventCompletion; }, snapshot: () => ({ hero: fixture.state.hero.attribute.toStructured(),