Skip to content

Commit cf108c2

Browse files
author
web3blind
committed
Complete gameplay recovery and QA coverage
1 parent b316fe7 commit cf108c2

15 files changed

Lines changed: 558 additions & 22 deletions

app/index.html

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@
118118
<script src="js/i18n/en.js?v=20260621d"></script>
119119

120120
<!-- Utilities -->
121-
<script src="js/utils/helpers.js"></script>
121+
<script src="js/utils/helpers.js?v=20260621l"></script>
122122
<script src="js/utils/crypto.js"></script>
123123
<script src="js/utils/a11y.js"></script>
124124

@@ -153,7 +153,7 @@
153153
<script src="js/engine/duel-state.js"></script>
154154
<script src="js/engine/validator.js"></script>
155155
<script src="js/engine/guild.js?v=20260621d"></script>
156-
<script src="js/engine/territory.js"></script>
156+
<script src="js/engine/territory.js?v=20260621l"></script>
157157
<script src="js/engine/crafting.js?v=20260621j"></script>
158158
<script src="js/engine/enchanting.js"></script>
159159
<script src="js/engine/marketplace.js"></script>
@@ -164,7 +164,7 @@
164164
<script src="js/engine/quest-system.js?v=20260621e"></script>
165165
<script src="js/engine/loci.js"></script>
166166
<script src="js/engine/npc-framework.js"></script>
167-
<script src="js/engine/state-engine.js?v=20260621k"></script>
167+
<script src="js/engine/state-engine.js?v=20260621l"></script>
168168
<script src="js/engine/daily-leaderboard-storage.js"></script>
169169
<script src="js/engine/daily-leaderboard.js"></script>
170170

@@ -181,13 +181,13 @@
181181
<script src="js/ui/screens/login.js"></script>
182182
<script src="js/ui/screens/onboarding.js"></script>
183183
<script src="js/ui/screens/home.js"></script>
184-
<script src="js/ui/screens/character.js"></script>
184+
<script src="js/ui/screens/character.js?v=20260621l"></script>
185185
<script src="js/ui/screens/hunt.js"></script>
186-
<script src="js/ui/screens/inventory.js"></script>
186+
<script src="js/ui/screens/inventory.js?v=20260621l"></script>
187187
<script src="js/ui/screens/chronicle.js?v=20260621h"></script>
188188
<script src="js/ui/screens/duel.js"></script>
189189
<script src="js/ui/screens/arena.js?v=20260621d"></script>
190-
<script src="js/ui/screens/guild.js?v=20260621d"></script>
190+
<script src="js/ui/screens/guild.js?v=20260621l"></script>
191191
<script src="js/ui/screens/map.js?v=20260621h"></script>
192192
<script src="js/ui/screens/marketplace.js?v=20260621k"></script>
193193
<script src="js/ui/screens/crafting.js?v=20260621j"></script>

app/js/engine/state-engine.js

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,30 @@ var StateEngine = (function() {
3737
};
3838
}
3939

40+
function _normalizeWorldState(state) {
41+
var empty = _createEmptyState();
42+
if (!state || typeof state !== 'object') return empty;
43+
for (var key in empty) {
44+
if (empty.hasOwnProperty(key) && typeof state[key] === 'undefined') {
45+
state[key] = empty[key];
46+
}
47+
}
48+
if (typeof state.headBlock !== 'number' || isNaN(state.headBlock) || state.headBlock < 0) state.headBlock = 0;
49+
if (typeof state.checkpointBlock !== 'number' || isNaN(state.checkpointBlock) || state.checkpointBlock < 0) state.checkpointBlock = state.headBlock || 0;
50+
state.characters = state.characters || {};
51+
state.inventories = state.inventories || {};
52+
state.guilds = state.guilds || {};
53+
state.territories = state.territories || {};
54+
state.recentActions = state.recentActions || [];
55+
state.social = state.social || { knownAccounts: [] };
56+
state.social.knownAccounts = state.social.knownAccounts || [];
57+
state.guildListings = state.guildListings || [];
58+
state.quests = state.quests || {};
59+
state.loci = state.loci || {};
60+
state.activeEvents = state.activeEvents || [];
61+
return state;
62+
}
63+
4064
/**
4165
* Initialize the state engine.
4266
* Tries to load from checkpoint, otherwise starts fresh.
@@ -52,7 +76,7 @@ var StateEngine = (function() {
5276

5377
CheckpointSystem.loadLatestCheckpoint('global', function(err, checkpoint) {
5478
if (checkpoint && checkpoint.state) {
55-
worldState = checkpoint.state;
79+
worldState = _normalizeWorldState(checkpoint.state);
5680
console.log('StateEngine: Loaded checkpoint at block', worldState.headBlock);
5781
} else {
5882
console.log('StateEngine: No checkpoint found, starting fresh');

app/js/engine/territory.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,12 @@ var TerritorySystem = (function() {
238238
siege.state = SIEGE_STATE.FAILED;
239239
result = { winner: 'defender', siege: siege };
240240
}
241+
// A siege resolution is itself a territory-control decision.
242+
// Prevent the periodic delegation recalculation in the same
243+
// block from immediately overwriting the resolved controller,
244+
// especially for fixture/early territories that have no
245+
// delegation rows yet.
246+
territory.lastRecalcBlock = blockNum;
241247
return result;
242248
}
243249
}

app/js/ui/screens/character.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ var CharacterScreen = (function() {
1212
var user = VizAccount.getCurrentUser();
1313
var ch = StateEngine.getCharacter(user);
1414
if (!ch) { ch = { name: user || 'Unknown', className: 'embercaster', level: 1, pot: 10, res: 6, swf: 8, int: 7, for_: 5, coreBonus: 0, spells: ['firebolt'], maxHp: 100, hp: 100 }; }
15+
ch.coreBonus = ch.coreBonus || 0;
16+
ch.spells = ch.spells || [];
1517

1618
var corePerStat = Math.floor((ch.coreBonus || 0) / 5);
1719
var totalPot = (typeof CharacterSystem !== 'undefined' && CharacterSystem.getTotalStat) ? CharacterSystem.getTotalStat(ch, 'pot') : ((ch.pot || 0) + corePerStat);

app/js/ui/screens/guild.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -746,7 +746,7 @@ var GuildScreen = (function() {
746746
html += '<div class="active-key-status active-key-missing">';
747747
html += '<p class="active-key-notice">' + t('guild_active_key_needed') + '</p>';
748748
html += '<label class="input-label" for="input-active-key">' + t('guild_active_key_label') + '</label>';
749-
html += '<input type="password" class="input-field" id="input-active-key" placeholder="5J..." autocomplete="off">';
749+
html += '<input type="password" class="input-field" id="input-active-key" placeholder="5J..." autocomplete="off" aria-label="' + t('guild_active_key_label') + '">';
750750
html += '<button class="btn btn-primary btn-sm" id="btn-save-active-key">' + t('guild_active_key_save') + '</button>';
751751
html += '</div>';
752752
}

app/js/ui/screens/inventory.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ var InventoryScreen = (function() {
9191

9292
html += '<div class="settings-field settings-toggle">';
9393
html += '<label for="inv-compact" class="settings-toggle-label">' + t('inv_compact_mode') + '</label>';
94-
html += '<button id="inv-compact" class="settings-toggle-btn' + (compact ? ' active' : '') + '" role="switch" aria-checked="' + compact + '">';
94+
html += '<button id="inv-compact" class="settings-toggle-btn' + (compact ? ' active' : '') + '" role="switch" aria-checked="' + compact + '" aria-label="' + Helpers.escapeHtml(t('inv_compact_mode')) + '">';
9595
html += '<span class="toggle-knob"></span>';
9696
html += '</button>';
9797
html += '</div>';

app/js/utils/helpers.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ var Helpers = (function() {
144144
* @returns {string}
145145
*/
146146
function formatNumber(num) {
147+
if (num === null || typeof num === 'undefined' || isNaN(num)) num = 0;
147148
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
148149
}
149150

app/sw.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Viz Magic — Service Worker
2-
var CACHE_NAME = 'viz-magic-v24';
2+
var CACHE_NAME = 'viz-magic-v25';
33
var ASSETS = [
44
'/',
55
'/index.html',

plan.md

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ Acceptance criteria:
307307

308308
## Remaining work item 5 — Archive Mirror / PM2 health
309309

310-
Status: production endpoint works; SQLite data size was around 9 MB in the last check. PM2 RAM was not rechecked in the final attempt because aaPanel cron PATH did not expose `pm2`/`node`.
310+
Status: completed in the 2026-06-21 08:03 UTC pass. aaPanel one-shot health check resolved the Node/PM2 path, showed `vizmagic-game-archive` online at ~37 MB RSS and `vizmagic-game-archive-indexer` online at ~70 MB RSS, and archive data size around 10 MB. Public `/archive-mirror/health`, `/archive-mirror/v1/guilds`, and `/archive-mirror/v1/range?...` were responsive. `/archive-mirror/v1/health` returns 404 on this Nginx mapping, while `/archive-mirror/health` is the live health path.
311311

312312
Goal: confirm Archive Mirror process RAM stays below the target ~200 MB and endpoints remain responsive.
313313

@@ -339,7 +339,7 @@ Acceptance criteria:
339339

340340
## Remaining work item 6 — Mobile accessibility / TalkBack QA
341341

342-
Status: needs focused pass.
342+
Status: completed in the 2026-06-21 08:03 UTC pass with `tests/core-screen-accessibility-smoke.test.js` and `node /tmp/vizmagic_screen_smoke.js`. All core screens rendered with visible text, no blank controls, no raw internal keys, and latest cache-busted scripts. High-impact fixes added: missing numeric fallback in `Helpers.formatNumber`, character fallback defaults for `coreBonus`/`spells`, accessible names for Inventory compact switch and Guild active-key input.
343343

344344
Goal: ensure core screens are practical for Denis with TalkBack.
345345

@@ -375,7 +375,7 @@ Acceptance criteria:
375375

376376
## Remaining work item 7 — World Boss / Territory / Siege
377377

378-
Status: deep mechanics not fully covered.
378+
Status: local fixture/replay smoke completed in the 2026-06-21 08:03 UTC pass. No disruptive live-chain war/siege/boss broadcast was made. `tests/world-boss-territory-siege-smoke.test.js` verifies replay of guild create, siege declare/commit, boss attack, and territory claim; it also caught and fixed same-block territory recalculation overwriting a siege winner.
379379

380380
Goal: verify these screens do not break, and distinguish smoke from full gameplay coverage.
381381

@@ -433,14 +433,16 @@ Suggested prompt for a new Hermes session:
433433

434434
## Completion ledger
435435

436+
Last updated: 2026-06-21 08:03 UTC.
437+
436438
Mark items here as the next session completes them:
437439

438440
- [x] Crafting E2E — local fixture + regression coverage; live-chain broadcast intentionally not spent
439-
- [ ] Marketplace E2E
440-
- [ ] Two-browser Guild smoke
441-
- [ ] Two-browser Arena smoke
442-
- [ ] Checkpoint recovery matrix
443-
- [ ] Archive Mirror / PM2 RAM health
444-
- [ ] Mobile accessibility / TalkBack QA
445-
- [ ] World Boss / Territory / Siege smoke
446-
- [ ] Economy / item consistency audit
441+
- [x] Marketplace E2E — state-engine authoritative list/cancel/buy, checkpoint on live action, replay transfer without duplication
442+
- [x] Two-browser Guild smoke — isolated-browser replay fixture confirms invite/member state on Browser B
443+
- [x] Two-browser Arena smoke — isolated-browser replay fixture confirms challenge visibility and accepted active duel state on Browser B
444+
- [x] Checkpoint recovery matrix — fresh/recent/old partial IndexedDB checkpoints normalize to the current schema without hanging
445+
- [x] Archive Mirror / PM2 RAM health — production archive endpoints responsive; PM2 archive API/indexer online under ~200 MB RSS
446+
- [x] Mobile accessibility / TalkBack QA — core screen smoke covers names/raw keys/blank controls; high-impact blockers fixed
447+
- [x] World Boss / Territory / Siege smoke — local replay fixture covers boss attack, siege lifecycle, and territory claim without live-chain mutation
448+
- [x] Economy / item consistency audit — catalog/loot/recipe/material coverage regression remains passing in `tests/player-bug-regressions.test.js`

tests/accessibility-regressions.test.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ const onboardingJs = read('app/js/ui/screens/onboarding.js');
2929
const settingsJs = read('app/js/ui/screens/settings.js');
3030
const accessibilityCss = read('app/css/accessibility.css');
3131
const mainCss = read('app/css/main.css');
32+
const helpersJs = read('app/js/utils/helpers.js');
33+
const characterJs = read('app/js/ui/screens/character.js');
34+
const inventoryJs = read('app/js/ui/screens/inventory.js');
35+
const guildJs = read('app/js/ui/screens/guild.js');
3236

3337
test('manifest does not lock orientation', function () {
3438
assert.ok(!Object.prototype.hasOwnProperty.call(manifest, 'orientation'), 'orientation lock should be removed');
@@ -77,6 +81,14 @@ test('skip link styles are present', function () {
7781
assert.ok(/\.skip-link/.test(mainCss), 'skip-link CSS missing');
7882
});
7983

84+
test('core screen fallbacks avoid blank controls and fixture crashes', function () {
85+
assert.ok(/typeof num === 'undefined'/.test(helpersJs), 'formatNumber should tolerate missing numeric values');
86+
assert.ok(/ch\.coreBonus = ch\.coreBonus \|\| 0/.test(characterJs), 'character screen should default missing coreBonus');
87+
assert.ok(/ch\.spells = ch\.spells \|\| \[\]/.test(characterJs), 'character screen should default missing spells');
88+
assert.ok(/id="inv-compact"[\s\S]*aria-label=/.test(inventoryJs), 'inventory compact switch needs an accessible name');
89+
assert.ok(/id="input-active-key"[\s\S]*aria-label=/.test(guildJs), 'guild active key input needs an accessible name for TalkBack');
90+
});
91+
8092
if (process.exitCode) {
8193
process.exit(process.exitCode);
8294
}

0 commit comments

Comments
 (0)