refactor(logic): 과도한 수치 호환 보정을 제거한다

Core 수치 상태는 JavaScript와 PostgreSQL의 자연 정밀도를 유지한다. 정수 상태 경계와 절삭, 수입 배분 및 사망자 분할 순서는 보존한다.
This commit is contained in:
2026-08-24 19:17:06 +00:00
parent fc9fa0c9a6
commit 60dcf815ea
32 changed files with 148 additions and 522 deletions
@@ -7,14 +7,7 @@ import { resolveAppliedNationRate } from './nationTaxRate.js';
type SemiAnnualResource = 'gold' | 'rice';
// REF-COMPAT:BEGIN ref-decimal-half-stabilization
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);
};
// REF-COMPAT:END ref-decimal-half-stabilization
const roundIntegerState = (value: number): number => Math.round(value);
const parseResource = (args: readonly unknown[]): SemiAnnualResource => {
const resource = args[0];
@@ -29,11 +22,9 @@ const resolveBasePopulationIncrease = (world: InMemoryTurnWorld): number => {
return typeof value === 'number' && Number.isFinite(value) ? value : 5_000;
};
const decayDomesticValue = (value: number): number => roundLegacyIntegerColumn(value * 0.99);
const decayDomesticValue = (value: number): number => roundIntegerState(value * 0.99);
// REF-COMPAT:BEGIN ref-mariadb-float-boundary
export const storeLegacySemiAnnualTrust = (value: number): number => Math.fround(Math.max(0, Math.min(100, value)));
// REF-COMPAT:END ref-mariadb-float-boundary
export const clampSemiAnnualTrust = (value: number): number => Math.max(0, Math.min(100, value));
const applyResourceMaintenance = (value: number, ratios: readonly [number, number][]): number => {
if (value <= 1_000) {
@@ -41,10 +32,10 @@ const applyResourceMaintenance = (value: number, ratios: readonly [number, numbe
}
for (const [threshold, ratio] of ratios) {
if (value > threshold) {
return roundLegacyIntegerColumn(value * ratio);
return roundIntegerState(value * ratio);
}
}
return roundLegacyIntegerColumn(value * 0.99);
return roundIntegerState(value * 0.99);
};
export const createProcessSemiAnnualHandler = (options: {
@@ -117,22 +108,19 @@ export const createProcessSemiAnnualHandler = (options: {
: 1 + populationRatio * (1 - securityRatio);
const trust = asNumber(city.meta.trust, 50);
world.updateCity(city.id, {
population: roundLegacyIntegerColumn(
population: roundIntegerState(
Math.min(city.populationMax, basePopulationIncrease + city.population * populationFactor)
),
agriculture: roundLegacyIntegerColumn(
agriculture: roundIntegerState(
Math.min(city.agricultureMax, city.agriculture * (1 + genericRatio))
),
commerce: roundLegacyIntegerColumn(Math.min(city.commerceMax, city.commerce * (1 + genericRatio))),
security: roundLegacyIntegerColumn(Math.min(city.securityMax, city.security * (1 + genericRatio))),
defence: roundLegacyIntegerColumn(Math.min(city.defenceMax, city.defence * (1 + genericRatio))),
wall: roundLegacyIntegerColumn(Math.min(city.wallMax, city.wall * (1 + genericRatio))),
commerce: roundIntegerState(Math.min(city.commerceMax, city.commerce * (1 + genericRatio))),
security: roundIntegerState(Math.min(city.securityMax, city.security * (1 + genericRatio))),
defence: roundIntegerState(Math.min(city.defenceMax, city.defence * (1 + genericRatio))),
wall: roundIntegerState(Math.min(city.wallMax, city.wall * (1 + genericRatio))),
meta: {
...city.meta,
// 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),
trust: clampSemiAnnualTrust(trust + trustDiff),
},
});
}