# How HoshinoBot Gacha Simulation Works: Probability Mechanics and Pool System

> Discover how HoshinoBot gacha simulation handles probabilities and its pool system. Understand the mechanics behind Princess Connect Re:Dive draws in this technical deep dive.

- Repository: [ice9coffee/hoshinobot](https://github.com/ice9coffee/hoshinobot)
- Tags: deep-dive
- Published: 2026-03-03

---

**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`](https://github.com/ice9coffee/hoshinobot/blob/main/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`](https://github.com/ice9coffee/hoshinobot/blob/main/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., `12` equals 1.2%)
- **`s3_prob`**: Base probability for non-UP 3-star characters  
- **`s2_prob`**: Probability for 2-star characters
- **`s1_prob`**: Calculated as `1000 - s2_prob - s3_prob` for 1-star characters (line 28)

Pool names (`MIX`, `JP`, `TW`, or `BL`) are stored per-group in [`group_pool_config.json`](https://github.com/ice9coffee/hoshinobot/blob/main/group_pool_config.json) and loaded during module initialization in [`hoshino/modules/priconne/gacha/__init__.py`](https://github.com/ice9coffee/hoshinobot/blob/main/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 from `self.up` (3★ UP) with 100 memory pieces
- If `pick <= s3_prob`: Returns a random character from `self.star3` (3★ non-UP) with 50 memory pieces  
- If `pick <= s2_prob + s3_prob`: Returns a random character from `self.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:

```python
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`](https://github.com/ice9coffee/hoshinobot/blob/main/hoshino/modules/priconne/gacha/__init__.py) (lines 19-25) under the `gacha` service. The system processes three primary user commands:

1. **Single draw** (`gacha_1`): Invokes `gacha_one` and awards "记忆碎片" proportional to the returned `hiishi` value (lines 102-117)
2. **Ten-pull** (`gacha_10`): Calls `gacha_ten`, concatenates dual 5-character team pictures, and applies a **super-lucky silence multiplier** when total memory pieces exceed 170 (lines 121-149)  
3. **Ten-well** (`gacha_tenjou`): Executes `gacha_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:

```python
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:

```python
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:

```python
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`](https://github.com/ice9coffee/hoshinobot/blob/main/config.yaml), eliminating floating-point precision issues
- **Threshold randomization**: `gacha_one` uses single `randint` calls 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`](https://github.com/ice9coffee/hoshinobot/blob/main/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`](https://github.com/ice9coffee/hoshinobot/blob/main/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`](https://github.com/ice9coffee/hoshinobot/blob/main/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.