fix: align scenario 2400 long-run parity

This commit is contained in:
2026-08-04 18:15:32 +00:00
parent 8b8a285bc7
commit cef6a90452
30 changed files with 521 additions and 184 deletions
@@ -27,6 +27,14 @@ const ACTION_KEY = 'che_물자조달';
export const roundLegacyAccumulatedInteger = (current: number, delta: number): number => Math.round(current + delta);
export const resolveLegacyExperienceLevel = (experience: number): number =>
Math.max(
0,
Math.min(255, experience < 1_000 ? Math.trunc(experience / 100) : Math.trunc(Math.sqrt(experience / 10)))
);
export const resolveLegacyDedicationLevel = (dedication: number): number =>
Math.max(0, Math.min(30, Math.ceil(Math.sqrt(dedication) / 10)));
export class ActionResolver<
TriggerState extends GeneralTriggerState = GeneralTriggerState,
> implements GeneralActionResolver<TriggerState, ProcureArgs> {
@@ -107,8 +115,10 @@ export class ActionResolver<
// the delta separately changes cancellation cases such as
// 4554 + (45 * 0.7 / 3): the delta is 10.499999999999998, while the
// accumulated binary value is exactly 4564.5 and persists as 4565.
const nextExp = roundLegacyAccumulatedInteger(general.experience, exp);
const nextDed = roundLegacyAccumulatedInteger(general.dedication, ded);
const rawNextExp = general.experience + exp;
const rawNextDed = general.dedication + ded;
const nextExp = Math.round(rawNextExp);
const nextDed = Math.round(rawNextDed);
let appliedScore = score;
if (context.city && [1, 3].includes(context.city.frontState)) {
@@ -167,6 +177,8 @@ export class ActionResolver<
dedication: nextDed,
meta: {
...general.meta,
explevel: resolveLegacyExperienceLevel(rawNextExp),
dedlevel: resolveLegacyDedicationLevel(rawNextDed),
[statKey]:
(typeof general.meta[statKey] === 'number' ? (general.meta[statKey] as number) : 0) + 1,
},
@@ -87,6 +87,8 @@ export interface CommerceInvestmentResult {
export interface CommerceInvestmentArgs {}
const DEFAULT_TRUST = 50;
export const resolveLegacyDomesticTrust = (trust: number | null | undefined, fallback = DEFAULT_TRUST): number =>
Math.max(trust ?? fallback, DEFAULT_TRUST);
const DEFAULT_FRONT_STATES = [1, 3];
const DEFAULT_CONFIG: InvestmentConfig = {
key: 'che_상업투자',
@@ -188,7 +190,7 @@ export class CommandResolver<TriggerState extends GeneralTriggerState = GeneralT
}
calcBaseScore(context: DomesticActionContext<TriggerState>, rng: RandomGenerator): number {
const trust = getMetaNumber(context.city.meta, 'trust') ?? this.env.defaultTrust ?? DEFAULT_TRUST;
const trust = resolveLegacyDomesticTrust(getMetaNumber(context.city.meta, 'trust'), this.env.defaultTrust);
const injuryMultiplier = (100 - context.general.injury) / 100;
const rawStats = {
@@ -220,8 +222,9 @@ export class CommandResolver<TriggerState extends GeneralTriggerState = GeneralT
resolve(context: DomesticActionContext<TriggerState>, rng: RandomGenerator): CommerceInvestmentResult {
const { gold: costGold, rice: costRice } = this.getCost(context);
const trust = getMetaNumber(context.city.meta, 'trust') ?? this.env.defaultTrust ?? DEFAULT_TRUST;
let score = clamp(this.calcBaseScore(context, rng), 1, Number.MAX_SAFE_INTEGER);
const trust = resolveLegacyDomesticTrust(getMetaNumber(context.city.meta, 'trust'), this.env.defaultTrust);
const calculatedBaseScore = this.calcBaseScore(context, rng);
let score = clamp(calculatedBaseScore, 1, Number.MAX_SAFE_INTEGER);
const ratio =
this.env.getCriticalRatio?.(context, this.config.statKey) ??
@@ -105,11 +105,8 @@ export const normalizeLegacyGeneratedDex = (
): [number, number, number, number, number] =>
values.map((value) => Math.trunc(value)) as [number, number, number, number, number];
export const resolveLegacySpecialityAge = (
retirementYear: number,
age: number,
divisor: number
): number => Math.round((retirementYear - age) / divisor) + age;
export const resolveLegacySpecialityAge = (retirementYear: number, age: number, divisor: number): number =>
Math.round((retirementYear - age) / divisor) + age;
const addMetaValue = (
meta: Record<string, TriggerValue>,
@@ -450,20 +447,14 @@ export class ActionResolver<
const meta: GeneralMeta = {
killturn,
npcType: NPC_TYPE,
explevel: 0,
dedlevel: 1,
crewTypeId: this.env.defaultCrewTypeId,
affinity,
birthYear,
deathYear,
specage: resolveLegacySpecialityAge(
context.retirementYear,
age,
12
),
specage2: resolveLegacySpecialityAge(
context.retirementYear,
age,
6
),
specage: resolveLegacySpecialityAge(context.retirementYear, age, 12),
specage2: resolveLegacySpecialityAge(context.retirementYear, age, 6),
dex1: dex[0],
dex2: dex[1],
dex3: dex[2],
@@ -168,3 +168,5 @@ export const loadGeneralTurnCommandSpecs = async (
}
return specs;
};
export { readLegacyCityTrust, storeLegacyCityTrust } from './legacyCityTrust.js';
+5 -1
View File
@@ -21,7 +21,11 @@ export const round = (value: number): number => {
return Math.round(value);
}
const corrected = value + Math.sign(value) * Number.EPSILON * Math.max(1, Math.abs(value));
// PHP 8.3's round() uses a wider half-boundary fuzz than one JavaScript
// ulp at four-digit battle totals. Accumulating several fractional battle
// rewards can land about three ulps below .5 (for example
// 8719.4999999999945), which PHP still rounds upward.
const corrected = value + Math.sign(value) * Number.EPSILON * Math.max(1, Math.abs(value)) * 4;
return corrected < 0 ? Math.ceil(corrected - 0.5) : Math.floor(corrected + 0.5);
};
+4
View File
@@ -519,6 +519,8 @@ const buildGeneralSeeds = (
source: contextLabel,
deathMonth,
initialTurnOffsetMicros,
explevel: 0,
dedlevel: 1,
// Ref's GeneralBuilder derives speciality ages from the scenario
// opening year even when installation stores a pre-opening age.
specage: resolveBootstrapSpecialityAge(scenario.startYear, birthYear, retirementYear, 12),
@@ -588,6 +590,8 @@ const buildGeneralSeeds = (
),
deathMonth,
npcType,
explevel: 0,
dedlevel: 1,
crewTypeId: defaultCrewTypeId,
specage: resolveBootstrapSpecialityAge(scenario.startYear, birthYear, retirementYear, 12),
specage2: resolveBootstrapSpecialityAge(scenario.startYear, birthYear, retirementYear, 6),