@allemandi/gacha-engine 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -45,8 +45,8 @@ const pools = [
45
45
  {
46
46
  rarity: 'SSR',
47
47
  items: [
48
- { name: 'Super Hobo', weight: 0.8, rateUp: true }, // Rate-up: 0.8% of SSR pool
49
- { name: 'Broke King', weight: 0.4 }, // Standard: 0.4% of SSR pool
48
+ { name: 'Super Hobo', weight: 0.8, rateUp: true },
49
+ { name: 'Broke King', weight: 0.4 },
50
50
  { name: 'Cardboard Hero', weight: 0.4 },
51
51
  { name: 'Newspaper Warmer', weight: 0.4 }
52
52
  ]
@@ -54,17 +54,25 @@ const pools = [
54
54
  {
55
55
  rarity: 'SR',
56
56
  items: [
57
- { name: 'Cold Salaryman', weight: 1.5, rateUp: true }, // Rate-up: 1.5% of SR pool
58
- { name: 'Numb Artist', weight: 1.8 }, // Standard 4-star rates
57
+ { name: 'Cold Salaryman', weight: 1.5, rateUp: true },
58
+ { name: 'Numb Artist', weight: 1.8 },
59
59
  { name: 'Crying Cook', weight: 1.8 },
60
60
  { name: 'Lonely Cat', weight: 1.8 }
61
61
  ]
62
+ },
63
+ {
64
+ rarity: 'R',
65
+ items: [
66
+ { name: 'Regular Joe', weight: 5.0 },
67
+ { name: 'Normal Person', weight: 5.0 }
68
+ ]
62
69
  }
63
70
  ];
64
71
 
65
72
  const rarityRates = {
66
- SSR: 0.01, // 1% chance for SSR (5-star)
67
- SR: 0.03, // 3% chance for SR (4-star)
73
+ SSR: 0.01, // 1% chance for SSR
74
+ SR: 0.05, // 5% chance for SR
75
+ R: 0.94 // 94% chance for R
68
76
  };
69
77
 
70
78
  const engine = new GachaEngine({ pools, rarityRates });
@@ -92,14 +100,25 @@ console.log(`Current rate-up items: ${rateUpItems.join(', ')}`);
92
100
  **CommonJS**
93
101
  ```js
94
102
  const { GachaEngine } = require('@allemandi/gacha-engine');
103
+
104
+ // Same configuration as above
105
+ const engine = new GachaEngine({ pools, rarityRates });
106
+ console.log('Single roll:', engine.roll());
95
107
  ```
96
108
 
97
- **UMD**
109
+ **UMD (Browser)**
98
110
  ```html
99
111
  <script src="https://unpkg.com/@allemandi/gacha-engine"></script>
100
112
  <script>
101
- const engine = new AllemandiGachaEngine.GachaEngine({
102
- rarityRates: { 'SSR': 0.01 },
113
+ // Access the GachaEngine class
114
+ const { GachaEngine } = window.AllemandiGachaEngine;
115
+
116
+ const engine = new GachaEngine({
117
+ rarityRates: {
118
+ SSR: 0.01,
119
+ SR: 0.05,
120
+ R: 0.94
121
+ },
103
122
  pools: [
104
123
  {
105
124
  rarity: 'SSR',
@@ -107,12 +126,24 @@ const { GachaEngine } = require('@allemandi/gacha-engine');
107
126
  { name: 'Park Master', weight: 0.7, rateUp: true },
108
127
  { name: 'Trash Titan', weight: 0.3 }
109
128
  ]
129
+ },
130
+ {
131
+ rarity: 'SR',
132
+ items: [
133
+ { name: 'Street Sweeper', weight: 1.0 }
134
+ ]
135
+ },
136
+ {
137
+ rarity: 'R',
138
+ items: [
139
+ { name: 'Regular Person', weight: 1.0 }
140
+ ]
110
141
  }
111
142
  ]
112
143
  });
113
144
 
114
145
  console.log('Single roll:', engine.roll());
115
- // Single roll: ['Park Master'] or ['Trash Titan']
146
+ console.log('Drop rate for Park Master:', engine.getItemDropRate('Park Master'));
116
147
  </script>
117
148
  ```
118
149
 
@@ -124,7 +155,7 @@ const { GachaEngine } = require('@allemandi/gacha-engine');
124
155
  Creates a new GachaEngine instance with validation.
125
156
 
126
157
  **Config Properties:**
127
- - `rarityRates` **(required)**: Object mapping rarity names to their base probabilities (must sum to ≤ 1.0)
158
+ - `rarityRates` **(required)**: Object mapping rarity names to their base probabilities (should sum to ≤ 1.0)
128
159
  - `pools` **(required)**: Array of rarity pools, each containing:
129
160
  - `rarity`: String identifier matching a key in `rarityRates`
130
161
  - `items`: Array of items with:
@@ -1,9 +1,23 @@
1
1
  import { GachaEngineConfig } from './types';
2
2
  export declare class GachaEngine {
3
+ private static readonly SCALE;
4
+ private static readonly MAX_SAFE_SCALE;
3
5
  private pools;
4
- private rarityRates;
5
- private dropRateCache;
6
+ private rarityRatesScaled;
7
+ private dropRateCacheScaled;
6
8
  constructor({ rarityRates, pools }: GachaEngineConfig);
9
+ /**
10
+ * Convert floating point rates to scaled integers
11
+ */
12
+ private scaleRarityRates;
13
+ /**
14
+ * Convert probability to scaled integer
15
+ */
16
+ private toScaled;
17
+ /**
18
+ * Convert scaled integer back to probability
19
+ */
20
+ private fromScaled;
7
21
  private validateConfig;
8
22
  getItemDropRate(name: string): number;
9
23
  getRarityProbability(rarity: string): number;
@@ -15,7 +29,19 @@ export declare class GachaEngine {
15
29
  dropRate: number;
16
30
  rarity: string;
17
31
  }[];
32
+ /**
33
+ * Get scaled drop rate for internal calculations
34
+ */
35
+ private getItemDropRateScaled;
18
36
  roll(count?: number): string[];
19
37
  private selectRarity;
20
38
  private selectItemFromPool;
39
+ /**
40
+ * Debug method to inspect scaled values
41
+ */
42
+ getDebugInfo(): {
43
+ scale: number;
44
+ rarityRatesScaled: Record<string, number>;
45
+ rarityRatesFloat: Record<string, number>;
46
+ };
21
47
  }
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- function t(t,r){(null==r||r>t.length)&&(r=t.length);for(var e=0,n=Array(r);e<r;e++)n[e]=t[e];return n}function r(r,e){var n="undefined"!=typeof Symbol&&r[Symbol.iterator]||r["@@iterator"];if(n)return(n=n.call(r)).next.bind(n);if(Array.isArray(r)||(n=function(r,e){if(r){if("string"==typeof r)return t(r,e);var n={}.toString.call(r).slice(8,-1);return"Object"===n&&r.constructor&&(n=r.constructor.name),"Map"===n||"Set"===n?Array.from(r):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?t(r,e):void 0}}(r))||e&&r&&"number"==typeof r.length){n&&(r=n);var i=0;return function(){return i>=r.length?{done:!0}:{done:!1,value:r[i++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}exports.GachaEngine=/*#__PURE__*/function(){function t(t){var r=t.rarityRates,e=t.pools;this.pools=void 0,this.rarityRates=void 0,this.dropRateCache=new Map,this.pools=e,this.rarityRates=r,this.validateConfig()}var e=t.prototype;return e.validateConfig=function(){var t=new Set(Object.keys(this.rarityRates)),e=new Set(this.pools.map(function(t){return t.rarity})),n=[].concat(e).filter(function(r){return!t.has(r)});if(n.length>0)throw new Error("Missing rarity rates for: "+n.join(", "));for(var i,o=r(this.pools);!(i=o()).done;){var a=i.value;if(0===a.items.length)throw new Error('Rarity "'+a.rarity+'" has no items');if(a.items.reduce(function(t,r){return t+r.weight},0)<=0)throw new Error('Rarity "'+a.rarity+'" has zero total weight')}},e.getItemDropRate=function(t){if(this.dropRateCache.has(t))return this.dropRateCache.get(t);for(var e,n=r(this.pools);!(e=n()).done;){var i=e.value,o=i.items.find(function(r){return r.name===t});if(o){var a=i.items.reduce(function(t,r){return t+r.weight},0),s=o.weight/a*this.rarityRates[i.rarity];return this.dropRateCache.set(t,s),s}}throw new Error('Item "'+t+'" not found')},e.getRarityProbability=function(t){if(!this.rarityRates[t])throw new Error('Rarity "'+t+'" not found');return this.rarityRates[t]},e.getCumulativeProbabilityForItem=function(t,r){var e=this.getItemDropRate(t);return 1-Math.pow(1-e,r)},e.getRollsForTargetProbability=function(t,r){var e=this.getItemDropRate(t);return e<=0?Infinity:Math.ceil(Math.log(1-r)/Math.log(1-e))},e.getRateUpItems=function(){return this.pools.flatMap(function(t){return t.items.filter(function(t){return t.rateUp}).map(function(t){return t.name})})},e.getAllItemDropRates=function(){var t=this;return this.pools.flatMap(function(r){return r.items.map(function(e){return{name:e.name,dropRate:t.getItemDropRate(e.name),rarity:r.rarity}})})},e.roll=function(t){var r=this;void 0===t&&(t=1);for(var e=[],n=function(){var t=r.selectRarity(),n=r.pools.find(function(r){return r.rarity===t}),i=r.selectItemFromPool(n);e.push(i.name)},i=0;i<t;i++)n();return e},e.selectRarity=function(){for(var t=Math.random(),r=0,e=0,n=Object.entries(this.rarityRates);e<n.length;e++){var i=n[e];if(t<=(r+=i[1]))return i[0]}return Object.keys(this.rarityRates)[0]},e.selectItemFromPool=function(t){for(var e,n=t.items.reduce(function(t,r){return t+r.weight},0),i=Math.random()*n,o=0,a=r(t.items);!(e=a()).done;){var s=e.value;if(i<=(o+=s.weight))return s}return t.items[0]},t}();
1
+ function t(t,r){(null==r||r>t.length)&&(r=t.length);for(var e=0,a=Array(r);e<r;e++)a[e]=t[e];return a}function r(r,e){var a="undefined"!=typeof Symbol&&r[Symbol.iterator]||r["@@iterator"];if(a)return(a=a.call(r)).next.bind(a);if(Array.isArray(r)||(a=function(r,e){if(r){if("string"==typeof r)return t(r,e);var a={}.toString.call(r).slice(8,-1);return"Object"===a&&r.constructor&&(a=r.constructor.name),"Map"===a||"Set"===a?Array.from(r):"Arguments"===a||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(a)?t(r,e):void 0}}(r))||e&&r&&"number"==typeof r.length){a&&(r=a);var n=0;return function(){return n>=r.length?{done:!0}:{done:!1,value:r[n++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function e(){return e=Object.assign?Object.assign.bind():function(t){for(var r=1;r<arguments.length;r++){var e=arguments[r];for(var a in e)({}).hasOwnProperty.call(e,a)&&(t[a]=e[a])}return t},e.apply(null,arguments)}var a,n=/*#__PURE__*/function(){function t(t){var r=t.rarityRates,e=t.pools;this.pools=void 0,this.rarityRatesScaled=void 0,this.dropRateCacheScaled=new Map,this.pools=e,this.rarityRatesScaled=this.scaleRarityRates(r),this.validateConfig(r)}var a=t.prototype;return a.scaleRarityRates=function(t){for(var r={},e=0,a=Object.entries(t);e<a.length;e++){var n=a[e],i=n[0],o=n[1];if(o<0||o>1)throw new Error('Rarity rate for "'+i+'" must be between 0 and 1, got '+o);r[i]=this.toScaled(o)}return r},a.toScaled=function(r){if(r>t.MAX_SAFE_SCALE/t.SCALE)throw new Error("Probability "+r+" too large for safe integer arithmetic");return Math.round(r*t.SCALE)},a.fromScaled=function(r){return r/t.SCALE},a.validateConfig=function(e){var a=new Set(Object.keys(this.rarityRatesScaled)),n=new Set(this.pools.map(function(t){return t.rarity})),i=Array.from(n).filter(function(t){return!a.has(t)});if(i.length>0)throw new Error("Missing rarity rates for: "+i.join(", "));var o=Object.values(e).reduce(function(t,r){return t+r},0),s=Object.values(this.rarityRatesScaled).reduce(function(t,r){return t+r},0);if(Math.abs(o-1)>1e-10)throw new Error("Rarity rates must sum to 1.0, got "+o);Math.abs(s-t.SCALE)>Object.keys(this.rarityRatesScaled).length&&console.warn("Scaled rates sum to "+s+", expected "+t.SCALE+". This is likely due to rounding.");for(var l,u=r(this.pools);!(l=u()).done;){var c=l.value;if(0===c.items.length)throw new Error('Rarity "'+c.rarity+'" has no items');if(c.items.reduce(function(t,r){return t+r.weight},0)<=0)throw new Error('Rarity "'+c.rarity+'" has zero total weight');for(var h,f=r(c.items);!(h=f()).done;){var d=h.value;if(d.weight<0)throw new Error('Item "'+d.name+'" weight must be non-negative, got '+d.weight)}var m=c.items.some(function(t){return t.weight>0});if(!m)throw new Error('Rarity "'+c.rarity+'" must have at least one item with positive weight')}},a.getItemDropRate=function(e){if(this.dropRateCacheScaled.has(e))return this.fromScaled(this.dropRateCacheScaled.get(e));for(var a,n=r(this.pools);!(a=n()).done;){var i=a.value,o=i.items.find(function(t){return t.name===e});if(o){if(0===o.weight)return this.dropRateCacheScaled.set(e,0),0;var s=i.items.reduce(function(t,r){return t+r.weight},0),l=this.rarityRatesScaled[i.rarity],u=this.toScaled(o.weight),c=this.toScaled(s),h=Math.round(u*l/t.SCALE),f=Math.round(h*t.SCALE/c);return this.dropRateCacheScaled.set(e,f),this.fromScaled(f)}}throw new Error('Item "'+e+'" not found')},a.getRarityProbability=function(t){if(!this.rarityRatesScaled[t])throw new Error('Rarity "'+t+'" not found');return this.fromScaled(this.rarityRatesScaled[t])},a.getCumulativeProbabilityForItem=function(r,e){var a=this.getItemDropRateScaled(r);if(0===a)return 0;if(a>=t.SCALE)return 1;var n=this.fromScaled(t.SCALE-a),i=Math.pow(n,e);return Math.min(1,Math.max(0,1-i))},a.getRollsForTargetProbability=function(t,r){if(r<=0)return 0;if(r>=1)return 1;var e=this.getItemDropRate(t);return e<=0?Infinity:e>=1?1:Math.ceil(Math.log(1-r)/Math.log(1-e))},a.getRateUpItems=function(){return this.pools.flatMap(function(t){return t.items.filter(function(t){return t.rateUp}).map(function(t){return t.name})})},a.getAllItemDropRates=function(){var t=this;return this.pools.flatMap(function(r){return r.items.map(function(e){return{name:e.name,dropRate:t.getItemDropRate(e.name),rarity:r.rarity}})})},a.getItemDropRateScaled=function(t){return this.dropRateCacheScaled.has(t)||this.getItemDropRate(t),this.dropRateCacheScaled.get(t)},a.roll=function(t){var r=this;void 0===t&&(t=1);for(var e=[],a=function(){var t=r.selectRarity(),a=r.pools.find(function(r){return r.rarity===t}),n=r.selectItemFromPool(a);e.push(n.name)},n=0;n<t;n++)a();return e},a.selectRarity=function(){for(var r=Math.floor(Math.random()*t.SCALE),e=0,a=0,n=Object.entries(this.rarityRatesScaled);a<n.length;a++){var i=n[a];if(r<(e+=i[1]))return i[0]}return Object.keys(this.rarityRatesScaled)[0]},a.selectItemFromPool=function(t){var a=this,n=t.items.filter(function(t){return t.weight>0});if(0===n.length)throw new Error('No selectable items in pool for rarity "'+t.rarity+'"');for(var i,o=n.map(function(t){return e({},t,{scaledWeight:a.toScaled(t.weight)})}),s=o.reduce(function(t,r){return t+r.scaledWeight},0),l=Math.floor(Math.random()*s),u=0,c=r(o);!(i=c()).done;){var h=i.value;if(l<(u+=h.scaledWeight))return{name:h.name,weight:h.weight}}return n[0]},a.getDebugInfo=function(){for(var r={},a=0,n=Object.entries(this.rarityRatesScaled);a<n.length;a++){var i=n[a];r[i[0]]=this.fromScaled(i[1])}return{scale:t.SCALE,rarityRatesScaled:e({},this.rarityRatesScaled),rarityRatesFloat:r}},t}();a=n,n.SCALE=1e6,n.MAX_SAFE_SCALE=Math.floor(Number.MAX_SAFE_INTEGER/a.SCALE),exports.GachaEngine=n;
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../src/gacha-engine.ts"],"sourcesContent":["import { RarityInput, GachaEngineConfig } from './types';\n\nexport class GachaEngine {\n private pools: RarityInput[];\n private rarityRates: Record<string, number>;\n private dropRateCache = new Map<string, number>();\n\n constructor({ rarityRates, pools }: GachaEngineConfig) {\n this.pools = pools;\n this.rarityRates = rarityRates;\n this.validateConfig();\n }\n\n private validateConfig(): void {\n const configuredRarities = new Set(Object.keys(this.rarityRates));\n const usedRarities = new Set(this.pools.map(p => p.rarity));\n const missing = [...usedRarities].filter(r => !configuredRarities.has(r));\n if (missing.length > 0) {\n throw new Error(`Missing rarity rates for: ${missing.join(', ')}`);\n }\n\n for (const pool of this.pools) {\n if (pool.items.length === 0) {\n throw new Error(`Rarity \"${pool.rarity}\" has no items`);\n }\n const totalWeight = pool.items.reduce((sum, i) => sum + i.weight, 0);\n if (totalWeight <= 0) {\n throw new Error(`Rarity \"${pool.rarity}\" has zero total weight`);\n }\n }\n }\n\n getItemDropRate(name: string): number {\n if (this.dropRateCache.has(name)) {\n return this.dropRateCache.get(name)!;\n }\n\n for (const pool of this.pools) {\n const item = pool.items.find(i => i.name === name);\n if (item) {\n const totalPoolWeight = pool.items.reduce((sum, i) => sum + i.weight, 0);\n const baseRarityRate = this.rarityRates[pool.rarity];\n const rate = (item.weight / totalPoolWeight) * baseRarityRate;\n this.dropRateCache.set(name, rate);\n return rate;\n }\n }\n throw new Error(`Item \"${name}\" not found`);\n }\n\n getRarityProbability(rarity: string): number {\n if (!this.rarityRates[rarity]) {\n throw new Error(`Rarity \"${rarity}\" not found`);\n }\n return this.rarityRates[rarity];\n }\n\n getCumulativeProbabilityForItem(name: string, rolls: number): number {\n const rate = this.getItemDropRate(name);\n return 1 - Math.pow(1 - rate, rolls);\n }\n\n getRollsForTargetProbability(name: string, targetProbability: number): number {\n const rate = this.getItemDropRate(name);\n if (rate <= 0) return Infinity;\n return Math.ceil(Math.log(1 - targetProbability) / Math.log(1 - rate));\n }\n\n getRateUpItems(): string[] {\n return this.pools.flatMap(p =>\n p.items.filter(i => i.rateUp).map(i => i.name)\n );\n }\n\n getAllItemDropRates(): { name: string; dropRate: number; rarity: string }[] {\n return this.pools.flatMap(p =>\n p.items.map(i => ({\n name: i.name,\n dropRate: this.getItemDropRate(i.name),\n rarity: p.rarity\n }))\n );\n }\n\n roll(count: number = 1): string[] {\n const results: string[] = [];\n for (let i = 0; i < count; i++) {\n const rarity = this.selectRarity();\n const pool = this.pools.find(p => p.rarity === rarity)!;\n const item = this.selectItemFromPool(pool);\n results.push(item.name);\n }\n return results;\n }\n\n private selectRarity(): string {\n const rand = Math.random();\n let cumulative = 0;\n for (const [rarity, rate] of Object.entries(this.rarityRates)) {\n cumulative += rate;\n if (rand <= cumulative) return rarity;\n }\n return Object.keys(this.rarityRates)[0];\n }\n\n private selectItemFromPool(pool: RarityInput): { name: string; weight: number } {\n const totalWeight = pool.items.reduce((sum, i) => sum + i.weight, 0);\n const rand = Math.random() * totalWeight;\n let cumulative = 0;\n for (const item of pool.items) {\n cumulative += item.weight;\n if (rand <= cumulative) return item;\n }\n return pool.items[0];\n }\n}"],"names":["GachaEngine","_ref","rarityRates","pools","this","dropRateCache","Map","validateConfig","_proto","prototype","configuredRarities","Set","Object","keys","usedRarities","map","p","rarity","missing","concat","filter","r","has","length","Error","join","_iterator","_step","_createForOfIteratorHelperLoose","done","pool","value","items","reduce","sum","i","weight","getItemDropRate","name","get","_iterator2","_step2","item","find","totalPoolWeight","rate","set","getRarityProbability","getCumulativeProbabilityForItem","rolls","Math","pow","getRollsForTargetProbability","targetProbability","Infinity","ceil","log","getRateUpItems","flatMap","rateUp","getAllItemDropRates","_this","dropRate","roll","count","_this2","results","_loop","selectRarity","selectItemFromPool","push","rand","random","cumulative","_i","_Object$entries","entries","_Object$entries$_i","_step3","totalWeight","_iterator3"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","sources":["../src/gacha-engine.ts"],"sourcesContent":["import { RarityInput, GachaEngineConfig } from './types';\n\nexport class GachaEngine {\n // Scale factor for fixed-point arithmetic (1,000,000 = 6 decimal places)\n private static readonly SCALE = 1000000;\n private static readonly MAX_SAFE_SCALE = Math.floor(Number.MAX_SAFE_INTEGER / this.SCALE);\n\n private pools: RarityInput[];\n private rarityRatesScaled: Record<string, number>; // Scaled to integers\n private dropRateCacheScaled = new Map<string, number>(); // Cache scaled rates\n\n constructor({ rarityRates, pools }: GachaEngineConfig) {\n this.pools = pools;\n this.rarityRatesScaled = this.scaleRarityRates(rarityRates);\n this.validateConfig(rarityRates);\n }\n\n /**\n * Convert floating point rates to scaled integers\n */\n private scaleRarityRates(rarityRates: Record<string, number>): Record<string, number> {\n const scaled: Record<string, number> = {};\n for (const [rarity, rate] of Object.entries(rarityRates)) {\n if (rate < 0 || rate > 1) {\n throw new Error(`Rarity rate for \"${rarity}\" must be between 0 and 1, got ${rate}`);\n }\n scaled[rarity] = this.toScaled(rate);\n }\n return scaled;\n }\n\n /**\n * Convert probability to scaled integer\n */\n private toScaled(probability: number): number {\n if (probability > GachaEngine.MAX_SAFE_SCALE / GachaEngine.SCALE) {\n throw new Error(`Probability ${probability} too large for safe integer arithmetic`);\n }\n return Math.round(probability * GachaEngine.SCALE);\n }\n\n /**\n * Convert scaled integer back to probability\n */\n private fromScaled(scaledInt: number): number {\n return scaledInt / GachaEngine.SCALE;\n }\n\n private validateConfig(originalRates: Record<string, number>): void {\n const configuredRarities = new Set(Object.keys(this.rarityRatesScaled));\n const usedRarities = new Set(this.pools.map(p => p.rarity));\n const missingArray = Array.from(usedRarities).filter(r => !configuredRarities.has(r));\n \n if (missingArray.length > 0) {\n throw new Error(`Missing rarity rates for: ${missingArray.join(', ')}`);\n }\n\n // Validate that rates sum to exactly 1.0 (within floating point precision)\n const totalRate = Object.values(originalRates).reduce((sum, rate) => sum + rate, 0);\n const totalScaled = Object.values(this.rarityRatesScaled).reduce((sum, rate) => sum + rate, 0);\n \n if (Math.abs(totalRate - 1.0) > 1e-10) {\n throw new Error(`Rarity rates must sum to 1.0, got ${totalRate}`);\n }\n\n // Ensure scaled rates sum to SCALE (accounting for rounding)\n if (Math.abs(totalScaled - GachaEngine.SCALE) > Object.keys(this.rarityRatesScaled).length) {\n console.warn(`Scaled rates sum to ${totalScaled}, expected ${GachaEngine.SCALE}. This is likely due to rounding.`);\n }\n\n for (const pool of this.pools) {\n if (pool.items.length === 0) {\n throw new Error(`Rarity \"${pool.rarity}\" has no items`);\n }\n \n const totalWeight = pool.items.reduce((sum, i) => sum + i.weight, 0);\n if (totalWeight <= 0) {\n throw new Error(`Rarity \"${pool.rarity}\" has zero total weight`);\n }\n\n // Validate that all weights are non-negative\n for (const item of pool.items) {\n if (item.weight < 0) {\n throw new Error(`Item \"${item.name}\" weight must be non-negative, got ${item.weight}`);\n }\n }\n\n // Ensure at least one item has positive weight\n const hasPositiveWeight = pool.items.some(item => item.weight > 0);\n if (!hasPositiveWeight) {\n throw new Error(`Rarity \"${pool.rarity}\" must have at least one item with positive weight`);\n }\n }\n }\n\n getItemDropRate(name: string): number {\n if (this.dropRateCacheScaled.has(name)) {\n return this.fromScaled(this.dropRateCacheScaled.get(name)!);\n }\n\n for (const pool of this.pools) {\n const item = pool.items.find(i => i.name === name);\n if (item) {\n // Handle zero weight items (never drop)\n if (item.weight === 0) {\n this.dropRateCacheScaled.set(name, 0);\n return 0;\n }\n\n const totalPoolWeight = pool.items.reduce((sum, i) => sum + i.weight, 0);\n const baseRarityRateScaled = this.rarityRatesScaled[pool.rarity];\n \n // Convert weights to scaled integers for perfect precision\n const itemWeightScaled = this.toScaled(item.weight);\n const totalWeightScaled = this.toScaled(totalPoolWeight);\n \n // Scaled arithmetic: (itemWeight * baseRate) / totalWeight\n const numeratorScaled = Math.round((itemWeightScaled * baseRarityRateScaled) / GachaEngine.SCALE);\n const rateScaled = Math.round((numeratorScaled * GachaEngine.SCALE) / totalWeightScaled);\n \n this.dropRateCacheScaled.set(name, rateScaled);\n return this.fromScaled(rateScaled);\n }\n }\n throw new Error(`Item \"${name}\" not found`);\n }\n\n getRarityProbability(rarity: string): number {\n if (!this.rarityRatesScaled[rarity]) {\n throw new Error(`Rarity \"${rarity}\" not found`);\n }\n return this.fromScaled(this.rarityRatesScaled[rarity]);\n }\n\n getCumulativeProbabilityForItem(name: string, rolls: number): number {\n const rateScaled = this.getItemDropRateScaled(name);\n \n if (rateScaled === 0) return 0;\n if (rateScaled >= GachaEngine.SCALE) return 1;\n \n // Calculate (1 - rate)^rolls using scaled arithmetic\n const failRateScaled = GachaEngine.SCALE - rateScaled;\n const failRate = this.fromScaled(failRateScaled);\n \n // For large rolls, we need to be careful with precision\n const cumulativeFailProbability = Math.pow(failRate, rolls);\n const cumulativeProbability = 1 - cumulativeFailProbability;\n \n return Math.min(1, Math.max(0, cumulativeProbability));\n }\n\n getRollsForTargetProbability(name: string, targetProbability: number): number {\n if (targetProbability <= 0) return 0;\n if (targetProbability >= 1) return 1;\n \n const rate = this.getItemDropRate(name);\n if (rate <= 0) return Infinity;\n if (rate >= 1) return 1;\n \n return Math.ceil(Math.log(1 - targetProbability) / Math.log(1 - rate));\n }\n\n getRateUpItems(): string[] {\n return this.pools.flatMap(p =>\n p.items.filter(i => i.rateUp).map(i => i.name)\n );\n }\n\n getAllItemDropRates(): { name: string; dropRate: number; rarity: string }[] {\n return this.pools.flatMap(p =>\n p.items.map(i => ({\n name: i.name,\n dropRate: this.getItemDropRate(i.name),\n rarity: p.rarity\n }))\n );\n }\n\n /**\n * Get scaled drop rate for internal calculations\n */\n private getItemDropRateScaled(name: string): number {\n if (this.dropRateCacheScaled.has(name)) {\n return this.dropRateCacheScaled.get(name)!;\n }\n \n // Trigger calculation and caching\n this.getItemDropRate(name);\n return this.dropRateCacheScaled.get(name)!;\n }\n\n roll(count: number = 1): string[] {\n const results: string[] = [];\n for (let i = 0; i < count; i++) {\n const rarity = this.selectRarity();\n const pool = this.pools.find(p => p.rarity === rarity)!;\n const item = this.selectItemFromPool(pool);\n results.push(item.name);\n }\n return results;\n }\n\n private selectRarity(): string {\n const rand = Math.floor(Math.random() * GachaEngine.SCALE);\n let cumulativeScaled = 0;\n \n for (const [rarity, rateScaled] of Object.entries(this.rarityRatesScaled)) {\n cumulativeScaled += rateScaled;\n if (rand < cumulativeScaled) return rarity;\n }\n \n // Fallback (should never happen with proper validation)\n return Object.keys(this.rarityRatesScaled)[0];\n }\n\n private selectItemFromPool(pool: RarityInput): { name: string; weight: number } {\n // Filter out zero-weight items (they can never be selected)\n const selectableItems = pool.items.filter(item => item.weight > 0);\n \n if (selectableItems.length === 0) {\n throw new Error(`No selectable items in pool for rarity \"${pool.rarity}\"`);\n }\n\n // Convert all weights to scaled integers for perfect precision\n const scaledItems = selectableItems.map(item => ({\n ...item,\n scaledWeight: this.toScaled(item.weight)\n }));\n \n const totalScaledWeight = scaledItems.reduce((sum, item) => sum + item.scaledWeight, 0);\n const rand = Math.floor(Math.random() * totalScaledWeight);\n let cumulative = 0;\n \n for (const item of scaledItems) {\n cumulative += item.scaledWeight;\n if (rand < cumulative) {\n return { name: item.name, weight: item.weight };\n }\n }\n \n // Fallback (should never happen)\n return selectableItems[0];\n }\n\n /**\n * Debug method to inspect scaled values\n */\n getDebugInfo(): {\n scale: number;\n rarityRatesScaled: Record<string, number>;\n rarityRatesFloat: Record<string, number>;\n } {\n const rarityRatesFloat: Record<string, number> = {};\n for (const [rarity, scaledRate] of Object.entries(this.rarityRatesScaled)) {\n rarityRatesFloat[rarity] = this.fromScaled(scaledRate);\n }\n \n return {\n scale: GachaEngine.SCALE,\n rarityRatesScaled: { ...this.rarityRatesScaled },\n rarityRatesFloat\n };\n }\n}"],"names":["GachaEngine","_ref","rarityRates","pools","rarityRatesScaled","dropRateCacheScaled","Map","this","scaleRarityRates","validateConfig","_proto","prototype","scaled","_i","_Object$entries","Object","entries","length","_Object$entries$_i","rarity","rate","Error","toScaled","probability","MAX_SAFE_SCALE","SCALE","Math","round","fromScaled","scaledInt","originalRates","configuredRarities","Set","keys","usedRarities","map","p","missingArray","Array","from","filter","r","has","join","totalRate","values","reduce","sum","totalScaled","abs","console","warn","_iterator","_step","_createForOfIteratorHelperLoose","done","pool","value","items","i","weight","_step2","_iterator2","item","name","hasPositiveWeight","some","getItemDropRate","get","_step3","_iterator3","find","set","totalPoolWeight","baseRarityRateScaled","itemWeightScaled","totalWeightScaled","numeratorScaled","rateScaled","getRarityProbability","getCumulativeProbabilityForItem","rolls","getItemDropRateScaled","failRate","cumulativeFailProbability","pow","min","max","getRollsForTargetProbability","targetProbability","Infinity","ceil","log","getRateUpItems","flatMap","rateUp","getAllItemDropRates","_this","dropRate","roll","count","_this2","results","_loop","selectRarity","selectItemFromPool","push","rand","floor","random","cumulativeScaled","_i2","_Object$entries2","_Object$entries2$_i","_this3","selectableItems","_step4","scaledItems","_extends","scaledWeight","totalScaledWeight","cumulative","_iterator4","getDebugInfo","rarityRatesFloat","_i3","_Object$entries3","_Object$entries3$_i","scale","Number","MAX_SAFE_INTEGER","_GachaEngine"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- function t(t,r){(null==r||r>t.length)&&(r=t.length);for(var e=0,n=Array(r);e<r;e++)n[e]=t[e];return n}function r(r,e){var n="undefined"!=typeof Symbol&&r[Symbol.iterator]||r["@@iterator"];if(n)return(n=n.call(r)).next.bind(n);if(Array.isArray(r)||(n=function(r,e){if(r){if("string"==typeof r)return t(r,e);var n={}.toString.call(r).slice(8,-1);return"Object"===n&&r.constructor&&(n=r.constructor.name),"Map"===n||"Set"===n?Array.from(r):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?t(r,e):void 0}}(r))||e&&r&&"number"==typeof r.length){n&&(r=n);var i=0;return function(){return i>=r.length?{done:!0}:{done:!1,value:r[i++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var e=/*#__PURE__*/function(){function t(t){var r=t.rarityRates,e=t.pools;this.pools=void 0,this.rarityRates=void 0,this.dropRateCache=new Map,this.pools=e,this.rarityRates=r,this.validateConfig()}var e=t.prototype;return e.validateConfig=function(){var t=new Set(Object.keys(this.rarityRates)),e=new Set(this.pools.map(function(t){return t.rarity})),n=[].concat(e).filter(function(r){return!t.has(r)});if(n.length>0)throw new Error("Missing rarity rates for: "+n.join(", "));for(var i,o=r(this.pools);!(i=o()).done;){var a=i.value;if(0===a.items.length)throw new Error('Rarity "'+a.rarity+'" has no items');if(a.items.reduce(function(t,r){return t+r.weight},0)<=0)throw new Error('Rarity "'+a.rarity+'" has zero total weight')}},e.getItemDropRate=function(t){if(this.dropRateCache.has(t))return this.dropRateCache.get(t);for(var e,n=r(this.pools);!(e=n()).done;){var i=e.value,o=i.items.find(function(r){return r.name===t});if(o){var a=i.items.reduce(function(t,r){return t+r.weight},0),s=o.weight/a*this.rarityRates[i.rarity];return this.dropRateCache.set(t,s),s}}throw new Error('Item "'+t+'" not found')},e.getRarityProbability=function(t){if(!this.rarityRates[t])throw new Error('Rarity "'+t+'" not found');return this.rarityRates[t]},e.getCumulativeProbabilityForItem=function(t,r){var e=this.getItemDropRate(t);return 1-Math.pow(1-e,r)},e.getRollsForTargetProbability=function(t,r){var e=this.getItemDropRate(t);return e<=0?Infinity:Math.ceil(Math.log(1-r)/Math.log(1-e))},e.getRateUpItems=function(){return this.pools.flatMap(function(t){return t.items.filter(function(t){return t.rateUp}).map(function(t){return t.name})})},e.getAllItemDropRates=function(){var t=this;return this.pools.flatMap(function(r){return r.items.map(function(e){return{name:e.name,dropRate:t.getItemDropRate(e.name),rarity:r.rarity}})})},e.roll=function(t){var r=this;void 0===t&&(t=1);for(var e=[],n=function(){var t=r.selectRarity(),n=r.pools.find(function(r){return r.rarity===t}),i=r.selectItemFromPool(n);e.push(i.name)},i=0;i<t;i++)n();return e},e.selectRarity=function(){for(var t=Math.random(),r=0,e=0,n=Object.entries(this.rarityRates);e<n.length;e++){var i=n[e];if(t<=(r+=i[1]))return i[0]}return Object.keys(this.rarityRates)[0]},e.selectItemFromPool=function(t){for(var e,n=t.items.reduce(function(t,r){return t+r.weight},0),i=Math.random()*n,o=0,a=r(t.items);!(e=a()).done;){var s=e.value;if(i<=(o+=s.weight))return s}return t.items[0]},t}();export{e as GachaEngine};
1
+ function t(t,r){(null==r||r>t.length)&&(r=t.length);for(var e=0,a=Array(r);e<r;e++)a[e]=t[e];return a}function r(r,e){var a="undefined"!=typeof Symbol&&r[Symbol.iterator]||r["@@iterator"];if(a)return(a=a.call(r)).next.bind(a);if(Array.isArray(r)||(a=function(r,e){if(r){if("string"==typeof r)return t(r,e);var a={}.toString.call(r).slice(8,-1);return"Object"===a&&r.constructor&&(a=r.constructor.name),"Map"===a||"Set"===a?Array.from(r):"Arguments"===a||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(a)?t(r,e):void 0}}(r))||e&&r&&"number"==typeof r.length){a&&(r=a);var i=0;return function(){return i>=r.length?{done:!0}:{done:!1,value:r[i++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function e(){return e=Object.assign?Object.assign.bind():function(t){for(var r=1;r<arguments.length;r++){var e=arguments[r];for(var a in e)({}).hasOwnProperty.call(e,a)&&(t[a]=e[a])}return t},e.apply(null,arguments)}var a,i=/*#__PURE__*/function(){function t(t){var r=t.rarityRates,e=t.pools;this.pools=void 0,this.rarityRatesScaled=void 0,this.dropRateCacheScaled=new Map,this.pools=e,this.rarityRatesScaled=this.scaleRarityRates(r),this.validateConfig(r)}var a=t.prototype;return a.scaleRarityRates=function(t){for(var r={},e=0,a=Object.entries(t);e<a.length;e++){var i=a[e],n=i[0],o=i[1];if(o<0||o>1)throw new Error('Rarity rate for "'+n+'" must be between 0 and 1, got '+o);r[n]=this.toScaled(o)}return r},a.toScaled=function(r){if(r>t.MAX_SAFE_SCALE/t.SCALE)throw new Error("Probability "+r+" too large for safe integer arithmetic");return Math.round(r*t.SCALE)},a.fromScaled=function(r){return r/t.SCALE},a.validateConfig=function(e){var a=new Set(Object.keys(this.rarityRatesScaled)),i=new Set(this.pools.map(function(t){return t.rarity})),n=Array.from(i).filter(function(t){return!a.has(t)});if(n.length>0)throw new Error("Missing rarity rates for: "+n.join(", "));var o=Object.values(e).reduce(function(t,r){return t+r},0),s=Object.values(this.rarityRatesScaled).reduce(function(t,r){return t+r},0);if(Math.abs(o-1)>1e-10)throw new Error("Rarity rates must sum to 1.0, got "+o);Math.abs(s-t.SCALE)>Object.keys(this.rarityRatesScaled).length&&console.warn("Scaled rates sum to "+s+", expected "+t.SCALE+". This is likely due to rounding.");for(var l,u=r(this.pools);!(l=u()).done;){var c=l.value;if(0===c.items.length)throw new Error('Rarity "'+c.rarity+'" has no items');if(c.items.reduce(function(t,r){return t+r.weight},0)<=0)throw new Error('Rarity "'+c.rarity+'" has zero total weight');for(var h,f=r(c.items);!(h=f()).done;){var d=h.value;if(d.weight<0)throw new Error('Item "'+d.name+'" weight must be non-negative, got '+d.weight)}var m=c.items.some(function(t){return t.weight>0});if(!m)throw new Error('Rarity "'+c.rarity+'" must have at least one item with positive weight')}},a.getItemDropRate=function(e){if(this.dropRateCacheScaled.has(e))return this.fromScaled(this.dropRateCacheScaled.get(e));for(var a,i=r(this.pools);!(a=i()).done;){var n=a.value,o=n.items.find(function(t){return t.name===e});if(o){if(0===o.weight)return this.dropRateCacheScaled.set(e,0),0;var s=n.items.reduce(function(t,r){return t+r.weight},0),l=this.rarityRatesScaled[n.rarity],u=this.toScaled(o.weight),c=this.toScaled(s),h=Math.round(u*l/t.SCALE),f=Math.round(h*t.SCALE/c);return this.dropRateCacheScaled.set(e,f),this.fromScaled(f)}}throw new Error('Item "'+e+'" not found')},a.getRarityProbability=function(t){if(!this.rarityRatesScaled[t])throw new Error('Rarity "'+t+'" not found');return this.fromScaled(this.rarityRatesScaled[t])},a.getCumulativeProbabilityForItem=function(r,e){var a=this.getItemDropRateScaled(r);if(0===a)return 0;if(a>=t.SCALE)return 1;var i=this.fromScaled(t.SCALE-a),n=Math.pow(i,e);return Math.min(1,Math.max(0,1-n))},a.getRollsForTargetProbability=function(t,r){if(r<=0)return 0;if(r>=1)return 1;var e=this.getItemDropRate(t);return e<=0?Infinity:e>=1?1:Math.ceil(Math.log(1-r)/Math.log(1-e))},a.getRateUpItems=function(){return this.pools.flatMap(function(t){return t.items.filter(function(t){return t.rateUp}).map(function(t){return t.name})})},a.getAllItemDropRates=function(){var t=this;return this.pools.flatMap(function(r){return r.items.map(function(e){return{name:e.name,dropRate:t.getItemDropRate(e.name),rarity:r.rarity}})})},a.getItemDropRateScaled=function(t){return this.dropRateCacheScaled.has(t)||this.getItemDropRate(t),this.dropRateCacheScaled.get(t)},a.roll=function(t){var r=this;void 0===t&&(t=1);for(var e=[],a=function(){var t=r.selectRarity(),a=r.pools.find(function(r){return r.rarity===t}),i=r.selectItemFromPool(a);e.push(i.name)},i=0;i<t;i++)a();return e},a.selectRarity=function(){for(var r=Math.floor(Math.random()*t.SCALE),e=0,a=0,i=Object.entries(this.rarityRatesScaled);a<i.length;a++){var n=i[a];if(r<(e+=n[1]))return n[0]}return Object.keys(this.rarityRatesScaled)[0]},a.selectItemFromPool=function(t){var a=this,i=t.items.filter(function(t){return t.weight>0});if(0===i.length)throw new Error('No selectable items in pool for rarity "'+t.rarity+'"');for(var n,o=i.map(function(t){return e({},t,{scaledWeight:a.toScaled(t.weight)})}),s=o.reduce(function(t,r){return t+r.scaledWeight},0),l=Math.floor(Math.random()*s),u=0,c=r(o);!(n=c()).done;){var h=n.value;if(l<(u+=h.scaledWeight))return{name:h.name,weight:h.weight}}return i[0]},a.getDebugInfo=function(){for(var r={},a=0,i=Object.entries(this.rarityRatesScaled);a<i.length;a++){var n=i[a];r[n[0]]=this.fromScaled(n[1])}return{scale:t.SCALE,rarityRatesScaled:e({},this.rarityRatesScaled),rarityRatesFloat:r}},t}();a=i,i.SCALE=1e6,i.MAX_SAFE_SCALE=Math.floor(Number.MAX_SAFE_INTEGER/a.SCALE);export{i as GachaEngine};
2
2
  //# sourceMappingURL=index.module.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.module.js","sources":["../src/gacha-engine.ts"],"sourcesContent":["import { RarityInput, GachaEngineConfig } from './types';\n\nexport class GachaEngine {\n private pools: RarityInput[];\n private rarityRates: Record<string, number>;\n private dropRateCache = new Map<string, number>();\n\n constructor({ rarityRates, pools }: GachaEngineConfig) {\n this.pools = pools;\n this.rarityRates = rarityRates;\n this.validateConfig();\n }\n\n private validateConfig(): void {\n const configuredRarities = new Set(Object.keys(this.rarityRates));\n const usedRarities = new Set(this.pools.map(p => p.rarity));\n const missing = [...usedRarities].filter(r => !configuredRarities.has(r));\n if (missing.length > 0) {\n throw new Error(`Missing rarity rates for: ${missing.join(', ')}`);\n }\n\n for (const pool of this.pools) {\n if (pool.items.length === 0) {\n throw new Error(`Rarity \"${pool.rarity}\" has no items`);\n }\n const totalWeight = pool.items.reduce((sum, i) => sum + i.weight, 0);\n if (totalWeight <= 0) {\n throw new Error(`Rarity \"${pool.rarity}\" has zero total weight`);\n }\n }\n }\n\n getItemDropRate(name: string): number {\n if (this.dropRateCache.has(name)) {\n return this.dropRateCache.get(name)!;\n }\n\n for (const pool of this.pools) {\n const item = pool.items.find(i => i.name === name);\n if (item) {\n const totalPoolWeight = pool.items.reduce((sum, i) => sum + i.weight, 0);\n const baseRarityRate = this.rarityRates[pool.rarity];\n const rate = (item.weight / totalPoolWeight) * baseRarityRate;\n this.dropRateCache.set(name, rate);\n return rate;\n }\n }\n throw new Error(`Item \"${name}\" not found`);\n }\n\n getRarityProbability(rarity: string): number {\n if (!this.rarityRates[rarity]) {\n throw new Error(`Rarity \"${rarity}\" not found`);\n }\n return this.rarityRates[rarity];\n }\n\n getCumulativeProbabilityForItem(name: string, rolls: number): number {\n const rate = this.getItemDropRate(name);\n return 1 - Math.pow(1 - rate, rolls);\n }\n\n getRollsForTargetProbability(name: string, targetProbability: number): number {\n const rate = this.getItemDropRate(name);\n if (rate <= 0) return Infinity;\n return Math.ceil(Math.log(1 - targetProbability) / Math.log(1 - rate));\n }\n\n getRateUpItems(): string[] {\n return this.pools.flatMap(p =>\n p.items.filter(i => i.rateUp).map(i => i.name)\n );\n }\n\n getAllItemDropRates(): { name: string; dropRate: number; rarity: string }[] {\n return this.pools.flatMap(p =>\n p.items.map(i => ({\n name: i.name,\n dropRate: this.getItemDropRate(i.name),\n rarity: p.rarity\n }))\n );\n }\n\n roll(count: number = 1): string[] {\n const results: string[] = [];\n for (let i = 0; i < count; i++) {\n const rarity = this.selectRarity();\n const pool = this.pools.find(p => p.rarity === rarity)!;\n const item = this.selectItemFromPool(pool);\n results.push(item.name);\n }\n return results;\n }\n\n private selectRarity(): string {\n const rand = Math.random();\n let cumulative = 0;\n for (const [rarity, rate] of Object.entries(this.rarityRates)) {\n cumulative += rate;\n if (rand <= cumulative) return rarity;\n }\n return Object.keys(this.rarityRates)[0];\n }\n\n private selectItemFromPool(pool: RarityInput): { name: string; weight: number } {\n const totalWeight = pool.items.reduce((sum, i) => sum + i.weight, 0);\n const rand = Math.random() * totalWeight;\n let cumulative = 0;\n for (const item of pool.items) {\n cumulative += item.weight;\n if (rand <= cumulative) return item;\n }\n return pool.items[0];\n }\n}"],"names":["GachaEngine","_ref","rarityRates","pools","this","dropRateCache","Map","validateConfig","_proto","prototype","configuredRarities","Set","Object","keys","usedRarities","map","p","rarity","missing","concat","filter","r","has","length","Error","join","_iterator","_step","_createForOfIteratorHelperLoose","done","pool","value","items","reduce","sum","i","weight","getItemDropRate","name","get","_iterator2","_step2","item","find","totalPoolWeight","rate","set","getRarityProbability","getCumulativeProbabilityForItem","rolls","Math","pow","getRollsForTargetProbability","targetProbability","Infinity","ceil","log","getRateUpItems","flatMap","rateUp","getAllItemDropRates","_this","dropRate","roll","count","_this2","results","_loop","selectRarity","selectItemFromPool","push","rand","random","cumulative","_i","_Object$entries","entries","_Object$entries$_i","_step3","totalWeight","_iterator3"],"mappings":"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"}
1
+ {"version":3,"file":"index.module.js","sources":["../src/gacha-engine.ts"],"sourcesContent":["import { RarityInput, GachaEngineConfig } from './types';\n\nexport class GachaEngine {\n // Scale factor for fixed-point arithmetic (1,000,000 = 6 decimal places)\n private static readonly SCALE = 1000000;\n private static readonly MAX_SAFE_SCALE = Math.floor(Number.MAX_SAFE_INTEGER / this.SCALE);\n\n private pools: RarityInput[];\n private rarityRatesScaled: Record<string, number>; // Scaled to integers\n private dropRateCacheScaled = new Map<string, number>(); // Cache scaled rates\n\n constructor({ rarityRates, pools }: GachaEngineConfig) {\n this.pools = pools;\n this.rarityRatesScaled = this.scaleRarityRates(rarityRates);\n this.validateConfig(rarityRates);\n }\n\n /**\n * Convert floating point rates to scaled integers\n */\n private scaleRarityRates(rarityRates: Record<string, number>): Record<string, number> {\n const scaled: Record<string, number> = {};\n for (const [rarity, rate] of Object.entries(rarityRates)) {\n if (rate < 0 || rate > 1) {\n throw new Error(`Rarity rate for \"${rarity}\" must be between 0 and 1, got ${rate}`);\n }\n scaled[rarity] = this.toScaled(rate);\n }\n return scaled;\n }\n\n /**\n * Convert probability to scaled integer\n */\n private toScaled(probability: number): number {\n if (probability > GachaEngine.MAX_SAFE_SCALE / GachaEngine.SCALE) {\n throw new Error(`Probability ${probability} too large for safe integer arithmetic`);\n }\n return Math.round(probability * GachaEngine.SCALE);\n }\n\n /**\n * Convert scaled integer back to probability\n */\n private fromScaled(scaledInt: number): number {\n return scaledInt / GachaEngine.SCALE;\n }\n\n private validateConfig(originalRates: Record<string, number>): void {\n const configuredRarities = new Set(Object.keys(this.rarityRatesScaled));\n const usedRarities = new Set(this.pools.map(p => p.rarity));\n const missingArray = Array.from(usedRarities).filter(r => !configuredRarities.has(r));\n \n if (missingArray.length > 0) {\n throw new Error(`Missing rarity rates for: ${missingArray.join(', ')}`);\n }\n\n // Validate that rates sum to exactly 1.0 (within floating point precision)\n const totalRate = Object.values(originalRates).reduce((sum, rate) => sum + rate, 0);\n const totalScaled = Object.values(this.rarityRatesScaled).reduce((sum, rate) => sum + rate, 0);\n \n if (Math.abs(totalRate - 1.0) > 1e-10) {\n throw new Error(`Rarity rates must sum to 1.0, got ${totalRate}`);\n }\n\n // Ensure scaled rates sum to SCALE (accounting for rounding)\n if (Math.abs(totalScaled - GachaEngine.SCALE) > Object.keys(this.rarityRatesScaled).length) {\n console.warn(`Scaled rates sum to ${totalScaled}, expected ${GachaEngine.SCALE}. This is likely due to rounding.`);\n }\n\n for (const pool of this.pools) {\n if (pool.items.length === 0) {\n throw new Error(`Rarity \"${pool.rarity}\" has no items`);\n }\n \n const totalWeight = pool.items.reduce((sum, i) => sum + i.weight, 0);\n if (totalWeight <= 0) {\n throw new Error(`Rarity \"${pool.rarity}\" has zero total weight`);\n }\n\n // Validate that all weights are non-negative\n for (const item of pool.items) {\n if (item.weight < 0) {\n throw new Error(`Item \"${item.name}\" weight must be non-negative, got ${item.weight}`);\n }\n }\n\n // Ensure at least one item has positive weight\n const hasPositiveWeight = pool.items.some(item => item.weight > 0);\n if (!hasPositiveWeight) {\n throw new Error(`Rarity \"${pool.rarity}\" must have at least one item with positive weight`);\n }\n }\n }\n\n getItemDropRate(name: string): number {\n if (this.dropRateCacheScaled.has(name)) {\n return this.fromScaled(this.dropRateCacheScaled.get(name)!);\n }\n\n for (const pool of this.pools) {\n const item = pool.items.find(i => i.name === name);\n if (item) {\n // Handle zero weight items (never drop)\n if (item.weight === 0) {\n this.dropRateCacheScaled.set(name, 0);\n return 0;\n }\n\n const totalPoolWeight = pool.items.reduce((sum, i) => sum + i.weight, 0);\n const baseRarityRateScaled = this.rarityRatesScaled[pool.rarity];\n \n // Convert weights to scaled integers for perfect precision\n const itemWeightScaled = this.toScaled(item.weight);\n const totalWeightScaled = this.toScaled(totalPoolWeight);\n \n // Scaled arithmetic: (itemWeight * baseRate) / totalWeight\n const numeratorScaled = Math.round((itemWeightScaled * baseRarityRateScaled) / GachaEngine.SCALE);\n const rateScaled = Math.round((numeratorScaled * GachaEngine.SCALE) / totalWeightScaled);\n \n this.dropRateCacheScaled.set(name, rateScaled);\n return this.fromScaled(rateScaled);\n }\n }\n throw new Error(`Item \"${name}\" not found`);\n }\n\n getRarityProbability(rarity: string): number {\n if (!this.rarityRatesScaled[rarity]) {\n throw new Error(`Rarity \"${rarity}\" not found`);\n }\n return this.fromScaled(this.rarityRatesScaled[rarity]);\n }\n\n getCumulativeProbabilityForItem(name: string, rolls: number): number {\n const rateScaled = this.getItemDropRateScaled(name);\n \n if (rateScaled === 0) return 0;\n if (rateScaled >= GachaEngine.SCALE) return 1;\n \n // Calculate (1 - rate)^rolls using scaled arithmetic\n const failRateScaled = GachaEngine.SCALE - rateScaled;\n const failRate = this.fromScaled(failRateScaled);\n \n // For large rolls, we need to be careful with precision\n const cumulativeFailProbability = Math.pow(failRate, rolls);\n const cumulativeProbability = 1 - cumulativeFailProbability;\n \n return Math.min(1, Math.max(0, cumulativeProbability));\n }\n\n getRollsForTargetProbability(name: string, targetProbability: number): number {\n if (targetProbability <= 0) return 0;\n if (targetProbability >= 1) return 1;\n \n const rate = this.getItemDropRate(name);\n if (rate <= 0) return Infinity;\n if (rate >= 1) return 1;\n \n return Math.ceil(Math.log(1 - targetProbability) / Math.log(1 - rate));\n }\n\n getRateUpItems(): string[] {\n return this.pools.flatMap(p =>\n p.items.filter(i => i.rateUp).map(i => i.name)\n );\n }\n\n getAllItemDropRates(): { name: string; dropRate: number; rarity: string }[] {\n return this.pools.flatMap(p =>\n p.items.map(i => ({\n name: i.name,\n dropRate: this.getItemDropRate(i.name),\n rarity: p.rarity\n }))\n );\n }\n\n /**\n * Get scaled drop rate for internal calculations\n */\n private getItemDropRateScaled(name: string): number {\n if (this.dropRateCacheScaled.has(name)) {\n return this.dropRateCacheScaled.get(name)!;\n }\n \n // Trigger calculation and caching\n this.getItemDropRate(name);\n return this.dropRateCacheScaled.get(name)!;\n }\n\n roll(count: number = 1): string[] {\n const results: string[] = [];\n for (let i = 0; i < count; i++) {\n const rarity = this.selectRarity();\n const pool = this.pools.find(p => p.rarity === rarity)!;\n const item = this.selectItemFromPool(pool);\n results.push(item.name);\n }\n return results;\n }\n\n private selectRarity(): string {\n const rand = Math.floor(Math.random() * GachaEngine.SCALE);\n let cumulativeScaled = 0;\n \n for (const [rarity, rateScaled] of Object.entries(this.rarityRatesScaled)) {\n cumulativeScaled += rateScaled;\n if (rand < cumulativeScaled) return rarity;\n }\n \n // Fallback (should never happen with proper validation)\n return Object.keys(this.rarityRatesScaled)[0];\n }\n\n private selectItemFromPool(pool: RarityInput): { name: string; weight: number } {\n // Filter out zero-weight items (they can never be selected)\n const selectableItems = pool.items.filter(item => item.weight > 0);\n \n if (selectableItems.length === 0) {\n throw new Error(`No selectable items in pool for rarity \"${pool.rarity}\"`);\n }\n\n // Convert all weights to scaled integers for perfect precision\n const scaledItems = selectableItems.map(item => ({\n ...item,\n scaledWeight: this.toScaled(item.weight)\n }));\n \n const totalScaledWeight = scaledItems.reduce((sum, item) => sum + item.scaledWeight, 0);\n const rand = Math.floor(Math.random() * totalScaledWeight);\n let cumulative = 0;\n \n for (const item of scaledItems) {\n cumulative += item.scaledWeight;\n if (rand < cumulative) {\n return { name: item.name, weight: item.weight };\n }\n }\n \n // Fallback (should never happen)\n return selectableItems[0];\n }\n\n /**\n * Debug method to inspect scaled values\n */\n getDebugInfo(): {\n scale: number;\n rarityRatesScaled: Record<string, number>;\n rarityRatesFloat: Record<string, number>;\n } {\n const rarityRatesFloat: Record<string, number> = {};\n for (const [rarity, scaledRate] of Object.entries(this.rarityRatesScaled)) {\n rarityRatesFloat[rarity] = this.fromScaled(scaledRate);\n }\n \n return {\n scale: GachaEngine.SCALE,\n rarityRatesScaled: { ...this.rarityRatesScaled },\n rarityRatesFloat\n };\n }\n}"],"names":["GachaEngine","_ref","rarityRates","pools","rarityRatesScaled","dropRateCacheScaled","Map","this","scaleRarityRates","validateConfig","_proto","prototype","scaled","_i","_Object$entries","Object","entries","length","_Object$entries$_i","rarity","rate","Error","toScaled","probability","MAX_SAFE_SCALE","SCALE","Math","round","fromScaled","scaledInt","originalRates","configuredRarities","Set","keys","usedRarities","map","p","missingArray","Array","from","filter","r","has","join","totalRate","values","reduce","sum","totalScaled","abs","console","warn","_iterator","_step","_createForOfIteratorHelperLoose","done","pool","value","items","i","weight","_step2","_iterator2","item","name","hasPositiveWeight","some","getItemDropRate","get","_step3","_iterator3","find","set","totalPoolWeight","baseRarityRateScaled","itemWeightScaled","totalWeightScaled","numeratorScaled","rateScaled","getRarityProbability","getCumulativeProbabilityForItem","rolls","getItemDropRateScaled","failRate","cumulativeFailProbability","pow","min","max","getRollsForTargetProbability","targetProbability","Infinity","ceil","log","getRateUpItems","flatMap","rateUp","getAllItemDropRates","_this","dropRate","roll","count","_this2","results","_loop","selectRarity","selectItemFromPool","push","rand","floor","random","cumulativeScaled","_i2","_Object$entries2","_Object$entries2$_i","_this3","selectableItems","_step4","scaledItems","_extends","scaledWeight","totalScaledWeight","cumulative","_iterator4","getDebugInfo","rarityRatesFloat","_i3","_Object$entries3","_Object$entries3$_i","scale","Number","MAX_SAFE_INTEGER","_GachaEngine"],"mappings":"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"}
package/dist/index.umd.js CHANGED
@@ -1,2 +1,2 @@
1
- !function(t,r){"object"==typeof exports&&"undefined"!=typeof module?r(exports):"function"==typeof define&&define.amd?define(["exports"],r):r((t||self).AllemandiGachaEngine={})}(this,function(t){function r(t,r){(null==r||r>t.length)&&(r=t.length);for(var e=0,n=Array(r);e<r;e++)n[e]=t[e];return n}function e(t,e){var n="undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(n)return(n=n.call(t)).next.bind(n);if(Array.isArray(t)||(n=function(t,e){if(t){if("string"==typeof t)return r(t,e);var n={}.toString.call(t).slice(8,-1);return"Object"===n&&t.constructor&&(n=t.constructor.name),"Map"===n||"Set"===n?Array.from(t):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?r(t,e):void 0}}(t))||e&&t&&"number"==typeof t.length){n&&(t=n);var i=0;return function(){return i>=t.length?{done:!0}:{done:!1,value:t[i++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}t.GachaEngine=/*#__PURE__*/function(){function t(t){var r=t.rarityRates,e=t.pools;this.pools=void 0,this.rarityRates=void 0,this.dropRateCache=new Map,this.pools=e,this.rarityRates=r,this.validateConfig()}var r=t.prototype;return r.validateConfig=function(){var t=new Set(Object.keys(this.rarityRates)),r=new Set(this.pools.map(function(t){return t.rarity})),n=[].concat(r).filter(function(r){return!t.has(r)});if(n.length>0)throw new Error("Missing rarity rates for: "+n.join(", "));for(var i,o=e(this.pools);!(i=o()).done;){var a=i.value;if(0===a.items.length)throw new Error('Rarity "'+a.rarity+'" has no items');if(a.items.reduce(function(t,r){return t+r.weight},0)<=0)throw new Error('Rarity "'+a.rarity+'" has zero total weight')}},r.getItemDropRate=function(t){if(this.dropRateCache.has(t))return this.dropRateCache.get(t);for(var r,n=e(this.pools);!(r=n()).done;){var i=r.value,o=i.items.find(function(r){return r.name===t});if(o){var a=i.items.reduce(function(t,r){return t+r.weight},0),s=o.weight/a*this.rarityRates[i.rarity];return this.dropRateCache.set(t,s),s}}throw new Error('Item "'+t+'" not found')},r.getRarityProbability=function(t){if(!this.rarityRates[t])throw new Error('Rarity "'+t+'" not found');return this.rarityRates[t]},r.getCumulativeProbabilityForItem=function(t,r){var e=this.getItemDropRate(t);return 1-Math.pow(1-e,r)},r.getRollsForTargetProbability=function(t,r){var e=this.getItemDropRate(t);return e<=0?Infinity:Math.ceil(Math.log(1-r)/Math.log(1-e))},r.getRateUpItems=function(){return this.pools.flatMap(function(t){return t.items.filter(function(t){return t.rateUp}).map(function(t){return t.name})})},r.getAllItemDropRates=function(){var t=this;return this.pools.flatMap(function(r){return r.items.map(function(e){return{name:e.name,dropRate:t.getItemDropRate(e.name),rarity:r.rarity}})})},r.roll=function(t){var r=this;void 0===t&&(t=1);for(var e=[],n=function(){var t=r.selectRarity(),n=r.pools.find(function(r){return r.rarity===t}),i=r.selectItemFromPool(n);e.push(i.name)},i=0;i<t;i++)n();return e},r.selectRarity=function(){for(var t=Math.random(),r=0,e=0,n=Object.entries(this.rarityRates);e<n.length;e++){var i=n[e];if(t<=(r+=i[1]))return i[0]}return Object.keys(this.rarityRates)[0]},r.selectItemFromPool=function(t){for(var r,n=t.items.reduce(function(t,r){return t+r.weight},0),i=Math.random()*n,o=0,a=e(t.items);!(r=a()).done;){var s=r.value;if(i<=(o+=s.weight))return s}return t.items[0]},t}()});
1
+ !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t||self).AllemandiGachaEngine={})}(this,function(t){function e(t,e){(null==e||e>t.length)&&(e=t.length);for(var r=0,a=Array(e);r<e;r++)a[r]=t[r];return a}function r(t,r){var a="undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(a)return(a=a.call(t)).next.bind(a);if(Array.isArray(t)||(a=function(t,r){if(t){if("string"==typeof t)return e(t,r);var a={}.toString.call(t).slice(8,-1);return"Object"===a&&t.constructor&&(a=t.constructor.name),"Map"===a||"Set"===a?Array.from(t):"Arguments"===a||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(a)?e(t,r):void 0}}(t))||r&&t&&"number"==typeof t.length){a&&(t=a);var n=0;return function(){return n>=t.length?{done:!0}:{done:!1,value:t[n++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function a(){return a=Object.assign?Object.assign.bind():function(t){for(var e=1;e<arguments.length;e++){var r=arguments[e];for(var a in r)({}).hasOwnProperty.call(r,a)&&(t[a]=r[a])}return t},a.apply(null,arguments)}var n,i=/*#__PURE__*/function(){function t(t){var e=t.rarityRates,r=t.pools;this.pools=void 0,this.rarityRatesScaled=void 0,this.dropRateCacheScaled=new Map,this.pools=r,this.rarityRatesScaled=this.scaleRarityRates(e),this.validateConfig(e)}var e=t.prototype;return e.scaleRarityRates=function(t){for(var e={},r=0,a=Object.entries(t);r<a.length;r++){var n=a[r],i=n[0],o=n[1];if(o<0||o>1)throw new Error('Rarity rate for "'+i+'" must be between 0 and 1, got '+o);e[i]=this.toScaled(o)}return e},e.toScaled=function(e){if(e>t.MAX_SAFE_SCALE/t.SCALE)throw new Error("Probability "+e+" too large for safe integer arithmetic");return Math.round(e*t.SCALE)},e.fromScaled=function(e){return e/t.SCALE},e.validateConfig=function(e){var a=new Set(Object.keys(this.rarityRatesScaled)),n=new Set(this.pools.map(function(t){return t.rarity})),i=Array.from(n).filter(function(t){return!a.has(t)});if(i.length>0)throw new Error("Missing rarity rates for: "+i.join(", "));var o=Object.values(e).reduce(function(t,e){return t+e},0),s=Object.values(this.rarityRatesScaled).reduce(function(t,e){return t+e},0);if(Math.abs(o-1)>1e-10)throw new Error("Rarity rates must sum to 1.0, got "+o);Math.abs(s-t.SCALE)>Object.keys(this.rarityRatesScaled).length&&console.warn("Scaled rates sum to "+s+", expected "+t.SCALE+". This is likely due to rounding.");for(var l,u=r(this.pools);!(l=u()).done;){var c=l.value;if(0===c.items.length)throw new Error('Rarity "'+c.rarity+'" has no items');if(c.items.reduce(function(t,e){return t+e.weight},0)<=0)throw new Error('Rarity "'+c.rarity+'" has zero total weight');for(var h,f=r(c.items);!(h=f()).done;){var d=h.value;if(d.weight<0)throw new Error('Item "'+d.name+'" weight must be non-negative, got '+d.weight)}var m=c.items.some(function(t){return t.weight>0});if(!m)throw new Error('Rarity "'+c.rarity+'" must have at least one item with positive weight')}},e.getItemDropRate=function(e){if(this.dropRateCacheScaled.has(e))return this.fromScaled(this.dropRateCacheScaled.get(e));for(var a,n=r(this.pools);!(a=n()).done;){var i=a.value,o=i.items.find(function(t){return t.name===e});if(o){if(0===o.weight)return this.dropRateCacheScaled.set(e,0),0;var s=i.items.reduce(function(t,e){return t+e.weight},0),l=this.rarityRatesScaled[i.rarity],u=this.toScaled(o.weight),c=this.toScaled(s),h=Math.round(u*l/t.SCALE),f=Math.round(h*t.SCALE/c);return this.dropRateCacheScaled.set(e,f),this.fromScaled(f)}}throw new Error('Item "'+e+'" not found')},e.getRarityProbability=function(t){if(!this.rarityRatesScaled[t])throw new Error('Rarity "'+t+'" not found');return this.fromScaled(this.rarityRatesScaled[t])},e.getCumulativeProbabilityForItem=function(e,r){var a=this.getItemDropRateScaled(e);if(0===a)return 0;if(a>=t.SCALE)return 1;var n=this.fromScaled(t.SCALE-a),i=Math.pow(n,r);return Math.min(1,Math.max(0,1-i))},e.getRollsForTargetProbability=function(t,e){if(e<=0)return 0;if(e>=1)return 1;var r=this.getItemDropRate(t);return r<=0?Infinity:r>=1?1:Math.ceil(Math.log(1-e)/Math.log(1-r))},e.getRateUpItems=function(){return this.pools.flatMap(function(t){return t.items.filter(function(t){return t.rateUp}).map(function(t){return t.name})})},e.getAllItemDropRates=function(){var t=this;return this.pools.flatMap(function(e){return e.items.map(function(r){return{name:r.name,dropRate:t.getItemDropRate(r.name),rarity:e.rarity}})})},e.getItemDropRateScaled=function(t){return this.dropRateCacheScaled.has(t)||this.getItemDropRate(t),this.dropRateCacheScaled.get(t)},e.roll=function(t){var e=this;void 0===t&&(t=1);for(var r=[],a=function(){var t=e.selectRarity(),a=e.pools.find(function(e){return e.rarity===t}),n=e.selectItemFromPool(a);r.push(n.name)},n=0;n<t;n++)a();return r},e.selectRarity=function(){for(var e=Math.floor(Math.random()*t.SCALE),r=0,a=0,n=Object.entries(this.rarityRatesScaled);a<n.length;a++){var i=n[a];if(e<(r+=i[1]))return i[0]}return Object.keys(this.rarityRatesScaled)[0]},e.selectItemFromPool=function(t){var e=this,n=t.items.filter(function(t){return t.weight>0});if(0===n.length)throw new Error('No selectable items in pool for rarity "'+t.rarity+'"');for(var i,o=n.map(function(t){return a({},t,{scaledWeight:e.toScaled(t.weight)})}),s=o.reduce(function(t,e){return t+e.scaledWeight},0),l=Math.floor(Math.random()*s),u=0,c=r(o);!(i=c()).done;){var h=i.value;if(l<(u+=h.scaledWeight))return{name:h.name,weight:h.weight}}return n[0]},e.getDebugInfo=function(){for(var e={},r=0,n=Object.entries(this.rarityRatesScaled);r<n.length;r++){var i=n[r];e[i[0]]=this.fromScaled(i[1])}return{scale:t.SCALE,rarityRatesScaled:a({},this.rarityRatesScaled),rarityRatesFloat:e}},t}();n=i,i.SCALE=1e6,i.MAX_SAFE_SCALE=Math.floor(Number.MAX_SAFE_INTEGER/n.SCALE),t.GachaEngine=i});
2
2
  //# sourceMappingURL=index.umd.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.umd.js","sources":["../src/gacha-engine.ts"],"sourcesContent":["import { RarityInput, GachaEngineConfig } from './types';\n\nexport class GachaEngine {\n private pools: RarityInput[];\n private rarityRates: Record<string, number>;\n private dropRateCache = new Map<string, number>();\n\n constructor({ rarityRates, pools }: GachaEngineConfig) {\n this.pools = pools;\n this.rarityRates = rarityRates;\n this.validateConfig();\n }\n\n private validateConfig(): void {\n const configuredRarities = new Set(Object.keys(this.rarityRates));\n const usedRarities = new Set(this.pools.map(p => p.rarity));\n const missing = [...usedRarities].filter(r => !configuredRarities.has(r));\n if (missing.length > 0) {\n throw new Error(`Missing rarity rates for: ${missing.join(', ')}`);\n }\n\n for (const pool of this.pools) {\n if (pool.items.length === 0) {\n throw new Error(`Rarity \"${pool.rarity}\" has no items`);\n }\n const totalWeight = pool.items.reduce((sum, i) => sum + i.weight, 0);\n if (totalWeight <= 0) {\n throw new Error(`Rarity \"${pool.rarity}\" has zero total weight`);\n }\n }\n }\n\n getItemDropRate(name: string): number {\n if (this.dropRateCache.has(name)) {\n return this.dropRateCache.get(name)!;\n }\n\n for (const pool of this.pools) {\n const item = pool.items.find(i => i.name === name);\n if (item) {\n const totalPoolWeight = pool.items.reduce((sum, i) => sum + i.weight, 0);\n const baseRarityRate = this.rarityRates[pool.rarity];\n const rate = (item.weight / totalPoolWeight) * baseRarityRate;\n this.dropRateCache.set(name, rate);\n return rate;\n }\n }\n throw new Error(`Item \"${name}\" not found`);\n }\n\n getRarityProbability(rarity: string): number {\n if (!this.rarityRates[rarity]) {\n throw new Error(`Rarity \"${rarity}\" not found`);\n }\n return this.rarityRates[rarity];\n }\n\n getCumulativeProbabilityForItem(name: string, rolls: number): number {\n const rate = this.getItemDropRate(name);\n return 1 - Math.pow(1 - rate, rolls);\n }\n\n getRollsForTargetProbability(name: string, targetProbability: number): number {\n const rate = this.getItemDropRate(name);\n if (rate <= 0) return Infinity;\n return Math.ceil(Math.log(1 - targetProbability) / Math.log(1 - rate));\n }\n\n getRateUpItems(): string[] {\n return this.pools.flatMap(p =>\n p.items.filter(i => i.rateUp).map(i => i.name)\n );\n }\n\n getAllItemDropRates(): { name: string; dropRate: number; rarity: string }[] {\n return this.pools.flatMap(p =>\n p.items.map(i => ({\n name: i.name,\n dropRate: this.getItemDropRate(i.name),\n rarity: p.rarity\n }))\n );\n }\n\n roll(count: number = 1): string[] {\n const results: string[] = [];\n for (let i = 0; i < count; i++) {\n const rarity = this.selectRarity();\n const pool = this.pools.find(p => p.rarity === rarity)!;\n const item = this.selectItemFromPool(pool);\n results.push(item.name);\n }\n return results;\n }\n\n private selectRarity(): string {\n const rand = Math.random();\n let cumulative = 0;\n for (const [rarity, rate] of Object.entries(this.rarityRates)) {\n cumulative += rate;\n if (rand <= cumulative) return rarity;\n }\n return Object.keys(this.rarityRates)[0];\n }\n\n private selectItemFromPool(pool: RarityInput): { name: string; weight: number } {\n const totalWeight = pool.items.reduce((sum, i) => sum + i.weight, 0);\n const rand = Math.random() * totalWeight;\n let cumulative = 0;\n for (const item of pool.items) {\n cumulative += item.weight;\n if (rand <= cumulative) return item;\n }\n return pool.items[0];\n }\n}"],"names":["GachaEngine","_ref","rarityRates","pools","this","dropRateCache","Map","validateConfig","_proto","prototype","configuredRarities","Set","Object","keys","usedRarities","map","p","rarity","missing","concat","filter","r","has","length","Error","join","_iterator","_step","_createForOfIteratorHelperLoose","done","pool","value","items","reduce","sum","i","weight","getItemDropRate","name","get","_iterator2","_step2","item","find","totalPoolWeight","rate","set","getRarityProbability","getCumulativeProbabilityForItem","rolls","Math","pow","getRollsForTargetProbability","targetProbability","Infinity","ceil","log","getRateUpItems","flatMap","rateUp","getAllItemDropRates","_this","dropRate","roll","count","_this2","results","_loop","selectRarity","selectItemFromPool","push","rand","random","cumulative","_i","_Object$entries","entries","_Object$entries$_i","_step3","totalWeight","_iterator3"],"mappings":"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"}
1
+ {"version":3,"file":"index.umd.js","sources":["../src/gacha-engine.ts"],"sourcesContent":["import { RarityInput, GachaEngineConfig } from './types';\n\nexport class GachaEngine {\n // Scale factor for fixed-point arithmetic (1,000,000 = 6 decimal places)\n private static readonly SCALE = 1000000;\n private static readonly MAX_SAFE_SCALE = Math.floor(Number.MAX_SAFE_INTEGER / this.SCALE);\n\n private pools: RarityInput[];\n private rarityRatesScaled: Record<string, number>; // Scaled to integers\n private dropRateCacheScaled = new Map<string, number>(); // Cache scaled rates\n\n constructor({ rarityRates, pools }: GachaEngineConfig) {\n this.pools = pools;\n this.rarityRatesScaled = this.scaleRarityRates(rarityRates);\n this.validateConfig(rarityRates);\n }\n\n /**\n * Convert floating point rates to scaled integers\n */\n private scaleRarityRates(rarityRates: Record<string, number>): Record<string, number> {\n const scaled: Record<string, number> = {};\n for (const [rarity, rate] of Object.entries(rarityRates)) {\n if (rate < 0 || rate > 1) {\n throw new Error(`Rarity rate for \"${rarity}\" must be between 0 and 1, got ${rate}`);\n }\n scaled[rarity] = this.toScaled(rate);\n }\n return scaled;\n }\n\n /**\n * Convert probability to scaled integer\n */\n private toScaled(probability: number): number {\n if (probability > GachaEngine.MAX_SAFE_SCALE / GachaEngine.SCALE) {\n throw new Error(`Probability ${probability} too large for safe integer arithmetic`);\n }\n return Math.round(probability * GachaEngine.SCALE);\n }\n\n /**\n * Convert scaled integer back to probability\n */\n private fromScaled(scaledInt: number): number {\n return scaledInt / GachaEngine.SCALE;\n }\n\n private validateConfig(originalRates: Record<string, number>): void {\n const configuredRarities = new Set(Object.keys(this.rarityRatesScaled));\n const usedRarities = new Set(this.pools.map(p => p.rarity));\n const missingArray = Array.from(usedRarities).filter(r => !configuredRarities.has(r));\n \n if (missingArray.length > 0) {\n throw new Error(`Missing rarity rates for: ${missingArray.join(', ')}`);\n }\n\n // Validate that rates sum to exactly 1.0 (within floating point precision)\n const totalRate = Object.values(originalRates).reduce((sum, rate) => sum + rate, 0);\n const totalScaled = Object.values(this.rarityRatesScaled).reduce((sum, rate) => sum + rate, 0);\n \n if (Math.abs(totalRate - 1.0) > 1e-10) {\n throw new Error(`Rarity rates must sum to 1.0, got ${totalRate}`);\n }\n\n // Ensure scaled rates sum to SCALE (accounting for rounding)\n if (Math.abs(totalScaled - GachaEngine.SCALE) > Object.keys(this.rarityRatesScaled).length) {\n console.warn(`Scaled rates sum to ${totalScaled}, expected ${GachaEngine.SCALE}. This is likely due to rounding.`);\n }\n\n for (const pool of this.pools) {\n if (pool.items.length === 0) {\n throw new Error(`Rarity \"${pool.rarity}\" has no items`);\n }\n \n const totalWeight = pool.items.reduce((sum, i) => sum + i.weight, 0);\n if (totalWeight <= 0) {\n throw new Error(`Rarity \"${pool.rarity}\" has zero total weight`);\n }\n\n // Validate that all weights are non-negative\n for (const item of pool.items) {\n if (item.weight < 0) {\n throw new Error(`Item \"${item.name}\" weight must be non-negative, got ${item.weight}`);\n }\n }\n\n // Ensure at least one item has positive weight\n const hasPositiveWeight = pool.items.some(item => item.weight > 0);\n if (!hasPositiveWeight) {\n throw new Error(`Rarity \"${pool.rarity}\" must have at least one item with positive weight`);\n }\n }\n }\n\n getItemDropRate(name: string): number {\n if (this.dropRateCacheScaled.has(name)) {\n return this.fromScaled(this.dropRateCacheScaled.get(name)!);\n }\n\n for (const pool of this.pools) {\n const item = pool.items.find(i => i.name === name);\n if (item) {\n // Handle zero weight items (never drop)\n if (item.weight === 0) {\n this.dropRateCacheScaled.set(name, 0);\n return 0;\n }\n\n const totalPoolWeight = pool.items.reduce((sum, i) => sum + i.weight, 0);\n const baseRarityRateScaled = this.rarityRatesScaled[pool.rarity];\n \n // Convert weights to scaled integers for perfect precision\n const itemWeightScaled = this.toScaled(item.weight);\n const totalWeightScaled = this.toScaled(totalPoolWeight);\n \n // Scaled arithmetic: (itemWeight * baseRate) / totalWeight\n const numeratorScaled = Math.round((itemWeightScaled * baseRarityRateScaled) / GachaEngine.SCALE);\n const rateScaled = Math.round((numeratorScaled * GachaEngine.SCALE) / totalWeightScaled);\n \n this.dropRateCacheScaled.set(name, rateScaled);\n return this.fromScaled(rateScaled);\n }\n }\n throw new Error(`Item \"${name}\" not found`);\n }\n\n getRarityProbability(rarity: string): number {\n if (!this.rarityRatesScaled[rarity]) {\n throw new Error(`Rarity \"${rarity}\" not found`);\n }\n return this.fromScaled(this.rarityRatesScaled[rarity]);\n }\n\n getCumulativeProbabilityForItem(name: string, rolls: number): number {\n const rateScaled = this.getItemDropRateScaled(name);\n \n if (rateScaled === 0) return 0;\n if (rateScaled >= GachaEngine.SCALE) return 1;\n \n // Calculate (1 - rate)^rolls using scaled arithmetic\n const failRateScaled = GachaEngine.SCALE - rateScaled;\n const failRate = this.fromScaled(failRateScaled);\n \n // For large rolls, we need to be careful with precision\n const cumulativeFailProbability = Math.pow(failRate, rolls);\n const cumulativeProbability = 1 - cumulativeFailProbability;\n \n return Math.min(1, Math.max(0, cumulativeProbability));\n }\n\n getRollsForTargetProbability(name: string, targetProbability: number): number {\n if (targetProbability <= 0) return 0;\n if (targetProbability >= 1) return 1;\n \n const rate = this.getItemDropRate(name);\n if (rate <= 0) return Infinity;\n if (rate >= 1) return 1;\n \n return Math.ceil(Math.log(1 - targetProbability) / Math.log(1 - rate));\n }\n\n getRateUpItems(): string[] {\n return this.pools.flatMap(p =>\n p.items.filter(i => i.rateUp).map(i => i.name)\n );\n }\n\n getAllItemDropRates(): { name: string; dropRate: number; rarity: string }[] {\n return this.pools.flatMap(p =>\n p.items.map(i => ({\n name: i.name,\n dropRate: this.getItemDropRate(i.name),\n rarity: p.rarity\n }))\n );\n }\n\n /**\n * Get scaled drop rate for internal calculations\n */\n private getItemDropRateScaled(name: string): number {\n if (this.dropRateCacheScaled.has(name)) {\n return this.dropRateCacheScaled.get(name)!;\n }\n \n // Trigger calculation and caching\n this.getItemDropRate(name);\n return this.dropRateCacheScaled.get(name)!;\n }\n\n roll(count: number = 1): string[] {\n const results: string[] = [];\n for (let i = 0; i < count; i++) {\n const rarity = this.selectRarity();\n const pool = this.pools.find(p => p.rarity === rarity)!;\n const item = this.selectItemFromPool(pool);\n results.push(item.name);\n }\n return results;\n }\n\n private selectRarity(): string {\n const rand = Math.floor(Math.random() * GachaEngine.SCALE);\n let cumulativeScaled = 0;\n \n for (const [rarity, rateScaled] of Object.entries(this.rarityRatesScaled)) {\n cumulativeScaled += rateScaled;\n if (rand < cumulativeScaled) return rarity;\n }\n \n // Fallback (should never happen with proper validation)\n return Object.keys(this.rarityRatesScaled)[0];\n }\n\n private selectItemFromPool(pool: RarityInput): { name: string; weight: number } {\n // Filter out zero-weight items (they can never be selected)\n const selectableItems = pool.items.filter(item => item.weight > 0);\n \n if (selectableItems.length === 0) {\n throw new Error(`No selectable items in pool for rarity \"${pool.rarity}\"`);\n }\n\n // Convert all weights to scaled integers for perfect precision\n const scaledItems = selectableItems.map(item => ({\n ...item,\n scaledWeight: this.toScaled(item.weight)\n }));\n \n const totalScaledWeight = scaledItems.reduce((sum, item) => sum + item.scaledWeight, 0);\n const rand = Math.floor(Math.random() * totalScaledWeight);\n let cumulative = 0;\n \n for (const item of scaledItems) {\n cumulative += item.scaledWeight;\n if (rand < cumulative) {\n return { name: item.name, weight: item.weight };\n }\n }\n \n // Fallback (should never happen)\n return selectableItems[0];\n }\n\n /**\n * Debug method to inspect scaled values\n */\n getDebugInfo(): {\n scale: number;\n rarityRatesScaled: Record<string, number>;\n rarityRatesFloat: Record<string, number>;\n } {\n const rarityRatesFloat: Record<string, number> = {};\n for (const [rarity, scaledRate] of Object.entries(this.rarityRatesScaled)) {\n rarityRatesFloat[rarity] = this.fromScaled(scaledRate);\n }\n \n return {\n scale: GachaEngine.SCALE,\n rarityRatesScaled: { ...this.rarityRatesScaled },\n rarityRatesFloat\n };\n }\n}"],"names":["GachaEngine","_ref","rarityRates","pools","rarityRatesScaled","dropRateCacheScaled","Map","this","scaleRarityRates","validateConfig","_proto","prototype","scaled","_i","_Object$entries","Object","entries","length","_Object$entries$_i","rarity","rate","Error","toScaled","probability","MAX_SAFE_SCALE","SCALE","Math","round","fromScaled","scaledInt","originalRates","configuredRarities","Set","keys","usedRarities","map","p","missingArray","Array","from","filter","r","has","join","totalRate","values","reduce","sum","totalScaled","abs","console","warn","_iterator","_step","_createForOfIteratorHelperLoose","done","pool","value","items","i","weight","_step2","_iterator2","item","name","hasPositiveWeight","some","getItemDropRate","get","_step3","_iterator3","find","set","totalPoolWeight","baseRarityRateScaled","itemWeightScaled","totalWeightScaled","numeratorScaled","rateScaled","getRarityProbability","getCumulativeProbabilityForItem","rolls","getItemDropRateScaled","failRate","cumulativeFailProbability","pow","min","max","getRollsForTargetProbability","targetProbability","Infinity","ceil","log","getRateUpItems","flatMap","rateUp","getAllItemDropRates","_this","dropRate","roll","count","_this2","results","_loop","selectRarity","selectItemFromPool","push","rand","floor","random","cumulativeScaled","_i2","_Object$entries2","_Object$entries2$_i","_this3","selectableItems","_step4","scaledItems","_extends","scaledWeight","totalScaledWeight","cumulative","_iterator4","getDebugInfo","rarityRatesFloat","_i3","_Object$entries3","_Object$entries3$_i","scale","Number","MAX_SAFE_INTEGER","_GachaEngine"],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -4,10 +4,22 @@ export interface RarityInput {
4
4
  }
5
5
  export interface GachaItem {
6
6
  name: string;
7
+ /**
8
+ * Weight determines relative probability within the rarity tier.
9
+ * Can be fractional (e.g., 0.5, 1.2) - will be converted to scaled integers internally.
10
+ * Use weight: 0 for items that should never drop (useful for placeholders or disabled items).
11
+ * Higher weights = higher probability within the rarity tier.
12
+ */
7
13
  weight: number;
8
14
  rateUp?: boolean;
9
15
  }
10
16
  export interface GachaEngineConfig {
17
+ /**
18
+ * Probability rates for each rarity tier.
19
+ * Must sum to exactly 1.0.
20
+ * Example: { "common": 0.85, "rare": 0.12, "legendary": 0.03 }
21
+ */
11
22
  rarityRates: Record<string, number>;
23
+ /** Array of rarity pools containing items */
12
24
  pools: RarityInput[];
13
25
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allemandi/gacha-engine",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Practical, type-safe toolkit for simulating and understanding gacha rates and rate-ups.",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.module.js",