The Danger Tree is a binary decision tree used by the AI to estimate the deal-in probability (hojuProb) for a specific discard. It is trained on historical game data and uses a Domain Specific Language (DSL) of tile-safety features (e.g., suji, kabe, urasuji) to navigate from the root to a leaf node containing a statistical probability.
The danger estimation system is split between a static data structure loaded at startup and a runtime evaluator that applies the current game state to that structure.
The following diagram illustrates how the embedded JSON is transformed into a runtime estimator and used during the decision process.
Diagram: Danger Tree Loading and Consumption
Sources: configs/danger_tree.go11-32 internal/domain/ai/danger.go11-24 internal/domain/ai/danger_scene.go14-25
The tree is composed of DecisionNode objects. Each node is either a branch (containing a feature to test) or a leaf (containing the final probability).
| Field | Type | Description |
|---|---|---|
average_prob | float64 | The statistical probability of dealing in at this node. |
conf_interval | [2]float64 | The confidence interval [low, high] for the probability. |
num_samples | int | Number of data points from the training set that reached this node. |
feature_name | *string | The DSL feature string to evaluate (e.g., "suji", "visible>=3"). nil for leaves. |
negative | *DecisionNode | The child node to visit if the feature evaluation returns false. |
positive | *DecisionNode | The child node to visit if the feature evaluation returns true. |
Sources: configs/danger_tree.go11-21
The tree is embedded into the binary using go:embed and unmarshaled via the configs package.
configs.LoadDangerTree() uses encoding/json/v2 to parse the raw bytes into a tree of DecisionNode pointers configs/danger_tree.go23-32DecisionNode implements the ai.DangerTreeNode interface configs/danger_tree.go34-60ai.NewDangerEstimator(), which wraps it in a DecisionTreeDangerEstimator internal/domain/ai/danger.go22-24Sources: configs/danger_tree.go23-60 internal/domain/ai/danger.go11-24
When DecisionTreeDangerEstimator.EstimateDealInProb is called, it performs the following steps:
SafeTiles (Genbutsu), it immediately returns 0 internal/domain/ai/danger.go36-38dangerScene, which captures a snapshot of the current round.State relative to the target opponent (e.g., their discards, riichi status, and visible tiles) internal/domain/ai/danger_scene.go27-60estimateDangerTreeProb traverses the tree starting from the root internal/domain/ai/danger.go43-64:
AverageProb internal/domain/ai/danger.go46-48FeatureName using dangerScene.evaluate() internal/domain/ai/danger.go53-56Positive or Negative child based on the boolean result internal/domain/ai/danger.go57-61The dangerScene.evaluate method supports a wide range of features defined in the training pipeline:
anpai, tsupai (honors), dora, suji, fanpai, ryenfonpai, bakaze, jikaze internal/domain/ai/danger_scene.go64-87urasuji, matagisuji, senkisuji, aida4ken internal/domain/ai/danger_scene.go95-117visible>=n: Checks if the tile is visible n+1 or more times internal/domain/ai/danger_scene.go127-133chances<=n: Kabe (wall) logic based on visible tiles internal/domain/ai/danger_scene.go120-126suji_in_tehais>=n: Number of suji tiles held in the bot's own hand internal/domain/ai/danger_scene.go148-154n_outer_prereach_sutehai: Positional discard patterns relative to 5 internal/domain/ai/danger_scene.go158-164min<=n<=max: Numerical range check for suit tiles internal/domain/ai/danger_scene.go155-157Diagram: Evaluation Logic Flow
Sources: internal/domain/ai/danger.go26-64 internal/domain/ai/danger_scene.go62-118
The evaluation of features relies on helper functions in danger_tiles.go to calculate complex Mahjong patterns:
isSujiOf: Checks if the discard is suji (1-4-7 etc.) relative to a set of tiles internal/domain/ai/danger_tiles.go63-78isNChanceOrLess: Implements Kabe (Wall) logic by checking if surrounding tiles are visible enough to make certain sequences impossible internal/domain/ai/danger_tiles.go92-114isUrasujiOf / isMatagisujiOf / isSenkisujiOf: Identifies discards that are dangerous based on discards that could have been part of a waiting sequence internal/domain/ai/danger_tiles.go140-174isOuter: Checks if a tile is "outside" another discard relative to the center tile 5 internal/domain/ai/danger_tiles.go176-203fanpaiValue: Calculates the value (1 or 2) of a honor tile based on Round and Seat winds internal/domain/ai/danger_tiles.go32-48