feat: improve combat feedback and progression
This commit is contained in:
164
scripts/verify-growth-consistency.mjs
Normal file
164
scripts/verify-growth-consistency.mjs
Normal file
@@ -0,0 +1,164 @@
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { createServer } from 'vite';
|
||||
|
||||
const storage = new Map();
|
||||
globalThis.window = {
|
||||
localStorage: {
|
||||
getItem(key) {
|
||||
return storage.has(key) ? storage.get(key) : null;
|
||||
},
|
||||
setItem(key, value) {
|
||||
storage.set(key, String(value));
|
||||
},
|
||||
removeItem(key) {
|
||||
storage.delete(key);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const server = await createServer({
|
||||
logLevel: 'error',
|
||||
server: { middlewareMode: true, hmr: false },
|
||||
appType: 'custom'
|
||||
});
|
||||
|
||||
try {
|
||||
const { calculateEquipmentBonuses } = await server.ssrLoadModule('/src/game/data/battleItems.ts');
|
||||
const { applyCampaignUnitProgress } = await server.ssrLoadModule('/src/game/data/unitProgress.ts');
|
||||
const {
|
||||
campaignStorageKey,
|
||||
createInitialCampaignState,
|
||||
loadCampaignState
|
||||
} = await server.ssrLoadModule('/src/game/state/campaignState.ts');
|
||||
|
||||
const equipment = {
|
||||
weapon: { itemId: 'green-dragon-glaive', level: 4, exp: 12 },
|
||||
armor: { itemId: 'oath-robe', level: 3, exp: 8 },
|
||||
accessory: { itemId: 'bravery-token', level: 5, exp: 4 }
|
||||
};
|
||||
const equipmentBonuses = calculateEquipmentBonuses(equipment);
|
||||
assert(
|
||||
sameJson(equipmentBonuses, { attack: 9, defense: 4, strategy: 5 }),
|
||||
`Expected item and slot-level equipment bonuses to share one calculation: ${JSON.stringify(equipmentBonuses)}`
|
||||
);
|
||||
|
||||
const scenarioUnit = createUnit({ equipment });
|
||||
const savedProgress = createUnit({
|
||||
level: 5,
|
||||
exp: 42,
|
||||
hp: 27,
|
||||
maxHp: 38,
|
||||
attack: 14,
|
||||
stats: { might: 81, intelligence: 85, leadership: 90, agility: 73, luck: 91 },
|
||||
equipment
|
||||
});
|
||||
const merged = applyCampaignUnitProgress(scenarioUnit, savedProgress);
|
||||
assert(
|
||||
merged.level === 5 &&
|
||||
merged.exp === 42 &&
|
||||
merged.hp === 27 &&
|
||||
merged.maxHp === 38 &&
|
||||
merged.attack === 14 &&
|
||||
sameJson(merged.stats, savedProgress.stats) &&
|
||||
sameJson(merged.equipment, savedProgress.equipment) &&
|
||||
merged.x === scenarioUnit.x &&
|
||||
merged.y === scenarioUnit.y,
|
||||
`Expected the next battle to preserve all saved growth while keeping authored deployment: ${JSON.stringify(merged)}`
|
||||
);
|
||||
merged.stats.might += 20;
|
||||
merged.equipment.weapon.level += 1;
|
||||
assert(
|
||||
savedProgress.stats.might === 81 && savedProgress.equipment.weapon.level === 4,
|
||||
'Expected merged campaign progress to be deeply cloned.'
|
||||
);
|
||||
|
||||
const legacyProgress = createUnit({ level: 5, exp: 17, hp: 28, equipment });
|
||||
const recovered = applyCampaignUnitProgress(scenarioUnit, legacyProgress);
|
||||
assert(
|
||||
recovered.level === 5 &&
|
||||
recovered.attack === 11 &&
|
||||
recovered.maxHp === 36 &&
|
||||
sameJson(recovered.stats, { might: 78, intelligence: 80, leadership: 86, agility: 70, luck: 88 }),
|
||||
`Expected older saves that lost level-up fields to recover authored growth floors: ${JSON.stringify(recovered)}`
|
||||
);
|
||||
|
||||
const rawSave = createInitialCampaignState();
|
||||
rawSave.step = 'first-camp';
|
||||
rawSave.roster = [{
|
||||
...savedProgress,
|
||||
attack: '14',
|
||||
stats: Object.fromEntries(Object.entries(savedProgress.stats).map(([key, value]) => [key, String(value)])),
|
||||
equipment: Object.fromEntries(
|
||||
Object.entries(savedProgress.equipment).map(([slot, state]) => [slot, { ...state, level: String(state.level), exp: String(state.exp) }])
|
||||
)
|
||||
}];
|
||||
storage.set(campaignStorageKey, JSON.stringify(rawSave));
|
||||
const loaded = loadCampaignState();
|
||||
const loadedUnit = loaded.roster[0];
|
||||
const loadedMerge = applyCampaignUnitProgress(scenarioUnit, loadedUnit);
|
||||
assert(
|
||||
loadedUnit.attack === 14 &&
|
||||
sameJson(loadedUnit.stats, savedProgress.stats) &&
|
||||
sameJson(loadedUnit.equipment, savedProgress.equipment) &&
|
||||
loadedMerge.attack === 14 &&
|
||||
sameJson(loadedMerge.stats, savedProgress.stats),
|
||||
`Expected legacy-compatible normalization and reload to retain every growth field: ${JSON.stringify({ loadedUnit, loadedMerge })}`
|
||||
);
|
||||
|
||||
const [battleSource, campSource] = await Promise.all([
|
||||
readFile('src/game/scenes/BattleScene.ts', 'utf8'),
|
||||
readFile('src/game/scenes/CampScene.ts', 'utf8')
|
||||
]);
|
||||
assert(
|
||||
battleSource.includes('return mergeCampaignUnitProgress(unit, progress);'),
|
||||
'Expected BattleScene campaign hydration to use the shared full-progress merger.'
|
||||
);
|
||||
assert(
|
||||
countOccurrences(battleSource, 'calculateEquipmentBonuses(unit.equipment).') === 3 &&
|
||||
countOccurrences(campSource, 'calculateEquipmentBonuses(unit.equipment).') === 3,
|
||||
'Expected CampScene display and BattleScene combat to use the same three equipment bonus projections.'
|
||||
);
|
||||
|
||||
console.log('Verified full character-growth carryover, legacy growth-floor recovery, save normalization, and shared camp/battle equipment level bonuses.');
|
||||
} finally {
|
||||
await server.close();
|
||||
}
|
||||
|
||||
function createUnit(overrides = {}) {
|
||||
return {
|
||||
id: 'liu-bei',
|
||||
name: '유비',
|
||||
faction: 'ally',
|
||||
className: '군주',
|
||||
classKey: 'lord',
|
||||
level: 3,
|
||||
exp: 0,
|
||||
hp: 32,
|
||||
maxHp: 32,
|
||||
attack: 9,
|
||||
move: 4,
|
||||
stats: { might: 76, intelligence: 78, leadership: 84, agility: 70, luck: 88 },
|
||||
equipment: {
|
||||
weapon: { itemId: 'training-sword', level: 1, exp: 0 },
|
||||
armor: { itemId: 'cloth-armor', level: 1, exp: 0 },
|
||||
accessory: { itemId: 'grain-pouch', level: 1, exp: 0 }
|
||||
},
|
||||
x: 3,
|
||||
y: 14,
|
||||
...overrides
|
||||
};
|
||||
}
|
||||
|
||||
function countOccurrences(source, needle) {
|
||||
return source.split(needle).length - 1;
|
||||
}
|
||||
|
||||
function sameJson(actual, expected) {
|
||||
return JSON.stringify(actual) === JSON.stringify(expected);
|
||||
}
|
||||
|
||||
function assert(condition, message) {
|
||||
if (!condition) {
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user