mirror of
https://github.com/motajs/template.git
synced 2026-09-22 08:10:16 +08:00
test(06-13): assert all CoreState key states and varied 10-step replay (G-06-09-A/B)
- seedState/mutateState/assertRestored now cover every key state: hero base attrs + modifier final atk + location, flags score/coins/stage, map two blocks + active status, enemy hp/atk, replay steps - Replay seeded with 10 @system/replay steps mixing Up/Right/Down/Left/Teleport/UseItem/Equip/Unequip and varied param types, asserted per step - New container test loops flags and replay through CoreState across the three compression tiers - Append 06-13 create-or-append section to 06-COVERAGE-MAP.md; existing #06-09-1..5 skips unchanged
This commit is contained in:
parent
8a7cda95ae
commit
992ca9d585
@ -315,3 +315,24 @@ D-40:真实英雄操作经 `replay.route.add(...)`(mover 方向步)录制
|
||||
本计划**无新增码**,复用既有可达码;两条为受阻塞的正确预期 `it.skip`(锚定既有
|
||||
`#06-04-1`/`#06-04-2`/`#06-03-1`,不新建 finding 条目),G-06-04-B 为可跑绿;
|
||||
不削弱也不删除任何既有用例或跳过,`06-TEST-FINDINGS.md` 无新增 `#06-12-N` 缺陷条目。
|
||||
|
||||
## 06-13 存档缺口补测(D-46)
|
||||
|
||||
本计划为**覆盖缺口补测**(G-06-09-A、G-06-09-B),**不引入新码**,复用 06-09 既有可达码
|
||||
(55/58/59/112/113/119/120/122/124/177/178 的既有覆盖保持不变);阶段结构为
|
||||
构件(hero 单档类三档化 + 经容器覆盖无参类)→ 组合(map tile 三档化)→
|
||||
完整/集成(CoreState 顶层全关键状态 + 录像 ≥10 步多样化 + flags/replay 容器三档)。
|
||||
|
||||
| 缺口 | 用例 | 文件 | 计划 |
|
||||
| --- | --- | --- | --- |
|
||||
| G-06-09-B | `restores a percentage modifier on the same instance in NoCompression`、`restores constant and consumable item tables across all compressions`、`restores attributes, modifiers and location across all compressions`、`restores location and rendering through the container across all compressions` | `data-base/src/hero/saveLoad.test.ts` | 06-13 |
|
||||
| G-06-09-B | `restores covered events across all compressions`(`StaticTile` 与 `DynamicTile` 各一条同名用例) | `data-base/src/map/saveLoad.test.ts` | 06-13 |
|
||||
| G-06-09-A | `round-trips every registered saveable across all compressions`(每类 saveable 全部关键状态逐一严格断言 + 录像 10 步多样化逐条 exact) | `data-state/test/saveablesRoundTrip.test.ts` | 06-13 |
|
||||
| G-06-09-B | `restores flags and replay through the container across all compressions` | `data-state/test/saveablesRoundTrip.test.ts` | 06-13 |
|
||||
|
||||
受既有缺陷阻塞的档位按 D-05 以正确预期 `it.skip` 登记,复用既有锚点、不新建 finding:
|
||||
`restores a percentage modifier on the same instance in LowCompression`、
|
||||
`restores a percentage modifier on the same instance in HighCompression`(锚定 `#06-09-1`)、
|
||||
`restores the equipped mapping through the container across all compressions`(锚定 `#06-09-2`);
|
||||
既有 `#06-09-1..5` 的 skip 全部保持原样。
|
||||
本计划**无新增码**;`06-TEST-FINDINGS.md` 无新增 `#06-13-N` 缺陷条目。
|
||||
|
||||
@ -4,10 +4,12 @@ import { logger } from '@motajs/common';
|
||||
import {
|
||||
type IEnemyAttr,
|
||||
type IHeroAttr,
|
||||
FaceDirection,
|
||||
SaveCompression
|
||||
} from '@user/data-common';
|
||||
import {
|
||||
Enemy,
|
||||
ValueModifier,
|
||||
type IGameMap,
|
||||
type IHeroStateSave,
|
||||
type IMapLayer,
|
||||
@ -75,10 +77,57 @@ interface SeededState {
|
||||
layer: IMapLayer;
|
||||
}
|
||||
|
||||
/** 为 5 个 saveable 写入已知关键状态 */
|
||||
interface ExpectedReplayStep {
|
||||
command: number;
|
||||
params: readonly (number | string | boolean)[];
|
||||
}
|
||||
|
||||
/** 顶层录像写入的 10 步多样化命令序列,命令与参数类型均不全相同 */
|
||||
const REPLAY_STEPS: readonly ExpectedReplayStep[] = [
|
||||
{ command: 0, params: [] },
|
||||
{ command: 1, params: [] },
|
||||
{ command: 2, params: [] },
|
||||
{ command: 3, params: [] },
|
||||
{ command: 4, params: [2, 3] },
|
||||
{ command: 5, params: [12] },
|
||||
{ command: 6, params: [3, 0, true] },
|
||||
{ command: 7, params: [0] },
|
||||
{ command: 1, params: [] },
|
||||
{ command: 5, params: ['potion'] }
|
||||
];
|
||||
|
||||
/** 向顶层录像写入 10 步混合命令,覆盖移动/瞬移/用道具/装备/卸下 */
|
||||
function seedReplay(state: CoreState): void {
|
||||
state.replaySystem.record(0);
|
||||
state.replaySystem.record(1);
|
||||
state.replaySystem.record(2);
|
||||
state.replaySystem.record(3);
|
||||
state.replaySystem.record(4, 2, 3);
|
||||
state.replaySystem.record(5, 12);
|
||||
state.replaySystem.record(6, 3, 0, true);
|
||||
state.replaySystem.record(7, 0);
|
||||
state.replaySystem.record(1);
|
||||
state.replaySystem.record(5, 'potion');
|
||||
}
|
||||
|
||||
/** 为 5 个 saveable 写入已知的全部关键状态 */
|
||||
function seedState(state: CoreState): SeededState {
|
||||
state.hero.getModifiableAttribute().set('hp', 88);
|
||||
state.hero.registerModifier('@system/value', () => new ValueModifier(5));
|
||||
state.hero.createAndInsertModifier('@system/value', 'atk');
|
||||
const attribute = state.hero.getModifiableAttribute();
|
||||
attribute.set('hp', 88);
|
||||
attribute.set('atk', 10);
|
||||
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();
|
||||
layer.setZIndex(0);
|
||||
@ -87,39 +136,84 @@ function seedState(state: CoreState): SeededState {
|
||||
new Map([['F1', new Map([[0, new Uint32Array(4)]])]])
|
||||
);
|
||||
layer.setBlock(5, 0, 0);
|
||||
layer.setBlock(7, 1, 1);
|
||||
|
||||
state.enemyManager.addPrefab(createEnemy());
|
||||
state.enemyManager.compareWith(new Map([[1, createEnemy()]]));
|
||||
state.enemyManager.modifyPrefabAttribute(1, prefab => {
|
||||
prefab.setAttribute('hp', 30);
|
||||
prefab.setAttribute('atk', 9);
|
||||
return prefab;
|
||||
});
|
||||
state.replaySystem.record(2, 7);
|
||||
|
||||
seedReplay(state);
|
||||
return { map, layer };
|
||||
}
|
||||
|
||||
/** 保存后修改每个 saveable 的关键状态,用于验证读档恢复 */
|
||||
/** 保存后修改每个 saveable 的全部关键状态,用于验证读档恢复 */
|
||||
function mutateState(state: CoreState, seeded: SeededState): void {
|
||||
state.hero.getModifiableAttribute().set('hp', 1);
|
||||
const attribute = state.hero.getModifiableAttribute();
|
||||
attribute.set('hp', 1);
|
||||
attribute.set('atk', 1);
|
||||
attribute.set('def', 0);
|
||||
attribute.set('money', 0);
|
||||
attribute.set('exp', 0);
|
||||
state.hero.location.setPos(9, 9);
|
||||
state.hero.location.setFloor('F9');
|
||||
state.hero.location.mover.setFaceDir(FaceDirection.Down);
|
||||
|
||||
state.flags.setFieldValue('score', 0);
|
||||
state.flags.setFieldValue('coins', 0);
|
||||
state.flags.setFieldValue('stage', 0);
|
||||
|
||||
seeded.layer.setBlock(0, 0, 0);
|
||||
seeded.layer.setBlock(0, 1, 1);
|
||||
seeded.map.setActiveStatus(false);
|
||||
|
||||
state.enemyManager.modifyPrefabAttribute(1, prefab => {
|
||||
prefab.setAttribute('hp', 99);
|
||||
prefab.setAttribute('atk', 1);
|
||||
return prefab;
|
||||
});
|
||||
|
||||
state.replaySystem.record(9, 1);
|
||||
}
|
||||
|
||||
/** 逐 saveable 断言关键字段回到存档点 */
|
||||
/** 逐 saveable 严格断言全部关键字段回到存档点 */
|
||||
function assertRestored(state: CoreState, seeded: SeededState): void {
|
||||
expect(state.hero.getModifiableAttribute().getBaseAttribute('hp')).toBe(88);
|
||||
const attribute = state.hero.getModifiableAttribute();
|
||||
expect(attribute.getBaseAttribute('hp')).toBe(88);
|
||||
expect(attribute.getBaseAttribute('atk')).toBe(10);
|
||||
expect(attribute.getBaseAttribute('def')).toBe(6);
|
||||
expect(attribute.getBaseAttribute('money')).toBe(20);
|
||||
expect(attribute.getBaseAttribute('exp')).toBe(30);
|
||||
expect(attribute.getFinalAttribute('atk')).toBe(15);
|
||||
expect(state.hero.getLocation()).toEqual({
|
||||
x: 3,
|
||||
y: 4,
|
||||
direction: FaceDirection.Up
|
||||
});
|
||||
expect(state.hero.location.floorId).toBe('F1');
|
||||
|
||||
expect(state.flags.getFieldValue<number>('score')).toBe(7);
|
||||
expect(state.flags.getFieldValue<number>('coins')).toBe(12);
|
||||
expect(state.flags.getFieldValue<number>('stage')).toBe(2);
|
||||
|
||||
expect(state.maps.isMapActive('F1')).toBe(true);
|
||||
expect(seeded.layer.getBlock(0, 0)).toBe(5);
|
||||
expect(seeded.layer.getBlock(1, 1)).toBe(7);
|
||||
|
||||
expect(state.enemyManager.getPrefab(1)!.getAttribute('hp')).toBe(30);
|
||||
expect(state.replaySystem.route.length).toBe(1);
|
||||
expect(state.replaySystem.route.get(0)).toEqual({
|
||||
command: 2,
|
||||
params: [7],
|
||||
index: 0
|
||||
expect(state.enemyManager.getPrefab(1)!.getAttribute('atk')).toBe(9);
|
||||
|
||||
const route = state.replaySystem.route;
|
||||
expect(route.length).toBe(REPLAY_STEPS.length);
|
||||
REPLAY_STEPS.forEach((step, index) => {
|
||||
expect(route.get(index)).toEqual({
|
||||
command: step.command,
|
||||
params: step.params,
|
||||
index
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -254,3 +348,37 @@ describe('CoreState save and load guards', () => {
|
||||
expect(result.info.map(info => info.code)).toContain(178);
|
||||
});
|
||||
});
|
||||
|
||||
describe('CoreState container coverage for compression-less saveables', () => {
|
||||
// 验证不接受压缩参数的 flags 与 replay 经容器三档往返均恢复到存档点
|
||||
it('restores flags and replay through the container across all compressions', () => {
|
||||
for (const compression of COMPRESSIONS) {
|
||||
const state = createCoreState();
|
||||
state.flags.setFieldValue('score', 5);
|
||||
state.flags.addFieldValue('stage', 3);
|
||||
state.replaySystem.record(1);
|
||||
state.replaySystem.record(2, 7);
|
||||
|
||||
const snapshot = state.saveState(compression);
|
||||
state.flags.setFieldValue('score', 0);
|
||||
state.flags.setFieldValue('stage', 0);
|
||||
state.replaySystem.record(9, 1);
|
||||
|
||||
state.loadState(snapshot, compression);
|
||||
|
||||
expect(state.flags.getFieldValue<number>('score')).toBe(5);
|
||||
expect(state.flags.getFieldValue<number>('stage')).toBe(3);
|
||||
expect(state.replaySystem.route.length).toBe(2);
|
||||
expect(state.replaySystem.route.get(0)).toEqual({
|
||||
command: 1,
|
||||
params: [],
|
||||
index: 0
|
||||
});
|
||||
expect(state.replaySystem.route.get(1)).toEqual({
|
||||
command: 2,
|
||||
params: [7],
|
||||
index: 1
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user