fix: Ref 게임 로직과 시나리오 풀 호환을 보정

월 경계, 전투 기술 상한, 연감과 베팅·설문·경매 정산 순서를 Ref 계약에 맞춘다.\n\n시나리오 일반 풀을 ENGINE mutation과 logical tick 기반으로 직렬화하고 914·915 catalog 및 조건부 100기 pool 실행 경계를 추가한다.\n\n경매 worker는 세대별 durable event만 만들고 ENGINE이 row lock 후 상태 전이와 정산을 단일 transaction으로 소유한다.
This commit is contained in:
2026-08-23 16:26:14 +00:00
parent bf6b7be7b0
commit 85591c68ad
114 changed files with 13327 additions and 901 deletions
+3
View File
@@ -4,6 +4,7 @@ import type { City, General, GeneralTriggerState, Nation } from '@sammo-ts/logic
import type { ActionLogger } from '@sammo-ts/logic/logging/actionLogger.js';
import type { WarStatName } from '@sammo-ts/logic/actionModules/types.js';
import type { WarUnit } from './units.js';
import type { WarTimeContext } from './types.js';
import { WarTriggerCaller } from './triggers.js';
export interface WarActionContext<TriggerState extends GeneralTriggerState = GeneralTriggerState> {
@@ -13,6 +14,8 @@ export interface WarActionContext<TriggerState extends GeneralTriggerState = Gen
log?: ActionLogger;
rng?: RandUtil;
unit?: WarUnit<TriggerState>;
time?: WarTimeContext;
maxTechLevel?: number;
}
export interface WarActionModule<TriggerState extends GeneralTriggerState = GeneralTriggerState> {
+1 -1
View File
@@ -520,7 +520,7 @@ export const resolveWarAftermath = <TriggerState extends GeneralTriggerState = G
let rice = (cityKilled / 100) * 0.8;
rice *= riceCoef;
rice *= getTechCost(getMetaNumber(defenderNation.meta, 'tech', 0));
rice *= getTechCost(getMetaNumber(defenderNation.meta, 'tech', 0), input.config.maxTechLevel);
rice *= resolveCityTrainAtmos(input.time.year, input.time.startYear) / 100 - 0.2;
rice = round(rice);
+8 -4
View File
@@ -278,7 +278,8 @@ export const resolveWarBattle = <TriggerState extends GeneralTriggerState = Gene
true,
resolveCrewType(crewTypeIndex, input.attacker.general.crewTypeId),
attackerLogger,
attackerPipeline
attackerPipeline,
input.time
);
const cityLogger = loggerFactory({
@@ -313,7 +314,8 @@ export const resolveWarBattle = <TriggerState extends GeneralTriggerState = Gene
false,
resolveCrewType(crewTypeIndex, defender.general.crewTypeId),
defenderLogger,
createPipeline(defender, crewTypeCatalog.warActionModule)
createPipeline(defender, crewTypeCatalog.warActionModule),
input.time
);
if (computeBattleOrder(unit, attackerUnit) <= 0) {
continue;
@@ -750,7 +752,8 @@ export const resolveDefenderOrder = <TriggerState extends GeneralTriggerState =
true,
resolveCrewType(crewTypeIndex, input.attacker.general.crewTypeId),
attackerLogger,
attackerPipeline
attackerPipeline,
input.time
);
const defenderUnits: WarUnitGeneral<TriggerState>[] = [];
@@ -770,7 +773,8 @@ export const resolveDefenderOrder = <TriggerState extends GeneralTriggerState =
false,
resolveCrewType(crewTypeIndex, defender.general.crewTypeId),
defenderLogger,
createPipeline(defender, crewTypeCatalog.warActionModule)
createPipeline(defender, crewTypeCatalog.warActionModule),
input.time
);
if (computeBattleOrder(unit, attackerUnit) <= 0) {
continue;
+1
View File
@@ -27,6 +27,7 @@ export interface WarEngineConfig {
maxAtmosByWar: number;
maxGeneralStat?: number;
statUpgradeLimit?: number;
maxTechLevel?: number;
castleCrewTypeId: number;
armTypes: WarArmTypes;
}
+8 -5
View File
@@ -14,7 +14,7 @@ import type { WarStatName } from '@sammo-ts/logic/actionModules/types.js';
import { getTechAbility, getTechCost } from '@sammo-ts/logic/world/unitSet.js';
import { LEGACY_DEFAULT_MAX_LEVEL } from '@sammo-ts/logic/scenario/constants.js';
import type { WarActionPipeline, WarActionContext } from '../actions.js';
import type { WarEngineConfig } from '../types.js';
import type { WarEngineConfig, WarTimeContext } from '../types.js';
import type { WarCrewType } from '../crewType.js';
import { clamp, clampMin, getMetaNumber, increaseMetaNumber, round } from '../utils.js';
import { WAR_CRITICAL_RANGE, WarUnit, resolveNationTech } from './base.js';
@@ -58,7 +58,8 @@ export class WarUnitGeneral<
isAttacker: boolean,
crewType: WarCrewType,
logger: ActionLogger,
pipeline: WarActionPipeline<TriggerState>
pipeline: WarActionPipeline<TriggerState>,
private readonly time?: WarTimeContext
) {
super(rng, config, crewType, logger, isAttacker, nation);
this.actionPipeline = pipeline;
@@ -89,6 +90,8 @@ export class WarUnitGeneral<
log: this.logger,
rng: this.rng,
unit: this,
...(this.time ? { time: this.time } : {}),
maxTechLevel: this.config.maxTechLevel ?? 12,
};
}
@@ -243,13 +246,13 @@ export class WarUnitGeneral<
ratio = 50 + ratio / 2;
}
const attack = this.getCrewType().attack + getTechAbility(tech);
const attack = this.getCrewType().attack + getTechAbility(tech, this.config.maxTechLevel);
return attack * (ratio / 100);
}
public override getComputedDefence(): number {
const tech = resolveNationTech(this.nation);
const defence = this.getCrewType().defence + getTechAbility(tech);
const defence = this.getCrewType().defence + getTechAbility(tech, this.config.maxTechLevel);
const crew = this.general.crew / (7000 / 30) + 70;
return defence * (crew / 100);
}
@@ -407,7 +410,7 @@ export class WarUnitGeneral<
rice *= 0.8;
}
rice *= this.getCrewType().rice;
rice *= getTechCost(resolveNationTech(this.nation));
rice *= getTechCost(resolveNationTech(this.nation), this.config.maxTechLevel);
rice = this.actionPipeline.onCalcStat(this.getActionContext(), 'killRice', rice);
return rice;
}