ball2d 0.3.0 → 0.3.2-next.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.
@@ -14,3 +14,7 @@ export declare class RoomAdmissionError extends Error {
14
14
  export declare function createRoom(config?: RoomConfig, options?: CreateRoomOptions): Promise<Room>;
15
15
  export declare function readReplay(blob: Blob): Promise<Replay>;
16
16
  export declare function validateStadium(source: string): StadiumValidation;
17
+ export type { AtmospherePolicy, AtmospherePack, AtmosphereCue, CommentaryCue, CommentaryWordTiming, MatchFact, CommentaryPolicy, CommentaryPolicyPatch, CommentaryPlayerFact, CommentaryPlayerFactInput, CommentaryConfiguration, CommentaryObservation, CommentaryGoalGeometry, } from './commentary.js';
18
+ export type { MatchIntelligencePolicy, MatchIntelligencePolicyPatch, MatchIntelligenceRole, MatchIntelligenceRoleInput, MatchIntelligenceRoleAssignment, MatchIntelligenceEvent, MatchIntelligenceEventKind, MatchIntelligenceSnapshot, MatchIntelligenceTeamTotals, MatchShotQuality, } from './match-intelligence.js';
19
+ export type { MatchXgFeatures, MatchXgVector, MatchXgDomain, MatchXgModel, MatchXgUnavailableReason, MatchXgEstimate, MatchXgTrust, } from './match-xg.js';
20
+ export type { MatchXgCollectionPolicy, MatchXgRuntimeConfig, MatchXgRuntimeReason, MatchXgRuntimeStatus, MatchKickEstimateEvent, } from './match-xg-runtime.js';
@@ -0,0 +1,276 @@
1
+ /** Presentation facts are independent of physics, account state, and sound assets. */
2
+ export type CommentaryLocale = 'tr' | 'en';
3
+ export type CommentaryTeam = 1 | 2;
4
+ export type CommentaryDose = 'off' | 'minimal' | 'balanced' | 'rich';
5
+ export type CommentaryIntentKind = 'kickoff' | 'restart' | 'goal.neutral' | 'goal.first' | 'goal.equalizer' | 'goal.lead' | 'goal.late-winner' | 'goal.consolation' | 'goal.own-goal' | 'post' | 'near-miss' | 'pressure' | 'pass' | 'pass-chain' | 'turnover' | 'assist' | 'save' | 'shot' | 'block' | 'counterattack' | 'attack-progress' | 'sustained-pressure' | 'tactical-summary' | 'match-end.win' | 'match-end.draw' | 'match-stop' | 'pause' | 'resume' | 'context.fact';
6
+ export interface CommentaryPlayerIdentity {
7
+ /** Room/connection generation qualified; never reuse for a new occupant of a slot. */
8
+ readonly sessionId: string;
9
+ readonly playerId: number;
10
+ readonly team: CommentaryTeam;
11
+ readonly name?: string;
12
+ }
13
+ export interface CommentaryMatchContext {
14
+ readonly phase: 'lobby' | 'playing' | 'goal' | 'finished';
15
+ readonly paused?: boolean;
16
+ /** Simulation seconds, not wall-clock time and not ticks. Zero limit means unlimited. */
17
+ readonly elapsed: number;
18
+ readonly timeLimit: number;
19
+ readonly scoreLimit: number;
20
+ readonly score: {
21
+ readonly red: number;
22
+ readonly blue: number;
23
+ };
24
+ }
25
+ export interface MatchFact {
26
+ readonly eventId: string;
27
+ /** Explicitly reset/baseline the consumer when joining a new stream. */
28
+ readonly streamId: string;
29
+ readonly sequence: number;
30
+ readonly tick: number;
31
+ readonly epoch: number;
32
+ readonly kind: 'kickoff' | 'restart' | 'goal' | 'pause' | 'resume' | 'end' | 'stop';
33
+ readonly context: CommentaryMatchContext;
34
+ readonly goal?: {
35
+ readonly team: CommentaryTeam;
36
+ readonly scorer: CommentaryPlayerIdentity | null;
37
+ readonly ownGoal: boolean;
38
+ };
39
+ readonly endReason?: 'time-limit' | 'score-limit' | 'draw' | 'script';
40
+ }
41
+ /** Optional human-supplied alignment; binding validation is not artistic or rights approval. */
42
+ export interface CommentaryWordTiming {
43
+ readonly schemaVersion: 1;
44
+ readonly audioSha256: string;
45
+ readonly textSha256: string;
46
+ readonly durationMs: number;
47
+ readonly words: readonly {
48
+ /** Exact UTF-16 offsets of each complete whitespace-delimited token. */
49
+ readonly startChar: number;
50
+ readonly endChar: number;
51
+ readonly startMs: number;
52
+ readonly endMs: number;
53
+ }[];
54
+ readonly annotation: {
55
+ readonly source: string;
56
+ readonly reviewer: string;
57
+ readonly reference: string;
58
+ };
59
+ }
60
+ export interface CommentaryCue {
61
+ readonly id: string;
62
+ readonly locale: CommentaryLocale;
63
+ readonly role: 'play-by-play' | 'analyst';
64
+ readonly family: CommentaryIntentKind;
65
+ /** Same idea/opening across different recordings: anti-repeat follows meaning. */
66
+ readonly semanticKey: string;
67
+ readonly openingKey: string;
68
+ readonly text: string;
69
+ readonly intensity: readonly [number, number];
70
+ readonly durationMs: number;
71
+ readonly onsetMs: number;
72
+ readonly interruptibleAtMs: readonly number[];
73
+ readonly cooldownMs: number;
74
+ readonly audio: {
75
+ readonly url: string;
76
+ readonly sha256: string;
77
+ } | null;
78
+ readonly wordTiming?: CommentaryWordTiming;
79
+ readonly provenance: {
80
+ readonly source: string;
81
+ readonly license: string;
82
+ readonly status: 'draft' | 'approved';
83
+ };
84
+ readonly fallbackId?: string;
85
+ /** No arbitrary runtime interpolation; a recorded full sentence must name its fact. */
86
+ readonly contextFactKey?: string;
87
+ readonly contextFactValue?: string | number;
88
+ }
89
+ export interface CommentaryFamilyPolicy {
90
+ readonly enabled?: boolean;
91
+ readonly cooldownMs?: number;
92
+ readonly priority?: number;
93
+ readonly ttlMs?: number;
94
+ }
95
+ export interface CommentaryPolicy {
96
+ readonly locale: CommentaryLocale;
97
+ readonly dose: CommentaryDose;
98
+ readonly reactionIntensity: number;
99
+ readonly maxDurationMs: number;
100
+ readonly minGapMs: number;
101
+ readonly semanticCooldownMs: number;
102
+ readonly historySize: number;
103
+ readonly traceSize: number;
104
+ readonly seed: number;
105
+ readonly families: Readonly<Partial<Record<CommentaryIntentKind, CommentaryFamilyPolicy>>>;
106
+ readonly contextFactsEnabled: boolean;
107
+ /** Host presentation ceilings. Omitted channels allow delivery; local consent still applies. */
108
+ readonly channels?: {
109
+ readonly audio?: boolean;
110
+ readonly caption?: boolean;
111
+ readonly chat?: boolean;
112
+ };
113
+ }
114
+ /** An optional pre-fetched annotation; this is never a gameplay authority or a fetch command. */
115
+ export interface CommentaryPlayerFact {
116
+ readonly id: string;
117
+ readonly player: CommentaryPlayerIdentity;
118
+ /** Stable key identifies a fully authored/recorded compatible cue. */
119
+ readonly key: string;
120
+ readonly value: string | number;
121
+ readonly source: string;
122
+ /** Provider trust is granted by the host integration, never by a client's payload. */
123
+ readonly trusted: boolean;
124
+ readonly issuedAtMs: number;
125
+ readonly expiresAtMs: number;
126
+ }
127
+ export interface CommentaryPlan {
128
+ readonly id: string;
129
+ readonly eventId: string;
130
+ readonly streamId: string;
131
+ readonly epoch: number;
132
+ readonly family: CommentaryIntentKind;
133
+ readonly cueId: string;
134
+ readonly text: string;
135
+ readonly locale: CommentaryLocale;
136
+ readonly role: CommentaryCue['role'];
137
+ readonly intensity: number;
138
+ readonly priority: number;
139
+ readonly createdAtMs: number;
140
+ readonly expiresAtMs: number;
141
+ readonly durationMs: number;
142
+ readonly wordTiming?: CommentaryWordTiming;
143
+ readonly canInterrupt: boolean;
144
+ readonly interruptibleAtMs: readonly number[];
145
+ readonly context: CommentaryMatchContext;
146
+ readonly confidence: 'authoritative' | 'supported';
147
+ readonly explanation: readonly string[];
148
+ readonly playerFact?: CommentaryPlayerFact;
149
+ /** Correlation never invents a result; it prevents retelling the same observed attack. */
150
+ readonly attackId?: string;
151
+ }
152
+ export interface CommentarySelectionAudit {
153
+ readonly catalog: number;
154
+ readonly eligible: number;
155
+ readonly ready: number;
156
+ readonly considered: number;
157
+ readonly outsideWindow: number;
158
+ readonly fallback: boolean;
159
+ readonly rejected: Readonly<Partial<Record<'language' | 'duration' | 'intensity' | 'context-fact' | 'not-ready' | 'cooldown', number>>>;
160
+ /** At most eight considered candidates; counts above describe the rest. */
161
+ readonly candidates: readonly {
162
+ readonly cueId: string;
163
+ readonly score: number;
164
+ readonly repetitionPenalty: number;
165
+ readonly tieBreaker: number;
166
+ readonly cooling: boolean;
167
+ }[];
168
+ }
169
+ export interface CommentaryTrace {
170
+ readonly atMs: number;
171
+ readonly eventId: string;
172
+ readonly action: 'reject' | 'silence' | 'plan' | 'played' | 'previewed' | 'displayed' | 'cancel' | 'defer';
173
+ readonly reason: string;
174
+ readonly cueId?: string;
175
+ readonly family?: CommentaryIntentKind;
176
+ readonly fact?: {
177
+ readonly kind: string;
178
+ readonly tick: number;
179
+ readonly epoch: number;
180
+ readonly context: CommentaryMatchContext;
181
+ };
182
+ readonly evidence?: readonly string[];
183
+ readonly selection?: CommentarySelectionAudit;
184
+ }
185
+ export interface CommentaryBaseline {
186
+ readonly streamId: string;
187
+ readonly sequence: number;
188
+ readonly epoch: number;
189
+ }
190
+ export type CommentaryPolicyPatch = Partial<CommentaryPolicy>;
191
+ export type CommentaryPlayerFactInput = Omit<CommentaryPlayerFact, 'player' | 'trusted'>;
192
+ export interface CommentaryGoalGeometry {
193
+ readonly id: string;
194
+ readonly p0: readonly [number, number];
195
+ readonly p1: readonly [number, number];
196
+ readonly defendingTeam: CommentaryTeam;
197
+ /** A known playable point on the field side. Off-centre/rotated stadiums need no origin assumption. */
198
+ readonly pitchPoint: readonly [number, number];
199
+ }
200
+ export interface CommentaryAnalysisFrame {
201
+ readonly streamId: string;
202
+ readonly epoch: number;
203
+ readonly tick: number;
204
+ readonly context: CommentaryMatchContext;
205
+ readonly ball: {
206
+ readonly x: number;
207
+ readonly y: number;
208
+ /** Units/second in world coordinates. The adapter converts native per-tick velocity. */
209
+ readonly vx: number;
210
+ readonly vy: number;
211
+ readonly radius: number;
212
+ };
213
+ readonly goals: readonly CommentaryGoalGeometry[];
214
+ readonly contact?: {
215
+ readonly kind: 'kick' | 'post' | 'player' | 'wall' | 'net';
216
+ readonly player?: CommentaryPlayerIdentity;
217
+ readonly goalId?: string;
218
+ };
219
+ }
220
+ export interface CommentaryObservation {
221
+ readonly eventId: string;
222
+ readonly streamId: string;
223
+ readonly epoch: number;
224
+ readonly tick: number;
225
+ readonly kind: 'directed-shot' | 'post' | 'near-miss' | 'pressure' | 'block';
226
+ readonly context: CommentaryMatchContext;
227
+ readonly confidence: 'supported';
228
+ readonly evidence: readonly string[];
229
+ readonly attackingTeam: CommentaryTeam;
230
+ readonly intensity: number;
231
+ readonly player?: CommentaryPlayerIdentity;
232
+ readonly attackId?: string;
233
+ }
234
+ export interface CommentaryConfiguration {
235
+ readonly policy: CommentaryPolicy;
236
+ readonly playerFacts: readonly CommentaryPlayerFact[];
237
+ /** Configuration transport converts expiry durations to the receiver's clock domain. */
238
+ readonly hostNowMs: number;
239
+ /** Omitted means unchanged; null restores the built-in catalog. */
240
+ readonly catalog?: readonly CommentaryCue[] | null;
241
+ readonly atmosphere: AtmospherePolicy;
242
+ /** Omitted means unchanged; null restores the built-in atmosphere assets. */
243
+ readonly atmospherePack?: AtmospherePack | null;
244
+ }
245
+ export interface AtmospherePolicy {
246
+ readonly enabled: boolean;
247
+ readonly bedIntensity: number;
248
+ readonly reactionIntensity: number;
249
+ readonly chantIntensity: number;
250
+ readonly chantCooldownMs: number;
251
+ /** Neutral is the default; the room must explicitly declare a supporter preference. */
252
+ readonly preferredTeam: 'neutral' | CommentaryTeam;
253
+ }
254
+ export interface AtmosphereCue {
255
+ readonly id: string;
256
+ readonly kind: 'bed' | 'tension' | 'goal' | 'disappointment' | 'chant' | 'post' | 'near-miss' | 'release';
257
+ readonly team: 'neutral' | CommentaryTeam;
258
+ readonly intensity: readonly [number, number];
259
+ /** Complete decoded recording duration, from 100 to 60,000 milliseconds. */
260
+ readonly durationMs: number;
261
+ /** Bed/tension/chant only; at least 100 ms, wholly inside the recording. */
262
+ readonly loop: {
263
+ readonly startMs: number;
264
+ readonly endMs: number;
265
+ } | null;
266
+ readonly audio: {
267
+ readonly url: string;
268
+ readonly sha256: string;
269
+ };
270
+ readonly provenance: CommentaryCue['provenance'];
271
+ }
272
+ export interface AtmospherePack {
273
+ readonly id: string;
274
+ readonly version: string;
275
+ readonly cues: readonly AtmosphereCue[];
276
+ }
@@ -1,4 +1,7 @@
1
+ import type { MatchXgTrust } from './match-xg.js';
1
2
  export interface CreateRoomOptions {
2
3
  /** Cancels startup only. Close the returned room explicitly after it opens. */
3
4
  signal?: AbortSignal;
5
+ /** Host-operator reviewed descriptor allowlist, captured at startup. Never supplied by guests. */
6
+ matchXgTrust?: MatchXgTrust;
4
7
  }
@@ -0,0 +1,128 @@
1
+ import type { CommentaryAnalysisFrame, CommentaryMatchContext, CommentaryPlayerIdentity, CommentaryTeam } from './commentary.js';
2
+ /** Roles are explicit host assignments. Being the player nearest a goal is not a role. */
3
+ export type MatchIntelligenceRole = 'goalkeeper' | 'outfield';
4
+ export interface MatchIntelligenceRoleAssignment {
5
+ readonly player: CommentaryPlayerIdentity;
6
+ readonly role: MatchIntelligenceRole;
7
+ }
8
+ export interface MatchIntelligenceRoleInput {
9
+ readonly playerId: number;
10
+ readonly role: MatchIntelligenceRole;
11
+ }
12
+ export interface MatchIntelligencePlayer {
13
+ readonly identity: CommentaryPlayerIdentity;
14
+ readonly x: number;
15
+ readonly y: number;
16
+ /** World units per simulation second, as for the ball. */
17
+ readonly vx: number;
18
+ readonly vy: number;
19
+ readonly radius: number;
20
+ readonly role?: MatchIntelligenceRole;
21
+ }
22
+ export interface MatchIntelligenceContact {
23
+ /** Unique within the stream; repeated delivery does not create another action. */
24
+ readonly id: string;
25
+ readonly tick: number;
26
+ readonly kind: 'kick' | 'player' | 'wall' | 'post';
27
+ readonly player?: CommentaryPlayerIdentity;
28
+ readonly goalId?: string;
29
+ }
30
+ export interface MatchIntelligenceFrame extends Omit<CommentaryAnalysisFrame, 'contact'> {
31
+ /** Authoritative active roster, at most 32. Overflow/invalid input produces unknown. */
32
+ readonly players: readonly MatchIntelligencePlayer[];
33
+ /** Physical contacts for this sampled interval, at most 32, oldest first. */
34
+ readonly contacts: readonly MatchIntelligenceContact[];
35
+ /** False when the adapter cannot prove complete contact coverage since its last frame.
36
+ * Control geometry may still be observed; pass/assist/shot continuity becomes unknown. */
37
+ readonly contactsComplete: boolean;
38
+ }
39
+ export interface MatchIntelligencePolicy {
40
+ readonly enabled: boolean;
41
+ readonly maxGapMs: number;
42
+ readonly controlHoldMs: number;
43
+ /** Extra distance beyond player radius + ball radius, in ball radii. */
44
+ readonly controlExtraRadii: number;
45
+ readonly controlRelativeSpeedRadii: number;
46
+ readonly passWindowMs: number;
47
+ readonly passMinTravelRadii: number;
48
+ readonly chainWindowMs: number;
49
+ readonly assistWindowMs: number;
50
+ readonly shotWindowMs: number;
51
+ readonly shotMinSpeedRadii: number;
52
+ readonly shotHorizonMs: number;
53
+ readonly saveAreaGoalWidths: number;
54
+ readonly progressGoalWidths: number;
55
+ readonly counterWindowMs: number;
56
+ readonly pressureHoldMs: number;
57
+ readonly pressureAreaGoalWidths: number;
58
+ readonly summaryWindowMs: number;
59
+ }
60
+ export type MatchIntelligencePolicyPatch = Partial<MatchIntelligencePolicy>;
61
+ /** Descriptive features only. None of these values is calibrated xG or a goal probability. */
62
+ export interface MatchShotQuality {
63
+ readonly distanceGoalWidths: number;
64
+ readonly openingAngleRadians: number;
65
+ readonly speedBallRadiiPerSecond: number;
66
+ readonly defendersInLane: number;
67
+ }
68
+ export type MatchIntelligenceEventKind = 'control-established'
69
+ /** Silent authority barrier for a departed/reassigned field identity. Does not assert
70
+ * a kick, lost possession or an opponent takeover; only retires matching live control. */
71
+ | 'player-retired'
72
+ /** Silent, positively observed controller kick/release. Carries player + attackId;
73
+ * distance/speed evidence must exceed the measured control boundary. No opponent takeover. */
74
+ | 'control-ended' | 'pass-completed' | 'pass-failed' | 'pass-chain' | 'assist-confirmed' | 'turnover' | 'directed-shot' | 'block' | 'goalkeeper-save' | 'attack-progress' | 'counterattack' | 'sustained-pressure' | 'tactical-summary';
75
+ export interface MatchIntelligenceEvent {
76
+ readonly eventId: string;
77
+ readonly streamId: string;
78
+ readonly epoch: number;
79
+ readonly tick: number;
80
+ readonly kind: MatchIntelligenceEventKind;
81
+ readonly team: CommentaryTeam;
82
+ readonly player?: CommentaryPlayerIdentity;
83
+ readonly otherPlayer?: CommentaryPlayerIdentity;
84
+ readonly goalId?: string;
85
+ readonly attackId?: string;
86
+ readonly context: CommentaryMatchContext;
87
+ readonly confidence: 'supported' | 'authoritative';
88
+ readonly evidence: readonly string[];
89
+ /** Bounded descriptive values, never arbitrary prose or a probability. */
90
+ readonly metrics: Readonly<Record<string, number>>;
91
+ readonly shotQuality?: MatchShotQuality;
92
+ }
93
+ export interface MatchIntelligenceTeamTotals {
94
+ readonly controlledMs: number;
95
+ readonly completedPasses: number;
96
+ readonly failedPasses: number;
97
+ readonly turnoversWon: number;
98
+ readonly directedShots: number;
99
+ readonly blocks: number;
100
+ readonly saves: number;
101
+ readonly confirmedAssists: number;
102
+ }
103
+ export interface MatchIntelligenceSnapshot {
104
+ readonly streamId: string | null;
105
+ readonly epoch: number | null;
106
+ readonly tick: number | null;
107
+ readonly status: 'uninitialized' | 'tracking' | 'paused' | 'unknown' | 'disabled';
108
+ readonly unknownReasons: readonly string[];
109
+ /** Observed control only; free/contested balls remain unassigned. */
110
+ readonly controller: CommentaryPlayerIdentity | null;
111
+ readonly passChain: {
112
+ readonly team: CommentaryTeam | null;
113
+ readonly completed: number;
114
+ };
115
+ readonly attack: {
116
+ readonly id: string;
117
+ readonly team: CommentaryTeam;
118
+ readonly goalId: string;
119
+ } | null;
120
+ readonly observedMs: number;
121
+ readonly uncontrolledMs: number;
122
+ readonly teams: {
123
+ readonly red: MatchIntelligenceTeamTotals;
124
+ readonly blue: MatchIntelligenceTeamTotals;
125
+ };
126
+ readonly emittedEvents: number;
127
+ readonly droppedEvents: number;
128
+ }
@@ -0,0 +1,37 @@
1
+ import type { CommentaryPlayerIdentity } from './commentary.js';
2
+ import type { MatchXgDomain, MatchXgEstimate, MatchXgFeatures, MatchXgModel, MatchXgUnavailableReason } from './match-xg.js';
3
+ /** Must match the independently reviewed offline collection population and feature stage. */
4
+ export interface MatchXgCollectionPolicy {
5
+ readonly featureStage: 'pre-kick' | 'post-kick';
6
+ readonly population: 'all-kicks-v1' | 'goalward-release-v1';
7
+ /** Integer simulation milliseconds, 100 through 60,000. */
8
+ readonly outcomeHorizonMs: number;
9
+ }
10
+ /** Host-local configuration. null disables inference; no model or approval is bundled. */
11
+ export interface MatchXgRuntimeConfig {
12
+ readonly model: MatchXgModel;
13
+ readonly policy: MatchXgCollectionPolicy;
14
+ }
15
+ export type MatchXgRuntimeReason = MatchXgUnavailableReason | 'disabled' | 'preparing' | 'ready' | 'closed' | 'physics-edit' | 'unsupported-geometry' | 'domain-change' | 'capture-discontinuity';
16
+ export interface MatchXgRuntimeStatus {
17
+ readonly state: 'disabled' | 'preparing' | 'ready' | 'unavailable' | 'invalidated';
18
+ readonly reason: MatchXgRuntimeReason;
19
+ readonly domain: MatchXgDomain | null;
20
+ readonly modelId: string | null;
21
+ /** Approval is the host operator's explicit assertion, not platform certification. */
22
+ readonly trustScope: 'host-configuration';
23
+ }
24
+ /** Frozen kick-time input and estimate. Host-local only; never a confirmed goal or spoken probability. */
25
+ export interface MatchKickEstimateEvent {
26
+ readonly eventId: string;
27
+ readonly streamId: string;
28
+ readonly epoch: number;
29
+ readonly tick: number;
30
+ readonly order: number;
31
+ readonly player: CommentaryPlayerIdentity;
32
+ readonly goalId: string;
33
+ readonly features: MatchXgFeatures;
34
+ readonly estimate: MatchXgEstimate;
35
+ readonly domain: MatchXgDomain;
36
+ readonly trustScope: 'host-configuration';
37
+ }
@@ -0,0 +1,88 @@
1
+ /** Feature units are bound by MatchXgDomain; end-step observations are not frozen kick inputs. */
2
+ export interface MatchXgFeatures {
3
+ /** Perpendicular playable-side distance to the goal line / mouth width. */
4
+ readonly distanceGoalWidths: number;
5
+ /** Smaller absolute angle between rays from the ball to the two posts, in [0, pi]. */
6
+ readonly openingAngleRadians: number;
7
+ /** Ball speed at domain.featureStage / current ball radius. Pre-kick is incoming speed;
8
+ * post-kick is immediately after the impulse, before clamp, gravity, damping or collisions.
9
+ * These are distinct feature definitions, never interchangeable. */
10
+ readonly speedBallRadiiPerSecond: number;
11
+ /** Opponents intersecting the ball-to-goal-center corridor; not a block probability. */
12
+ readonly defendersInLane: number;
13
+ }
14
+ export type MatchXgVector = readonly [
15
+ distance: number,
16
+ angle: number,
17
+ speed: number,
18
+ defenders: number
19
+ ];
20
+ /** Supplied from the authoritative match configuration, never from the candidate model alone. */
21
+ export interface MatchXgDomain {
22
+ readonly engineId: string;
23
+ /** SHA-256 of the integration's canonical stadium physics/goal geometry. */
24
+ readonly geometrySha256: string;
25
+ /** SHA-256 of the exact eligibility, attribution and analysis policy used to collect samples. */
26
+ readonly policySha256: string;
27
+ readonly featuresRevision: string;
28
+ readonly featureStage: 'pre-kick' | 'post-kick' | 'end-step';
29
+ /** Game action populations, not a claim that a kick was an intentional football shot. */
30
+ readonly population: 'all-kicks-v1' | 'goalward-release-v1' | 'legacy-end-step-on-target-v1';
31
+ readonly shotDefinitionRevision: string;
32
+ /** Prediction target: a goal attributable to this shot within this simulation-time horizon. */
33
+ readonly outcomeHorizonMs: number;
34
+ }
35
+ /** Offline descriptor only. No bundled/trained model or telemetry is implied by this contract. */
36
+ export interface MatchXgModel {
37
+ readonly schemaVersion: 1;
38
+ readonly id: string;
39
+ readonly purpose: 'production' | 'technical-fixture';
40
+ readonly kind: 'logistic';
41
+ readonly domain: MatchXgDomain;
42
+ /** logit = intercept + sum(weights[i] * (feature[i] - means[i]) / scales[i]). */
43
+ readonly intercept: number;
44
+ readonly weights: MatchXgVector;
45
+ readonly means: MatchXgVector;
46
+ readonly scales: MatchXgVector;
47
+ /** Explicit evaluated support. Outside this box inference returns null, never extrapolates. */
48
+ readonly minimums: MatchXgVector;
49
+ readonly maximums: MatchXgVector;
50
+ /** Structural metric ranges are not an acceptance threshold; offline review owns calibration quality. */
51
+ readonly evaluation: {
52
+ readonly trainingDatasetSha256: string;
53
+ readonly evaluationDatasetSha256: string;
54
+ readonly evaluationReportSha256: string;
55
+ readonly splitUnit: 'match';
56
+ readonly heldOutSamples: number;
57
+ readonly heldOutGoals: number;
58
+ readonly brier: number;
59
+ readonly logLoss: number;
60
+ readonly ece: number;
61
+ readonly eceBins: number;
62
+ };
63
+ /** References to reviewed evidence, not self-authenticating approval or scientific proof. */
64
+ readonly provenance: {
65
+ readonly provider: string;
66
+ readonly source: string;
67
+ readonly license: string;
68
+ readonly reviewedBy: string;
69
+ readonly reviewReference: string;
70
+ };
71
+ }
72
+ export type MatchXgUnavailableReason = 'missing-model' | 'invalid-domain' | 'invalid-descriptor' | 'domain-mismatch' | 'technical-fixture' | 'untrusted-model' | 'invalid-features' | 'outside-model-support';
73
+ export type MatchXgEstimate = {
74
+ readonly xG: null;
75
+ readonly status: 'uncalibrated';
76
+ readonly reason: MatchXgUnavailableReason;
77
+ readonly modelId?: string;
78
+ } | {
79
+ readonly xG: number;
80
+ /** Explicit integration approval; validation/inference do not prove scientific calibration. */
81
+ readonly status: 'reviewed-model';
82
+ readonly reason: 'trusted-offline-model';
83
+ readonly modelId: string;
84
+ };
85
+ export interface MatchXgTrust {
86
+ /** Separate integration-owned allowlist. Never accept this list from the candidate descriptor. */
87
+ readonly trustedModels?: readonly MatchXgModel[];
88
+ }
@@ -13,3 +13,7 @@ export type NodeRoom = Room & {
13
13
  readonly closed: Promise<void>;
14
14
  };
15
15
  export declare function createRoom(config: NodeRoomConfig, creation?: CreateRoomOptions): Promise<NodeRoom>;
16
+ export type { AtmospherePolicy, AtmospherePack, AtmosphereCue, CommentaryCue, CommentaryWordTiming, MatchFact, CommentaryPolicy, CommentaryPolicyPatch, CommentaryPlayerFact, CommentaryPlayerFactInput, CommentaryConfiguration, CommentaryObservation, CommentaryGoalGeometry, } from './commentary.js';
17
+ export type { MatchIntelligencePolicy, MatchIntelligencePolicyPatch, MatchIntelligenceRole, MatchIntelligenceRoleInput, MatchIntelligenceRoleAssignment, MatchIntelligenceEvent, MatchIntelligenceEventKind, MatchIntelligenceSnapshot, MatchIntelligenceTeamTotals, MatchShotQuality, } from './match-intelligence.js';
18
+ export type { MatchXgFeatures, MatchXgVector, MatchXgDomain, MatchXgModel, MatchXgUnavailableReason, MatchXgEstimate, MatchXgTrust, } from './match-xg.js';
19
+ export type { MatchXgCollectionPolicy, MatchXgRuntimeConfig, MatchXgRuntimeReason, MatchXgRuntimeStatus, MatchKickEstimateEvent, } from './match-xg-runtime.js';
@@ -3,7 +3,7 @@ import type { TeamStyles } from './team.js';
3
3
  import type { MatchState } from './match-state.js';
4
4
  export type ReplayCommand = {
5
5
  tick: number;
6
- kind: 'input' | 'team' | 'start' | 'stop' | 'pause' | 'scoreLimit' | 'timeLimit' | 'kickRate' | 'join' | 'disc' | 'finishDraw';
6
+ kind: 'input' | 'team' | 'start' | 'stop' | 'pause' | 'scoreLimit' | 'timeLimit' | 'kickRate' | 'join' | 'disc';
7
7
  properties?: DiscPropertyPatch;
8
8
  slot: number;
9
9
  value: number;
@@ -0,0 +1,48 @@
1
+ import type { MatchKickEstimateEvent } from './match-xg-runtime.js';
2
+ import type { HostPlayer, HostScores } from './player.js';
3
+ import type { CommentaryObservation, MatchFact } from './commentary.js';
4
+ import type { MatchIntelligenceEvent } from './match-intelligence.js';
5
+ /**
6
+ * Application callbacks a host room invokes. All are optional and run with the room
7
+ * as `this`; they observe the room and may call its commands.
8
+ */
9
+ export interface RoomHooks {
10
+ /** Immutable event-time context captured before legacy match callbacks. */
11
+ onMatchFact?: (fact: MatchFact) => void;
12
+ /** Conservative geometry evidence; never a confirmed goal, pass, save, or player rank. */
13
+ onMatchObservation?: (observation: CommentaryObservation) => void;
14
+ /** Bounded authoritative-derived analysis, copied before application callbacks. No persisted statistics. */
15
+ onMatchIntelligenceEvent?: (event: MatchIntelligenceEvent) => void;
16
+ /** Host-local frozen kick estimate; trust is an operator assertion, never platform certification. */
17
+ onMatchKickEstimate?: (event: MatchKickEstimateEvent) => void;
18
+ onRecordingComplete?: (blob: Blob, reason: string) => void;
19
+ onRoomLink?: (url: string) => void;
20
+ onPlayerJoin?: (p: HostPlayer) => void;
21
+ onPlayerTeamChange?: (p: HostPlayer, byPlayer: HostPlayer | null) => void;
22
+ onPlayerMuteChange?: (p: HostPlayer, byPlayer: HostPlayer | null) => void;
23
+ onPlayerAdminChange?: (p: HostPlayer, byPlayer: HostPlayer | null) => void;
24
+ onGameStart?: (byPlayer: HostPlayer | null) => void;
25
+ onGameStop?: (byPlayer: HostPlayer | null) => void;
26
+ onTeamVictory?: (scores: HostScores) => void;
27
+ /** Legacy Ball2D callback; prefer onTeamVictory. */
28
+ onGameVictory?: (scores: HostScores) => void;
29
+ onGamePauseChange?: (paused: boolean) => void;
30
+ onGamePause?: (byPlayer: HostPlayer | null) => void;
31
+ onGameUnpause?: (byPlayer: HostPlayer | null) => void;
32
+ onPlayerLeave?: (p: HostPlayer) => void;
33
+ onPlayerKicked?: (p: HostPlayer, reason: string, ban: boolean, byPlayer: HostPlayer | null) => void;
34
+ onPlayerActivity?: (p: HostPlayer) => void;
35
+ /** Runs only when a peer changes its accepted key state; prevInput is the previous authoritative bitmask. */
36
+ onPlayerInput?: (p: HostPlayer, prevInput: number) => void;
37
+ onPlayerChat?: (p: HostPlayer, text: string) => boolean | void;
38
+ /** Host-visible direct chat; return false to suppress delivery to both participants. */
39
+ onPlayerDirectChat?: (p: HostPlayer, recipient: HostPlayer, text: string) => boolean | void;
40
+ onPlayerBallKick?: (p: HostPlayer) => void;
41
+ onTeamGoal?: (team: 1 | 2) => void;
42
+ onPositionsReset?: () => void;
43
+ onStadiumChange?: (name: string, byPlayer: HostPlayer | null) => void;
44
+ onTeamsLockChange?: (locked: boolean, byPlayer: HostPlayer | null) => void;
45
+ onKickRateLimitSet?: (min: number, rate: number, burst: number, byPlayer: HostPlayer | null) => void;
46
+ onGameTick?: () => void;
47
+ onError?: (message: string) => void;
48
+ }