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 17 additions and 116 deletions

View File

@@ -88,26 +88,6 @@ export type AppleIapSubscriptionOptionsGql = {
productIds: Array<Scalars["String"]["output"]>; productIds: Array<Scalars["String"]["output"]>;
}; };
export type AuthBootstrapGql = {
__typename?: "AuthBootstrapGQL";
status: AuthBootstrapStatusEnum;
user?: Maybe<UserGql>;
};
export enum AuthBootstrapStatusEnum {
NeedsProfile = "NEEDS_PROFILE",
Registered = "REGISTERED",
RegistrationDisabled = "REGISTRATION_DISABLED",
}
export enum AuthRegistrationErrorEnum {
InvalidUsername = "INVALID_USERNAME",
RegistrationDisabled = "REGISTRATION_DISABLED",
UnsupportedProvider = "UNSUPPORTED_PROVIDER",
UsernameUnavailable = "USERNAME_UNAVAILABLE",
VerifiedEmailRequired = "VERIFIED_EMAIL_REQUIRED",
}
export type BallTrajectoryGql = { export type BallTrajectoryGql = {
__typename?: "BallTrajectoryGQL"; __typename?: "BallTrajectoryGQL";
ballId: Scalars["Int"]["output"]; ballId: Scalars["Int"]["output"];
@@ -299,12 +279,6 @@ export type CommentGql = {
user: UserGql; user: UserGql;
}; };
export type CompleteAuthRegistrationGql = {
__typename?: "CompleteAuthRegistrationGQL";
errorCode?: Maybe<AuthRegistrationErrorEnum>;
user?: Maybe<UserGql>;
};
export type CountLeaderboardGql = { export type CountLeaderboardGql = {
__typename?: "CountLeaderboardGQL"; __typename?: "CountLeaderboardGQL";
entries: Array<UserShotCountEntry>; entries: Array<UserShotCountEntry>;
@@ -409,7 +383,6 @@ export type DeployedConfigGql = {
defaultAndroidRecordingFormat: StreamSegmentTypeEnum; defaultAndroidRecordingFormat: StreamSegmentTypeEnum;
devMode: Scalars["Boolean"]["output"]; devMode: Scalars["Boolean"]["output"];
environment: Scalars["String"]["output"]; environment: Scalars["String"]["output"];
externalSignInProviderAvailability: Array<ExternalSignInProviderAvailability>;
firebase: Scalars["Boolean"]["output"]; firebase: Scalars["Boolean"]["output"];
minimumAllowedAppVersion: Scalars["String"]["output"]; minimumAllowedAppVersion: Scalars["String"]["output"];
quotaEnforcementEnabled: Scalars["Boolean"]["output"]; quotaEnforcementEnabled: Scalars["Boolean"]["output"];
@@ -472,25 +445,6 @@ export type EnumAggregation = {
feature: Scalars["String"]["input"]; feature: Scalars["String"]["input"];
}; };
export enum ExternalSignInPlatform {
Android = "ANDROID",
Ios = "IOS",
Web = "WEB",
}
export enum ExternalSignInProvider {
Apple = "APPLE",
Facebook = "FACEBOOK",
Google = "GOOGLE",
}
export type ExternalSignInProviderAvailability = {
__typename?: "ExternalSignInProviderAvailability";
enabled: Scalars["Boolean"]["output"];
platform: ExternalSignInPlatform;
provider: ExternalSignInProvider;
};
export type FilterInput = export type FilterInput =
| { | {
andFilters: Array<FilterInput>; andFilters: Array<FilterInput>;
@@ -2664,7 +2618,6 @@ export type Mutation = {
cancelCameraClaimSession: CameraClaimSession; cancelCameraClaimSession: CameraClaimSession;
cancelSubscription: UserSubscriptionStatusGql; cancelSubscription: UserSubscriptionStatusGql;
commentOnVideo: Scalars["Boolean"]["output"]; commentOnVideo: Scalars["Boolean"]["output"];
completeAuthRegistration: CompleteAuthRegistrationGql;
createBucketSet: BucketSetGql; createBucketSet: BucketSetGql;
createCameraClaimSession: CameraClaimSession; createCameraClaimSession: CameraClaimSession;
createChallenge: Challenge; createChallenge: Challenge;
@@ -2749,11 +2702,6 @@ export type MutationCommentOnVideoArgs = {
videoId: Scalars["Int"]["input"]; videoId: Scalars["Int"]["input"];
}; };
export type MutationCompleteAuthRegistrationArgs = {
agreesToMarketing: Scalars["Boolean"]["input"];
username: Scalars["String"]["input"];
};
export type MutationCreateBucketSetArgs = { export type MutationCreateBucketSetArgs = {
params: CreateBucketSetInput; params: CreateBucketSetInput;
}; };
@@ -3253,7 +3201,6 @@ export type Query = {
doesUsernameExist: Scalars["Boolean"]["output"]; doesUsernameExist: Scalars["Boolean"]["output"];
getAggregatedShotMetrics: Array<AggregateResultGql>; getAggregatedShotMetrics: Array<AggregateResultGql>;
getAppleAppAccountToken: Scalars["String"]["output"]; getAppleAppAccountToken: Scalars["String"]["output"];
getAuthBootstrap: AuthBootstrapGql;
getAvailableSubscriptionOptions: StripeSubscriptionOptionsGql; getAvailableSubscriptionOptions: StripeSubscriptionOptionsGql;
getBucketSet?: Maybe<BucketSetGql>; getBucketSet?: Maybe<BucketSetGql>;
getDeployedConfig: DeployedConfigGql; getDeployedConfig: DeployedConfigGql;
@@ -3848,10 +3795,10 @@ export type SessionCoachFeedbackGql = {
}; };
export enum SessionCoachFeedbackResponseEnum { export enum SessionCoachFeedbackResponseEnum {
AlreadyWorkingOnThis = "ALREADY_WORKING_ON_THIS",
Helpful = "HELPFUL", Helpful = "HELPFUL",
NotEnoughEvidence = "NOT_ENOUGH_EVIDENCE", NotEnoughEvidence = "NOT_ENOUGH_EVIDENCE",
NotInsightful = "NOT_INSIGHTFUL", WrongFocus = "WRONG_FOCUS",
NotRelevant = "NOT_RELEVANT",
} }
export type SessionCoachGql = { export type SessionCoachGql = {
@@ -4022,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;
@@ -7876,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>>;
@@ -16657,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

@@ -42,25 +42,6 @@ type AppleIapSubscriptionOptionsGQL {
productIds: [String!]! productIds: [String!]!
} }
type AuthBootstrapGQL {
status: AuthBootstrapStatusEnum!
user: UserGQL
}
enum AuthBootstrapStatusEnum {
REGISTERED
NEEDS_PROFILE
REGISTRATION_DISABLED
}
enum AuthRegistrationErrorEnum {
USERNAME_UNAVAILABLE
REGISTRATION_DISABLED
VERIFIED_EMAIL_REQUIRED
INVALID_USERNAME
UNSUPPORTED_PROVIDER
}
type BallTrajectoryGQL { type BallTrajectoryGQL {
ballId: Int! ballId: Int!
points: [TrajectoryPointGQL!]! points: [TrajectoryPointGQL!]!
@@ -244,11 +225,6 @@ type CommentGQL {
replyToCommentId: Int replyToCommentId: Int
} }
type CompleteAuthRegistrationGQL {
user: UserGQL
errorCode: AuthRegistrationErrorEnum
}
type CountLeaderboardGQL { type CountLeaderboardGQL {
entries: [UserShotCountEntry!]! entries: [UserShotCountEntry!]!
} }
@@ -274,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
@@ -361,7 +342,6 @@ type DeployedConfigGQL {
storageLimitEnforcementEnabled: Boolean! storageLimitEnforcementEnabled: Boolean!
capabilityEnforcementEnabled: Boolean! capabilityEnforcementEnabled: Boolean!
bannerMessages: [BannerGQL!]! bannerMessages: [BannerGQL!]!
externalSignInProviderAvailability: [ExternalSignInProviderAvailability!]!
defaultAndroidRecordingFormat: StreamSegmentTypeEnum! defaultAndroidRecordingFormat: StreamSegmentTypeEnum!
bucketUrl: String! bucketUrl: String!
} }
@@ -418,24 +398,6 @@ input EnumAggregation {
feature: String! feature: String!
} }
enum ExternalSignInPlatform {
WEB
IOS
ANDROID
}
enum ExternalSignInProvider {
GOOGLE
APPLE
FACEBOOK
}
type ExternalSignInProviderAvailability {
provider: ExternalSignInProvider!
platform: ExternalSignInPlatform!
enabled: Boolean!
}
input FilterInput @oneOf { input FilterInput @oneOf {
andFilters: [FilterInput!] andFilters: [FilterInput!]
orFilters: [FilterInput!] orFilters: [FilterInput!]
@@ -781,10 +743,6 @@ type Mutation {
response: SessionCoachFeedbackResponseEnum! response: SessionCoachFeedbackResponseEnum!
): SessionCoachFeedbackGQL! ): SessionCoachFeedbackGQL!
requestSessionCoachGeneration(videoId: Int!): SessionCoachGQL! requestSessionCoachGeneration(videoId: Int!): SessionCoachGQL!
completeAuthRegistration(
username: String!
agreesToMarketing: Boolean!
): CompleteAuthRegistrationGQL!
getProfileImageUploadLink( getProfileImageUploadLink(
fileExt: String = ".png" fileExt: String = ".png"
): GetProfileUploadLinkReturn! ): GetProfileUploadLinkReturn!
@@ -1166,7 +1124,6 @@ type Query {
): [ShotGQL!]! ): [ShotGQL!]!
getShotsByIds(ids: [Int!]!): [ShotGQL!]! getShotsByIds(ids: [Int!]!): [ShotGQL!]!
getUsageStats(days: Int! = 30): UsageStatsGQL! getUsageStats(days: Int! = 30): UsageStatsGQL!
getAuthBootstrap: AuthBootstrapGQL!
getUser(userId: Int!): UserGQL getUser(userId: Int!): UserGQL
doesUsernameExist(candidateUsername: String!): Boolean! doesUsernameExist(candidateUsername: String!): Boolean!
getLoggedInUser: UserGQL getLoggedInUser: UserGQL
@@ -1455,8 +1412,8 @@ type SessionCoachFeedbackGQL {
enum SessionCoachFeedbackResponseEnum { enum SessionCoachFeedbackResponseEnum {
HELPFUL HELPFUL
NOT_RELEVANT WRONG_FOCUS
NOT_INSIGHTFUL ALREADY_WORKING_ON_THIS
NOT_ENOUGH_EVIDENCE NOT_ENOUGH_EVIDENCE
} }
@@ -1617,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 {
@@ -2011,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!