Tree statistics are database-side opening views. They start from a filtered set of games, read the ply hint stored in each filter value, and group the next move played at that point. The result is not a decoded forest of games; it is a compact set of TreeNode values suitable for opening explorers, novelty workflows and "what was played here?" interfaces.
setPositionSearchFilter() usually prepares the input. It rewrites an HFilter so matching games are included and the stored byte is ply + 1: value one means the game reaches the requested position at the start, value two means after the first half-move, and so on. getTreeStat() then consumes that filter and folds all visible next moves into tree nodes.
For each visible game, scidBaseT reads the filter value, subtracts one to recover the ply, and asks the index-backed stored-line code for the move at that ply. Stored lines cover common opening prefixes and let the tree answer many early-position questions without decoding game text. If the stored line has no move for that ply, the database opens a lightweight game view and reads the move from the encoded game record.
The tree node itself is deliberately small. TreeNode owns the candidate FullMove, the total frequency, result buckets, rating sums and year sums. Convenience methods such as score(), eloPerformance(), avgElo() and avgYear() turn those sums into the numbers a caller normally wants to display. The returned nodes are sorted by descending game count.
This diagram shows the public tree model and the nearby database machinery it depends on. The stored-line and game-view boxes are conceptual here: callers do not own those objects, but they explain why tree queries can often stay in metadata and only decode games when the index shortcut is not enough.
TreeNode is the only value returned to the caller. HFilter controls both inclusion and the ply to inspect, IndexEntry supplies result/rating/year metadata and the stored-line code, and FullMove identifies the candidate move.