mirror of
https://github.com/motajs/template.git
synced 2026-09-12 17:58:51 +08:00
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
This commit is contained in:
parent
e4e39f7b2b
commit
45e3b1f05d
@ -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<void> {
|
||||
for (let index = 0; index < 1000 && !sandbox.ended; index++) {
|
||||
await new Promise<void>(resolve => setTimeout(resolve, 0));
|
||||
}
|
||||
if (!sandbox.ended) throw new Error('replay did not reach normal end');
|
||||
}
|
||||
@ -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<void>;
|
||||
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<void>();
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
@ -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 等待真实移动与事件完成后才结束并改变事件层矩阵
|
||||
|
||||
@ -50,6 +50,7 @@ interface IClosedLoopFixture {
|
||||
readonly route: IReplayArray;
|
||||
readonly sandbox: IReplaySandbox;
|
||||
readonly expected: IReplayVerifierExpectedSnapshot;
|
||||
readonly eventCompletion: Promise<void>;
|
||||
}
|
||||
|
||||
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(),
|
||||
|
||||
Loading…
Reference in New Issue
Block a user