pull-rates.ts

Pull-rate tables, pity system, banner definitions, and pull-result type for the gacha-style ship summon system. Two banners (Sun, Moon); universal pull tickets work on either banner.

Exports

SymbolKindPurpose
SHIP_RARITY_RATESRecord<ShipRarity, number>Base drop weights by ship rarity
PITY_RARE_THRESHOLDconst numberHard pity: guaranteed rare+ at N pulls without one
PITY_EPIC_THRESHOLDconst numberHard pity: guaranteed epic+ at N pulls without one
SOFT_PITY_STARTconst numberPull count where soft-pity rate ramp begins
SOFT_PITY_RATE_PER_PULLconst numberPer-pull epic-rate increment during soft pity
TEN_PULL_GUARANTEED_MIN_RARITYShipRarityMinimum rarity of one slot in a 10-pull
BannerIdtype alias'sun' | 'moon'
BannerDefinterfaceBanner shape (id, name, description)
BANNERSRecord<BannerId, BannerDef>Banner registry
PullResultinterfaceSingle-pull payload (type, entityId, rarity, isNew)

Ship rarity rates

RarityBase rate
common0.60
uncommon0.25
rare0.10
epic0.05
legendary0.00

Sums to 1.00. legendary is 0 at base — only reachable through future rate-up / event mechanics (the file has an empty FEATURED RATE-UP section reserved for this).

Pity system

  • PITY_RARE_THRESHOLD = 10 — after 10 pulls without a rare+, the next pull is guaranteed rare or higher.
  • PITY_EPIC_THRESHOLD = 50 — after 50 pulls without an epic+, the next pull is guaranteed epic or higher.
  • SOFT_PITY_START = 40 — starting at pull 40 (without an epic), the epic rate increases by SOFT_PITY_RATE_PER_PULL = 0.02 (2%) per additional pull, ramping toward the hard cap at 50.

10-pull guarantee

TEN_PULL_GUARANTEED_MIN_RARITY = 'uncommon'. In any 10-pull, at least one result is uncommon or higher.

Banners

Two static banners declared in BANNERS:

IDNameDescription
sunSunShips of light and radiance.
moonMoonShips of shadow and fortune.

Banner choice is cosmetic at the data layer — the same SHIP_RARITY_RATES apply to both. Per-banner ship pools live elsewhere (consumer of BannerId).

PullResult shape

interface PullResult {
  type: 'ship';        // only 'ship' today; reserved for future non-ship pulls
  entityId: string;    // ship template ID, e.g. 'dart_common'
  rarity: string;
  isNew: boolean;      // first time entity is acquired
}

isNew is used by the metagame to flag duplicate conversion / first-acquire UX.

Dependencies

  • ShipRarity from ./ships — drives the keys of SHIP_RARITY_RATES and the literal type of TEN_PULL_GUARANTEED_MIN_RARITY.

Consumers

The pull resolution logic, banner UI, and pity-counter store read these constants. Search SHIP_RARITY_RATES, PITY_RARE_THRESHOLD, PITY_EPIC_THRESHOLD, SOFT_PITY_START, BANNERS, PullResult for call sites.

EXTRACT-CANDIDATE

  • Per-banner rate overrides are not modeled — if Sun/Moon need different base rates or pools, a BannerDef.rates?: Partial<Record<ShipRarity, number>> field (or a sibling BANNER_POOLS map) belongs here.
  • FEATURED RATE-UP section header exists with no body — featured-ship rate-up (typical gacha mechanic) is stubbed and unspecified.
  • legendary: 0.00 means legendary ships are unreachable through base pulls; the path to legendary (rate-up event, exchange shop, milestone reward) is undocumented in this file.
  • PullResult.type is hardcoded to 'ship' but typed as a literal — extending to currency / cosmetic / artifact pulls requires widening this union.
  • Pity counters themselves (current streak, last-epic pull index) are state, not data — they live in a store; this file declares only the thresholds.
  • 10-pull guarantee implementation (whether enforced by re-rolling, by replacing the lowest, or by reserving a slot) is not specified by the constant alone.