Skip to content

03 11 Config Expressions

evets17 edited this page Apr 2, 2026 · 3 revisions

03 11 Config Expressions

This page explains expression in Sprint Boost.

What expressions are

Expressions let Sprint Boost turn raw live values into more useful values.

A simple way to think about them:

  • game_info reads what the game is doing right now
  • expression reshapes that data into something easier to display, save, or reuse

Expressions are especially helpful when the game stores data in a way that is not immediately player-friendly.

Why expressions matter

Expressions are useful when you want to:

  • combine multiple raw values into one final value
  • decide whether a game is in a certain state
  • build text or filenames from live values
  • define logic once and reuse it in several places

Common examples:

  • build a full score from separate score digits
  • create a reusable in_game or game_over flag
  • build an image path like track_mud.png from a label value
  • create one derived score and use it in both play_stats and display text

Where expressions can be used

Expressions are relevant for:

  • boost_mode = "enhanced"

Like game_info and play_stats, expressions are part of the enhanced runtime flow.

The three expression types

Sprint Boost supports three expression namespaces:

  • expression.int
  • expression.bool
  • expression.string

Each one is meant for a different kind of result.

expression.int

Use expression.int when you want a number.

Common uses:

  • score
  • timer
  • combined counters
  • math based on one or more game_info values

Example:

[expression.int.score]
sum = [
  { game_info = "score_lo" },
  { mul = [{ game_info = "score_hi" }, 1000] }
]

This is a good fit when a game stores one value in pieces.

expression.bool

Use expression.bool when you want true/false logic.

Common uses:

  • whether a run has started
  • whether player 1 is active
  • whether a special state is currently active
  • whether another config feature should be allowed to update or save

Example:

[expression.bool.has_started]
gt = [{ expression.int = "score" }, 0]

This is useful when you want a simple yes/no rule that can be reused elsewhere.

expression.string

Use expression.string when you want text.

Common uses:

  • state labels
  • image names or paths
  • text fragments used in overlays
  • readable values built from labels and strings

Example:

[expression.string.terrain_art]
concat = ["{gv.assets_images}/track_", { gil = "condition" }, ".png"]

This is useful when a live label should help choose art or text dynamically.

What expressions can read from

Expressions can pull input from a few different places.

game_info

Use this when the starting point is a numeric live value read from game memory.

{ game_info = "score" }

This is the most common expression input.

gil / gi_label

Use this when you want the text label for a game_info value instead of its number.

{ gil = "state" }

That is useful for readable text or filename building.

global_variable

Use this when part of the expression should come from a global path or shared folder value.

{ global_variable = "assets_images" }

This is most useful in expression.string definitions.

Other expressions

Expressions can build on other expressions.

Pitfall is a good real-world example of this.

In Pitfall, Sprint Boost first turns each raw score digit into a usable number, then combines those digit expressions into one final score:

[expression.int.score_ones]
if = [
  { eq = [{ game_info = "score_ones_raw" }, 0] },
  0,
  { div = [{ sub = [{ game_info = "score_ones_raw" }, 135] }, 8] }
]

[expression.int.score_tens]
if = [
  { eq = [{ game_info = "score_tens_raw" }, 0] },
  0,
  { div = [{ sub = [{ game_info = "score_tens_raw" }, 135] }, 8] }
]

[expression.int.score_hundreds]
if = [
  { eq = [{ game_info = "score_hundreds_raw" }, 0] },
  0,
  { div = [{ sub = [{ game_info = "score_hundreds_raw" }, 135] }, 8] }
]

[expression.int.score_thousands]
if = [
  { eq = [{ game_info = "score_thousands_raw" }, 0] },
  0,
  { div = [{ sub = [{ game_info = "score_thousands_raw" }, 135] }, 8] }
]

[expression.int.score_ten_thousands]
if = [
  { eq = [{ game_info = "score_ten_thousands_raw" }, 0] },
  0,
  { div = [{ sub = [{ game_info = "score_ten_thousands_raw" }, 135] }, 8] }
]

[expression.int.derived_score]
sum = [
  { expression.int = "score_ones" },
  { mul = [{ expression.int = "score_tens" }, 10] },
  { mul = [{ expression.int = "score_hundreds" }, 100] },
  { mul = [{ expression.int = "score_thousands" }, 1000] },
  { mul = [{ expression.int = "score_ten_thousands" }, 10000] }
]

In plain language, this means:

  • several smaller expressions clean up the individual digits
  • a final expression reuses those smaller expressions
  • the result is one derived_score value that other config sections can use

This is what makes expressions reusable. You can define small building blocks once and then combine them into something more useful.

Typed references

Type matters when one expression refers to another.

That means:

  • expression.int can only reference an int expression
  • expression.bool can only reference a bool expression
  • expression.string can only reference a string expression

This keeps the config predictable and avoids mixing numbers, logic, and text in confusing ways.

Supported operators by result type

Not every operator fits every expression type.

This is the simplest way to think about it:

  • number expressions use math-style operators
  • boolean expressions use yes/no logic and comparisons
  • string expressions use text-building operators

Operators that produce expression.int

Use these when the result should be a number:

  • sum
  • sub
  • mul
  • div
  • mod
  • min
  • max
  • bit_and
  • bit_or
  • bit_xor
  • lshift
  • rshift
  • clamp
  • if
  • case

These are the main tools for:

  • score math
  • packed-value cleanup
  • range limiting
  • combining several raw values into one final number

Operators that produce expression.bool

Use these when the result should be true or false:

  • and
  • or
  • not
  • eq
  • neq
  • lt
  • lte
  • gt
  • gte
  • if
  • case

Practical guidance:

  • eq and neq are the most flexible comparison operators
  • lt, lte, gt, and gte are for numeric comparisons
  • and, or, and not are for combining simpler true/false rules into one reusable gate

Operators that produce expression.string

Use these when the result should be text:

  • concat
  • format
  • if
  • case

Practical guidance:

  • concat is the simple choice for joining pieces of text together
  • format is useful when you want to build a string from a template with named parts

Input forms that are not really "operators"

Expressions can also be simple direct references instead of operator blocks.

Examples:

{ game_info = "score" }
{ gil = "state" }
{ global_variable = "assets_images" }
{ expression.int = "derived_score" }

These are not operators by themselves. They are direct inputs that your expressions can build on.

Common expression building blocks

Most users only need a small set of expression patterns.

Add values with sum

Use when several values should become one total.

[expression.int.total_score]
sum = [
  { game_info = "score_lo" },
  { mul = [{ game_info = "score_hi" }, 1000] }
]

Multiply or scale with mul

Use when one value represents a place value or multiplier.

{ mul = [{ game_info = "score_hi" }, 1000] }

Subtract with sub

Use when a raw value needs to be shifted into a usable range.

{ sub = [{ game_info = "raw_digit" }, 135] }

Divide with div

Use when a raw value needs to be scaled down.

{ div = [{ sub = [{ game_info = "raw_digit" }, 135] }, 8] }

Compare with eq, ne, gt, gte, lt, lte

Use these when the goal is a true/false answer.

[expression.bool.active_p1]
eq = [{ game_info = "active_player" }, 1]

Choose between values with if

Use if when one expression should behave differently depending on a condition.

[expression.int.safe_digit]
if = [
  { eq = [{ game_info = "raw_digit" }, 0] },
  0,
  { div = [{ sub = [{ game_info = "raw_digit" }, 135] }, 8] }
]

This is a common way to clean up unusual raw values.

Build text with concat

Use when you want to join text and live values together.

[expression.string.banner_text]
concat = ["State: ", { gil = "state" }]

A practical way to use expressions

A good beginner workflow is:

  1. Use game_info to read the raw values you know.
  2. Use expression to turn those raw values into something useful.
  3. Use the expression result in display or play_stats.

That keeps each part of the config easier to understand.

How expressions relate to game_info

game_info and expressions usually work together.

Helpful mental model:

  • game_info is the raw input layer
  • expressions are the cleanup and reusable logic layer

If a game already stores a value in a clean form, you may not need an expression.

If a game stores a value in pieces, packed formats, or game-specific codes, expressions are often the cleanest next step.

How expressions relate to play_stats

Expressions are often the best source for play_stats when the thing you want to track is not directly available from raw memory.

Example:

[play_stats.players.p1.tracked.score]
expression = { int = "score" }
mode = "final"

This means:

  • let expressions define what "score" really is
  • let play_stats track and save that value

Expressions can also be used in play_stats conditions.

Example:

[[play_stats.players.p1.save_conditions]]
expression = { bool = "has_started" }
op = "eq"
value = true

That lets you save only when a reusable condition is true.

Session triggers can also watch expression.int or expression.bool values when those are the best signal for an end-of-run or state change.

How expressions relate to display

Expressions can be shown directly in display text and substitution-enabled paths.

Sprint Boost exposes expression values as:

  • {ei.<key>} for int expressions
  • {eb.<key>} for bool expressions
  • {es.<key>} for string expressions

Example text:

[[display.layouts.hud.elements]]
type = "text"
text = "Score: {ei.score}"
x = 20
y = 20
width = 280
height = 40
color = "#FFFFFF"
align = "left"
size = 24

Example image path:

[[display.layouts.hud.elements]]
type = "image"
source = "{es.terrain_art}"
fallback = "{gv.assets_images}/track_unknown.png"
x = 20
y = 80
width = 140
height = 48
scaling = "fit"
alpha = 1.0

This is a good way to keep display layouts simple. Instead of repeating logic inside the layout, you can define the logic once in expression and reuse the result.

What happens when an expression cannot resolve

If an expression cannot produce a valid value at a given moment, Sprint Boost simply leaves that expression out for that poll.

In plain language:

  • the expression is not treated as permanently broken
  • it is just unavailable for that moment
  • when the needed input becomes valid again, the expression can appear again

This is another reason to build expressions in small, understandable steps.

Practical tips

  • Start with one derived value, not ten.
  • Give expression keys clear names like score, has_started, or state_label.
  • Keep raw memory reading in game_info.
  • Use expressions for cleanup, combination, and reusable logic.
  • If the same math or condition is needed in two places, move it into an expression.
  • Prefer simple, readable expressions over clever ones.

A small complete example

[game_info.values.score_lo]
address = "0x0208"
decode = "u8_lo"

[game_info.values.score_hi]
address = "0x0207"
decode = "u8_lo"

[expression.int.score]
sum = [
  { game_info = "score_lo" },
  { mul = [{ game_info = "score_hi" }, 100] }
]

[expression.bool.has_started]
gt = [{ expression.int = "score" }, 0]

[play_stats.players.p1.tracked.score]
expression = { int = "score" }
mode = "final"

This is a good starter pattern because it shows the full chain:

  • read raw values
  • build a better value
  • use that value in another feature

Next step

Continue to:

Clone this wiki locally