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
| Symbol | Kind | Purpose |
|---|---|---|
SHIP_RARITY_RATES | Record<ShipRarity, number> | Base drop weights by ship rarity |
PITY_RARE_THRESHOLD | const number | Hard pity: guaranteed rare+ at N pulls without one |
PITY_EPIC_THRESHOLD | const number | Hard pity: guaranteed epic+ at N pulls without one |
SOFT_PITY_START | const number | Pull count where soft-pity rate ramp begins |
SOFT_PITY_RATE_PER_PULL | const number | Per-pull epic-rate increment during soft pity |
TEN_PULL_GUARANTEED_MIN_RARITY | ShipRarity | Minimum rarity of one slot in a 10-pull |
BannerId | type alias | 'sun' | 'moon' |
BannerDef | interface | Banner shape (id, name, description) |
BANNERS | Record<BannerId, BannerDef> | Banner registry |
PullResult | interface | Single-pull payload (type, entityId, rarity, isNew) |
Ship rarity rates
| Rarity | Base rate |
|---|---|
| common | 0.60 |
| uncommon | 0.25 |
| rare | 0.10 |
| epic | 0.05 |
| legendary | 0.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 bySOFT_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:
| ID | Name | Description |
|---|---|---|
sun | Sun | Ships of light and radiance. |
moon | Moon | Ships 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
ShipRarityfrom./ships— drives the keys ofSHIP_RARITY_RATESand the literal type ofTEN_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 siblingBANNER_POOLSmap) belongs here. FEATURED RATE-UPsection header exists with no body — featured-ship rate-up (typical gacha mechanic) is stubbed and unspecified.legendary: 0.00means legendary ships are unreachable through base pulls; the path to legendary (rate-up event, exchange shop, milestone reward) is undocumented in this file.PullResult.typeis 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.