/**
 * Card-driven secondary-mission scoring, 10th-edition tactical model.
 *
 * Drawn secondaries are *held* in hand across rounds and **scored once**: the
 * player asserts which of a card's awards they achieved, the engine computes the
 * VP (clamped to the card's cap), records it against the current battle round,
 * and the card is then discarded. There is no multi-turn per-card accrual — a
 * card pays out exactly once.
 *
 * Why "asserted" rather than evaluated: there is no board-state model here, so
 * an award's `when` condition is a human-readable label (see
 * `translate/scoring.ts`'s `describeScoringCard`, which this module never
 * modifies), not something the engine checks. The player ticks the awards they
 * made; the engine does the arithmetic, the OR-tier resolution, the cumulative
 * sums, and the cap.
 *
 * Deck-level rules the card schema deliberately omits live here as constants —
 * chiefly the 5 VP-per-card ceiling of the Tactical approach. The Fixed approach
 * instead uses each award's printed `vp_max`.
 *
 * `PlayerGame` is a plain JSON-serializable object so a UI can persist a whole
 * match (two of them) to localStorage and rehydrate without a revival step.
 *
 * CONFORMANCE: mirrored by the Rust `wh40kdc::scoring` module and pinned by the
 * `conformance/scoring` corpus (this is the reference/oracle implementation).
 * The public shapes below are the surface that port mirrors — changing them is
 * a cross-impl, `SPEC_VERSION`-bumping change.
 */
import type { SecondaryCard } from "../generated.js";
import type { ScoringAward, ScoringMode } from "../translate/scoring.js";
/** The Tactical approach caps a single secondary's score at this many VP. */
export declare const TACTICAL_CARD_CAP = 5;
/** Battle rounds in a game. */
export declare const ROUNDS = 5;
/** Per-player VP ceiling (WTC sheet: grand total out of 100). */
export declare const GAME_VP_CAP = 100;
/** An award the player ticks when scoring, with a count for per-instance awards. */
export interface AssertedAward {
    award: ScoringAward;
    /** Instances achieved (for `vp_per` awards); defaults to 1. */
    count?: number;
}
/** VP recorded against a single battle round. */
export interface RoundCell {
    primary: number;
    secondary: number;
}
/** A scored secondary, kept so the record can be shown and undone. */
export interface ScoreEntry {
    cardId: string;
    /** Battle round (1-based) the card was scored in. */
    round: number;
    vp: number;
}
/** One player's whole-game scoring state. Plain data — safe to JSON round-trip. */
export interface PlayerGame {
    /** Scoring approach: filters `mode` awards and sets the per-score cap. */
    approach: ScoringMode;
    /** Drawn-but-unscored secondaries, by card id. Scoring removes a card from here. */
    handIds: string[];
    /** Per-round VP, index 0 = round 1. Always length {@link ROUNDS}. */
    rounds: RoundCell[];
    /** Log of scored secondaries, in scoring order — the editable record. */
    log: ScoreEntry[];
}
/** A fresh player game for the given approach (defaults to tactical). */
export declare function emptyPlayerGame(approach?: ScoringMode): PlayerGame;
/** Read a card's `awards`, typed (the generated `SecondaryCard` leaves them opaque). */
export declare function awardsOf(card: SecondaryCard): ScoringAward[];
/**
 * The awards a player scores under `approach`. An award with no `mode` is flat
 * (it scores the same either way); an award tagged `fixed`/`tactical` scores
 * only under the matching approach.
 */
export declare function awardsForApproach(card: SecondaryCard, approach: ScoringMode): ScoringAward[];
/**
 * VP for a single asserted award. A flat `vp` ignores `count`; a `vp_per` award
 * scores `vp_per × count`, with `count` clamped to `per_max` when present.
 */
export declare function scoreAward(award: ScoringAward, count?: number): number;
/**
 * VP from everything asserted in one scoring, before the card cap. Awards
 * sharing an `exclusive_group` resolve as "only the highest scores" (the card's
 * literal OR between tier rows); everything else, including `cumulative` "+"
 * rows, sums.
 */
export declare function scoreTurn(asserted: AssertedAward[]): number;
/**
 * A card's per-score VP ceiling under `approach`. Tactical is the universal
 * {@link TACTICAL_CARD_CAP}. Fixed uses the largest `vp_max` printed on the
 * card's scorable awards, or `Infinity` when none is printed (uncapped).
 */
export declare function scoreCap(card: SecondaryCard, approach: ScoringMode): number;
/**
 * The VP a single scoring of `card` grants under `approach`: the asserted awards'
 * total, clamped to the card's cap. This is the amount banked when the card is
 * scored (and then discarded).
 */
export declare function scoreSecondaryEvent(asserted: AssertedAward[], card: SecondaryCard, approach: ScoringMode): number;
/**
 * The primary VP a single battle round's scoring grants: the asserted awards'
 * total, clamped to the per-round cap (`mission.vp_per_round_cap`, 15 by 11e
 * default). Unlike a secondary, a primary is scored once *per round* against the
 * same card, so the ceiling is the round cap rather than a per-card cap — there
 * is no tactical 5-VP rule on primary. The per-game primary cap
 * (`vp_per_game_cap`) is applied separately by {@link setPrimary}, which sees
 * the other rounds' totals.
 */
export declare function scorePrimaryEvent(asserted: AssertedAward[], roundCap: number): number;
/** Add secondary VP to a battle round (1-based). Pure — returns new state. */
export declare function recordSecondary(pg: PlayerGame, round: number, vp: number): PlayerGame;
/**
 * Score a held secondary: add its VP to the round, append it to the log, and
 * discard it from hand. Pure. The caller computes `vp` via
 * {@link scoreSecondaryEvent}.
 */
export declare function scoreSecondary(pg: PlayerGame, round: number, cardId: string, vp: number): PlayerGame;
/**
 * Undo a logged scoring by index: subtract its VP from its round, drop the log
 * entry, and return the card to hand so it can be re-scored. Pure; a no-op for
 * an out-of-range index.
 */
export declare function removeScore(pg: PlayerGame, index: number): PlayerGame;
/**
 * Set primary VP for a battle round (1-based) to a clamped value. Pure.
 *
 * `caps` bounds the stored value by both the per-round ceiling (`roundCap`,
 * `mission.vp_per_round_cap`) and the remaining per-game primary room
 * (`gameCap`, `mission.vp_per_game_cap`) — the latter computed against the
 * *other* rounds' primary, so no sequence of round scores can push the primary
 * game total past `gameCap`. With no caps both default to `Infinity`, leaving
 * only the floor-at-zero clamp (the historical behavior).
 */
export declare function setPrimary(pg: PlayerGame, round: number, vp: number, caps?: {
    roundCap?: number;
    gameCap?: number;
}): PlayerGame;
/** Put a drawn card in hand (no duplicates). Pure. */
export declare function addToHand(pg: PlayerGame, cardId: string): PlayerGame;
/** Remove a card from hand (e.g. on score or discard). Pure. */
export declare function removeFromHand(pg: PlayerGame, cardId: string): PlayerGame;
/** Total primary VP across the game. */
export declare function playerPrimary(pg: PlayerGame): number;
/** Total secondary VP across the game. */
export declare function playerSecondary(pg: PlayerGame): number;
/** Grand total VP, capped at {@link GAME_VP_CAP}. */
export declare function playerTotal(pg: PlayerGame): number;
/**
 * The WTC 20-point result from two grand totals. The winner's margin maps onto
 * 11 bands (0-5 → 10-10 draw, 6-10 → 11-9, ... 51+ → 20-0); the loser gets the
 * complement. `a`/`b` correspond to the argument order.
 */
export declare function wtcResult(totalA: number, totalB: number): {
    a: number;
    b: number;
};
//# sourceMappingURL=index.d.ts.map