feat(battle-sim): add battle simulation scripts and refactor schemas

This commit is contained in:
2025-12-30 11:27:37 +00:00
parent 56e662a7db
commit 4de688d642
4 changed files with 103 additions and 96 deletions
+2
View File
@@ -6,6 +6,8 @@
"scripts": {
"build": "tsdown -c ../../tsdown.config.ts -F @sammo-ts/game-api",
"dev": "tsdown -c ../../tsdown.config.ts -F @sammo-ts/game-api --watch",
"worker": "GAME_API_ROLE=battle-sim-worker node dist/index.js",
"worker:dev": "GAME_API_ROLE=battle-sim-worker tsdown -c ../../tsdown.config.ts -F @sammo-ts/game-api --watch",
"lint": "node -e \"console.log('lint not configured')\"",
"test": "vitest run --config vitest.config.ts",
"typecheck": "tsc --noEmit"
+98
View File
@@ -0,0 +1,98 @@
import { z } from 'zod';
import type { BattleSimRequestPayload } from './types.js';
export const zBattleSimGeneral = z.object({
no: z.number().int().positive(),
name: z.string().min(1),
nation: z.number().int().positive(),
turntime: z.string().min(1),
personal: z.string().nullable(),
special2: z.string().nullable(),
crew: z.number().int().min(0),
crewtype: z.number().int().positive(),
atmos: z.number().int().min(0),
train: z.number().int().min(0),
intel: z.number().int().min(0),
intel_exp: z.number().int().min(0),
book: z.string().nullable(),
strength: z.number().int().min(0),
strength_exp: z.number().int().min(0),
weapon: z.string().nullable(),
injury: z.number().int().min(0),
leadership: z.number().int().min(0),
leadership_exp: z.number().int().min(0),
horse: z.string().nullable(),
item: z.string().nullable(),
explevel: z.number().int().min(0),
experience: z.number().int().min(0),
dedication: z.number().int().min(0),
officer_level: z.number().int().min(1),
officer_city: z.number().int().positive(),
gold: z.number().int().min(0),
rice: z.number().int().min(0),
dex1: z.number().int().min(0),
dex2: z.number().int().min(0),
dex3: z.number().int().min(0),
dex4: z.number().int().min(0),
dex5: z.number().int().min(0),
recent_war: z.string().nullable(),
warnum: z.number().int().min(0),
killnum: z.number().int().min(0),
killcrew: z.number().int().min(0),
inheritBuff: z.union([z.record(z.string(), z.number()), z.array(z.number())]).optional(),
});
export const zBattleSimCity = z.object({
city: z.number().int().positive(),
nation: z.number().int().min(0),
supply: z.number().int().min(0),
name: z.string().min(1),
pop: z.number().min(0),
agri: z.number().min(0),
comm: z.number().min(0),
secu: z.number().min(0),
def: z.number().min(0),
wall: z.number().min(0),
trust: z.number().min(0),
level: z.number().int().min(1),
pop_max: z.number().min(0),
agri_max: z.number().min(0),
comm_max: z.number().min(0),
secu_max: z.number().min(0),
def_max: z.number().min(0),
wall_max: z.number().min(0),
dead: z.number().min(0),
state: z.number().int().min(0),
conflict: z.string(),
});
export const zBattleSimNation = z.object({
type: z.string().min(1),
tech: z.number().min(0),
level: z.number().int().min(1),
capital: z.number().int().min(0),
nation: z.number().int().min(0),
name: z.string().min(1),
gold: z.number().min(0),
rice: z.number().min(0),
gennum: z.number().int().min(1),
});
export const zBattleSimRequest: z.ZodType<BattleSimRequestPayload> = z.object({
action: z.enum(['reorder', 'battle']),
seed: z.string().optional(),
repeatCnt: z.number().int().min(1).max(1000),
year: z.number().int().min(0),
month: z.number().int().min(1).max(12),
attackerGeneral: zBattleSimGeneral,
attackerCity: zBattleSimCity,
attackerNation: zBattleSimNation,
defenderGenerals: z.array(zBattleSimGeneral),
defenderCity: zBattleSimCity,
defenderNation: zBattleSimNation,
});
export const zBattleSimJobId = z.object({
jobId: z.string().min(1),
});
+2 -96
View File
@@ -28,7 +28,7 @@ import {
type MessageView,
} from './messages/store.js';
import { buildBattleSimJobPayload } from './battleSim/environment.js';
import type { BattleSimRequestPayload } from './battleSim/types.js';
import { zBattleSimJobId, zBattleSimRequest } from './battleSim/schema.js';
const zRunReason = z.enum(['schedule', 'manual', 'poke']);
const zMessageType = z.enum(['private', 'public', 'national', 'diplomacy']);
@@ -39,96 +39,6 @@ const zTurnRunBudget = z.object({
catchUpCap: z.number().int().positive(),
});
const zBattleSimGeneral = z.object({
no: z.number().int().positive(),
name: z.string().min(1),
nation: z.number().int().positive(),
turntime: z.string().min(1),
personal: z.string().nullable(),
special2: z.string().nullable(),
crew: z.number().int().min(0),
crewtype: z.number().int().positive(),
atmos: z.number().int().min(0),
train: z.number().int().min(0),
intel: z.number().int().min(0),
intel_exp: z.number().int().min(0),
book: z.string().nullable(),
strength: z.number().int().min(0),
strength_exp: z.number().int().min(0),
weapon: z.string().nullable(),
injury: z.number().int().min(0),
leadership: z.number().int().min(0),
leadership_exp: z.number().int().min(0),
horse: z.string().nullable(),
item: z.string().nullable(),
explevel: z.number().int().min(0),
experience: z.number().int().min(0),
dedication: z.number().int().min(0),
officer_level: z.number().int().min(1),
officer_city: z.number().int().positive(),
gold: z.number().int().min(0),
rice: z.number().int().min(0),
dex1: z.number().int().min(0),
dex2: z.number().int().min(0),
dex3: z.number().int().min(0),
dex4: z.number().int().min(0),
dex5: z.number().int().min(0),
recent_war: z.string().nullable(),
warnum: z.number().int().min(0),
killnum: z.number().int().min(0),
killcrew: z.number().int().min(0),
inheritBuff: z.union([z.record(z.string(), z.number()), z.array(z.number())]).optional(),
});
const zBattleSimCity = z.object({
city: z.number().int().positive(),
nation: z.number().int().min(0),
supply: z.number().int().min(0),
name: z.string().min(1),
pop: z.number().min(0),
agri: z.number().min(0),
comm: z.number().min(0),
secu: z.number().min(0),
def: z.number().min(0),
wall: z.number().min(0),
trust: z.number().min(0),
level: z.number().int().min(1),
pop_max: z.number().min(0),
agri_max: z.number().min(0),
comm_max: z.number().min(0),
secu_max: z.number().min(0),
def_max: z.number().min(0),
wall_max: z.number().min(0),
dead: z.number().min(0),
state: z.number().int().min(0),
conflict: z.string(),
});
const zBattleSimNation = z.object({
type: z.string().min(1),
tech: z.number().min(0),
level: z.number().int().min(1),
capital: z.number().int().min(0),
nation: z.number().int().min(0),
name: z.string().min(1),
gold: z.number().min(0),
rice: z.number().min(0),
gennum: z.number().int().min(1),
});
const zBattleSimRequest: z.ZodType<BattleSimRequestPayload> = z.object({
action: z.enum(['reorder', 'battle']),
seed: z.string().optional(),
repeatCnt: z.number().int().min(1).max(1000),
year: z.number().int().min(0),
month: z.number().int().min(1).max(12),
attackerGeneral: zBattleSimGeneral,
attackerCity: zBattleSimCity,
attackerNation: zBattleSimNation,
defenderGenerals: z.array(zBattleSimGeneral),
defenderCity: zBattleSimCity,
defenderNation: zBattleSimNation,
});
const buildShiftAmountSchema = (maxTurns: number) =>
z.number()
@@ -177,11 +87,7 @@ export const appRouter = router({
return ctx.battleSim.simulate(payload);
}),
getSimulation: procedure
.input(
z.object({
jobId: z.string().min(1),
})
)
.input(zBattleSimJobId)
.query(async ({ ctx, input }) => {
const result = await ctx.battleSim.getSimulationResult(input.jobId);
if (!result) {
+1
View File
@@ -101,6 +101,7 @@ export const createGameApiServer = async () => {
profile: config.profileName,
}));
app.addHook('onClose', async () => {
await flushSubscriber.stop();
await flushSubscriberClient.quit();