Skip to content

Commit 80fefa5

Browse files
author
web3blind
committed
test: expand world systems replay coverage
1 parent b9fdace commit 80fefa5

1 file changed

Lines changed: 116 additions & 27 deletions

File tree

tests/world-boss-territory-siege-smoke.test.js

Lines changed: 116 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ function startAppServer() {
2626
});
2727
}
2828

29-
function makeBlock(blockNum, sender, type, data) {
29+
function makeFixtureBlock(blockNum, sender, type, data) {
3030
var blockId = String(blockNum).padStart(8, '0') + 'abcdefabcdefabcdefabcdefabcdefab';
3131
return {
3232
__blockNum: blockNum,
@@ -47,51 +47,140 @@ async function run() {
4747
await page.goto('http://127.0.0.1:' + APP_PORT + '/?world-systems=smoke', { waitUntil: 'networkidle0', timeout: 15000 });
4848

4949
var result = await page.evaluate(function(blocks) {
50+
function makeFixtureBlock(blockNum, sender, type, data) {
51+
var blockId = String(blockNum).padStart(8, '0') + 'abcdefabcdefabcdefabcdefabcdefab';
52+
return {
53+
__blockNum: blockNum,
54+
previous: '0000000000000000000000000000000000000000',
55+
block_id: blockId,
56+
witness_signature: blockId,
57+
timestamp: '2026-06-21T00:00:00',
58+
transactions: [{ operations: [[ 'custom', { required_regular_auths: [sender], required_active_auths: [], id: 'VM', json: JSON.stringify({ p: 'VM', v: 1, b: blockNum - 1, t: type, d: data }) } ]] }]
59+
};
60+
}
61+
62+
function process(block) {
63+
var processed = BlockProcessor.processBlock(block, block.__blockNum);
64+
var events = StateEngine.processBlock(processed);
65+
return { blockNum: block.__blockNum, actions: processed.vmActions.length, events: events.map(function(ev) { return ev.type; }), rawEvents: events };
66+
}
67+
68+
function hasEvent(outputs, type) {
69+
for (var i = 0; i < outputs.length; i++) {
70+
if (outputs[i].events.indexOf(type) !== -1) return true;
71+
}
72+
return false;
73+
}
74+
75+
function initCharacter(account, level, pot) {
76+
var state = StateEngine.getState();
77+
state.characters[account] = { name: account, className: 'embercaster', level: level, hp: 500, maxHp: 500, pot: pot, res: 6, swf: 5, int: 7, for_: 4, coreBonus: 0, spells: ['firebolt'] };
78+
state.inventories[account] = [];
79+
}
80+
5081
localStorage.setItem('viz_magic_session', JSON.stringify({ currentUser: 'world-alpha', users: { 'world-alpha': { regular_key: 'fixture' } } }));
5182
VizAccount.init();
83+
84+
// Scenario 1: replay-only happy path for boss attack + attacker siege claim.
5285
StateEngine.reset();
53-
var state = StateEngine.getState();
54-
state.characters['world-alpha'] = { name: 'World Alpha', className: 'embercaster', level: 8, hp: 120, maxHp: 120, pot: 10, res: 6, swf: 5, int: 7, for_: 4, coreBonus: 0, spells: ['firebolt'] };
55-
state.inventories['world-alpha'] = [];
86+
initCharacter('world-alpha', 8, 10);
5687
var outputs = [];
57-
for (var i = 0; i < blocks.length; i++) {
58-
var block = blocks[i];
59-
var processed = BlockProcessor.processBlock(block, block.__blockNum);
60-
var events = StateEngine.processBlock(processed);
61-
outputs.push({ blockNum: block.__blockNum, actions: processed.vmActions.length, events: events.map(function(ev) { return ev.type; }) });
62-
}
88+
for (var i = 0; i < blocks.length; i++) outputs.push(process(blocks[i]));
6389
var st = StateEngine.getState();
6490
App.navigateTo('world-boss');
6591
var bossText = document.getElementById('screen-world-boss').innerText;
6692
App.navigateTo('map');
6793
var mapText = document.getElementById('screen-map').innerText;
68-
return {
94+
var happy = {
6995
outputs: outputs,
96+
hasGuild: hasEvent(outputs, 'guild_created'),
97+
hasSiegeDeclared: hasEvent(outputs, 'siege_declared'),
98+
hasSiegeContribution: hasEvent(outputs, 'siege_contribution'),
99+
hasBossAttack: hasEvent(outputs, 'boss_attacked'),
100+
hasTerritoryClaim: hasEvent(outputs, 'territory_claimed'),
70101
boss: st.worldBoss,
71102
territory: st.territories.commons_first_light,
72103
guild: st.guilds['world-guild'],
73104
bossText: bossText,
74105
mapText: mapText
75106
};
107+
108+
// Scenario 2: expired active boss must not accept attacks or mutate damage.
109+
StateEngine.reset();
110+
initCharacter('expired-alpha', 10, 20);
111+
var expiredState = StateEngine.getState();
112+
expiredState.worldBoss = WorldBoss.spawnBoss(1000, 1, WorldBoss.BOSS_ACCOUNT);
113+
expiredState.worldBoss.endBlock = 1100;
114+
var expiredBefore = expiredState.worldBoss.totalDamage;
115+
var expiredOutput = process(makeFixtureBlock(1200, 'expired-alpha', 'boss.attack', { spell: 'firebolt' }));
116+
var expiredAfter = StateEngine.getState().worldBoss.totalDamage;
117+
118+
// Scenario 3: high-power local fixture can defeat the boss and expose loot UI without chain mutation.
119+
StateEngine.reset();
120+
initCharacter('slayer-alpha', 5000, 20000);
121+
var defeatOutput = process(makeFixtureBlock(5000, 'slayer-alpha', 'boss.attack', { spell: 'firebolt' }));
122+
var defeatState = StateEngine.getState();
123+
var loot = WorldBoss.distributeLoot(defeatState.worldBoss);
124+
App.navigateTo('world-boss');
125+
var defeatedText = document.getElementById('screen-world-boss').innerText;
126+
127+
// Scenario 4: defender wins an expired siege; territory must remain with defender guild.
128+
StateEngine.reset();
129+
initCharacter('attacker-alpha', 20, 20);
130+
initCharacter('defender-alpha', 20, 20);
131+
var defenderOutputs = [];
132+
defenderOutputs.push(process(makeFixtureBlock(6001, 'defender-alpha', 'guild.create', { id: 'def-guild', name: 'Defenders', tag: 'DEF', school: 'stonewarden' })));
133+
defenderOutputs.push(process(makeFixtureBlock(6002, 'attacker-alpha', 'guild.create', { id: 'atk-guild', name: 'Attackers', tag: 'ATK', school: 'embercaster' })));
134+
var defenderState = StateEngine.getState();
135+
defenderState.territories = TerritorySystem.initTerritories(6002);
136+
defenderState.territories.commons_first_light.controllerGuild = 'def-guild';
137+
defenderState.territories.commons_first_light.controllerSince = 6002;
138+
defenderOutputs.push(process(makeFixtureBlock(6003, 'attacker-alpha', 'siege.declare', { territory_id: 'commons_first_light', guild_id: 'atk-guild' })));
139+
defenderOutputs.push(process(makeFixtureBlock(6004, 'attacker-alpha', 'siege.commit', { siege_ref: 'siege_commons_first_light_atk-guild_6003', energy: 10 })));
140+
defenderOutputs.push(process(makeFixtureBlock(6005, 'defender-alpha', 'siege.commit', { siege_ref: 'siege_commons_first_light_atk-guild_6003', energy: 50 })));
141+
defenderOutputs.push(process(makeFixtureBlock(210000, 'attacker-alpha', 'territory.claim', { territory_id: 'commons_first_light', siege_ref: 'siege_commons_first_light_atk-guild_6003' })));
142+
var defenderTerritory = StateEngine.getState().territories.commons_first_light;
143+
144+
return {
145+
happy: happy,
146+
expired: { output: expiredOutput, before: expiredBefore, after: expiredAfter },
147+
defeated: { output: defeatOutput, boss: defeatState.worldBoss, loot: loot, text: defeatedText },
148+
defender: { outputs: defenderOutputs, territory: defenderTerritory }
149+
};
76150
}, [
77-
makeBlock(3001, 'world-alpha', 'guild.create', { id: 'world-guild', name: 'World Guild', tag: 'WRL', school: 'embercaster' }),
78-
makeBlock(3002, 'world-alpha', 'siege.declare', { territory_id: 'commons_first_light', guild_id: 'world-guild' }),
79-
makeBlock(3003, 'world-alpha', 'siege.commit', { siege_ref: 'siege_commons_first_light_world-guild_3002', energy: 77 }),
80-
makeBlock(3004, 'world-alpha', 'boss.attack', { spell: 'firebolt' }),
81-
makeBlock(205000, 'world-alpha', 'territory.claim', { territory_id: 'commons_first_light', siege_ref: 'siege_commons_first_light_world-guild_3002' })
151+
makeFixtureBlock(3001, 'world-alpha', 'guild.create', { id: 'world-guild', name: 'World Guild', tag: 'WRL', school: 'embercaster' }),
152+
makeFixtureBlock(3002, 'world-alpha', 'siege.declare', { territory_id: 'commons_first_light', guild_id: 'world-guild' }),
153+
makeFixtureBlock(3003, 'world-alpha', 'siege.commit', { siege_ref: 'siege_commons_first_light_world-guild_3002', energy: 77 }),
154+
makeFixtureBlock(3004, 'world-alpha', 'boss.attack', { spell: 'firebolt' }),
155+
makeFixtureBlock(205000, 'world-alpha', 'territory.claim', { territory_id: 'commons_first_light', siege_ref: 'siege_commons_first_light_world-guild_3002' })
82156
]);
83157

84-
var allEvents = result.outputs.reduce(function(acc, item) { return acc.concat(item.events); }, []);
85-
assert.ok(allEvents.indexOf('guild_created') !== -1, 'guild create should replay');
86-
assert.ok(allEvents.indexOf('siege_declared') !== -1, 'siege declare should replay');
87-
assert.ok(allEvents.indexOf('siege_contribution') !== -1, 'siege contribution should replay');
88-
assert.ok(allEvents.indexOf('boss_attacked') !== -1, 'boss attack should replay');
89-
assert.ok(allEvents.indexOf('territory_claimed') !== -1, 'territory claim should replay after siege expiry: ' + JSON.stringify(result.outputs));
90-
assert.ok(result.boss && result.boss.totalDamage > 0, 'boss state should accumulate deterministic damage');
91-
assert.strictEqual(result.territory.controllerGuild, 'world-guild', 'attacker guild should control territory after uncontested claim: ' + JSON.stringify({ outputs: result.outputs, territory: result.territory }));
92-
assert.ok(result.guild && result.guild.members['world-alpha'], 'guild state should persist founder membership');
93-
assert.ok(result.bossText.indexOf('World Boss') !== -1, 'world boss screen should render after populated state');
94-
assert.ok(result.mapText.indexOf('World Map') !== -1, 'map/territory screen should render after populated state');
158+
assert.ok(result.happy.hasGuild, 'guild create should replay');
159+
assert.ok(result.happy.hasSiegeDeclared, 'siege declare should replay');
160+
assert.ok(result.happy.hasSiegeContribution, 'siege contribution should replay');
161+
assert.ok(result.happy.hasBossAttack, 'boss attack should replay');
162+
assert.ok(result.happy.hasTerritoryClaim, 'territory claim should replay after siege expiry: ' + JSON.stringify(result.happy.outputs));
163+
assert.ok(result.happy.boss && result.happy.boss.totalDamage > 0, 'boss state should accumulate deterministic damage');
164+
assert.strictEqual(result.happy.territory.controllerGuild, 'world-guild', 'attacker guild should control territory after uncontested claim: ' + JSON.stringify({ outputs: result.happy.outputs, territory: result.happy.territory }));
165+
assert.ok(result.happy.guild && result.happy.guild.members['world-alpha'], 'guild state should persist founder membership');
166+
assert.ok(result.happy.bossText.indexOf('World Boss') !== -1, 'world boss screen should render after populated state');
167+
assert.ok(result.happy.mapText.indexOf('World Map') !== -1, 'map/territory screen should render after populated state');
168+
169+
assert.deepStrictEqual(result.expired.output.events, [], 'expired active boss should not accept attack: ' + JSON.stringify(result.expired));
170+
assert.strictEqual(result.expired.after, result.expired.before, 'expired boss damage must not mutate');
171+
172+
assert.ok(result.defeated.output.events.indexOf('boss_attacked') !== -1, 'defeat attack should emit boss_attacked');
173+
assert.ok(result.defeated.output.events.indexOf('boss_defeated') !== -1, 'high-power fixture should defeat boss');
174+
assert.strictEqual(result.defeated.boss.currentHp, 0, 'defeated boss HP should clamp to zero');
175+
assert.strictEqual(result.defeated.boss.defeated, true, 'defeated flag should be true');
176+
assert.ok(result.defeated.loot.length === 1 && result.defeated.loot[0].account === 'slayer-alpha', 'loot distribution should include contributor: ' + JSON.stringify(result.defeated.loot));
177+
assert.ok(result.defeated.text.indexOf('Defeated') !== -1 || result.defeated.text.indexOf('Повержен') !== -1, 'defeated UI should render loot/defeated state');
178+
179+
var defenderEvents = result.defender.outputs.reduce(function(acc, item) { return acc.concat(item.events); }, []);
180+
assert.ok(defenderEvents.indexOf('territory_claimed') !== -1, 'defender siege claim should resolve');
181+
assert.strictEqual(result.defender.territory.controllerGuild, 'def-guild', 'defender guild should retain territory after stronger defense: ' + JSON.stringify(result.defender));
182+
assert.strictEqual(result.defender.territory.activeSieges[0].state, 'failed', 'defended siege should be marked failed');
183+
95184
console.log('PASS world boss territory siege replay smoke');
96185
} finally {
97186
if (browser) await browser.close();

0 commit comments

Comments
 (0)