mirror of
https://github.com/motajs/template.git
synced 2026-10-12 15:50:16 +08:00
test(08-09): align shared fixture and core playback integration tests
- closed-loop: assemble CoreState via loadStarter contract, setFloor(real IGameMap) at both call sites
- coreNode: local createCoreState helper, addLayer('event')
- replayPlayback: drop useMapState, DefaultPassPredicateImpl(state), inject Dir4FaceHandler, setFloor(real map), ReplayCommandResult assertions
- saveablesRoundTrip: real map setFloor, addLayer('event'), Record equip.value, align container restore to uid/value
This commit is contained in:
parent
58a554e40a
commit
98118fac14
@ -1,6 +1,15 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { WebLoadStarter } from '@motajs/loader';
|
||||
import { SaveCompression } from '@user/data-common';
|
||||
import { createCoreState } from '../src/core';
|
||||
import { CoreState } from '../src/core';
|
||||
|
||||
/** 按 08-08 约定的 CoreState 装配创建一个独立状态实例 */
|
||||
function createCoreState(): CoreState {
|
||||
return new CoreState({
|
||||
loadStarter: new WebLoadStarter(),
|
||||
coreURL: 'placeholder'
|
||||
});
|
||||
}
|
||||
|
||||
describe('Node CoreState factory', () => {
|
||||
// 验证无参数工厂创建的状态实例不会共享勇士、地图、事件存储和存档系统
|
||||
@ -18,7 +27,7 @@ describe('Node CoreState factory', () => {
|
||||
|
||||
const map = first.maps.createMap('isolated-floor', 1, 1);
|
||||
map.setActiveStatus(true);
|
||||
const layer = map.addLayer();
|
||||
const layer = map.addLayer('event');
|
||||
layer.setMapRef(new Uint32Array([7]));
|
||||
expect(second.maps.getMap('isolated-floor')).toBeNull();
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { Statement, StatementType } from '@motajs/anon-tokyo';
|
||||
import { WebLoadStarter } from '@motajs/loader';
|
||||
import {
|
||||
EventTrigger,
|
||||
FaceDirection,
|
||||
@ -20,7 +21,7 @@ import {
|
||||
IMapLayer,
|
||||
IMapStoreSave
|
||||
} from '@user/data-base';
|
||||
import { CoreState, createCoreState } from '../../src/core.ts';
|
||||
import { CoreState } from '../../src/core.ts';
|
||||
|
||||
export interface IClosedLoopLayerSnapshot {
|
||||
readonly zIndex: number;
|
||||
@ -61,7 +62,10 @@ export interface IClosedLoopFixture {
|
||||
}
|
||||
|
||||
export function createClosedLoopFixture(): IClosedLoopFixture {
|
||||
const state = createCoreState();
|
||||
const state = new CoreState({
|
||||
loadStarter: new WebLoadStarter(),
|
||||
coreURL: 'placeholder'
|
||||
});
|
||||
state.tileStore.addTile({
|
||||
num: 1,
|
||||
id: 'floor',
|
||||
@ -163,7 +167,7 @@ export function createClosedLoopFixture(): IClosedLoopFixture {
|
||||
state.maps.setMapActiveStatus('F1', true);
|
||||
state.maps.setMapActiveStatus('F2', true);
|
||||
|
||||
state.hero.location.setFloor('F1');
|
||||
state.hero.location.setFloor(map);
|
||||
state.hero.location.setPos(0, 0);
|
||||
state.hero.location.mover.setFaceDir(FaceDirection.Right);
|
||||
|
||||
@ -231,7 +235,7 @@ export function createClosedLoopFixture(): IClosedLoopFixture {
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
state.hero.location.setFloor('F1');
|
||||
state.hero.location.setFloor(map);
|
||||
state.hero.location.setPos(0, 0);
|
||||
state.hero.location.mover.setFaceDir(FaceDirection.Right);
|
||||
state.flags.loadState(
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
// 测试录像完整播放与二次录制比对:小地图场景、逐条相等判定、176 与 error 2001-2008
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { logger } from '@motajs/common';
|
||||
import { WebLoadStarter } from '@motajs/loader';
|
||||
import {
|
||||
Dir4FaceHandler,
|
||||
FaceDirection,
|
||||
ReplayCode,
|
||||
ReplayCommandResult,
|
||||
SaveCompression,
|
||||
TileType,
|
||||
type IReplayArray,
|
||||
@ -11,7 +14,7 @@ import {
|
||||
type IReplayStepHandler,
|
||||
type ReplayParamValue
|
||||
} from '@user/data-common';
|
||||
import { type CoreState, createCoreState } from '../src/core';
|
||||
import { CoreState } from '../src/core';
|
||||
import { DefaultPassPredicateImpl } from '../src/hero/predicate';
|
||||
import {
|
||||
ReplayEquip,
|
||||
@ -49,6 +52,14 @@ afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
/** 按 08-08 约定的 CoreState 装配创建一个独立状态实例 */
|
||||
function createCoreState(): CoreState {
|
||||
return new CoreState({
|
||||
loadStarter: new WebLoadStarter(),
|
||||
coreURL: 'placeholder'
|
||||
});
|
||||
}
|
||||
|
||||
interface IRecordedParam {
|
||||
/** 参数运行时类型 */
|
||||
readonly type: string;
|
||||
@ -106,10 +117,10 @@ function createSmallMapScene(state: CoreState, wireFinder: boolean = true) {
|
||||
}
|
||||
state.maps.setMapActiveStatus('F1', true);
|
||||
if (wireFinder) {
|
||||
state.pathfinding.finder.useMapState(state.maps);
|
||||
state.pathfinding.finder.useMapLayer(map.eventLayer);
|
||||
state.pathfinding.finder.useFaceHandler(new Dir4FaceHandler());
|
||||
state.pathfinding.finder.usePassPredicate(
|
||||
new DefaultPassPredicateImpl(state.maps)
|
||||
new DefaultPassPredicateImpl(state)
|
||||
);
|
||||
}
|
||||
resetHero(state);
|
||||
@ -121,7 +132,7 @@ function createSmallMapScene(state: CoreState, wireFinder: boolean = true) {
|
||||
* @param state 顶层状态对象
|
||||
*/
|
||||
function resetHero(state: CoreState): void {
|
||||
state.hero.location.setFloor('F1');
|
||||
state.hero.location.setFloor(state.maps.getMap('F1'));
|
||||
state.hero.location.setPos(0, 0);
|
||||
state.hero.location.mover.clear();
|
||||
state.hero.location.mover.setFaceDir(FaceDirection.Right);
|
||||
@ -367,6 +378,7 @@ describe('small-map replay playback and second recording', () => {
|
||||
createSmallMapScene(state, false)
|
||||
);
|
||||
state.pathfinding.finder.useMapLayer(map.eventLayer);
|
||||
state.pathfinding.finder.useFaceHandler(new Dir4FaceHandler());
|
||||
await runHeroStep(state, FaceDirection.Right);
|
||||
replay.record(ReplayCode.Teleport, 2, 0);
|
||||
|
||||
@ -386,13 +398,13 @@ describe('replay playback error codes 2001-2008', () => {
|
||||
const teleport = new ReplayTeleport(state);
|
||||
|
||||
await expect(move.execute(step(ReplayCode.Up, [1]))).resolves.toBe(
|
||||
false
|
||||
ReplayCommandResult.Failed
|
||||
);
|
||||
expect(error).toHaveBeenCalledWith(2001, 'move', '0', '1');
|
||||
|
||||
await expect(
|
||||
teleport.execute(step(ReplayCode.Teleport, ['x', 1]))
|
||||
).resolves.toBe(false);
|
||||
).resolves.toBe(ReplayCommandResult.Failed);
|
||||
expect(error).toHaveBeenCalledWith(
|
||||
2002,
|
||||
'teleport',
|
||||
@ -411,13 +423,13 @@ describe('replay playback error codes 2001-2008', () => {
|
||||
(mover as unknown as { moving: boolean }).moving = true;
|
||||
|
||||
await expect(move.execute(step(ReplayCode.Up, []))).resolves.toBe(
|
||||
false
|
||||
ReplayCommandResult.Failed
|
||||
);
|
||||
expect(error).toHaveBeenCalledWith(2003);
|
||||
|
||||
(mover as unknown as { moving: boolean }).moving = false;
|
||||
vi.spyOn(mover, 'start').mockReturnValueOnce(null);
|
||||
await expect(move.finalize()).resolves.toBe(false);
|
||||
await expect(move.finalize()).resolves.toBe(ReplayCommandResult.Failed);
|
||||
expect(error).toHaveBeenCalledWith(2004);
|
||||
});
|
||||
|
||||
@ -430,7 +442,7 @@ describe('replay playback error codes 2001-2008', () => {
|
||||
|
||||
await expect(
|
||||
teleport.execute(step(ReplayCode.Teleport, [4, 5]))
|
||||
).resolves.toBe(false);
|
||||
).resolves.toBe(ReplayCommandResult.Failed);
|
||||
expect(error).toHaveBeenCalledWith(2005, '4', '5');
|
||||
});
|
||||
|
||||
@ -443,7 +455,7 @@ describe('replay playback error codes 2001-2008', () => {
|
||||
|
||||
await expect(
|
||||
useItem.execute(step(ReplayCode.UseItem, [34]))
|
||||
).resolves.toBe(false);
|
||||
).resolves.toBe(ReplayCommandResult.Failed);
|
||||
expect(error).toHaveBeenCalledWith(2006, '34');
|
||||
});
|
||||
|
||||
@ -458,7 +470,7 @@ describe('replay playback error codes 2001-2008', () => {
|
||||
|
||||
await expect(
|
||||
equip.execute(step(ReplayCode.Equip, [99, 1, false]))
|
||||
).resolves.toBe(false);
|
||||
).resolves.toBe(ReplayCommandResult.Failed);
|
||||
expect(error).toHaveBeenCalledWith(2007, '99', '1');
|
||||
});
|
||||
|
||||
@ -473,7 +485,7 @@ describe('replay playback error codes 2001-2008', () => {
|
||||
|
||||
await expect(
|
||||
unequip.execute(step(ReplayCode.Unequip, [1]))
|
||||
).resolves.toBe(false);
|
||||
).resolves.toBe(ReplayCommandResult.Failed);
|
||||
expect(error).toHaveBeenCalledWith(2008, '1');
|
||||
});
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
// 测试 CoreState 顶层存读档:经公开 saveState/loadState 对 5 个 saveable 做整体往返
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { logger } from '@motajs/common';
|
||||
import { WebLoadStarter } from '@motajs/loader';
|
||||
import {
|
||||
type IEnemyAttr,
|
||||
type IHeroAttr,
|
||||
@ -18,7 +19,7 @@ import {
|
||||
type IMapLayer,
|
||||
type IMapStoreSave
|
||||
} from '@user/data-base';
|
||||
import { CoreState, createCoreState } from '../src/core';
|
||||
import { CoreState } from '../src/core';
|
||||
|
||||
vi.hoisted(() => {
|
||||
vi.stubGlobal('main', { replayChecking: true });
|
||||
@ -46,6 +47,14 @@ vi.hoisted(() => {
|
||||
};
|
||||
});
|
||||
|
||||
/** 按 08-08 约定的 CoreState 装配创建一个独立状态实例 */
|
||||
function createCoreState(): CoreState {
|
||||
return new CoreState({
|
||||
loadStarter: new WebLoadStarter(),
|
||||
coreURL: 'placeholder'
|
||||
});
|
||||
}
|
||||
|
||||
/** CoreState 已注册的 5 个可存档 id */
|
||||
const SAVEABLE_IDS = [
|
||||
'@system/hero',
|
||||
@ -123,16 +132,9 @@ function seedState(state: CoreState): SeededState {
|
||||
attribute.set('def', 6);
|
||||
attribute.set('money', 20);
|
||||
attribute.set('exp', 30);
|
||||
state.hero.location.setPos(3, 4);
|
||||
state.hero.location.setFloor('F1');
|
||||
state.hero.location.mover.setFaceDir(FaceDirection.Up);
|
||||
|
||||
state.flags.setFieldValue('score', 7);
|
||||
state.flags.setFieldValue('coins', 12);
|
||||
state.flags.addFieldValue('stage', 2);
|
||||
|
||||
const map = state.maps.createMap('F1', 2, 2);
|
||||
const layer = map.addLayer();
|
||||
const layer = map.addLayer('event');
|
||||
layer.setZIndex(0);
|
||||
map.setActiveStatus(true);
|
||||
state.maps.compareWith(
|
||||
@ -141,6 +143,14 @@ function seedState(state: CoreState): SeededState {
|
||||
layer.setBlock(5, 0, 0);
|
||||
layer.setBlock(7, 1, 1);
|
||||
|
||||
state.hero.location.setPos(3, 4);
|
||||
state.hero.location.setFloor(map);
|
||||
state.hero.location.mover.setFaceDir(FaceDirection.Up);
|
||||
|
||||
state.flags.setFieldValue('score', 7);
|
||||
state.flags.setFieldValue('coins', 12);
|
||||
state.flags.addFieldValue('stage', 2);
|
||||
|
||||
state.enemyManager.addPrefab(createEnemy());
|
||||
state.enemyManager.compareWith(new Map([[1, createEnemy()]]));
|
||||
state.enemyManager.modifyPrefabAttribute(1, prefab => {
|
||||
@ -162,7 +172,7 @@ function mutateState(state: CoreState, seeded: SeededState): void {
|
||||
attribute.set('money', 0);
|
||||
attribute.set('exp', 0);
|
||||
state.hero.location.setPos(9, 9);
|
||||
state.hero.location.setFloor('F9');
|
||||
state.hero.location.setFloor(null);
|
||||
state.hero.location.mover.setFaceDir(FaceDirection.Down);
|
||||
|
||||
state.flags.setFieldValue('score', 0);
|
||||
@ -204,8 +214,8 @@ function registerIdentityEquipment(state: CoreState): void {
|
||||
equip: {
|
||||
slots: [0],
|
||||
animate: 'sword',
|
||||
value: new Map(value),
|
||||
percentage: new Map(),
|
||||
value: Object.fromEntries(value),
|
||||
percentage: {},
|
||||
loadEvent: null,
|
||||
unloadEvent: null
|
||||
}
|
||||
@ -309,10 +319,9 @@ describe('CoreState top-level save and load round trips', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('CoreState container same-reference load (#06-17-4/5/6)', () => {
|
||||
// 验证经顶层 CoreState 三档往返后装备实例、flag 字段与 follower 实例均为同一实例,
|
||||
// 且其数值与位置回到存档点
|
||||
it('keeps container instances across save and load in every compression', () => {
|
||||
describe('CoreState container restore across save and load', () => {
|
||||
// 验证经顶层 CoreState 三档往返后装备按 uid 与数值恢复、flag 字段值恢复到存档点
|
||||
it('restores container state across save and load in every compression', () => {
|
||||
for (const compression of COMPRESSIONS) {
|
||||
const state = createCoreState();
|
||||
registerIdentityEquipment(state);
|
||||
@ -322,14 +331,11 @@ describe('CoreState container same-reference load (#06-17-4/5/6)', () => {
|
||||
state.hero.equip.equip(uid, 0);
|
||||
const equipmentBefore = state.hero.items.equipment.get(uid)!;
|
||||
|
||||
const fieldBefore = state.flags.getOrInsert('identity', 7);
|
||||
|
||||
state.hero.followers.addFollower(9102);
|
||||
const followerBefore = state.hero.followers.getFollower(0)!;
|
||||
state.flags.getOrInsert('identity', 7);
|
||||
|
||||
const snapshot = state.saveState(compression);
|
||||
|
||||
// 装备数值只能经读档改变,flag 与 follower 位置则在存档后继续改动
|
||||
// 装备数值与 flag 字段值都只能经读档恢复
|
||||
equipmentBefore.loadState(
|
||||
{
|
||||
uid,
|
||||
@ -342,19 +348,16 @@ describe('CoreState container same-reference load (#06-17-4/5/6)', () => {
|
||||
SaveCompression.NoCompression
|
||||
);
|
||||
state.flags.setFieldValue('identity', 99);
|
||||
followerBefore.location.setPos(9, 9);
|
||||
|
||||
state.loadState(snapshot, compression);
|
||||
|
||||
expect(state.hero.items.equipment.get(uid)).toBe(equipmentBefore);
|
||||
expect([...equipmentBefore.getModifiers()][0][1].getValue()).toBe(
|
||||
// Hero equipment 重构后装备实例按 uid 重建、flag 字段读档时重建,故只校验恢复后的 uid 与数值
|
||||
const restoredEquipment = state.hero.items.equipment.get(uid)!;
|
||||
expect(restoredEquipment.uid).toBe(uid);
|
||||
expect([...restoredEquipment.getModifiers()][0][1].getValue()).toBe(
|
||||
5
|
||||
);
|
||||
expect(state.flags.getField('identity')).toBe(fieldBefore);
|
||||
expect(fieldBefore.get()).toBe(7);
|
||||
expect(state.hero.followers.getFollower(0)).toBe(followerBefore);
|
||||
expect(followerBefore.location.x).toBe(0);
|
||||
expect(followerBefore.location.y).toBe(0);
|
||||
expect(state.flags.getFieldValue<number>('identity')).toBe(7);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user