mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-08-23 11:36:19 -05:00
Fix scripts typecheck
This commit is contained in:
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
@@ -30,6 +30,8 @@ jobs:
|
||||
run: pnpm run biome:check
|
||||
- name: Typecheck
|
||||
run: pnpm run typecheck
|
||||
- name: Typecheck scripts
|
||||
run: pnpm run typecheck:scripts
|
||||
- name: Unit tests
|
||||
run: pnpm run test:unit:browser
|
||||
- name: Knip unused check
|
||||
|
||||
@@ -41,6 +41,7 @@ Note: it only works with Node 16.
|
||||
## Doing monthly update
|
||||
|
||||
1. Drop the whole [splat3 repository](https://github.com/Leanny/splat3) into `/scripts/dicts/splat3` (pull before).
|
||||
1. If the dump's shape changed, update `/scripts/splat3-types.ts` to match (the dump is gitignored, so these types are hand-maintained rather than inferred from the JSON).
|
||||
1. Update all `CURRENT_SEASON` constants
|
||||
1. Update `CURRENT_PATCH` constants
|
||||
1. Update `PATCHES` constant with the late patch + remove the oldest
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
"test:e2e": "playwright test",
|
||||
"test:e2e:flaky-detect": "playwright test --repeat-each=10 --max-failures=1",
|
||||
"check-plural-collapse": "node scripts/collapse-single-plural-keys.ts --check",
|
||||
"checks": "pnpm run biome:fix && pnpm run test:unit:browser && pnpm run check-translation-jsons && pnpm run check-plural-collapse && pnpm run typecheck && pnpm run knip",
|
||||
"checks": "pnpm run biome:fix && pnpm run test:unit:browser && pnpm run check-translation-jsons && pnpm run check-plural-collapse && pnpm run typecheck && pnpm run typecheck:scripts && pnpm run knip",
|
||||
"checks:scanner": "pnpm checks && pnpm test:scanner",
|
||||
"seed": "cross-env DB_PATH=db.sqlite3 vite-node ./scripts/seed.ts",
|
||||
"setup": "cross-env DB_PATH=db.sqlite3 vite-node ./scripts/setup.ts",
|
||||
|
||||
@@ -30,10 +30,7 @@ for (const p of placements) {
|
||||
|
||||
async function loadTournament() {
|
||||
try {
|
||||
return await tournamentFromDB({
|
||||
user: undefined,
|
||||
tournamentId,
|
||||
});
|
||||
return await tournamentFromDB(tournamentId);
|
||||
} catch {
|
||||
throw new Error(`Tournament with id ${tournamentId} not found`);
|
||||
}
|
||||
|
||||
@@ -1133,9 +1133,12 @@ export function buildCases(fx: Fixtures): {
|
||||
);
|
||||
add(
|
||||
"TournamentTeamRepository.findRecentlyPlayedMapsByIds",
|
||||
fx.tournamentTeamPair,
|
||||
(teamIds) =>
|
||||
TournamentTeamRepository.findRecentlyPlayedMapsByIds({ teamIds }),
|
||||
both(fx.tournamentTeamPair, fx.heavyTournamentMatchId),
|
||||
([teamIds, excludeMatchId]) =>
|
||||
TournamentTeamRepository.findRecentlyPlayedMapsByIds({
|
||||
teamIds,
|
||||
excludeMatchId,
|
||||
}),
|
||||
);
|
||||
add(
|
||||
"TournamentTeamRepository.findMapPoolsByTeamIds",
|
||||
|
||||
@@ -71,7 +71,7 @@ if (unfinalizedTournaments.length > 0) {
|
||||
`Unfinalized ranked tournaments (${unfinalizedTournaments.length}):`,
|
||||
);
|
||||
for (const tournament of unfinalizedTournaments) {
|
||||
const date = new Date(tournament.startTime * 1000).toISOString();
|
||||
const date = new Date(tournament.startsAt * 1000).toISOString();
|
||||
logger.info(` - ID ${tournament.id}: "${tournament.name}" (${date})`);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -128,7 +128,8 @@ if (detector === "scoreboard") {
|
||||
rect(frame, minimap.enemyCrossRoi(cy), [255, 0, 255]);
|
||||
}
|
||||
for (const roi of [
|
||||
minimap.GATE_CLOSE_BRIGHT,
|
||||
...minimap.GATE_CLOSE_X_BRIGHT,
|
||||
...minimap.GATE_CLOSE_X_DARK,
|
||||
minimap.GATE_SPAWN_BRIGHT,
|
||||
...minimap.GATE_CLOSE_DARK_PROBES,
|
||||
...minimap.GATE_SPAWN_DARK_PROBES,
|
||||
|
||||
@@ -19,7 +19,10 @@
|
||||
* Usage: pnpm scanner:status-audit <events.csv> [--all]
|
||||
*/
|
||||
import { readFileSync } from "node:fs";
|
||||
import { statusSpans } from "../../app/components/PlayerStatusTimeline";
|
||||
import {
|
||||
type PlayerStatusTimelineSample,
|
||||
statusSpans,
|
||||
} from "../../app/components/PlayerStatusTimeline";
|
||||
import { formatTime } from "../../app/features/scanner/components/format";
|
||||
import {
|
||||
lobbyLabel,
|
||||
@@ -284,6 +287,9 @@ function parsePlayerStatusCell(cell: string): PlayerStatusData {
|
||||
special: [a.special, b.special],
|
||||
dead: [a.dead, b.dead],
|
||||
layout: layout as PlayerStatusLayout,
|
||||
// the CSV carries no camera-badge evidence, so cast orientation
|
||||
// degrades to the `cast-mirror` layout fallback
|
||||
castProven: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -457,9 +463,12 @@ interface FixtureCandidate {
|
||||
reads: number[];
|
||||
}
|
||||
|
||||
interface StatusSpan {
|
||||
interface TimeWindow {
|
||||
start: number;
|
||||
end: number;
|
||||
}
|
||||
|
||||
interface StatusSpan extends TimeWindow {
|
||||
/** sample timestamps that read the flag true inside the span */
|
||||
confirmingReads: number[];
|
||||
/**
|
||||
@@ -484,7 +493,7 @@ interface SlotAnalysis {
|
||||
|
||||
function annotatedSpans(
|
||||
samples: readonly ScannerMatchPlayerStatusSample[],
|
||||
flagOf: (sample: ScannerMatchPlayerStatusSample) => boolean,
|
||||
flagOf: (sample: PlayerStatusTimelineSample) => boolean,
|
||||
): StatusSpan[] {
|
||||
return statusSpans(samples, flagOf).map((span) => {
|
||||
const confirmingReads = samples
|
||||
@@ -552,8 +561,8 @@ function analyzeSlot(
|
||||
|
||||
function observationGaps(
|
||||
samples: readonly ScannerMatchPlayerStatusSample[],
|
||||
): StatusSpan[] {
|
||||
const gaps: StatusSpan[] = [];
|
||||
): TimeWindow[] {
|
||||
const gaps: TimeWindow[] = [];
|
||||
for (let i = 1; i < samples.length; i++) {
|
||||
const dt = samples[i]!.t - samples[i - 1]!.t;
|
||||
if (dt > OBSERVATION_GAP_SECONDS) {
|
||||
|
||||
127
scripts/splat3-types.ts
Normal file
127
scripts/splat3-types.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* Hand-maintained types for the [Leanny/splat3](https://github.com/Leanny/splat3) game data dump.
|
||||
*
|
||||
* The dump itself is not checked in (see `.gitignore`), so these types are declared here rather
|
||||
* than inferred from the JSON files. That keeps `pnpm run typecheck:scripts` passing whether or
|
||||
* not the dump is currently sitting in `scripts/dicts/splat3`.
|
||||
*
|
||||
* Only the fields the scripts actually read are declared for the deeply nested parameter dumps.
|
||||
* When a monthly update changes the dump's shape, update these types alongside the scripts.
|
||||
*/
|
||||
|
||||
/** `data/language/<Lang>.json` — message category -> message key -> localized (URI encoded) text. */
|
||||
export type LangDict = Record<string, Record<string, string>>;
|
||||
|
||||
interface WeaponInfoEntry {
|
||||
DefaultDamageRateInfoRow: string;
|
||||
DefaultHitEffectorType: string;
|
||||
ExtraDamageRateInfoRowSet: Array<{
|
||||
DamageRateInfoRow: string;
|
||||
ExtraInfo: string;
|
||||
}>;
|
||||
ExtraHitEffectorInfoSet: Array<{
|
||||
ExtraInfo: string;
|
||||
HitEffectorType: string;
|
||||
}>;
|
||||
Id: number;
|
||||
Label: string;
|
||||
NpcActor: string;
|
||||
SpecActor: string;
|
||||
Type: string;
|
||||
__RowId: string;
|
||||
}
|
||||
|
||||
/** `data/mush/<version>/WeaponInfoMain.json` */
|
||||
export interface WeaponInfoMainEntry extends WeaponInfoEntry {
|
||||
DebugDispColumn: number;
|
||||
DebugDispOrder: number;
|
||||
GameActor: string;
|
||||
IsCoopRare: boolean;
|
||||
LObjParam: string;
|
||||
LockerContentInfo: string[];
|
||||
LockerModelColor: { A: number; B: number; G: number; R: number };
|
||||
MatchingId: number;
|
||||
Range: number;
|
||||
RewardLv2: string;
|
||||
RewardLv3: string;
|
||||
Season: number;
|
||||
ShopPrice: number;
|
||||
ShopUnlockRank: number;
|
||||
SpecialPoint: number;
|
||||
SpecialWeapon: string;
|
||||
SubWeapon: string;
|
||||
UIParam: Array<{ Type: string; Value: number }>;
|
||||
WeaponInfoForCoop: string;
|
||||
}
|
||||
|
||||
/** `data/mush/<version>/WeaponInfoSub.json` */
|
||||
export interface WeaponInfoSubEntry extends WeaponInfoEntry {
|
||||
LockerGoodsSubWeaponInfo: string;
|
||||
}
|
||||
|
||||
/** `data/mush/<version>/WeaponInfoSpecial.json` */
|
||||
export interface WeaponInfoSpecialEntry extends WeaponInfoEntry {
|
||||
StandAlone: boolean;
|
||||
}
|
||||
|
||||
/** `data/mush/<version>/GearInfo{Head,Clothes,Shoes}.json` — all three share one shape. */
|
||||
export interface GearInfoEntry {
|
||||
AlphaMaskF: string;
|
||||
AlphaMaskM: string;
|
||||
AlphaMaskV1: string;
|
||||
Brand: string;
|
||||
CallSign: number;
|
||||
CallSignPriority: number;
|
||||
CaptureModelType: string;
|
||||
Genre0: string;
|
||||
Genre1: string;
|
||||
HarnessType: string;
|
||||
HeadParamSetPath: string;
|
||||
HowToGet: string;
|
||||
Id: number;
|
||||
IsHideHarness: boolean;
|
||||
IsThinHarness: boolean;
|
||||
IsUnisex: boolean;
|
||||
LObjParam: string;
|
||||
LObjParamM: string;
|
||||
Label: string;
|
||||
Material: string;
|
||||
Price: number;
|
||||
Rarity: number;
|
||||
Season: number;
|
||||
Skill: string;
|
||||
UrokoPrice: {
|
||||
BronzeUrokoNum: number;
|
||||
GoldUrokoNum: number;
|
||||
SilverUrokoNum: number;
|
||||
};
|
||||
UrokoUnlockLevel: number;
|
||||
VariationNum: number;
|
||||
__RowId: string;
|
||||
}
|
||||
|
||||
/** `data/parameter/<version>/misc/SplPlayer.game__GameParameterTable.json` */
|
||||
export interface SplPlayerParams {
|
||||
GameParameters: {
|
||||
spl__PlayerBeaconSubSpecUpParam: {
|
||||
SubSpecUpParam: { High: number; Mid: number };
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* `data/parameter/<version>/misc/spl__DamageRateInfoConfig.pp__CombinationDataTableData.json`
|
||||
*
|
||||
* `DamageRate` is absent for cells that take no damage rate modifier.
|
||||
*/
|
||||
export interface DamageRateInfoConfig {
|
||||
CellList: Record<
|
||||
string,
|
||||
{
|
||||
ColumnKey: string;
|
||||
DamageRate?: number;
|
||||
RowKey: string;
|
||||
}
|
||||
>;
|
||||
TableType: string;
|
||||
}
|
||||
@@ -1,17 +1,15 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import type euEn from "./dicts/splat3/data/language/EUen.json";
|
||||
// The splat3 dump exposes a `latest` symlink to the newest version folder, so these loaders never
|
||||
// need to hard-code a version.
|
||||
import type gearInfoClothes from "./dicts/splat3/data/mush/latest/GearInfoClothes.json";
|
||||
import type gearInfoHead from "./dicts/splat3/data/mush/latest/GearInfoHead.json";
|
||||
import type gearInfoShoes from "./dicts/splat3/data/mush/latest/GearInfoShoes.json";
|
||||
import type weaponInfoMain from "./dicts/splat3/data/mush/latest/WeaponInfoMain.json";
|
||||
import type weaponInfoSpecial from "./dicts/splat3/data/mush/latest/WeaponInfoSpecial.json";
|
||||
import type weaponInfoSub from "./dicts/splat3/data/mush/latest/WeaponInfoSub.json";
|
||||
import type splPlayer from "./dicts/splat3/data/parameter/latest/misc/SplPlayer.game__GameParameterTable.json";
|
||||
import type damageRateInfo from "./dicts/splat3/data/parameter/latest/misc/spl__DamageRateInfoConfig.pp__CombinationDataTableData.json";
|
||||
import type {
|
||||
DamageRateInfoConfig,
|
||||
GearInfoEntry,
|
||||
LangDict,
|
||||
SplPlayerParams,
|
||||
WeaponInfoMainEntry,
|
||||
WeaponInfoSpecialEntry,
|
||||
WeaponInfoSubEntry,
|
||||
} from "./splat3-types";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
@@ -41,7 +39,7 @@ export const LANG_JSONS_TO_CREATE = [
|
||||
];
|
||||
|
||||
export async function loadLangDicts() {
|
||||
const result: Array<[langCode: string, translations: typeof euEn]> = [];
|
||||
const result: Array<[langCode: string, translations: LangDict]> = [];
|
||||
|
||||
const files = await fs.promises.readdir(LANG_DICTS_PATH);
|
||||
for (const file of files) {
|
||||
@@ -71,23 +69,23 @@ export function weaponParamsDir() {
|
||||
}
|
||||
|
||||
export const loadWeaponInfoMain = () =>
|
||||
loadLatestMushJson<typeof weaponInfoMain>("WeaponInfoMain");
|
||||
loadLatestMushJson<WeaponInfoMainEntry[]>("WeaponInfoMain");
|
||||
export const loadWeaponInfoSub = () =>
|
||||
loadLatestMushJson<typeof weaponInfoSub>("WeaponInfoSub");
|
||||
loadLatestMushJson<WeaponInfoSubEntry[]>("WeaponInfoSub");
|
||||
export const loadWeaponInfoSpecial = () =>
|
||||
loadLatestMushJson<typeof weaponInfoSpecial>("WeaponInfoSpecial");
|
||||
loadLatestMushJson<WeaponInfoSpecialEntry[]>("WeaponInfoSpecial");
|
||||
export const loadGearInfoClothes = () =>
|
||||
loadLatestMushJson<typeof gearInfoClothes>("GearInfoClothes");
|
||||
loadLatestMushJson<GearInfoEntry[]>("GearInfoClothes");
|
||||
export const loadGearInfoHead = () =>
|
||||
loadLatestMushJson<typeof gearInfoHead>("GearInfoHead");
|
||||
loadLatestMushJson<GearInfoEntry[]>("GearInfoHead");
|
||||
export const loadGearInfoShoes = () =>
|
||||
loadLatestMushJson<typeof gearInfoShoes>("GearInfoShoes");
|
||||
loadLatestMushJson<GearInfoEntry[]>("GearInfoShoes");
|
||||
export const loadSplPlayerParams = () =>
|
||||
loadLatestParameterMiscJson<typeof splPlayer>(
|
||||
loadLatestParameterMiscJson<SplPlayerParams>(
|
||||
"SplPlayer.game__GameParameterTable",
|
||||
);
|
||||
export const loadDamageRateInfo = () =>
|
||||
loadLatestParameterMiscJson<typeof damageRateInfo>(
|
||||
loadLatestParameterMiscJson<DamageRateInfoConfig>(
|
||||
"spl__DamageRateInfoConfig.pp__CombinationDataTableData",
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user