fix: match scenario 2601 monthly seed progression

This commit is contained in:
2026-08-03 18:56:23 +00:00
parent 2f17584075
commit 58b9a230a7
92 changed files with 3696 additions and 587 deletions
@@ -7,7 +7,12 @@ import { resolveAppliedNationRate } from './nationTaxRate.js';
type SemiAnnualResource = 'gold' | 'rice';
const roundLegacyIntegerColumn = (value: number): number => Math.round(value);
const roundLegacyIntegerColumn = (value: number): number => {
// MariaDB evaluates the decimal rate expression before ROUND(). Binary
// arithmetic can instead produce values such as 2029.4999999999998.
const stabilized = Number(value.toPrecision(15));
return stabilized >= 0 ? Math.floor(stabilized + 0.5) : Math.ceil(stabilized - 0.5);
};
const parseResource = (args: readonly unknown[]): SemiAnnualResource => {
const resource = args[0];
@@ -24,6 +29,8 @@ const resolveBasePopulationIncrease = (world: InMemoryTurnWorld): number => {
const decayDomesticValue = (value: number): number => roundLegacyIntegerColumn(value * 0.99);
export const storeLegacySemiAnnualTrust = (value: number): number => Math.fround(Math.max(0, Math.min(100, value)));
const applyResourceMaintenance = (value: number, ratios: readonly [number, number][]): number => {
if (value <= 1_000) {
return value;
@@ -81,6 +88,9 @@ export const createProcessSemiAnnualHandler = (options: {
const basePopulationIncrease = resolveBasePopulationIncrease(world);
for (const nation of world.listNations()) {
if (nation.id <= 0) {
continue;
}
const rate = resolveAppliedNationRate(nation.meta);
let populationRatio = (30 - rate) / 200;
const trait = options.nationTraits?.get(nation.typeCode);
@@ -115,7 +125,10 @@ export const createProcessSemiAnnualHandler = (options: {
wall: roundLegacyIntegerColumn(Math.min(city.wallMax, city.wall * (1 + genericRatio))),
meta: {
...city.meta,
trust: Math.max(0, Math.min(100, trust + trustDiff)),
// Ref's UPDATE persists trust to a MariaDB FLOAT before
// the next monthly action reads it. Core stores this
// field in JSON, so emulate that binary32 boundary here.
trust: storeLegacySemiAnnualTrust(trust + trustDiff),
},
});
}
@@ -124,6 +137,14 @@ export const createProcessSemiAnnualHandler = (options: {
for (const general of world.listGenerals()) {
const current = general[resource];
const next = applyResourceMaintenance(current, [[10_000, 0.97]]);
if (
process.env.SEED_PARITY_MONTHLY_RESOURCE_TRACE === '1' &&
(process.env.AI_TRACE_GENERAL_IDS ?? '').split(',').includes(String(general.id))
) {
process.stdout.write(
`MONTHLY_RESOURCE_CORE ${JSON.stringify({ action: 'ProcessSemiAnnual', resource, generalId: general.id, current, next })}\n`
);
}
if (next !== current) {
world.updateGeneral(general.id, { [resource]: next });
}