From ff310d6cfe9b3ee3414ef7740b55652e059453cd Mon Sep 17 00:00:00 2001 From: FalconChipp <112320693+FalconChipp@users.noreply.github.com> Date: Fri, 17 Apr 2026 15:30:30 +0100 Subject: [PATCH 01/10] Add pull-rate types and derive rates Introduce pull rate support across the codebase: add PullRateValue, SpecialVariantPullRate and PullRates types and extend Set/Card interfaces (interfaces.d.ts, meta API types). Add example pullRates data to a sample set file. Implement deriveCardPullRates util to compute card.pullRate and variantPullRates from set.pullRates (server/compiler/utils/pull-rates.ts) and wire it into card compilation (cardUtil.ts). Expose set.pullRates in set output (setUtil.ts). This enables defining rarity and special-variant odds at the set/booster level and deriving per-card rates at compile time. --- data/Mega Evolution/Ascended Heroes.ts | 27 ++++++++++- interfaces.d.ts | 64 ++++++++++++++++++++++++++ meta/definitions/api.d.ts | 13 ++++++ server/compiler/utils/cardUtil.ts | 2 + server/compiler/utils/pull-rates.ts | 60 ++++++++++++++++++++++++ server/compiler/utils/setUtil.ts | 3 +- 6 files changed, 167 insertions(+), 2 deletions(-) create mode 100644 server/compiler/utils/pull-rates.ts diff --git a/data/Mega Evolution/Ascended Heroes.ts b/data/Mega Evolution/Ascended Heroes.ts index cdc339c3b6..0ecefc856a 100644 --- a/data/Mega Evolution/Ascended Heroes.ts +++ b/data/Mega Evolution/Ascended Heroes.ts @@ -30,7 +30,32 @@ const set: Set = { thirdParty: { cardmarket: 6395, tcgplayer: 24541 - } + }, + + + // EXAMPLE DATA FOR TESTING: TO RECTIFY + pullRates: { + rarities: { + 'Common': '3 in 5', + 'Uncommon': '1 in 5', + 'Rare': '1 in 5', + 'Double rare': '1 in 5', + 'Illustration rare': '1 in 9', + 'Ultra Rare': '1 in 12', + 'Special illustration rare': '1 in 78', + 'Hyper rare': '1 in 200', + }, + specialVariants: [ + { + match: { type: 'reverse', foil: 'pokeball' }, + rate: '1 in 40' + }, + { + match: { type: 'reverse', foil: 'energy' }, + rate: '1 in 4' + } + ] + }, } export default set diff --git a/interfaces.d.ts b/interfaces.d.ts index ee5c21b950..282d772bde 100644 --- a/interfaces.d.ts +++ b/interfaces.d.ts @@ -144,6 +144,44 @@ export type Types = 'Colorless' | 'Darkness' | 'Dragon' | type ISODate = `${number}-${number}-${number}` +/** + * Represents a pull rate value. + * Can be a simple display string (e.g. '1 in 8') or an object + * with an optional decimal percent for programmatic use. + * + * @example '1 in 8' + * @example { display: '1 in 8', percent: 12.5 } +*/ +export type PullRateValue = | string | { + display: string + percent?: number +} + +/** + * A rule that matches a card variant and assigns it a pull rate. + * All fields defined on `match` must equal the corresponding fields + * on the card variant. Fields not present on `match` are ignored. + * + * @example { match: { type: 'reverse', foil: 'masterball' }, rate: '1 in 40' } + */ +export interface SpecialVariantPullRate { + match: Partial + rate: PullRateValue +} + +/** + * Pull rates for a set or booster. + * - `rarities` maps Card rarity strings directly to pull rates + * - `specialVariants` defines pull rates for specific variant treatments + * + * Card-level pullRate and variantPullRates are derived from these — + * they are never authored manually on individual cards. + */ +export interface PullRates { + rarities?: Partial> + specialVariants?: SpecialVariantPullRate[] +} + export interface Set { id: string name: Languages @@ -160,8 +198,22 @@ export interface Set { boosters?: Record + + /** + * Optional pull rate override for this specific booster. + * Only define when this booster has meaningfully different + * odds from the set-level pullRates. + */ + pullRates?: PullRates }> + /** + * The pull rate for a given rarity or variant. + * Can be a simple display string (e.g. '1 in 8') or an object + * with an optional decimal percent for programmatic use. + */ + pullRates?: PullRates + releaseDate: ISODate | Languages thirdParty?: { @@ -254,6 +306,18 @@ export interface Card { */ set: Set + /** + * Derived pull rates for each of the card's detailed variants. + * Populated from the parent set's pullRates.specialVariants. + * Only present when the card has detailed variants and at least + * one matching rule exists on the set. + */ + pullRate?: PullRateValue + variantPullRates?: Array<{ + variant: variant_detailed + rate: PullRateValue + }> + /** * Card regulation Mark * diff --git a/meta/definitions/api.d.ts b/meta/definitions/api.d.ts index d50764874b..2637828662 100644 --- a/meta/definitions/api.d.ts +++ b/meta/definitions/api.d.ts @@ -148,6 +148,13 @@ export interface Set extends SetResume { cardmarket?: number tcgplayer?: number } + pullRates?: { + rarities?: Record + specialVariants?: Array<{ + match: Record + rate: string | { display: string; percent?: number } + }> + } } export interface CardResume { id: string; @@ -340,6 +347,12 @@ export interface Card extends CardResume { boosters?: Array updated: string + + pullRate?: string | { display: string; percent?: number } + variantPullRates?: Array<{ + variant: Record + rate: string | { display: string; percent?: number } + }> } /** diff --git a/server/compiler/utils/cardUtil.ts b/server/compiler/utils/cardUtil.ts index a67127b873..3d3fe0f122 100644 --- a/server/compiler/utils/cardUtil.ts +++ b/server/compiler/utils/cardUtil.ts @@ -7,6 +7,7 @@ import translate from './translationUtil' import { DB_PATH, cardIsLegal, fetchRemoteFile, getDataFolder, getLastEdit, resolveText, smartGlob } from './util' import { objectMap, objectPick } from '@dzeio/object-util' import { formatVariant, variantToIdentifier } from "./variantUtil.ts"; +import { deriveCardPullRates } from "./pull-rates"; export async function getCardPictures(cardId: string, card: Card, lang: SupportedLanguages): Promise { try { @@ -156,6 +157,7 @@ export async function cardToCardSingle(localId: string, card: Card, lang: Suppor trainerType: translate('trainerType', card.trainerType, lang) as any, energyType: translate('energyType', card.energyType, lang) as any, regulationMark: card.regulationMark, + ...deriveCardPullRates(card), legal: { standard: cardIsLegal('standard', card, localId), diff --git a/server/compiler/utils/pull-rates.ts b/server/compiler/utils/pull-rates.ts new file mode 100644 index 0000000000..ef04b44871 --- /dev/null +++ b/server/compiler/utils/pull-rates.ts @@ -0,0 +1,60 @@ +import { Card, variant_detailed } from '../../../interfaces' + +/** + * Returns true if all rules defined on 'rule' match the corresponding + * fields on 'variant'. Fields not present on 'rule' are ignored + */ + +function variantMatchesRule( + variant: variant_detailed, + rule: Partial +): boolean { + return (Object.keys(rule) as Array).every( + (key) => variant[key] === rule[key] + ) +} + +/** + * Derives pullRate and variantPullRates for a card from its parent set's + * pullRates. Returns only the fields that have matches — omits both if + * no data is available. + */ +export function deriveCardPullRates(card: Card): Pick { + const result: Pick = {} + + const pullRates = card.set.pullRates + if (!pullRates) return result + + // Rarity pull rate + if (pullRates.rarities && card.rarity) { + const rate = pullRates.rarities[card.rarity] + if ( rate !== undefined) { + result.pullRate = rate + } + } + + // Variant pull rates — only for detailed variant arrays + if ( + pullRates.specialVariants && + Array.isArray(card.variants) && + card.variants.length > 0 + ) { + const detailedVariants = card.variants as variant_detailed[] + const matched: NonNullable = [] + + for (const variant of detailedVariants) { + const rule = pullRates.specialVariants.find((r) => + variantMatchesRule(variant, r.match) + ) + if (rule) { + matched.push({ variant, rate: rule.rate }) + } + } + + if (matched.length > 0) { + result.variantPullRates = matched + } + } + + return result +} diff --git a/server/compiler/utils/setUtil.ts b/server/compiler/utils/setUtil.ts index 81856c8dc8..5271485614 100644 --- a/server/compiler/utils/setUtil.ts +++ b/server/compiler/utils/setUtil.ts @@ -134,6 +134,7 @@ export async function setToSetSingle(set: Set, lang: SupportedLanguages): Promis name: resolveText(booster.name, lang), // images will be coming soon... })) : undefined, - thirdParty: set.thirdParty + thirdParty: set.thirdParty, + pullRates: set.pullRates } } From c04c382a600a386dc422c2587687559a8a8db416 Mon Sep 17 00:00:00 2001 From: FalconChipp <112320693+FalconChipp@users.noreply.github.com> Date: Fri, 17 Apr 2026 16:04:23 +0100 Subject: [PATCH 02/10] Refactor pull-rate derivation to variant level Move pull rate derivation from the card level to individual variants. Add pullRate to variant_detailed in the API types, update internal interfaces commentary, and remove card-level pullRate/variantPullRates fields. Replace deriveCardPullRates with deriveVariantPullRate in server code and attach the derived pullRate to each variants_detailed entry in cardToCardSingle. --- data/Mega Evolution/Ascended Heroes.ts | 2 +- interfaces.d.ts | 16 +------- meta/definitions/api.d.ts | 7 +--- server/compiler/utils/cardUtil.ts | 13 +++--- server/compiler/utils/pull-rates.ts | 55 ++++++-------------------- 5 files changed, 23 insertions(+), 70 deletions(-) diff --git a/data/Mega Evolution/Ascended Heroes.ts b/data/Mega Evolution/Ascended Heroes.ts index 0ecefc856a..b5675af566 100644 --- a/data/Mega Evolution/Ascended Heroes.ts +++ b/data/Mega Evolution/Ascended Heroes.ts @@ -33,7 +33,7 @@ const set: Set = { }, - // EXAMPLE DATA FOR TESTING: TO RECTIFY + // Arbitary data for testing pullRates: { rarities: { 'Common': '3 in 5', diff --git a/interfaces.d.ts b/interfaces.d.ts index 282d772bde..23bc3a77b7 100644 --- a/interfaces.d.ts +++ b/interfaces.d.ts @@ -174,8 +174,8 @@ export interface SpecialVariantPullRate { * - `rarities` maps Card rarity strings directly to pull rates * - `specialVariants` defines pull rates for specific variant treatments * - * Card-level pullRate and variantPullRates are derived from these — - * they are never authored manually on individual cards. + * Pull rates are derived from these and exposed on each entry in + * variants_detailed — they are never authored manually on individual cards. */ export interface PullRates { rarities?: Partial> @@ -306,18 +306,6 @@ export interface Card { */ set: Set - /** - * Derived pull rates for each of the card's detailed variants. - * Populated from the parent set's pullRates.specialVariants. - * Only present when the card has detailed variants and at least - * one matching rule exists on the set. - */ - pullRate?: PullRateValue - variantPullRates?: Array<{ - variant: variant_detailed - rate: PullRateValue - }> - /** * Card regulation Mark * diff --git a/meta/definitions/api.d.ts b/meta/definitions/api.d.ts index 2637828662..6c84075ed4 100644 --- a/meta/definitions/api.d.ts +++ b/meta/definitions/api.d.ts @@ -68,6 +68,7 @@ interface variant_detailed { tcgplayer?: number } variantId: string + pullRate?: string | { display: string; percent?: number } } export interface SetResume { @@ -347,12 +348,6 @@ export interface Card extends CardResume { boosters?: Array updated: string - - pullRate?: string | { display: string; percent?: number } - variantPullRates?: Array<{ - variant: Record - rate: string | { display: string; percent?: number } - }> } /** diff --git a/server/compiler/utils/cardUtil.ts b/server/compiler/utils/cardUtil.ts index 3d3fe0f122..436880c000 100644 --- a/server/compiler/utils/cardUtil.ts +++ b/server/compiler/utils/cardUtil.ts @@ -7,7 +7,7 @@ import translate from './translationUtil' import { DB_PATH, cardIsLegal, fetchRemoteFile, getDataFolder, getLastEdit, resolveText, smartGlob } from './util' import { objectMap, objectPick } from '@dzeio/object-util' import { formatVariant, variantToIdentifier } from "./variantUtil.ts"; -import { deriveCardPullRates } from "./pull-rates"; +import { deriveVariantPullRate } from './pull-rates.ts' export async function getCardPictures(cardId: string, card: Card, lang: SupportedLanguages): Promise { try { @@ -103,13 +103,13 @@ export async function cardToCardSingle(localId: string, card: Card, lang: Suppor }, variants_detailed: Array.isArray(card.variants) - ? await Promise.all(card.variants.map(async (variant, index) => { - const variantId = variantToIdentifier(variant); - let formattedVariant = formatVariant(variant,lang) - + ? await Promise.all(card.variants.map(async (variant) => { + const variantId = variantToIdentifier(variant) + const formattedVariant = formatVariant(variant, lang) return { ...formattedVariant, - variantId + variantId, + pullRate: deriveVariantPullRate(variant, card) } as ApiVariantDetailed })) : variantsToVariantsDetailed(card.variants, lang), @@ -157,7 +157,6 @@ export async function cardToCardSingle(localId: string, card: Card, lang: Suppor trainerType: translate('trainerType', card.trainerType, lang) as any, energyType: translate('energyType', card.energyType, lang) as any, regulationMark: card.regulationMark, - ...deriveCardPullRates(card), legal: { standard: cardIsLegal('standard', card, localId), diff --git a/server/compiler/utils/pull-rates.ts b/server/compiler/utils/pull-rates.ts index ef04b44871..85e357a36e 100644 --- a/server/compiler/utils/pull-rates.ts +++ b/server/compiler/utils/pull-rates.ts @@ -1,10 +1,9 @@ -import { Card, variant_detailed } from '../../../interfaces' +import { Card, PullRateValue, variant_detailed } from '../../../interfaces' /** * Returns true if all rules defined on 'rule' match the corresponding * fields on 'variant'. Fields not present on 'rule' are ignored */ - function variantMatchesRule( variant: variant_detailed, rule: Partial @@ -15,46 +14,18 @@ function variantMatchesRule( } /** - * Derives pullRate and variantPullRates for a card from its parent set's - * pullRates. Returns only the fields that have matches — omits both if - * no data is available. + * Derives the pull rate for a specific variant from its parent set's pullRates. + * Returns undefined if no matching rule exists. */ -export function deriveCardPullRates(card: Card): Pick { - const result: Pick = {} - +export function deriveVariantPullRate( + variant: variant_detailed, + card: Card +): PullRateValue | undefined { const pullRates = card.set.pullRates - if (!pullRates) return result - - // Rarity pull rate - if (pullRates.rarities && card.rarity) { - const rate = pullRates.rarities[card.rarity] - if ( rate !== undefined) { - result.pullRate = rate - } - } - - // Variant pull rates — only for detailed variant arrays - if ( - pullRates.specialVariants && - Array.isArray(card.variants) && - card.variants.length > 0 - ) { - const detailedVariants = card.variants as variant_detailed[] - const matched: NonNullable = [] + if (!pullRates?.specialVariants) return undefined - for (const variant of detailedVariants) { - const rule = pullRates.specialVariants.find((r) => - variantMatchesRule(variant, r.match) - ) - if (rule) { - matched.push({ variant, rate: rule.rate }) - } - } - - if (matched.length > 0) { - result.variantPullRates = matched - } - } - - return result -} + const rule = pullRates.specialVariants.find((r) => + variantMatchesRule(variant, r.match) + ) + return rule?.rate +} \ No newline at end of file From eb474640f565d20859784df8b3054750872caa4c Mon Sep 17 00:00:00 2001 From: FalconChipp <112320693+FalconChipp@users.noreply.github.com> Date: Fri, 17 Apr 2026 16:29:03 +0100 Subject: [PATCH 03/10] Include percent in pullRates and update type Convert pullRates entries in Ascended Heroes from plain strings to objects with display and numeric percent values (e.g. { display: '1 in 5', percent: 20 }) and update specialVariants.rate to the same shape. Update PullRateValue in interfaces.d.ts to require percent so code can rely on a numeric probability for calculations --- data/Mega Evolution/Ascended Heroes.ts | 40 +++++++++++--------------- interfaces.d.ts | 2 +- 2 files changed, 18 insertions(+), 24 deletions(-) diff --git a/data/Mega Evolution/Ascended Heroes.ts b/data/Mega Evolution/Ascended Heroes.ts index b5675af566..0a1a4d9164 100644 --- a/data/Mega Evolution/Ascended Heroes.ts +++ b/data/Mega Evolution/Ascended Heroes.ts @@ -33,29 +33,23 @@ const set: Set = { }, - // Arbitary data for testing - pullRates: { - rarities: { - 'Common': '3 in 5', - 'Uncommon': '1 in 5', - 'Rare': '1 in 5', - 'Double rare': '1 in 5', - 'Illustration rare': '1 in 9', - 'Ultra Rare': '1 in 12', - 'Special illustration rare': '1 in 78', - 'Hyper rare': '1 in 200', - }, - specialVariants: [ - { - match: { type: 'reverse', foil: 'pokeball' }, - rate: '1 in 40' - }, - { - match: { type: 'reverse', foil: 'energy' }, - rate: '1 in 4' - } - ] - }, + // Arbitary data for testing + pullRates: { + rarities: { + 'Common': { display: '3 in 5', percent: 60 }, + 'Uncommon': { display: '1 in 5', percent: 20 }, + 'Rare': { display: '1 in 5', percent: 20 }, + 'Double rare': { display: '1 in 5', percent: 20 }, + 'Illustration rare': { display: '1 in 9', percent: 11.11 }, + 'Ultra Rare': { display: '1 in 12', percent: 8.33 }, + 'Special illustration rare': { display: '1 in 78', percent: 1.28 }, + 'Hyper rare': { display: '1 in 200', percent: 0.5 }, + }, + specialVariants: [ + { match: { type: 'reverse', foil: 'pokeball' }, rate: { display: '1 in 40', percent: 2.5 } }, + { match: { type: 'reverse', foil: 'energy' }, rate: { display: '1 in 4', percent: 25 } } + ] + } } export default set diff --git a/interfaces.d.ts b/interfaces.d.ts index 23bc3a77b7..32b802f1a2 100644 --- a/interfaces.d.ts +++ b/interfaces.d.ts @@ -154,7 +154,7 @@ type ISODate = `${number}-${number}-${number}` */ export type PullRateValue = | string | { display: string - percent?: number + percent: number } /** From 9b201ebb54eca009f1ec51c979705ab41c537b2c Mon Sep 17 00:00:00 2001 From: FalconChipp <112320693+FalconChipp@users.noreply.github.com> Date: Fri, 17 Apr 2026 20:32:02 +0100 Subject: [PATCH 04/10] Code and comment clean up --- data/Mega Evolution/Ascended Heroes.ts | 1 - interfaces.d.ts | 4 ++-- server/compiler/utils/pull-rates.ts | 2 +- server/compiler/utils/setUtil.ts | 3 ++- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/data/Mega Evolution/Ascended Heroes.ts b/data/Mega Evolution/Ascended Heroes.ts index 0a1a4d9164..6f654679ec 100644 --- a/data/Mega Evolution/Ascended Heroes.ts +++ b/data/Mega Evolution/Ascended Heroes.ts @@ -32,7 +32,6 @@ const set: Set = { tcgplayer: 24541 }, - // Arbitary data for testing pullRates: { rarities: { diff --git a/interfaces.d.ts b/interfaces.d.ts index 32b802f1a2..362a2071d7 100644 --- a/interfaces.d.ts +++ b/interfaces.d.ts @@ -147,7 +147,7 @@ type ISODate = `${number}-${number}-${number}` /** * Represents a pull rate value. * Can be a simple display string (e.g. '1 in 8') or an object - * with an optional decimal percent for programmatic use. + * with a required decimal percent for programmatic use. * * @example '1 in 8' * @example { display: '1 in 8', percent: 12.5 } @@ -162,7 +162,7 @@ export type PullRateValue = | string | { * All fields defined on `match` must equal the corresponding fields * on the card variant. Fields not present on `match` are ignored. * - * @example { match: { type: 'reverse', foil: 'masterball' }, rate: '1 in 40' } + * @example { match: { type: 'reverse', foil: 'masterball' }, rate: '1 in 40', percent: 2.5 } */ export interface SpecialVariantPullRate { match: Partial diff --git a/server/compiler/utils/pull-rates.ts b/server/compiler/utils/pull-rates.ts index 85e357a36e..d08dd4366b 100644 --- a/server/compiler/utils/pull-rates.ts +++ b/server/compiler/utils/pull-rates.ts @@ -28,4 +28,4 @@ export function deriveVariantPullRate( variantMatchesRule(variant, r.match) ) return rule?.rate -} \ No newline at end of file +} diff --git a/server/compiler/utils/setUtil.ts b/server/compiler/utils/setUtil.ts index 5271485614..06212e09f1 100644 --- a/server/compiler/utils/setUtil.ts +++ b/server/compiler/utils/setUtil.ts @@ -134,7 +134,8 @@ export async function setToSetSingle(set: Set, lang: SupportedLanguages): Promis name: resolveText(booster.name, lang), // images will be coming soon... })) : undefined, + pullRates: set.pullRates, + thirdParty: set.thirdParty, - pullRates: set.pullRates } } From 14fc852f4b97f85930bfdf2e8f70c335431ba657 Mon Sep 17 00:00:00 2001 From: FalconChipp <112320693+FalconChipp@users.noreply.github.com> Date: Fri, 17 Apr 2026 21:44:53 +0100 Subject: [PATCH 05/10] Adjust pull rates and add Mega Attack Rare Update Ascended Heroes set data and interfaces to reflect corrected rarities and pull-rate rules. --- data/Mega Evolution/Ascended Heroes.ts | 22 +++++++++------------- data/Mega Evolution/Ascended Heroes/265.ts | 2 +- data/Mega Evolution/Ascended Heroes/266.ts | 2 +- data/Mega Evolution/Ascended Heroes/267.ts | 2 +- data/Mega Evolution/Ascended Heroes/268.ts | 2 +- data/Mega Evolution/Ascended Heroes/269.ts | 2 +- data/Mega Evolution/Ascended Heroes/270.ts | 2 +- data/Mega Evolution/Ascended Heroes/271.ts | 2 +- interfaces.d.ts | 19 ++++++++++--------- 9 files changed, 26 insertions(+), 29 deletions(-) diff --git a/data/Mega Evolution/Ascended Heroes.ts b/data/Mega Evolution/Ascended Heroes.ts index 6f654679ec..946d8abb88 100644 --- a/data/Mega Evolution/Ascended Heroes.ts +++ b/data/Mega Evolution/Ascended Heroes.ts @@ -32,22 +32,18 @@ const set: Set = { tcgplayer: 24541 }, - // Arbitary data for testing + pullRates: { rarities: { - 'Common': { display: '3 in 5', percent: 60 }, - 'Uncommon': { display: '1 in 5', percent: 20 }, - 'Rare': { display: '1 in 5', percent: 20 }, - 'Double rare': { display: '1 in 5', percent: 20 }, - 'Illustration rare': { display: '1 in 9', percent: 11.11 }, - 'Ultra Rare': { display: '1 in 12', percent: 8.33 }, - 'Special illustration rare': { display: '1 in 78', percent: 1.28 }, - 'Hyper rare': { display: '1 in 200', percent: 0.5 }, + 'Double rare': { display: '1 in 5', percent: 20 }, + 'Illustration rare': { display: '1 in 9', percent: 11.11 }, + 'Ultra Rare': { display: '1 in 21', percent: 4.76 }, + 'Mega Attack Rare': { display: '1 in 29', percent: 3.45 }, + 'Special illustration rare': { display: '1 in 70', percent: 1.43 }, + 'Mega Hyper Rare': { display: '1 in 540', percent: 0.19 }, }, - specialVariants: [ - { match: { type: 'reverse', foil: 'pokeball' }, rate: { display: '1 in 40', percent: 2.5 } }, - { match: { type: 'reverse', foil: 'energy' }, rate: { display: '1 in 4', percent: 25 } } - ] + // specialVariants omitted — reverse foils are fixed guaranteed slots + // per card, not probabilistic pull rates } } diff --git a/data/Mega Evolution/Ascended Heroes/265.ts b/data/Mega Evolution/Ascended Heroes/265.ts index d958383e5f..18b7d6fffb 100644 --- a/data/Mega Evolution/Ascended Heroes/265.ts +++ b/data/Mega Evolution/Ascended Heroes/265.ts @@ -23,7 +23,7 @@ const card: Card = { }, illustrator: "Saboteri", - rarity: "Ultra Rare", + rarity: "Mega Attack Rare", category: "Pokemon", hp: 310, types: ["Water"], diff --git a/data/Mega Evolution/Ascended Heroes/266.ts b/data/Mega Evolution/Ascended Heroes/266.ts index e214459805..4db70494b6 100644 --- a/data/Mega Evolution/Ascended Heroes/266.ts +++ b/data/Mega Evolution/Ascended Heroes/266.ts @@ -24,7 +24,7 @@ const card: Card = { }, illustrator: "DOM", - rarity: "Ultra Rare", + rarity: "Mega Attack Rare", category: "Pokemon", hp: 350, types: ["Lightning"], diff --git a/data/Mega Evolution/Ascended Heroes/267.ts b/data/Mega Evolution/Ascended Heroes/267.ts index 25bfcf7487..86a2408b64 100644 --- a/data/Mega Evolution/Ascended Heroes/267.ts +++ b/data/Mega Evolution/Ascended Heroes/267.ts @@ -15,7 +15,7 @@ const card: Card = { }, illustrator: "DOM", - rarity: "Ultra Rare", + rarity: "Mega Attack Rare", category: "Pokemon", hp: 270, types: ["Psychic"], diff --git a/data/Mega Evolution/Ascended Heroes/268.ts b/data/Mega Evolution/Ascended Heroes/268.ts index 331b65ba7a..228f2bc6b3 100644 --- a/data/Mega Evolution/Ascended Heroes/268.ts +++ b/data/Mega Evolution/Ascended Heroes/268.ts @@ -15,7 +15,7 @@ const card: Card = { }, illustrator: "Taiga Kasai", - rarity: "Ultra Rare", + rarity: "Mega Attack Rare", category: "Pokemon", hp: 250, types: ["Fighting"], diff --git a/data/Mega Evolution/Ascended Heroes/269.ts b/data/Mega Evolution/Ascended Heroes/269.ts index dbb47cbc7d..1b8e9c358a 100644 --- a/data/Mega Evolution/Ascended Heroes/269.ts +++ b/data/Mega Evolution/Ascended Heroes/269.ts @@ -24,7 +24,7 @@ const card: Card = { }, illustrator: "Taiga Kasai", - rarity: "Ultra Rare", + rarity: "Mega Attack Rare", category: "Pokemon", hp: 350, types: ["Darkness"], diff --git a/data/Mega Evolution/Ascended Heroes/270.ts b/data/Mega Evolution/Ascended Heroes/270.ts index 9757c6d85a..2baa7e1352 100644 --- a/data/Mega Evolution/Ascended Heroes/270.ts +++ b/data/Mega Evolution/Ascended Heroes/270.ts @@ -24,7 +24,7 @@ const card: Card = { }, illustrator: "Taiga Kasai", - rarity: "Ultra Rare", + rarity: "Mega Attack Rare", category: "Pokemon", hp: 330, types: ["Darkness"], diff --git a/data/Mega Evolution/Ascended Heroes/271.ts b/data/Mega Evolution/Ascended Heroes/271.ts index 8837afde21..9b30f5c290 100644 --- a/data/Mega Evolution/Ascended Heroes/271.ts +++ b/data/Mega Evolution/Ascended Heroes/271.ts @@ -23,7 +23,7 @@ const card: Card = { }, illustrator: "DOM", - rarity: "Ultra Rare", + rarity: "Mega Attack Rare", category: "Pokemon", hp: 370, types: ["Dragon"], diff --git a/interfaces.d.ts b/interfaces.d.ts index 362a2071d7..6e0d4295c0 100644 --- a/interfaces.d.ts +++ b/interfaces.d.ts @@ -158,14 +158,14 @@ export type PullRateValue = | string | { } /** - * A rule that matches a card variant and assigns it a pull rate. - * All fields defined on `match` must equal the corresponding fields - * on the card variant. Fields not present on `match` are ignored. - * - * @example { match: { type: 'reverse', foil: 'masterball' }, rate: '1 in 40', percent: 2.5 } - */ -export interface SpecialVariantPullRate { - match: Partial + * A pull-rate rule for a specific card variant. + * + * Any variant fields present on this object are used to match against a + * card's detailed variant entry. Fields that are not present are ignored. + * + * @example { type: 'reverse', foil: 'masterball', rate: { display: '1 in 40', percent: 2.5 } } +*/ +export interface SpecialVariantPullRate extends Partial { rate: PullRateValue } @@ -283,7 +283,8 @@ export interface Card { 'Shiny rare VMAX' | 'Special illustration rare' | 'Ultra Rare' | 'Uncommon' // Black White rare | 'Black White Rare' - | 'Mega Hyper Rare' + | 'Mega Hyper Rare' + | 'Mega Attack Rare' // Pokémon TCG Pocket Rarities | 'One Diamond' | 'Two Diamond' | 'Three Diamond' | 'Four Diamond' | 'One Star' | 'Two Star' | 'Three Star' | 'Crown' | 'One Shiny' | 'Two Shiny' From 8fd30397193fcdef86a6ffbcc9e8fe6a1dfaf6c0 Mon Sep 17 00:00:00 2001 From: FalconChipp <112320693+FalconChipp@users.noreply.github.com> Date: Fri, 17 Apr 2026 21:53:28 +0100 Subject: [PATCH 06/10] Add translations for mega attack rare --- meta/translations/de.json | 3 ++- meta/translations/es.json | 3 ++- meta/translations/fr.json | 3 ++- meta/translations/it.json | 3 ++- meta/translations/pt.json | 3 ++- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/meta/translations/de.json b/meta/translations/de.json index 085a7c92e7..37622ac386 100644 --- a/meta/translations/de.json +++ b/meta/translations/de.json @@ -57,7 +57,8 @@ "Two Shiny": "Deux Chromatique", "Three Shiny": "Un Chromatique", "Black White Rare": "Schwarz-Weiß Selten", - "Mega Hyper Rare": "Mega Hyper Selten" + "Mega Hyper Rare": "Mega Hyper Selten", + "Mega Attack Rare": "Mega Angriff Selten" }, "stage": { "Baby": "Baby", diff --git a/meta/translations/es.json b/meta/translations/es.json index 4d46aacd39..5ac531444b 100644 --- a/meta/translations/es.json +++ b/meta/translations/es.json @@ -57,7 +57,8 @@ "Two Shiny": "Deux Chromatique", "Three Shiny": "Un Chromatique", "Black White Rare": "Rara Blanco y Negro", - "Mega Hyper Rare": "Mega Hiper Raro" + "Mega Hyper Rare": "Mega Hiper Raro", + "Mega Attack Rare": "Mega Ataque Rara" }, "stage": { "Baby": "Bebé", diff --git a/meta/translations/fr.json b/meta/translations/fr.json index e069e129e1..3cf3a39dd9 100644 --- a/meta/translations/fr.json +++ b/meta/translations/fr.json @@ -56,7 +56,8 @@ "Two Shiny": "Deux Chromatiques", "Three Shiny": "Trois Chromatiques", "Black White Rare": "Rare Noir Blanc", - "Mega Hyper Rare": "Méga Hyper Rare" + "Mega Hyper Rare": "Méga Hyper Rare", + "Mega Attack Rare": "Mega Attaque Rare" }, "stage": { "Baby": "Bébé", diff --git a/meta/translations/it.json b/meta/translations/it.json index b18f3292f3..15909ac27a 100644 --- a/meta/translations/it.json +++ b/meta/translations/it.json @@ -57,7 +57,8 @@ "Two Shiny": "Deux Chromatique", "Three Shiny": "Un Chromatique", "Black White Rare": "Rara Bianco e Nero", - "Mega Hyper Rare": "Mega Iper Raro" + "Mega Hyper Rare": "Mega Iper Raro", + "Mega Attack Rare": "Mega Attacco Rara" }, "stage": { "Baby": "Bambino", diff --git a/meta/translations/pt.json b/meta/translations/pt.json index 75dcbbd0fb..b445ce0b12 100644 --- a/meta/translations/pt.json +++ b/meta/translations/pt.json @@ -56,7 +56,8 @@ "Two Shiny": "Deux Chromatique", "Three Shiny": "Un Chromatique", "Black White Rare": "Rara Preto e Branco", - "Mega Hyper Rare": "Mega Hiper Raro" + "Mega Hyper Rare": "Mega Hiper Raro", + "Mega Attack Rare": "Mega Ataque Rara" }, "stage": { "Baby": "Bebê", From 0cd7e0174e5741d33b2158c024b660b74a000942 Mon Sep 17 00:00:00 2001 From: FalconChipp <112320693+FalconChipp@users.noreply.github.com> Date: Fri, 17 Apr 2026 22:11:35 +0100 Subject: [PATCH 07/10] Narrow specialVariants.match type Allow for specialVariations to be undefined if no data is available --- meta/definitions/api.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta/definitions/api.d.ts b/meta/definitions/api.d.ts index 6c84075ed4..8f1d35ed72 100644 --- a/meta/definitions/api.d.ts +++ b/meta/definitions/api.d.ts @@ -152,7 +152,7 @@ export interface Set extends SetResume { pullRates?: { rarities?: Record specialVariants?: Array<{ - match: Record + match: Record rate: string | { display: string; percent?: number } }> } From ec7fb24a64140e933f0b6ad3c7a4111b563303f8 Mon Sep 17 00:00:00 2001 From: FalconChipp <112320693+FalconChipp@users.noreply.github.com> Date: Fri, 17 Apr 2026 22:39:51 +0100 Subject: [PATCH 08/10] Ignore rate field when matching specialVariants --- meta/definitions/api.d.ts | 6 ++---- server/compiler/utils/pull-rates.ts | 7 ++++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/meta/definitions/api.d.ts b/meta/definitions/api.d.ts index 8f1d35ed72..914b7c6350 100644 --- a/meta/definitions/api.d.ts +++ b/meta/definitions/api.d.ts @@ -149,12 +149,10 @@ export interface Set extends SetResume { cardmarket?: number tcgplayer?: number } + pullRates?: { rarities?: Record - specialVariants?: Array<{ - match: Record - rate: string | { display: string; percent?: number } - }> + specialVariants?: Array> & { rate: string | { display: string; percent?: number } }> } } export interface CardResume { diff --git a/server/compiler/utils/pull-rates.ts b/server/compiler/utils/pull-rates.ts index d08dd4366b..c270ab6d88 100644 --- a/server/compiler/utils/pull-rates.ts +++ b/server/compiler/utils/pull-rates.ts @@ -24,8 +24,9 @@ export function deriveVariantPullRate( const pullRates = card.set.pullRates if (!pullRates?.specialVariants) return undefined - const rule = pullRates.specialVariants.find((r) => - variantMatchesRule(variant, r.match) - ) + const rule = pullRates.specialVariants.find((r) => { + const { rate, ...match } = r + return variantMatchesRule(variant, match) + }) return rule?.rate } From a322edd7c49e2aa695b786241f8004beacee5eac Mon Sep 17 00:00:00 2001 From: FalconChipp <112320693+FalconChipp@users.noreply.github.com> Date: Fri, 17 Apr 2026 22:51:23 +0100 Subject: [PATCH 09/10] Allow non-string values in specialVariants type Replace Partial> with Record for specialVariants in meta/definitions/api.d.ts. This broadens accepted value types for variant properties (no longer limited to strings) while keeping the required rate field, preventing type errors when variant values are non-string. --- meta/definitions/api.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta/definitions/api.d.ts b/meta/definitions/api.d.ts index 914b7c6350..da51a6b9c7 100644 --- a/meta/definitions/api.d.ts +++ b/meta/definitions/api.d.ts @@ -152,7 +152,7 @@ export interface Set extends SetResume { pullRates?: { rarities?: Record - specialVariants?: Array> & { rate: string | { display: string; percent?: number } }> + specialVariants?: Array & { rate: string | { display: string; percent?: number } }> } } export interface CardResume { From 4558dc08b856337405cc8b2c6561f7423ba8e652 Mon Sep 17 00:00:00 2001 From: FalconChipp <112320693+FalconChipp@users.noreply.github.com> Date: Fri, 17 Apr 2026 23:39:27 +0100 Subject: [PATCH 10/10] Add pull rate types and derive card pull rates Introduce typed PullRateValue and PullRates in API definitions and add an optional Card.pullRate field. Implement deriveCardPullRate and refine deriveVariantPullRate to resolve rarity- and variant-level pull rates from a set. Populate card.pullRate when serializing cards and expose set.pullRates (rarities + mapped specialVariants) in set serialisation. --- data/Mega Evolution/Ascended Heroes.ts | 1 - meta/definitions/api.d.ts | 21 ++++++++++---- server/compiler/utils/cardUtil.ts | 3 +- server/compiler/utils/pull-rates.ts | 39 +++++++++++++++++--------- server/compiler/utils/setUtil.ts | 24 ++++++++-------- 5 files changed, 56 insertions(+), 32 deletions(-) diff --git a/data/Mega Evolution/Ascended Heroes.ts b/data/Mega Evolution/Ascended Heroes.ts index 946d8abb88..300d260787 100644 --- a/data/Mega Evolution/Ascended Heroes.ts +++ b/data/Mega Evolution/Ascended Heroes.ts @@ -32,7 +32,6 @@ const set: Set = { tcgplayer: 24541 }, - pullRates: { rarities: { 'Double rare': { display: '1 in 5', percent: 20 }, diff --git a/meta/definitions/api.d.ts b/meta/definitions/api.d.ts index da51a6b9c7..37e093c37a 100644 --- a/meta/definitions/api.d.ts +++ b/meta/definitions/api.d.ts @@ -71,6 +71,13 @@ interface variant_detailed { pullRate?: string | { display: string; percent?: number } } +export type PullRateValue = string | { display: string; percent: number } + +export interface PullRates { + rarities?: Record + specialVariants?: Array<{ rate: PullRateValue } & Record> +} + export interface SetResume { id: string; name: string; @@ -149,12 +156,9 @@ export interface Set extends SetResume { cardmarket?: number tcgplayer?: number } - - pullRates?: { - rarities?: Record - specialVariants?: Array & { rate: string | { display: string; percent?: number } }> - } + pullRates?: PullRates } + export interface CardResume { id: string; localId: string; @@ -189,6 +193,13 @@ export interface Card extends CardResume { * - Secret Rare */ rarity: string; + + /** + * Derived pull rate for the card's rarity. + * Populated from the parent set's pullRates.rarities. + */ + pullRate?: PullRateValue + /** * Card Category * diff --git a/server/compiler/utils/cardUtil.ts b/server/compiler/utils/cardUtil.ts index 436880c000..2537797a3d 100644 --- a/server/compiler/utils/cardUtil.ts +++ b/server/compiler/utils/cardUtil.ts @@ -7,7 +7,7 @@ import translate from './translationUtil' import { DB_PATH, cardIsLegal, fetchRemoteFile, getDataFolder, getLastEdit, resolveText, smartGlob } from './util' import { objectMap, objectPick } from '@dzeio/object-util' import { formatVariant, variantToIdentifier } from "./variantUtil.ts"; -import { deriveVariantPullRate } from './pull-rates.ts' +import { deriveCardPullRate, deriveVariantPullRate } from './pull-rates.ts' export async function getCardPictures(cardId: string, card: Card, lang: SupportedLanguages): Promise { try { @@ -91,6 +91,7 @@ export async function cardToCardSingle(localId: string, card: Card, lang: Suppor name: resolveText(card.name, lang) as string, rarity: translate('rarity', card.rarity, lang) as any, + pullRate: deriveCardPullRate(card), set: await setToSetSimple(card.set, lang), variants : Array.isArray(card.variants) ? diff --git a/server/compiler/utils/pull-rates.ts b/server/compiler/utils/pull-rates.ts index c270ab6d88..16554c7991 100644 --- a/server/compiler/utils/pull-rates.ts +++ b/server/compiler/utils/pull-rates.ts @@ -5,12 +5,12 @@ import { Card, PullRateValue, variant_detailed } from '../../../interfaces' * fields on 'variant'. Fields not present on 'rule' are ignored */ function variantMatchesRule( - variant: variant_detailed, - rule: Partial + variant: variant_detailed, + rule: Partial ): boolean { - return (Object.keys(rule) as Array).every( - (key) => variant[key] === rule[key] - ) + return (Object.keys(rule) as Array).every( + (key) => variant[key] === rule[key] + ) } /** @@ -18,15 +18,26 @@ function variantMatchesRule( * Returns undefined if no matching rule exists. */ export function deriveVariantPullRate( - variant: variant_detailed, - card: Card + variant: variant_detailed, + card: Card ): PullRateValue | undefined { - const pullRates = card.set.pullRates - if (!pullRates?.specialVariants) return undefined + const pullRates = card.set.pullRates + if (!pullRates?.specialVariants) return undefined - const rule = pullRates.specialVariants.find((r) => { - const { rate, ...match } = r - return variantMatchesRule(variant, match) - }) - return rule?.rate + const rule = pullRates.specialVariants.find((r) => { + const { rate, ...match } = r + return variantMatchesRule(variant, match as Partial) + }) + return rule?.rate } + +/** + * Derives the pull rate for a card's rarity from its parent set's pullRates. + * Returns undefined if no match exists. + */ +export function deriveCardPullRate(card: Card): PullRateValue | undefined { + const pullRates = card.set.pullRates + if (!pullRates?.rarities || !card.rarity) return undefined + + return pullRates.rarities[card.rarity] +} \ No newline at end of file diff --git a/server/compiler/utils/setUtil.ts b/server/compiler/utils/setUtil.ts index 06212e09f1..3316448099 100644 --- a/server/compiler/utils/setUtil.ts +++ b/server/compiler/utils/setUtil.ts @@ -56,10 +56,7 @@ export async function getSetPictures(set: Set, lang: SupportedLanguages): Promis const file = await fetchRemoteFile('https://assets.tcgdex.net/datas.json') const logoExists = file[lang]?.[set.serie.id]?.[set.id]?.logo ? `https://assets.tcgdex.net/${lang}/${set.serie.id}/${set.id}/logo` : undefined const symbolExists = file.univ?.[set.serie.id]?.[set.id]?.symbol ? `https://assets.tcgdex.net/univ/${set.serie.id}/${set.id}/symbol` : undefined - return [ - logoExists, - symbolExists - ] + return [logoExists, symbolExists] } catch { return [undefined, undefined] } @@ -81,7 +78,7 @@ export async function setToSetSimple(set: Set, lang: SupportedLanguages): Promis } function getVariantCountForType(card: Card, type: 'normal' | 'reverse' | 'holo' | 'firstEdition'): number { - if( card.variants === undefined || card.variants === null) { + if (card.variants === undefined || card.variants === null) { return 0; } @@ -103,11 +100,11 @@ export async function setToSetSingle(set: Set, lang: SupportedLanguages): Promis const pics = await getSetPictures(set, lang) return { cardCount: { - firstEd: cards.reduce((count, card) => count + getVariantCountForType(card[1],"firstEdition"), 0), - holo: cards.reduce((count, card) => count + getVariantCountForType(card[1],"holo"), 0), - normal: cards.reduce((count, card) => count + getVariantCountForType(card[1],"normal"), 0), + firstEd: cards.reduce((count, card) => count + getVariantCountForType(card[1], "firstEdition"), 0), + holo: cards.reduce((count, card) => count + getVariantCountForType(card[1], "holo"), 0), + normal: cards.reduce((count, card) => count + getVariantCountForType(card[1], "normal"), 0), official: set.cardCount.official, - reverse: cards.reduce((count, card) => count + getVariantCountForType(card[1],"reverse"), 0), + reverse: cards.reduce((count, card) => count + getVariantCountForType(card[1], "reverse"), 0), total: Math.max(set.cardCount.official, cards.length) }, cards: await Promise.all(cards.map(([id, card]) => cardToCardSimple(id, card, lang))), @@ -134,8 +131,13 @@ export async function setToSetSingle(set: Set, lang: SupportedLanguages): Promis name: resolveText(booster.name, lang), // images will be coming soon... })) : undefined, - pullRates: set.pullRates, - + pullRates: set.pullRates ? { + rarities: set.pullRates.rarities, + specialVariants: set.pullRates.specialVariants?.map(({ rate, ...rest }) => ({ + ...rest, + rate + })) + } : undefined, thirdParty: set.thirdParty, } }