Bridge Training

Validation

Structural deal validation, soft save warnings and the save decision, with the complete code registry.

Import from @workspace/bridge/validation. Pure TypeScript, synchronous, usable from React hooks and from the server alike. The engine supplies the variant-specific queries.

validateDeal

validateDeal(input: DealValidationInput): DealValidationResult

Runs the structural rule set. Hand checks always apply; the auction rules dispatch on the engine's input axis (engine.ctx.auctionInput), never on the mode name.

DealValidationInput

Prop

Type

CodeWhen checkedConditionparams
HANDS_INCOMPLETEAlwaysA hand does not have the variant's card count{ players }
DUPLICATE_CARDSAlwaysA card appears in more than one hand{ cards }
CONTRACT_MISSINGsequence or contract axisNo contract can be derived from the auction
DECLARER_NOT_SOUTHsequence or contract axisThe derived declarer is not South{ declarer }
TRUMP_MISSINGtrump axisNo denomination was authored
validation.ts
const engine = createBridgeEngine(createBridgeContext({ teachingMode: "auction" }));

const structural: DealValidationResult = validateDeal({
  hands: createHands({ N: "AKQ2.KQ3.A54.K32", S: "7654.A87.T98.AT8" }),
  auction: new AuctionBuilder().fromCompact("S / 1NT pass 3NT AP").build(),
  engine,
});
structural.isValid; // true: complete hands, 3NT by South

decideSave

decideSave<E, Code>(structural: { isValid; errors: E[] }, warningChecks?: readonly WarningCheck<Code>[]): SaveDecision<E, Code>

Combines a structural result with soft warning checks into one outcome.

StatusMeaningPayload
blockedStructural result invalid, do not saveerrors: E[]
confirm-requiredAt least one check fired, ask before savingwarnings, a non-empty tuple in registration order
readySave now

A WarningCheck<Code> is () => SaveWarning<Code> | null. Checks are closures: resolve async data first, then build the check.

createParWarningCheck

createParWarningCheck(input: ParWarningInput): WarningCheck<ParWarningCode>

Fires EXPECTED_OUTSIDE_PAR when the chosen expected result is unreachable for the player's partnership, using the same NS-frame predicate as the result selector. Any null input makes the check a no-op. params carries objective and max, both NS-frame.

ParWarningInput

Prop

Type

validation.ts
const hint: NSParHint = { tricks: 9, partnership: "NS" };

const decision: SaveDecision<ValidationError, typeof EXPECTED_OUTSIDE_PAR_CODE> = decideSave(
  structural,
  [createParWarningCheck({ expectedTricks: 10, hint })],
);

switch (decision.status) {
  case "blocked":
    decision.errors; // ValidationError[]
    break;
  case "confirm-required":
    decision.warnings[0].code; // "EXPECTED_OUTSIDE_PAR"
    decision.warnings[0].params; // { objective: 10, max: 9 }
    break;
  case "ready":
    break;
}

Codes

VALIDATION_ERROR_CODE

Terminal codes, they block the save. ValidationErrorCode is their union.

KeyValue
HANDS_INCOMPLETE"HANDS_INCOMPLETE"
DUPLICATE_CARDS"DUPLICATE_CARDS"
CONTRACT_MISSING"CONTRACT_MISSING"
TRUMP_MISSING"TRUMP_MISSING"
DECLARER_NOT_SOUTH"DECLARER_NOT_SOUTH"

WARNING_CODE

Soft codes, they require confirmation. WarningCode is their union.

KeyValue
EXPECTED_OUTSIDE_PAR"EXPECTED_OUTSIDE_PAR"

EXPECTED_OUTSIDE_PAR_CODE is an alias for WARNING_CODE.EXPECTED_OUTSIDE_PAR, and ParWarningCode is its type.

Types

ValidationError

Prop

Type

DealValidationResult

Prop

Type

SaveWarning

Generic over Code extends string, defaulting to WarningCode.

Prop

Type

SaveDecision

SaveDecision<E, Code> is the union { status: "blocked"; errors: E[] } | { status: "ready" } | { status: "confirm-required"; warnings: [SaveWarning<Code>, ...SaveWarning<Code>[]] }.