-
Notifications
You must be signed in to change notification settings - Fork 0
03 09 Config play_stats
This page explains play_stats in Sprint Boost.
play_stats tracks selected values during play and can persist those values between sessions.
A common first use is high scores, but play_stats is not limited to high scores.
play_stats can track any value sourced from game_info or from expression, including:
- score
- lives-related counters
- items collected/used
- custom game-specific counters
- derived values built from several live values
- reusable true/false state checks used for save logic
If it can be represented through game_info directly, or built from game_info through expressions, it can usually be used by play_stats.
Key benefits:
- Persistence: keep tracked records between sessions
- Leaderboards: display ranked historical values. play_stats is a pre-requisite for the leaderboards layout element.
- Dynamic display layouts: show tracked values in custom overlays/screens
- Realtime display values: show current tracked session values live
play_stats is relevant for:
boost_mode = "enhanced"
In current Sprint Boost behavior, play-stats runtime flow is used with enhanced display/runtime paths.
[play_stats]
enabled = true
stats_file = "{gv.game_play_stats_path}"
auto_save_quit = true
auto_save_reset = true
[play_stats.players.p1]
label = "P1"
[play_stats.players.p1.tracked.score]
game_info = "p1_score"
mode = "final"What this does:
- Enables play-stats tracking
- Saves to a persistent stats file
- Tracks P1 score using
game_info = "p1_score"
Expressions are often the easiest way to make play_stats useful.
This is especially true when the game does not store a player-friendly value directly.
Common examples:
- build one real score from several raw score digits
- create a reusable
game_overflag - create a reusable
in_gameoron_title_screenflag
High-level rule of thumb:
- use
game_infoto read raw live values - use
expressionto clean up or combine those values - use
play_statsto track, save, and display the result
Example using an expression as the tracked value:
[play_stats.players.p1.tracked.score]
expression = { int = "derived_score" }
mode = "final"This means Sprint Boost is not tracking a raw memory value directly. It is tracking a score that was already built somewhere else in the config.
play_stats supports tracking for one or more players.
You can track the same stat pattern for multiple players, each under its own key:
play_stats.players.p1play_stats.players.p2- or any player key name you choose
For single-player games, a simple key like player is perfectly fine:
[play_stats.players.player]
label = "Player"players.<player>.label is used as the player name shown in leaderboards.
Practical tip:
- Short labels like
P1/P2are usually best, especially when leaderboard width is limited.
You can persist stats with:
-
auto_save_quit = true(save on quit) -
auto_save_reset = true(save on reset) - manual save command (
save_play_stats) from menu/hotkey
This gives you flexible control over when data is written.
You can also save when a configured session trigger fires:
play_stats.session_triggers.auto_save = true
play_stats tracks values within a play-stats session.
Important reset setting:
-
clear_stats_on_resetcontrols whether tracked session values are cleared when reset is triggered through Sprint Boost reset flow. - In most cases, keeping this
true(default) is best.
Another useful pattern has started to show up in real game configs:
- set
auto_save_reset = false - set
clear_stats_on_reset = false - let
session_triggershandle the save and session restart instead
This works well when both of these paths naturally return the game to the same title screen or menu:
- starting a new game from inside the game
- using reset
In that kind of setup, one in-game trigger can often handle both cases more cleanly than separate reset-based behavior.
High-level idea:
- use the same session trigger for both "reset returned to title" and "the game naturally returned to title"
- let that title-screen or menu transition be the thing that ends the run
- if reset causes that transition, the save still happens right away, but through the session-trigger path
- that means you often do not need separate reset auto-save or reset clear behavior
Thunder Castle is a good example of this pattern. Its config uses a bool expression for on_title_screen, then lets a session trigger handle the rollover:
[play_stats]
auto_save_reset = false
clear_stats_on_reset = false
[play_stats.session_triggers]
auto_save = true
[play_stats.session_triggers.back_to_menu]
expression = { bool = "on_title_screen" }
trigger = { mode = "changes_to", value = true }In plain language, this means:
- use the return-to-title moment as the single source of truth for ending the run
- let reset reuse that same path when reset sends the game back to title
- avoid split behavior where reset and in-game transitions try to manage sessions separately
Reset caveat:
- If a reset happens outside Sprint Boost control flow (for example through an emulator-side
kbdhackfilereset hotkey), Sprint Boost does not reliably know a reset occurred. - In that case, play-stats session values may not be cleared at that moment.
Use session_triggers when a game has a natural run-ending moment that does not always use emulator reset.
Common example: end the play-stats session when lives change to 0.
[play_stats.session_triggers]
auto_save = true
[play_stats.session_triggers.game_over]
game_info = "p1_lives"
trigger = { mode = "changes_to", value = 0 }What this does:
- Watches a
game_infovalue. - Fires when that value changes to the target.
- Starts a new play-stats session so the next run begins fresh.
- If
auto_save = true, saves immediately when it fires.
This is useful for games where the run ends inside the game itself instead of through a normal emulator reset.
It is also useful for games where reset and "start new game" both lead back to the same title-screen state, because one session trigger can cover both flows.
Common examples:
- game over screen
- return to title screen
- back to menu after both players are dead
- level or round boundaries when you want each segment saved separately
Session triggers can watch:
- a
game_infovalue - an
expression.intvalue - an
expression.boolvalue
That makes expressions especially helpful when the game-over signal is not stored as one clean memory value.
Example using a bool expression:
[play_stats.session_triggers.back_to_menu]
expression = { bool = "on_title_screen" }
trigger = { mode = "changes_to", value = true }In plain language, that means:
- watch a reusable true/false expression
- when it changes from false to true
- treat that as the end of the current run
Optional conditions:
[[play_stats.session_triggers.game_over.conditions]]
game_info = "p2_lives"
op = "eq"
value = 0In plain terms, the trigger only fires when all conditions are true at that moment.
This lets you keep the watched trigger simple, then add extra checks only when needed.
Practical notes:
-
changes_tois edge-based: first observed value does not fire. - The watched value must leave the target and come back before the same trigger can fire again.
- If
auto_save = true, Sprint Boost saves the current session before it starts the next one.
play_stats.players.<player>.tracked.<stat>.mode supports:
-
final: save the most recent value. -
peak: save the highest value seen. -
lowest: save the lowest value seen. -
count_change: count every value change. -
count_increase: count how many times value increased. -
count_increase_reset_aware: count increases and reset-boundary carry-ins. -
count_decrease: count how many times value decreased. -
amount_increased: accumulate total increase amount. -
amount_increased_reset_aware: accumulate increases with reset-boundary carry-in handling. -
amount_decreased: accumulate total decrease amount.
[play_stats.players.p1.tracked.food_bags_collected]
game_info = "food_count"
mode = "count_increase"
[play_stats.players.p1.tracked.food_consumed]
game_info = "food_count"
mode = "amount_decreased"This tracks two behavior stats from the same underlying value:
- how many collection events occurred
- how much total resource was consumed
Use save_conditions to skip trivial runs.
You can define more than one save condition for a player.
When multiple conditions are defined, all conditions must pass for that player record to be saved.
Each condition uses one source:
-
game_info = "..."checks a livegame_infovalue. -
play_stat = "..."checks that player's tracked play-stats value. -
expression = { int = "..." }checks a numeric expression. -
expression = { bool = "..." }checks a true/false expression. -
expression = { string = "..." }checks a text expression.
Use one source per condition entry (do not combine both in the same entry).
[[play_stats.players.p1.save_conditions]]
game_info = "p1_score"
op = "gt"
value = 0Meaning: only save when score is greater than 0.
Example using a tracked play stat:
[[play_stats.players.p1.save_conditions]]
play_stat = "score"
op = "gt"
value = 0Meaning: only save when the tracked score stat is greater than 0.
Example using a bool expression:
[[play_stats.players.p1.save_conditions]]
expression = { bool = "has_started" }
op = "eq"
value = trueMeaning: only save when the reusable has_started expression says the run really began.
Use update_conditions to update a stat only when a condition is true (for example active player checks in 2-player games).
[play_stats.players.p1.tracked.score]
game_info = "active_score"
mode = "amount_increased"
[[play_stats.players.p1.tracked.score.update_conditions]]
game_info = "active_player"
op = "eq"
value = 1Like save conditions, update conditions can also use expressions when that reads more naturally than repeating raw memory checks.
Live tracked values can be shown in display substitutions:
{ps.<player>.<stat_key>}- shorthand
{ps.<stat_key>}
Example:
[[display.layouts.hud.elements]]
type = "text"
text = "P1 Total: {ps.p1.score}"
x = 20
y = 30
width = 300
height = 40
color = "#FFFFFF"
align = "left"
size = 26play_stats follows normal config precedence:
- folder-level can define shared tracking patterns
- game-level can override tracked stats and save behavior
In practice, most tracked keys are game-specific because they depend on each game’s game_info setup.
- Start with one tracked stat and confirm it saves correctly.
- Add additional stats one at a time.
- Use high score as a starter pattern, then expand into game-specific metrics.
- Use manual save for intentional persistence workflows.
- You can manually edit the play_stats save file's player_label attribute if wanting to put a person's identifier on a particular stat, then that name will display in leaderboard if not too long.
Continue to: