How HoshinoBot Gacha Simulation Works: Probability Mechanics and Pool System
The HoshinoBot gacha system simulates Princess Connect! Re:Dive draws using integer probability weights per 1000, implementing single draws, ten-pulls with guaranteed 3★ on the 10th pull, and a ten-well pity system in the Gacha class at hoshino/modules/priconne/gacha/gacha.py.
The ice9coffee/hoshinobot repository provides a comprehensive gacha (抽卡) simulation module for the popular game Princess Connect! Re:Dive. This system handles multiple regional pools (MIX, JP, TW, BL) with configurable drop rates, pity mechanics, and reward calculations. Understanding how HoshinoBot gacha simulation processes probabilities requires examining the core Gacha class and its interaction with group-specific configurations.
Pool Configuration and Probability Structure
The Gacha class initializes by loading YAML-style configuration files via util.load_config(__file__) in hoshino/modules/priconne/gacha/gacha.py (lines 25-30). Each pool defines four critical probability fields expressed as integers per 1000 to avoid floating-point errors:
up_prob: The weight for drawing an UP 3-star character (e.g.,12equals 1.2%)s3_prob: Base probability for non-UP 3-star characterss2_prob: Probability for 2-star characterss1_prob: Calculated as1000 - s2_prob - s3_probfor 1-star characters (line 28)
Pool names (MIX, JP, TW, or BL) are stored per-group in group_pool_config.json and loaded during module initialization in hoshino/modules/priconne/gacha/__init__.py (lines 56-60). The sum of all probability weights always equals exactly 1000, ensuring deterministic outcome distribution.
Single Draw Implementation (gacha_one)
The core random selection logic resides in gacha_one (lines 35-58). This method implements a discrete probability distribution using a single random.randint(1, total_) call where total_ = s3_prob + s2_prob + s1_prob (always 1000).
The algorithm compares the random pick against cumulative thresholds:
- If
pick <= up_prob: Returns a random character fromself.up(3★ UP) with 100 memory pieces - If
pick <= s3_prob: Returns a random character fromself.star3(3★ non-UP) with 50 memory pieces - If
pick <= s2_prob + s3_prob: Returns a random character fromself.star2(2★) with 10 memory pieces - Otherwise: Returns a random character from
self.star1(1★) with 1 memory piece
This threshold-based approach guarantees that the configured weights directly translate to exact draw probabilities without complex weighting algorithms.
Ten-Pull Mechanics and Guaranteed Drops
The gacha_ten method (lines 61-76) simulates a standard ten-pull with pity mechanics matching the official game. The implementation performs nine normal draws followed by a modified 10th draw:
c, y = self.gacha_one(up, s3, s2, s1) # draws 1-9
c, y = self.gacha_one(up, s3, s2 + s1, 0) # 10th draw: s1 forced to 0
By setting the 1-star probability to zero and combining 2-star and 1-star weights into the 2-star threshold, the 10th draw guarantees a 3-star character. If up_prob is non-zero, the conditional probability of drawing an UP character on this guaranteed slot equals up_prob / (s3_prob + up_prob).
Ten-Well (天井) Pity System
For deep simulations, gacha_tenjou (lines 79-112) implements the 天井 (pity) system, drawing until reaching a configurable line count. Standard pools use 200 draws, while the BL pool uses 300 draws (self.tenjou_line).
The method executes repeated groups of ten draws, where each group follows the same 9+1 pattern as gacha_ten. During execution, it tracks first_up_pos to record the draw position of the first UP character encountered. The simulation returns a structured dictionary containing lists of drawn characters by rarity (up, s3, s2, s1) and the first-UP position.
The "天井率" displayed to users derives from pool-specific definitions (e.g., 24.54% for standard pools, 12.16% for BL), calculated based on the configured up_prob and total 3-star rates.
Command Handling and Reward Distribution
Command registration occurs in hoshino/modules/priconne/gacha/__init__.py (lines 19-25) under the gacha service. The system processes three primary user commands:
- Single draw (
gacha_1): Invokesgacha_oneand awards "记忆碎片" proportional to the returnedhiishivalue (lines 102-117) - Ten-pull (
gacha_10): Callsgacha_ten, concatenates dual 5-character team pictures, and applies a super-lucky silence multiplier when total memory pieces exceed 170 (lines 121-149) - Ten-well (
gacha_tenjou): Executesgacha_tenjou, shuffles 3★ results for visual variety, and generates detailed summaries including goddess stone counts and first-UP position (lines 152-185)
Practical Code Examples
To simulate a single draw from the default MIX pool:
from hoshino.modules.priconne.gacha.gacha import Gacha
gacha = Gacha(pool_name="MIX")
character, reward = gacha.gacha_one(
up_prob=gacha.up_prob,
s3_prob=gacha.s3_prob,
s2_prob=gacha.s2_prob,
)
print(f"Got {character.name} ({character.star}★), reward = {reward}")
To execute a ten-pull and analyze results:
result, total_reward = gacha.gacha_ten()
print("10‑pull results:")
for ch in result:
print(f" - {ch.name} ({ch.star}★)")
print(f"Total memory pieces earned: {total_reward}")
To simulate a ten-well and locate the first UP character:
tenjou = gacha.gacha_tenjou()
print(f"First UP appeared at draw #{tenjou['first_up_pos']}")
print(f"UP count: {len(tenjou['up'])}, 3★ non‑UP: {len(tenjou['s3'])}")
Summary
- Integer-based probabilities: All rates store as per-1000 weights in
config.yaml, eliminating floating-point precision issues - Threshold randomization:
gacha_oneuses singlerandintcalls with cumulative comparisons for O(1) rarity determination - Guaranteed 3★ mechanics: Ten-pulls force 1-star probability to zero on the 10th draw, ensuring minimum rarity drops
- Configurable pity lines: Ten-well simulations respect pool-specific limits (200 for MIX/JP/TW, 300 for BL) and track UP character acquisition points
- Modular reward system: Memory piece values scale by rarity (100/50/10/1) and integrate with the bot's group configuration system
Frequently Asked Questions
How are gacha probabilities stored in HoshinoBot?
HoshinoBot stores all gacha probabilities as integer values per 1000 in YAML configuration files loaded via util.load_config(__file__). The Gacha class extracts up_prob, s3_prob, and s2_prob directly, calculating s1_prob as the remainder to ensure weights sum to exactly 1000. This approach guarantees precise probability distributions without floating-point rounding errors.
Does the ten-pull guarantee really force a 3-star character?
Yes. According to lines 61-76 in gacha.py, the 10th draw in gacha_ten modifies the probability parameters by setting the 1-star weight to zero and redistributing that weight to 2-star outcomes. This mathematical adjustment ensures the 10th draw always selects from the 3-star pool (either UP or standard), matching the official Princess Connect! Re:Dive gacha mechanics.
What is the difference between gacha_ten and gacha_tenjou?
gacha_ten performs exactly one ten-pull sequence (9 normal draws + 1 guaranteed 3★), while gacha_tenjou simulates continuous ten-pulls until reaching the pity line count—200 draws for most pools or 300 for the BL pool. The ten-well method tracks the first appearance of UP characters and calculates comprehensive statistics across the entire simulation, whereas the standard ten-pull returns only the immediate results.
How does the bot determine which pool to use for a group?
Pool selection depends on group_pool_config.json, which stores the active pool name (MIX, JP, TW, or BL) per group ID. When processing commands in hoshino/modules/priconne/gacha/__init__.py (lines 56-60), the bot loads this configuration and instantiates the Gacha class with the corresponding pool parameters, ensuring each group maintains independent gacha settings.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →