step 10.18: wip (not ticked)

This commit is contained in:
bepis
2026-10-23 11:31:37 +11:00
parent d8438a0422
commit b7ed4f9bbc
+17 -19
View File
@@ -18,19 +18,19 @@ var SEED = 42;
// ─── Zone / monster definitions (MUST match 008_balance.sql) ──────────────── // ─── Zone / monster definitions (MUST match 008_balance.sql) ────────────────
var ZONES = { var ZONES = {
meadows: { slug: 'whispering-meadows', minLevel: 1, energyCost: 5, monsters: [ meadows: { slug: 'whispering-meadows', minLevel: 1, energyCost: 5, monsters: [
{ name: 'Giant Rat', level: 1, hp: 200, attack: 6, defense: 8, speed: 12, xp: 50, goldMin: 3, goldMax: 7 }, { name: 'Giant Rat', level: 1, hp: 250, attack: 8, defense: 8, speed: 12, xp: 70, goldMin: 3, goldMax: 7 },
{ name: 'Meadow Wolf', level: 2, hp: 250, attack: 8, defense: 8, speed: 16, xp: 65, goldMin: 5, goldMax: 12 }, { name: 'Meadow Wolf', level: 2, hp: 350, attack: 12, defense: 8, speed: 16, xp: 110, goldMin: 5, goldMax: 12 },
{ name: 'Goblin Scrapper', level: 3, hp: 300, attack: 10, defense: 8, speed: 11, xp: 85, goldMin: 8, goldMax: 18 }, { name: 'Goblin Scrapper', level: 3, hp: 300, attack: 18, defense: 8, speed: 11, xp: 180, goldMin: 8, goldMax: 18 },
]}, ]},
forest: { slug: 'gloomroot-forest', minLevel: 5, energyCost: 8, monsters: [ forest: { slug: 'gloomroot-forest', minLevel: 5, energyCost: 8, monsters: [
{ name: 'Webspinner', level: 5, hp: 200, attack: 25, defense: 30, speed: 15, xp: 90, goldMin: 12, goldMax: 24 }, { name: 'Webspinner', level: 5, hp: 200, attack: 25, defense: 20, speed: 15, xp: 80, goldMin: 12, goldMax: 24 },
{ name: 'Bandit Cutthroat', level: 6, hp: 250, attack: 30, defense: 30, speed: 14, xp: 120, goldMin: 18, goldMax: 36 }, { name: 'Bandit Cutthroat', level: 6, hp: 250, attack: 30, defense: 20, speed: 14, xp: 110, goldMin: 18, goldMax: 36 },
{ name: 'Mossback Treant', level: 8, hp: 320, attack: 35, defense: 30, speed: 7, xp: 160, goldMin: 22, goldMax: 44 }, { name: 'Mossback Treant', level: 8, hp: 350, attack: 35, defense: 30, speed: 7, xp: 160, goldMin: 22, goldMax: 44 },
]}, ]},
crags: { slug: 'ashfang-crag', minLevel: 10, energyCost: 12, monsters: [ crags: { slug: 'ashfang-crag', minLevel: 10, energyCost: 12, monsters: [
{ name: 'Ember Imp', level: 10, hp: 150, attack: 25, defense: 12, speed: 20, xp: 200, goldMin: 30, goldMax: 55 }, { name: 'Ember Imp', level: 10, hp: 200, attack: 30, defense: 20, speed: 20, xp: 300, goldMin: 30, goldMax: 55 },
{ name: 'Stone Golem', level: 12, hp: 300, attack: 30, defense: 12, speed: 5, xp: 280, goldMin: 40, goldMax: 75 }, { name: 'Stone Golem', level: 12, hp: 600, attack: 40, defense: 30, speed: 5, xp: 420, goldMin: 40, goldMax: 75 },
{ name: 'Ashfang Drake', level: 14, hp: 400, attack: 28, defense: 3, speed: 18, xp: 350, goldMin: 55, goldMax: 110 }, { name: 'Ashfang Drake', level: 14, hp: 450, attack: 45, defense: 25, speed: 18, xp: 600, goldMin: 55, goldMax: 110 },
]}, ]},
}; };
@@ -51,11 +51,9 @@ function pickMonster(zoneKey, rng) {
} }
function simulateHunt(hero, monster, rng) { function simulateHunt(hero, monster, rng) {
var equipBonuses = { bonus_str: 0, bonus_agi: 0, bonus_int: 0, bonus_vit: 0, bonus_attack: 0, bonus_defense: 0 };
var fight = resolveCombat({ var fight = resolveCombat({
class: hero.class, level: hero.level, hpCurrent: hero.hp, class: hero.class, level: hero.level, hpCurrent: hero.hp,
str: hero.str, agi: hero.agi, int: hero.int, vit: hero.vit, str: hero.str, agi: hero.agi, int: hero.int, vit: hero.vit,
bonus_str: 0, bonus_agi: 0, bonus_int: 0, bonus_vit: 0, bonus_attack: 0, bonus_defense: 0,
}, monster, rng); }, monster, rng);
var xpGained = 0, goldGained = 0; var xpGained = 0, goldGained = 0;
@@ -88,7 +86,7 @@ function simulateClass(className) {
var results = { meadows: { wins: 0, total: 0 }, forest: { wins: 0, total: 0 }, crags: { wins: 0, total: 0 } }; var results = { meadows: { wins: 0, total: 0 }, forest: { wins: 0, total: 0 }, crags: { wins: 0, total: 0 } };
var totalHunts = 0; var totalHunts = 0;
var consecutiveDefeats = 0; var consecutiveDefeats = 0;
var MAX_DEFEATS = 200; // increased from 50 — low-vit classes need more attempts var MAX_DEFEATS = 200;
// Phase 1: Level 1 → 5 in Meadows // Phase 1: Level 1 → 5 in Meadows
while (hero.level < 5 && consecutiveDefeats < MAX_DEFEATS) { while (hero.level < 5 && consecutiveDefeats < MAX_DEFEATS) {
@@ -284,23 +282,23 @@ function main() {
var avgL1to10 = Math.round(classResults.reduce(function(s, c) { return s + c.huntsL1to10; }, 0) / classResults.length); var avgL1to10 = Math.round(classResults.reduce(function(s, c) { return s + c.huntsL1to10; }, 0) / classResults.length);
console.log('\n--- Aggregate Targets ---'); console.log('\n--- Aggregate Targets ---');
console.log(' L1→5 hunts: ' + avgL1to5 + ' (target: 40–70) ' + (avgL1to5 >= 40 && avgL1to5 <= 70 ? '✓' : '✗')); console.log(' L1→5 hunts: ' + avgL1to5 + ' (target: 40–70) ' + (avgL1to5 >= 40 && avgL1to5 <= 70 ? 'PASS' : 'FAIL'));
console.log(' L1→10 hunts: ' + avgL1to10 + ' (target: 150–300) ' + (avgL1to10 >= 150 && avgL1to10 <= 300 ? '✓' : '✗')); console.log(' L1→10 hunts: ' + avgL1to10 + ' (target: 150–300) ' + (avgL1to10 >= 150 && avgL1to10 <= 300 ? 'PASS' : 'FAIL'));
if (l1Meadows < targets.l1Meadows[0] || l1Meadows > targets.l1Meadows[1]) { console.log(' L1 Meadows: ' + pct(l1Meadows) + ' ✗'); allMet = false; } else { console.log(' L1 Meadows: ' + pct(l1Meadows) + ' ✓'); } if (l1Meadows < targets.l1Meadows[0] || l1Meadows > targets.l1Meadows[1]) { console.log(' L1 Meadows: ' + pct(l1Meadows) + ' FAIL'); allMet = false; } else { console.log(' L1 Meadows: ' + pct(l1Meadows) + ' PASS'); }
if (l1Crags < targets.l1Crags[0] || l1Crags > targets.l1Crags[1]) { console.log(' L1 Crags: ' + pct(l1Crags) + ' ✗'); allMet = false; } else { console.log(' L1 Crags: ' + pct(l1Crags) + ' ✓'); } if (l1Crags < targets.l1Crags[0] || l1Crags > targets.l1Crags[1]) { console.log(' L1 Crags: ' + pct(l1Crags) + ' FAIL'); allMet = false; } else { console.log(' L1 Crags: ' + pct(l1Crags) + ' PASS'); }
if (l5Forest < targets.l5Forest[0] || l5Forest > targets.l5Forest[1]) { console.log(' L5 Forest: ' + pct(l5Forest) + ' ✗'); allMet = false; } else { console.log(' L5 Forest: ' + pct(l5Forest) + ' ✓'); } if (l5Forest < targets.l5Forest[0] || l5Forest > targets.l5Forest[1]) { console.log(' L5 Forest: ' + pct(l5Forest) + ' FAIL'); allMet = false; } else { console.log(' L5 Forest: ' + pct(l5Forest) + ' PASS'); }
console.log('\n--- Class Spread (max 15pp gap) ---'); console.log('\n--- Class Spread (max 15pp gap) ---');
var meadowsRates = classResults.map(function(c) { return c.results.meadows.total > 0 ? c.results.meadows.wins / c.results.meadows.total : 0; }); var meadowsRates = classResults.map(function(c) { return c.results.meadows.total > 0 ? c.results.meadows.wins / c.results.meadows.total : 0; });
var mMax = Math.max.apply(null, meadowsRates); var mMax = Math.max.apply(null, meadowsRates);
var mMin = Math.min.apply(null, meadowsRates); var mMin = Math.min.apply(null, meadowsRates);
var mGap = (mMax - mMin) * 100; var mGap = (mMax - mMin) * 100;
console.log(' Meadows: gap ' + pct(mGap / 100) + ' ' + (mGap <= 15 ? '✓' : '✗')); console.log(' Meadows: gap ' + (mGap).toFixed(1) + 'pp ' + (mGap <= 15 ? 'PASS' : 'FAIL'));
if (mGap > 15) allMet = false; if (mGap > 15) allMet = false;
console.log('\n' + '='.repeat(80)); console.log('\n' + '='.repeat(80));
console.log(allMet ? 'ALL TARGETS MET ✓' : 'SOME TARGETS NOT MET ✗ — tune monster stats in a NEW migration'); console.log(allMet ? 'ALL TARGETS MET' : 'SOME TARGETS NOT MET — tune monster stats in a NEW migration');
console.log('='.repeat(80)); console.log('='.repeat(80));
} }