refactor(logic): 과도한 수치 호환 보정을 제거한다
Core 수치 상태는 JavaScript와 PostgreSQL의 자연 정밀도를 유지한다. 정수 상태 경계와 절삭, 수입 배분 및 사망자 분할 순서는 보존한다.
This commit is contained in:
@@ -27,11 +27,6 @@ import {
|
||||
} from './che_상업투자.js';
|
||||
import { JosaUtil } from '@sammo-ts/common';
|
||||
import { clamp } from 'es-toolkit';
|
||||
import {
|
||||
addLegacyStoredFloat,
|
||||
readLegacyStoredFloat,
|
||||
toLegacyStoredFloat,
|
||||
} from '@sammo-ts/logic/compat/legacyFloat.js';
|
||||
|
||||
export interface TechResearchArgs {}
|
||||
|
||||
@@ -59,18 +54,7 @@ const readTech = (nation: Nation): number => {
|
||||
return typeof tech === 'number' && Number.isFinite(tech) ? tech : 0;
|
||||
};
|
||||
|
||||
// 레거시 nation.tech는 MariaDB FLOAT이며 다음 명령 재조회 시 6자리 유효숫자로 양자화된다.
|
||||
// Ref stores tech in a MariaDB FLOAT column. FLOAT applies binary32
|
||||
// quantization on write; its six-significant-digit text rendering happens
|
||||
// only when the value is read, not on every update.
|
||||
export const toLegacyStoredTech = toLegacyStoredFloat;
|
||||
|
||||
// mysqli renders a MariaDB FLOAT with six significant decimal digits before
|
||||
// PHP performs the next command's arithmetic. Model that read boundary, then
|
||||
// model the binary32 write boundary separately.
|
||||
export const readLegacyStoredTech = readLegacyStoredFloat;
|
||||
|
||||
export const addLegacyStoredTech = addLegacyStoredFloat;
|
||||
export const addTech = (current: number, delta: number): number => current + delta;
|
||||
|
||||
export class ActionDefinition<
|
||||
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
||||
@@ -143,7 +127,7 @@ export class ActionDefinition<
|
||||
|
||||
context.nation.meta = {
|
||||
...context.nation.meta,
|
||||
tech: addLegacyStoredTech(currentTech, techScore / generalCount),
|
||||
tech: addTech(currentTech, techScore / generalCount),
|
||||
};
|
||||
context.general.gold = Math.max(0, context.general.gold - result.costGold);
|
||||
context.general.experience += result.exp;
|
||||
|
||||
@@ -26,9 +26,7 @@ interface ProcureContext<
|
||||
const ACTION_NAME = '물자조달';
|
||||
const ACTION_KEY = 'che_물자조달';
|
||||
|
||||
// REF-COMPAT:BEGIN ref-int-column-write-rounding
|
||||
export const roundLegacyAccumulatedInteger = (current: number, delta: number): number => Math.round(current + delta);
|
||||
// REF-COMPAT:END ref-int-column-write-rounding
|
||||
export const roundAccumulatedInteger = (current: number, delta: number): number => Math.round(current + delta);
|
||||
|
||||
export const resolveLegacyExperienceLevel = (experience: number): number =>
|
||||
Math.max(
|
||||
@@ -123,8 +121,8 @@ export class ActionResolver<
|
||||
// accumulated binary value is exactly 4564.5 and persists as 4565.
|
||||
const rawNextExp = general.experience + exp;
|
||||
const rawNextDed = general.dedication + ded;
|
||||
const nextExp = Math.round(rawNextExp);
|
||||
const nextDed = Math.round(rawNextDed);
|
||||
const nextExp = roundAccumulatedInteger(general.experience, exp);
|
||||
const nextDed = roundAccumulatedInteger(general.dedication, ded);
|
||||
|
||||
let appliedScore = score;
|
||||
if (context.city && [1, 3].includes(context.city.frontState)) {
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { GeneralTriggerState } from '@sammo-ts/logic/domain/entities.js';
|
||||
import { LogCategory, LogFormat, LogScope } from '@sammo-ts/logic/logging/types.js';
|
||||
|
||||
import type { GeneralTurnCommandSpec } from './index.js';
|
||||
import { readLegacyCityTrust, storeLegacyCityTrust } from './legacyCityTrust.js';
|
||||
import { adjustCityTrust, resolveCityTrustValue } from './cityTrust.js';
|
||||
import {
|
||||
STRATEGY_ARGS_SCHEMA,
|
||||
StrategyActionDefinition,
|
||||
@@ -43,9 +43,8 @@ export class ActionResolver<
|
||||
result: StrategyResult<TriggerState>,
|
||||
effects: GeneralActionEffect<TriggerState>[]
|
||||
): void {
|
||||
const currentTrust =
|
||||
typeof context.destCity.meta.trust === 'number' ? readLegacyCityTrust(context.destCity.meta.trust) : 50;
|
||||
const nextTrust = storeLegacyCityTrust(Math.max(0, currentTrust - result.secondaryAmount));
|
||||
const currentTrust = resolveCityTrustValue(context.destCity.meta.trust);
|
||||
const nextTrust = adjustCityTrust(currentTrust, -result.secondaryAmount);
|
||||
|
||||
effects.push(
|
||||
createCityPatchEffect(
|
||||
|
||||
@@ -22,8 +22,7 @@ import {
|
||||
updateDomesticCriticalMeta,
|
||||
} from './che_상업투자.js';
|
||||
import { JosaUtil } from '@sammo-ts/common';
|
||||
import { clamp } from 'es-toolkit';
|
||||
import { readLegacyCityTrust, storeLegacyCityTrust } from './legacyCityTrust.js';
|
||||
import { adjustCityTrust, resolveCityTrustValue } from './cityTrust.js';
|
||||
|
||||
export interface TrustActionArgs {}
|
||||
|
||||
@@ -45,8 +44,7 @@ const CONFIG: InvestmentConfig = {
|
||||
};
|
||||
|
||||
const readTrust = (city: City): number => {
|
||||
const trust = city.meta.trust;
|
||||
return typeof trust === 'number' && Number.isFinite(trust) ? readLegacyCityTrust(trust) : DEFAULT_TRUST;
|
||||
return resolveCityTrustValue(city.meta.trust, DEFAULT_TRUST);
|
||||
};
|
||||
|
||||
const remainCityTrust = (): Constraint => ({
|
||||
@@ -105,7 +103,7 @@ export class ActionDefinition<
|
||||
const trustDelta = result.score / 10;
|
||||
context.city.meta = {
|
||||
...context.city.meta,
|
||||
trust: storeLegacyCityTrust(clamp(readTrust(context.city) + trustDelta, 0, 100)),
|
||||
trust: adjustCityTrust(readTrust(context.city), trustDelta),
|
||||
};
|
||||
context.general.rice = Math.max(0, context.general.rice - result.costGold);
|
||||
context.general.experience += result.exp;
|
||||
|
||||
@@ -30,7 +30,7 @@ import {
|
||||
isCrewTypeAvailable,
|
||||
} from '@sammo-ts/logic/world/unitSet.js';
|
||||
import { parseArgsWithSchema } from '../parseArgs.js';
|
||||
import { readLegacyCityTrust, storeLegacyCityTrust } from './legacyCityTrust.js';
|
||||
import { adjustCityTrust, resolveCityTrustValue } from './cityTrust.js';
|
||||
import { applyLegacyInjury, finalizeLegacyStat } from './legacyGeneralStat.js';
|
||||
|
||||
export interface RecruitEnvironment {
|
||||
@@ -61,14 +61,7 @@ const DEFAULT_MIN_POP = 30000;
|
||||
const DEFAULT_TRUST = 50;
|
||||
const MIN_CREW = 100;
|
||||
|
||||
// REF-COMPAT:BEGIN ref-php-half-rounding
|
||||
// PHP round() compensates for the small binary drift around a half boundary.
|
||||
// Ref then converts the result to int through Util::round().
|
||||
export const roundLegacyRecruitCost = (value: number): number => {
|
||||
const corrected = value + Math.sign(value) * Number.EPSILON * Math.max(1, Math.abs(value));
|
||||
return corrected < 0 ? Math.ceil(corrected - 0.5) : Math.floor(corrected + 0.5);
|
||||
};
|
||||
// REF-COMPAT:END ref-php-half-rounding
|
||||
export const roundRecruitCost = (value: number): number => Math.round(value);
|
||||
|
||||
export const ARGS_SCHEMA = z.preprocess(
|
||||
(raw) => {
|
||||
@@ -330,7 +323,7 @@ export class CommandResolver<TriggerState extends GeneralTriggerState = GeneralT
|
||||
crewType ? { armType: crewType.armType } : undefined
|
||||
);
|
||||
return {
|
||||
gold: roundLegacyRecruitCost(adjustedGold * costOffset),
|
||||
gold: roundRecruitCost(adjustedGold * costOffset),
|
||||
rice: Math.round(adjustedRice),
|
||||
applied: plan.applied,
|
||||
requested: plan.requested,
|
||||
@@ -444,9 +437,9 @@ export class ActionResolver<
|
||||
const costOffset = this.env.costOffset ?? DEFAULT_COST_OFFSET;
|
||||
const recruitPop = this.command.getRecruitPopulation(context, appliedCrew);
|
||||
const nextPopulation = Math.max(city.population - recruitPop, 0);
|
||||
const baseTrust = readLegacyCityTrust(readCityTrust(city, this.env.defaultTrust ?? DEFAULT_TRUST));
|
||||
const baseTrust = resolveCityTrustValue(readCityTrust(city, this.env.defaultTrust ?? DEFAULT_TRUST));
|
||||
const trustLoss = city.population > 0 ? (recruitPop / city.population / costOffset) * 100 : 0;
|
||||
const nextTrust = storeLegacyCityTrust(Math.max(baseTrust - trustLoss, 0));
|
||||
const nextTrust = adjustCityTrust(baseTrust, -trustLoss);
|
||||
|
||||
const actionName = this.env.actionName ?? ACTION_NAME;
|
||||
const [nextCrewTypeId, nextCrew, nextTrain, nextAtmos] =
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { clamp } from 'es-toolkit';
|
||||
|
||||
export const resolveCityTrustValue = (value: unknown, fallback = 50): number =>
|
||||
typeof value === 'number' && Number.isFinite(value) ? value : fallback;
|
||||
|
||||
export const adjustCityTrust = (current: number, delta: number): number => clamp(current + delta, 0, 100);
|
||||
@@ -198,4 +198,4 @@ export const loadGeneralTurnCommandSpecs = async (
|
||||
return specs;
|
||||
};
|
||||
|
||||
export { readLegacyCityTrust } from './legacyCityTrust.js';
|
||||
export { adjustCityTrust, resolveCityTrustValue } from './cityTrust.js';
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
/**
|
||||
* MariaDB stores city.trust as FLOAT (binary32), while the PHP driver exposes
|
||||
* the value rounded to six significant decimal digits on the next read.
|
||||
* Keep those boundaries separate so later SQL expressions use binary32 state.
|
||||
*/
|
||||
import { readLegacyStoredFloat, toLegacyStoredFloat } from '@sammo-ts/logic/compat/legacyFloat.js';
|
||||
|
||||
// REF-COMPAT:BEGIN ref-mariadb-float-boundary
|
||||
export const storeLegacyCityTrust = (value: number): number => toLegacyStoredFloat(value);
|
||||
|
||||
export const readLegacyCityTrust = (value: number): number => readLegacyStoredFloat(value);
|
||||
// REF-COMPAT:END ref-mariadb-float-boundary
|
||||
@@ -1,5 +1,3 @@
|
||||
// REF-COMPAT:BEGIN ref-stat-injury-truncation
|
||||
export const applyLegacyInjury = (value: number, injury: number): number => value * ((100 - injury) / 100);
|
||||
|
||||
export const finalizeLegacyStat = (value: number): number => Math.trunc(value);
|
||||
// REF-COMPAT:END ref-stat-injury-truncation
|
||||
|
||||
Reference in New Issue
Block a user