wows-compute 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,80 @@
1
+ import { r as Coverage, v as RatedValue } from "./index-DoUlnTgG.mjs";
2
+ import { i as ArenaRoster } from "./types-DwmuyfXs.mjs";
3
+ import { o as groupDivisions } from "./index-_QacyXDj.mjs";
4
+ //#region src/strength/index.d.ts
5
+ export type ShipRole = 'submarine' | 'aircraft-carrier' | 'destroyer' | 'battleship' | 'cruiser-functional' | 'cruiser-firepower' | 'cruiser' | 'unknown';
6
+ /** Display / analysis order; not a composite score. */
7
+ export declare const ROLE_ORDER: readonly ShipRole[];
8
+ export interface ShipCatalogLike {
9
+ role?: (shipId: number) => ShipRole | null;
10
+ tier?: (shipId: number) => number | null;
11
+ }
12
+ export interface PlayerRatingSnapshot {
13
+ pr?: RatedValue | null;
14
+ /** Lifetime win rate in percent, e.g. `54.3`. */
15
+ winRate?: number | null;
16
+ battles?: number | null;
17
+ }
18
+ export type PlayerRatingValue = RatedValue | PlayerRatingSnapshot;
19
+ export type PlayerRatings = ReadonlyMap<number, PlayerRatingValue>;
20
+ export interface PlayerPoint {
21
+ accountId: number | null;
22
+ name: string | null;
23
+ /** `null` for pre-battle rosters; relations are known, team ids are not. */
24
+ teamId: number | null;
25
+ shipId: number | null;
26
+ shipRole: ShipRole;
27
+ tier: number | null;
28
+ divisionId: number | null;
29
+ /** Percentage, e.g. `54.3`. */
30
+ winRate: number | null;
31
+ pr: number | null;
32
+ prExact: boolean;
33
+ battles: number | null;
34
+ isBot: boolean;
35
+ }
36
+ export interface ReferenceLines {
37
+ winRateMedian: number | null;
38
+ prMedian: number | null;
39
+ }
40
+ export type Quadrant = 'q1' | 'q2' | 'q3' | 'q4' | 'unknown';
41
+ export interface RoleWeightProfile {
42
+ weights?: Partial<Record<ShipRole, number>>;
43
+ }
44
+ export interface RoleMatchup {
45
+ role: ShipRole;
46
+ allyCount: number;
47
+ enemyCount: number;
48
+ allyMedianPr: number | null;
49
+ enemyMedianPr: number | null;
50
+ allyMedianWinRate: number | null;
51
+ enemyMedianWinRate: number | null;
52
+ /** Ally median minus enemy median; `null` when either side lacks values. */
53
+ prDiff: number | null;
54
+ winRateDiff: number | null;
55
+ weight: number | null;
56
+ sample: {
57
+ ally: number;
58
+ enemy: number;
59
+ };
60
+ coverage: Coverage;
61
+ }
62
+ /** Maps a roster to scatter-plot points. Bots and hidden players stay in the list. */
63
+ export declare function buildPlayerPoints(roster: ArenaRoster, ratings: PlayerRatings, catalog?: ShipCatalogLike): PlayerPoint[];
64
+ export declare function median(values: readonly number[]): number | null;
65
+ export declare function referenceLines(points: readonly PlayerPoint[]): ReferenceLines;
66
+ /**
67
+ * Quadrants relative to the reference lines.
68
+ *
69
+ * q1 high win rate + high PR, q2 low win rate + high PR,
70
+ * q3 low + low, q4 high win rate + low PR.
71
+ */
72
+ export declare function quadrantOf(point: PlayerPoint, reference: ReferenceLines): Quadrant;
73
+ /**
74
+ * Per-role ally/enemy comparison.
75
+ *
76
+ * Returns differences and samples only; no composite score and no prediction.
77
+ */
78
+ export declare function pairByRole(ally: readonly PlayerPoint[], enemy: readonly PlayerPoint[], profile?: RoleWeightProfile): RoleMatchup[];
79
+ //#endregion
80
+ export { groupDivisions };
@@ -0,0 +1,154 @@
1
+ import { i as groupDivisions } from "./match-J2T9QiGg.mjs";
2
+ //#region src/strength/index.ts
3
+ /** Display / analysis order; not a composite score. */
4
+ const ROLE_ORDER = [
5
+ "submarine",
6
+ "aircraft-carrier",
7
+ "destroyer",
8
+ "battleship",
9
+ "cruiser-functional",
10
+ "cruiser-firepower",
11
+ "cruiser",
12
+ "unknown"
13
+ ];
14
+ function isRatedValue(value) {
15
+ return "value" in value && "basis" in value && "exact" in value;
16
+ }
17
+ function normalize(value) {
18
+ if (value === void 0) return {
19
+ pr: null,
20
+ winRate: null,
21
+ battles: null
22
+ };
23
+ if (isRatedValue(value)) return {
24
+ pr: value,
25
+ winRate: null,
26
+ battles: value.sample.matches
27
+ };
28
+ return {
29
+ pr: value.pr ?? null,
30
+ winRate: value.winRate ?? null,
31
+ battles: value.battles ?? value.pr?.sample.matches ?? null
32
+ };
33
+ }
34
+ /** Maps a roster to scatter-plot points. Bots and hidden players stay in the list. */
35
+ function buildPlayerPoints(roster, ratings, catalog) {
36
+ return roster.participants.map((participant) => {
37
+ const rating = participant.accountId === null ? normalize(void 0) : normalize(ratings.get(participant.accountId));
38
+ return {
39
+ accountId: participant.accountId,
40
+ name: participant.name,
41
+ teamId: null,
42
+ shipId: participant.shipId,
43
+ shipRole: participant.shipId === null ? "unknown" : catalog?.role?.(participant.shipId) ?? "unknown",
44
+ tier: participant.shipId === null ? null : catalog?.tier?.(participant.shipId) ?? null,
45
+ divisionId: null,
46
+ winRate: rating.winRate,
47
+ pr: rating.pr?.value ?? null,
48
+ prExact: rating.pr?.exact ?? false,
49
+ battles: rating.battles,
50
+ isBot: participant.isBot
51
+ };
52
+ });
53
+ }
54
+ function median(values) {
55
+ if (values.length === 0) return null;
56
+ const sorted = [...values].sort((a, b) => a - b);
57
+ const middle = Math.floor(sorted.length / 2);
58
+ if (sorted.length % 2 === 1) return sorted[middle];
59
+ return (sorted[middle - 1] + sorted[middle]) / 2;
60
+ }
61
+ function referenceLines(points) {
62
+ return {
63
+ winRateMedian: median(points.map((point) => point.winRate).filter((value) => value !== null)),
64
+ prMedian: median(points.map((point) => point.pr).filter((value) => value !== null))
65
+ };
66
+ }
67
+ /**
68
+ * Quadrants relative to the reference lines.
69
+ *
70
+ * q1 high win rate + high PR, q2 low win rate + high PR,
71
+ * q3 low + low, q4 high win rate + low PR.
72
+ */
73
+ function quadrantOf(point, reference) {
74
+ if (point.winRate === null || point.pr === null || reference.winRateMedian === null || reference.prMedian === null) return "unknown";
75
+ const highWinRate = point.winRate >= reference.winRateMedian;
76
+ const highPr = point.pr >= reference.prMedian;
77
+ if (highWinRate) return highPr ? "q1" : "q4";
78
+ return highPr ? "q2" : "q3";
79
+ }
80
+ function roleCoverage(ally, enemy) {
81
+ const coverage = {
82
+ total: 0,
83
+ resolved: 0,
84
+ hidden: 0,
85
+ bot: 0,
86
+ failed: 0
87
+ };
88
+ for (const point of [...ally, ...enemy]) {
89
+ coverage.total += 1;
90
+ if (point.isBot) {
91
+ coverage.bot += 1;
92
+ continue;
93
+ }
94
+ if (point.accountId === null) {
95
+ coverage.hidden += 1;
96
+ continue;
97
+ }
98
+ if (point.pr === null) {
99
+ coverage.failed += 1;
100
+ continue;
101
+ }
102
+ coverage.resolved += 1;
103
+ }
104
+ return coverage;
105
+ }
106
+ function diff(left, right) {
107
+ return left === null || right === null ? null : left - right;
108
+ }
109
+ /**
110
+ * Per-role ally/enemy comparison.
111
+ *
112
+ * Returns differences and samples only; no composite score and no prediction.
113
+ */
114
+ function pairByRole(ally, enemy, profile) {
115
+ const allyByRole = /* @__PURE__ */ new Map();
116
+ const enemyByRole = /* @__PURE__ */ new Map();
117
+ for (const point of ally) {
118
+ const list = allyByRole.get(point.shipRole) ?? [];
119
+ list.push(point);
120
+ allyByRole.set(point.shipRole, list);
121
+ }
122
+ for (const point of enemy) {
123
+ const list = enemyByRole.get(point.shipRole) ?? [];
124
+ list.push(point);
125
+ enemyByRole.set(point.shipRole, list);
126
+ }
127
+ return ROLE_ORDER.filter((role) => allyByRole.has(role) || enemyByRole.has(role)).map((role) => {
128
+ const allies = allyByRole.get(role) ?? [];
129
+ const enemies = enemyByRole.get(role) ?? [];
130
+ const allyMedianPr = median(allies.map((point) => point.pr).filter((value) => value !== null));
131
+ const enemyMedianPr = median(enemies.map((point) => point.pr).filter((value) => value !== null));
132
+ const allyMedianWinRate = median(allies.map((point) => point.winRate).filter((value) => value !== null));
133
+ const enemyMedianWinRate = median(enemies.map((point) => point.winRate).filter((value) => value !== null));
134
+ return {
135
+ role,
136
+ allyCount: allies.length,
137
+ enemyCount: enemies.length,
138
+ allyMedianPr,
139
+ enemyMedianPr,
140
+ allyMedianWinRate,
141
+ enemyMedianWinRate,
142
+ prDiff: diff(allyMedianPr, enemyMedianPr),
143
+ winRateDiff: diff(allyMedianWinRate, enemyMedianWinRate),
144
+ weight: profile?.weights?.[role] ?? null,
145
+ sample: {
146
+ ally: allies.length,
147
+ enemy: enemies.length
148
+ },
149
+ coverage: roleCoverage(allies, enemies)
150
+ };
151
+ });
152
+ }
153
+ //#endregion
154
+ export { ROLE_ORDER, buildPlayerPoints, groupDivisions, median, pairByRole, quadrantOf, referenceLines };
@@ -0,0 +1,175 @@
1
+ //#region src/match/types.d.ts
2
+ type MatchGroup = 'pvp' | 'ranked' | 'coop' | 'clan' | 'brawl' | 'event' | 'unknown';
3
+ type Relation = 'self' | 'ally' | 'enemy' | 'unknown';
4
+ type Outcome = 'win' | 'loss' | 'draw' | 'unknown';
5
+ /** Statistical outcome: a draw counts as a loss for both teams. */
6
+ type StatOutcome = 'win' | 'loss';
7
+ type RecordQuality = 'meta-only' | 'partial' | 'complete';
8
+ type ResultValue = string | number | boolean | null | ResultValue[] | {
9
+ [key: string]: ResultValue;
10
+ };
11
+ interface ArenaRoster {
12
+ provisionalId: string;
13
+ battleTime: number | null;
14
+ gameMode: number | null;
15
+ matchGroup: MatchGroup;
16
+ mapId: number | null;
17
+ mapName: string | null;
18
+ clientVersion: string | null;
19
+ durationSec: number | null;
20
+ playersPerTeam: number | null;
21
+ participants: RosterParticipant[];
22
+ raw: unknown;
23
+ }
24
+ interface RosterParticipant {
25
+ accountId: number | null;
26
+ name: string | null;
27
+ clanId: number | null;
28
+ clanTag: string | null;
29
+ shipId: number | null;
30
+ relation: Relation;
31
+ isBot: boolean;
32
+ }
33
+ interface MatchRecord {
34
+ arenaId: string;
35
+ provisionalIds: string[];
36
+ battleTime: number | null;
37
+ gameMode: number | null;
38
+ matchGroup: MatchGroup;
39
+ mapId: number | null;
40
+ mapName: string | null;
41
+ clientVersion: string | null;
42
+ durationSec: number | null;
43
+ winnerTeamId: number | null;
44
+ outcome: Outcome;
45
+ participants: MatchParticipant[];
46
+ quality: RecordQuality;
47
+ ingestedAt: number;
48
+ parsedAt: number;
49
+ mappingVersion: string | null;
50
+ source: MatchSource;
51
+ raw?: unknown;
52
+ }
53
+ interface MatchSource {
54
+ kind: 'replay';
55
+ fileName?: string;
56
+ size?: number;
57
+ mtimeMs?: number;
58
+ }
59
+ interface MatchParticipant {
60
+ accountId: number | null;
61
+ name: string | null;
62
+ clanId: number | null;
63
+ clanTag: string | null;
64
+ shipId: number | null;
65
+ teamId: number | null;
66
+ relation: Relation;
67
+ isBot: boolean;
68
+ divisionId: number | null;
69
+ result: PlayerResult | null;
70
+ }
71
+ interface PlayerResult {
72
+ damage: number | null;
73
+ frags: number | null;
74
+ exp: number | null;
75
+ rawExp: number | null;
76
+ isAlive: boolean | null;
77
+ lifeTimeSec: number | null;
78
+ scoutingDamage: number | null;
79
+ killerAccountId: number | null;
80
+ interactions: Record<string, Record<string, ResultValue>>;
81
+ fields: Record<string, ResultValue>;
82
+ }
83
+ interface ArenaInfoVehicle {
84
+ id?: number | null;
85
+ name?: string | null;
86
+ shipId?: number | null;
87
+ relation?: number | null;
88
+ }
89
+ /** Structural subset of `tempArenaInfo.json`; unknown keys are preserved in `raw`. */
90
+ interface ArenaInfoLike {
91
+ matchGroup?: string | null;
92
+ gameMode?: number | null;
93
+ mapId?: number | null;
94
+ mapName?: string | null;
95
+ clientVersionFromExe?: string | null;
96
+ duration?: number | null;
97
+ battleDuration?: number | null;
98
+ playersPerTeam?: number | null;
99
+ dateTime?: string | null;
100
+ vehicles?: readonly ArenaInfoVehicle[] | null;
101
+ [key: string]: unknown;
102
+ }
103
+ interface ReplayVehicleLike {
104
+ id?: number | null;
105
+ name?: string | null;
106
+ shipId?: number | null;
107
+ relation?: number | null;
108
+ }
109
+ interface ReplayMetaLike {
110
+ dateTime?: string | null;
111
+ mapId?: number | null;
112
+ mapName?: string | null;
113
+ gameMode?: number | null;
114
+ gameType?: string | null;
115
+ matchGroup?: string | null;
116
+ clientVersionFromExe?: string | null;
117
+ duration?: number | null;
118
+ battleDuration?: number | null;
119
+ playersPerTeam?: number | null;
120
+ playerID?: number | null;
121
+ playerName?: string | null;
122
+ vehicles?: readonly ReplayVehicleLike[] | null;
123
+ [key: string]: unknown;
124
+ }
125
+ interface ReplayPlayerLike {
126
+ accountId: number | null;
127
+ name: string | null;
128
+ clanId: number | null;
129
+ clanTag: string | null;
130
+ teamId: number | null;
131
+ shipId: number | null;
132
+ damage: number | null;
133
+ frags: number | null;
134
+ exp: number | null;
135
+ rawExp: number | null;
136
+ isAlive: boolean | null;
137
+ lifeTimeSec: number | null;
138
+ scoutingDamage: number | null;
139
+ killerAccountId: number | null;
140
+ interactions: Record<string, Record<string, ResultValue>>;
141
+ fields: Record<string, ResultValue>;
142
+ }
143
+ interface ReplayCommonLike {
144
+ winnerTeamId?: number | null;
145
+ durationSec?: number | null;
146
+ gameMode?: number | null;
147
+ scenarioName?: string | null;
148
+ startDt?: number | null;
149
+ }
150
+ interface ReplayResultsLike {
151
+ arenaId?: number | null;
152
+ accountId?: number | null;
153
+ mappingVersion?: string | null;
154
+ common?: ReplayCommonLike | null;
155
+ players: readonly ReplayPlayerLike[];
156
+ raw?: unknown;
157
+ }
158
+ /** Structural subset of `ParsedReplay` from `wows-replay-parser`. */
159
+ interface ReplayFacts {
160
+ meta: ReplayMetaLike;
161
+ battleResults: ReplayResultsLike | null;
162
+ }
163
+ interface ArenaKeyInput {
164
+ arenaId?: number | null;
165
+ battleTime: number | null;
166
+ mapId: number | null;
167
+ accountIds: readonly (number | null)[];
168
+ }
169
+ interface Division {
170
+ teamId: number | null;
171
+ divisionId: number;
172
+ participants: MatchParticipant[];
173
+ }
174
+ //#endregion
175
+ export { StatOutcome as S, ReplayPlayerLike as _, Division as a, ResultValue as b, MatchRecord as c, PlayerResult as d, RecordQuality as f, ReplayMetaLike as g, ReplayFacts as h, ArenaRoster as i, MatchSource as l, ReplayCommonLike as m, ArenaInfoVehicle as n, MatchGroup as o, Relation as p, ArenaKeyInput as r, MatchParticipant as s, ArenaInfoLike as t, Outcome as u, ReplayResultsLike as v, RosterParticipant as x, ReplayVehicleLike as y };
package/package.json CHANGED
@@ -1,6 +1,68 @@
1
1
  {
2
2
  "name": "wows-compute",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "1.0.0",
4
+ "description": "Pure TypeScript computation kernels for World of Warships match records, ratings and aggregates.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "engines": {
8
+ "node": ">=22.18.0"
9
+ },
10
+ "keywords": [
11
+ "world-of-warships",
12
+ "wows",
13
+ "personal-rating",
14
+ "statistics",
15
+ "compute"
16
+ ],
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.mts",
20
+ "import": "./dist/index.mjs"
21
+ },
22
+ "./match": {
23
+ "types": "./dist/match.d.mts",
24
+ "import": "./dist/match.mjs"
25
+ },
26
+ "./metrics": {
27
+ "types": "./dist/metrics.d.mts",
28
+ "import": "./dist/metrics.mjs"
29
+ },
30
+ "./strength": {
31
+ "types": "./dist/strength.d.mts",
32
+ "import": "./dist/strength.mjs"
33
+ },
34
+ "./package.json": "./package.json"
35
+ },
36
+ "files": [
37
+ "dist"
38
+ ],
39
+ "dependencies": {
40
+ "wows-number-api-sdk": "^1.0.1"
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "^26.6.5",
44
+ "tsdown": "^0.23.1",
45
+ "typescript": "^7.0.2",
46
+ "vitest": "^5.0.5",
47
+ "wows-replay-parser": "^1.0.1"
48
+ },
49
+ "repository": {
50
+ "type": "git",
51
+ "url": "git+https://github.com/urara-chiya/wows-devkit.git",
52
+ "directory": "packages/wows-compute"
53
+ },
54
+ "homepage": "https://github.com/urara-chiya/wows-devkit#readme",
55
+ "bugs": {
56
+ "url": "https://github.com/urara-chiya/wows-devkit/issues"
57
+ },
58
+ "publishConfig": {
59
+ "access": "public"
60
+ },
61
+ "scripts": {
62
+ "build": "tsdown",
63
+ "dev": "tsdown --watch",
64
+ "test": "vitest run",
65
+ "test:watch": "vitest",
66
+ "typecheck": "tsc --noEmit"
67
+ }
6
68
  }