Compare commits

..

1 Commits

Author SHA1 Message Date
Dean Wenstrand
f724ec6c0d Let shot filters select a single player within a video
Adds playerIndex to FilterInput, backing ShooterIdentity.player_index --
the cluster-assigned shooter, 0-based and stable only within one video.

Deliberately separate from userId, which is populated only once a cluster
has been confirmed against a registered account. Multiplayer sessions are
usually played against someone without a Railbird login, so filtering by
userId cannot express "the other player" in the common case; the cluster
index always can.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 11:08:26 -07:00
3 changed files with 101 additions and 11 deletions

View File

@@ -476,6 +476,7 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -520,6 +521,7 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -564,6 +566,7 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -608,6 +611,7 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -652,6 +656,7 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -696,6 +701,7 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -740,6 +746,7 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -784,6 +791,7 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -828,6 +836,7 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -872,6 +881,7 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -916,6 +926,7 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -960,6 +971,7 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -1004,6 +1016,7 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -1048,6 +1061,7 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -1092,6 +1106,7 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -1136,6 +1151,7 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -1180,6 +1196,7 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -1224,6 +1241,7 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -1268,6 +1286,7 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -1312,6 +1331,7 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -1356,6 +1376,7 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -1400,6 +1421,7 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -1444,6 +1466,7 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -1488,6 +1511,7 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -1532,6 +1556,7 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -1576,6 +1601,7 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -1620,6 +1646,7 @@ export type FilterInput =
missAngleInDegrees: FloatRangeFilter; missAngleInDegrees: FloatRangeFilter;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -1664,6 +1691,7 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter: FilterInput; notFilter: FilterInput;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -1708,6 +1736,7 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters: Array<FilterInput>; orFilters: Array<FilterInput>;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -1752,6 +1781,52 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex: Array<Scalars["Int"]["input"]>;
runId?: never;
runLength?: never;
shotDirection?: never;
spinType?: never;
tableSize?: never;
tags?: never;
targetPocketAngle?: never;
targetPocketAngleDirection?: never;
targetPocketDistance?: never;
totalDistance?: never;
userId?: never;
username?: never;
videoId?: never;
}
| {
andFilters?: never;
annotations?: never;
backcut?: never;
bankAngle?: never;
bankDistance?: never;
createdAt?: never;
cueAngleAfterObject?: never;
cueBallSpeed?: never;
cueObjectAngle?: never;
cueObjectDistance?: never;
cueSpeedAfterObject?: never;
difficulty?: never;
falsePositiveScore?: never;
fargoRating?: never;
intendedPocketType?: never;
isBreakHeuristic?: never;
isDirect?: never;
isLeft?: never;
isLeftMiss?: never;
isRight?: never;
isRightMiss?: never;
isStraight?: never;
kickAngle?: never;
kickDistance?: never;
make?: never;
marginOfErrorInDegrees?: never;
missAngleInDegrees?: never;
notFilter?: never;
orFilters?: never;
playerIndex?: never;
runId: Array<Scalars["Int"]["input"]>; runId: Array<Scalars["Int"]["input"]>;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -1796,6 +1871,7 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength: FloatRangeFilter; runLength: FloatRangeFilter;
shotDirection?: never; shotDirection?: never;
@@ -1840,6 +1916,7 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection: Array<ShotDirectionEnum>; shotDirection: Array<ShotDirectionEnum>;
@@ -1884,6 +1961,7 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -1928,6 +2006,7 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -1972,6 +2051,7 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -2016,6 +2096,7 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -2060,6 +2141,7 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -2104,6 +2186,7 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -2148,6 +2231,7 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -2192,6 +2276,7 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -2236,6 +2321,7 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -2280,6 +2366,7 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -3882,7 +3969,6 @@ export type ShotProjectionGql = {
export type ShotTableStateGql = { export type ShotTableStateGql = {
__typename?: "ShotTableStateGQL"; __typename?: "ShotTableStateGQL";
frameImage?: Maybe<Scalars["String"]["output"]>;
frameIndex: Scalars["Int"]["output"]; frameIndex: Scalars["Int"]["output"];
shotId: Scalars["Int"]["output"]; shotId: Scalars["Int"]["output"];
tableState: TableStateGql; tableState: TableStateGql;
@@ -7736,7 +7822,6 @@ export type GetShotTableStateQuery = {
shotId: number; shotId: number;
videoId: number; videoId: number;
frameIndex: number; frameIndex: number;
frameImage?: string | null;
tableState: { tableState: {
__typename?: "TableStateGQL"; __typename?: "TableStateGQL";
identifierToPosition: Array<Array<number>>; identifierToPosition: Array<Array<number>>;
@@ -16517,7 +16602,6 @@ export const GetShotTableStateDocument = gql`
shotId shotId
videoId videoId
frameIndex frameIndex
frameImage
tableState { tableState {
...TableStateLayoutFields ...TableStateLayoutFields
} }

View File

@@ -44,10 +44,9 @@ fragment DetectionPocketPointFields on PocketPointsGQL {
# positions. Boxes, pocket boxes, and source points share the crop's # positions. Boxes, pocket boxes, and source points share the crop's
# coordinate space; the crop locates that space in the full frame. # coordinate space; the crop locates that space in the full frame.
# #
# Only the photo query selects this. The shot query returns its source # Only the photo query selects this. The live and shot queries read a
# frame (frameImage) but positions there come from the stored homography, # frame out of a video the app never holds, so there is no image to
# so there are no fresh boxes to draw; the live query returns no image at # overlay, and getLiveTableState is polled every five seconds.
# all and is polled every five seconds.
fragment TableStateDetectionFields on TableStateGQL { fragment TableStateDetectionFields on TableStateGQL {
identifierToBox { identifierToBox {
...DetectionBoxFields ...DetectionBoxFields
@@ -98,9 +97,6 @@ query GetShotTableState($shotId: Int!) {
shotId shotId
videoId videoId
frameIndex frameIndex
# The b64 JPEG frame the layout was detected in, so the client can show
# the shot's actual starting frame instead of the video's thumbnail.
frameImage
tableState { tableState {
...TableStateLayoutFields ...TableStateLayoutFields
} }

View File

@@ -250,6 +250,11 @@ input CreatePoolHallCameraInput {
claimTermsText: String = null claimTermsText: String = null
} }
input CreateShotShareLinkInput {
shotIds: [Int!]!
paddingSeconds: Float = null
}
input CreatePoolHallInput { input CreatePoolHallInput {
name: String! name: String!
address: String = null address: String = null
@@ -408,6 +413,7 @@ input FilterInput @oneOf {
userId: [Int!] userId: [Int!]
runId: [Int!] runId: [Int!]
username: [String!] username: [String!]
playerIndex: [Int!]
fargoRating: FloatRangeFilter fargoRating: FloatRangeFilter
make: [Boolean!] make: [Boolean!]
tags: [VideoTagInput!] tags: [VideoTagInput!]
@@ -1568,7 +1574,6 @@ type ShotTableStateGQL {
videoId: Int! videoId: Int!
frameIndex: Int! frameIndex: Int!
tableState: TableStateGQL! tableState: TableStateGQL!
frameImage: String
} }
input ShotsOrderingComponent @oneOf { input ShotsOrderingComponent @oneOf {
@@ -1962,6 +1967,11 @@ type UserSubscriptionStatusGQL {
stripeSubscriptionId: String stripeSubscriptionId: String
} }
type ShareLinkGQL {
slug: String!
url: String!
}
type VideoExportJobGQL { type VideoExportJobGQL {
id: Int! id: Int!
videoId: Int! videoId: Int!