Files
dwroller/scripts/simulate-midplay-rules.js
Alex 7873f450da feat: rules midgame priority system, mission enhancements, and requisition shop improvements
- Add midgame priority ranking for rules (midgamePriority field)
- Enhance rules search to support midgame filtering and sorting
- Improve mission simulation and midgame tracking
- Fix RequisitionShop player name handling
- Add rules midplay simulation tests
- Update MissionTab with midgame scene tracking
- Add backend logging for rules updates
2026-06-24 12:09:25 +02:00

140 lines
4.4 KiB
JavaScript

#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const repoRoot = path.resolve(__dirname, '..');
const rulesPath = path.join(repoRoot, 'database', 'rules', 'rules-database.json');
const data = JSON.parse(fs.readFileSync(rulesPath, 'utf8'));
const rules = data.rules || [];
const scenarios = [
{
actor: 'Player',
moment: 'Tyranid Warrior hits Brother Lucian; player checks whether a Reaction can cancel it.',
query: 'dodge',
expected: ['Dodge']
},
{
actor: 'GM',
moment: 'Heavy bolter lays down fire across a corridor.',
query: 'suppressing fire pinning',
expected: ['Suppressing Fire', 'Pinning']
},
{
actor: 'Player',
moment: 'Devastator fires full auto and needs additional hit rules.',
query: 'full auto burst',
expected: ['Full Auto Burst']
},
{
actor: 'GM',
moment: 'A mob of gaunts becomes one mass enemy.',
query: 'horde magnitude damage',
expected: ['Hordes']
},
{
actor: 'Player',
moment: 'Librarian rolls psychic backlash.',
query: 'perils of the warp',
expected: ['Perils of the Warp']
},
{
actor: 'GM',
moment: 'Kill-team takes blast damage in Squad Mode.',
query: 'cohesion damage',
expected: ['Cohesion Damage']
},
{
actor: 'Player',
moment: 'Mission prep pauses to buy gear.',
query: 'requisition renown',
expected: ['Requisition', 'Renown']
},
{
actor: 'GM',
moment: 'Enemy causes Fear and the table needs the modifier.',
query: 'fear test',
expected: ['Fear']
}
];
function includes(value, term) {
return String(value || '').toLowerCase().includes(term);
}
function score(rule, query) {
const term = query.toLowerCase();
const tokens = term.split(/\s+/).filter(Boolean);
const title = String(rule.title || '').toLowerCase();
const summary = String(rule.summary || '').toLowerCase();
const content = String(rule.content || '').toLowerCase();
const tags = (rule.tags || []).map(v => String(v).toLowerCase());
const aliases = (rule.aliases || []).map(v => String(v).toLowerCase());
const tokenHits = tokens.reduce((sum, token) => {
return sum +
(title.includes(token) ? 8 : 0) +
(aliases.some(a => a.includes(token)) ? 6 : 0) +
(tags.some(a => a.includes(token)) ? 4 : 0) +
(summary.includes(token) ? 3 : 0) +
(content.includes(token) ? 1 : 0);
}, 0);
return (title === term ? 100 : 0) +
(aliases.includes(term) ? 90 : 0) +
(title.startsWith(term) ? 50 : 0) +
(includes(rule.title, term) ? 20 : 0) +
(aliases.some(a => a.includes(term)) ? 18 : 0) +
(tags.some(t => t.includes(term)) ? 15 : 0) +
(includes(rule.summary, term) ? 8 : 0) +
Number(rule.midgamePriority || 0) +
tokenHits +
(includes(rule.content, term) ? 1 : 0);
}
function search(query, limit = 5) {
const term = query.toLowerCase();
const tokens = term.split(/\s+/).filter(Boolean);
return rules
.filter(rule => {
const haystacks = [
rule.title,
rule.summary,
rule.content,
rule.category,
...(rule.tags || []),
...(rule.aliases || [])
].map(v => String(v || '').toLowerCase());
return tokens.every(token => haystacks.some(v => v.includes(token))) ||
haystacks.some(v => v.includes(term));
})
.sort((a, b) => score(b, query) - score(a, query))
.slice(0, limit);
}
let failures = 0;
console.log('Mid-play rules lookup simulation\n');
for (const scenario of scenarios) {
const results = search(scenario.query);
const titles = results.map(r => r.title);
const ok = scenario.expected.some(expected => titles.slice(0, 3).includes(expected));
if (!ok) failures += 1;
console.log(`${scenario.actor}: ${scenario.moment}`);
console.log(` Query: ${scenario.query}`);
console.log(` Top results: ${titles.slice(0, 3).join(' | ') || '(none)'}`);
if (results[0]) {
const page = results[0].page ? ` p.${results[0].page}${results[0].pageEnd && results[0].pageEnd !== results[0].page ? `-${results[0].pageEnd}` : ''}` : '';
console.log(` Opens: ${results[0].sourceAbbr || results[0].source}${page} [${results[0].sourceMethod}, ${Math.round(Number(results[0].confidence || 0) * 100)}%]`);
}
console.log(` ${ok ? 'PASS' : 'FAIL'}\n`);
}
if (failures) {
console.error(`${failures} mid-play lookup scenario(s) failed.`);
process.exit(1);
}
console.log('All mid-play lookup scenarios passed.');