step.params

Typed, string-free mission parameters entered via the setup UI.

A parameter is a single value the operator dials in on the robot’s screen during the setup mission (e.g. a positional offset, a speed) and that the normal mission code then reads back for its calculations.

The design goal is to never expose a string key at the call site. A parameter is declared once as a typed descriptor on a ParamSet. Python’s __set_name__ hands the descriptor its own attribute name, so the internal storage key is derived automatically — a typo becomes an AttributeError at import time instead of a silently-wrong 0.0 at runtime, and IDE autocomplete/rename work across the whole codebase.

Declare parameters once:

from raccoon import NumberParam, ParamSet


class P(ParamSet):
    cube_offset = NumberParam(default=0.0, unit="cm", persist=True)
    ramp_speed = NumberParam(default=0.5)

Ask for them in the setup mission — .ask() returns a ready-to-use step and pulls unit/range/default straight off the descriptor:

class MySetup(SetupMission):
    def sequence(self) -> Sequential:
        return seq(
            [
                P.cube_offset.ask("Cube-row offset"),
                P.ramp_speed.ask("Ramp speed"),
            ]
        )

Read them in normal mission code — typed, autocompleted, rename-safe:

def sequence(self) -> Sequential:
    off = P.cube_offset.get()  # -> float
    return seq([drive_forward(cm=60 + off)])

Or bind the value lazily inside a declarative seq([...]) tree with the existing defer() primitive, so the calculation happens at execution time regardless of build order:

seq(
    [
        defer(lambda r: drive_forward(cm=60 + P.cube_offset.get())),
    ]
)

Persistence is opt-in: persist=True mirrors the value into racoon.calibration.yml (via the shared CalibrationStore) so it survives a process restart and is reused under --no-calibrate. Without it a parameter lives only in RAM for the current process — set once in setup, read by every later mission.

Profiles (game-table / side scoping)

Some parameters differ per physical game table and per starting side. Mark such a parameter scoped=True and it is stored under the active profile — a (game-table, side) pair chosen once during setup — instead of globally. .get() transparently resolves against whatever profile is active, so mission code never mentions the table or side:

class P(ParamSet):
    cube_offset = NumberParam(default=0.0, unit="cm", scoped=True, persist=True)
    drum_speed = NumberParam(default=0.5)  # global — unaffected by profile

Drive the whole flow from the setup mission with a single step: it shows one keypad screen where the operator types the table number and taps A / B (e.g. 1B) and — if a complete set of saved values already exists for that profile — offers to reuse them (asking first) instead of re-entering every value:

class MySetup(SetupMission):
    def sequence(self) -> Sequential:
        return seq(
            [
                configure_mission(
                    params=[
                        (P.cube_offset, "Cube-row offset"),
                    ],
                ),
            ]
        )

Un-scoped parameters ignore the active profile completely, so mixing scoped and global parameters in one mission is fine.

Attributes

ParamPrompt

Classes

NumberParam

A numeric mission parameter, declared once as a ParamSet field.

ParamSet

Base for a group of parameter declarations.

AskNumber

Setup step: ask the operator for a numeric parameter via the screen.

ConfigureMission

Setup step: enter a game-table / side profile, then fill scoped params.

Functions

reset_params(→ None)

Clear all runtime parameter values (test helper).

set_active_profile(→ None)

Select the active (game-table, side) profile for scoped parameters.

get_active_profile(→ tuple[str, str] | None)

Return the active (game-table, side) profile, or None.

clear_active_profile(→ None)

Forget the active profile (test helper; persisted YAML is untouched).

load_persisted_profile(→ tuple[str, str] | None)

Restore and activate the last persisted profile, if any.

ask(→ step.base.Step)

Ask the operator for a numeric parameter during setup.

configure_mission(→ step.base.Step)

Enter a game-table / side profile, then fill the scoped parameters.

Module Contents

step.params.reset_params() None

Clear all runtime parameter values (test helper).

Only touches the in-memory cache and the active profile; persisted YAML entries are left alone.

step.params.set_active_profile(table: str, side: str, *, persist: bool = True) None

Select the active (game-table, side) profile for scoped parameters.

Parameters:
  • table – Game-table name (as shown in the setup menu).

  • side – Starting side (e.g. "A" / "B").

  • persist – When True (default) the selection is mirrored into the calibration YAML so a restart / --no-calibrate run can restore it without re-asking.

step.params.get_active_profile() tuple[str, str] | None

Return the active (game-table, side) profile, or None.

step.params.clear_active_profile() None

Forget the active profile (test helper; persisted YAML is untouched).

step.params.load_persisted_profile() tuple[str, str] | None

Restore and activate the last persisted profile, if any.

Used on the --no-calibrate path so a headless run reuses the profile (and therefore the scoped values) picked during an earlier interactive setup. Returns the activated profile, or None when nothing is stored.

class step.params.NumberParam(default: float = 0.0, *, unit: str = '', persist: bool = False, scoped: bool = False, key: str | None = None)

A numeric mission parameter, declared once as a ParamSet field.

The descriptor owns the value’s metadata (default, unit, persistence) and, via __set_name__, its own storage key — so callers interact with the attribute, never a string:

class P(ParamSet):
    cube_offset = NumberParam(default=0.0, unit="cm")


P.cube_offset.get()  # -> float
P.cube_offset.ask("Offset...")  # -> Step for the setup mission
Parameters:
  • default – Value returned by get() before anything is entered.

  • unit – Display unit shown on the input screen (e.g. "cm").

  • persist – When True, entered values are mirrored to racoon.calibration.yml and reused across restarts / --no-calibrate.

  • scoped – When True, the value is stored per active profile (game-table + side) rather than globally — see configure_mission(). Combine with persist=True so saved per-profile values survive a restart and can be reused.

  • key – Explicit storage key. Normally omitted — the attribute name is used automatically. Only needed when declaring a parameter outside a class body (e.g. a module-level constant).

default
unit = ''
persist = False
scoped = False
property key: str
get() float

Return the current value, or default if none was entered.

set(value: float) None

Set the value directly.

is_set() bool

Whether a value has been entered/persisted (vs. falling back to default).

ask(prompt: str, *, title: str = 'Setup') step.base.Step

Return a setup step that asks the operator for this value.

The input screen is pre-filled with the current value (persisted or default) and shows the declared unit. Cancelling keeps the existing value. Under --no-calibrate the UI is skipped and the persisted/default value is used as-is.

Parameters:
  • prompt – Question shown above the numeric keypad.

  • title – Screen title. Defaults to "Setup".

Returns:

An AskNumber step, ready to drop into a seq([...]).

class step.params.ParamSet

Base for a group of parameter declarations.

Subclassing is optional — __set_name__ works on any class — but it provides all() for iterating declared parameters (handy for tests or a “reset everything” screen).

Example:

class P(ParamSet):
    cube_offset = NumberParam(default=0.0, unit="cm")
    ramp_speed = NumberParam(default=0.5)
classmethod all() list[NumberParam]

Return every NumberParam declared on this class.

class step.params.AskNumber(param: NumberParam, prompt: str, *, title: str = 'Setup')

Bases: raccoon.ui.step.UIStep

Setup step: ask the operator for a numeric parameter via the screen.

Prefer param.ask("...") or the ask() factory over constructing this directly. Shows a numeric keypad pre-filled with the parameter’s current value and stores whatever the operator confirms back into the parameter.

Under --no-calibrate the screen is skipped entirely and the existing (persisted or default) value is kept, matching the behaviour of the calibration steps.

step.params.ask(param: NumberParam, prompt: str, *, title: str = 'Setup') step.base.Step

Ask the operator for a numeric parameter during setup.

Free-function form of NumberParam.ask(); both build the same step. Some prefer ask(P.cube_offset, "...") over P.cube_offset.ask("...").

Parameters:
  • param – The NumberParam to fill in.

  • prompt – Question shown above the numeric keypad.

  • title – Screen title. Defaults to "Setup".

Returns:

An AskNumber step, ready to drop into a seq([...]).

Example:

from raccoon import ask, NumberParam, ParamSet


class P(ParamSet):
    cube_offset = NumberParam(default=0.0, unit="cm")


seq([ask(P.cube_offset, "Cube-row offset")])
step.params.ParamPrompt
class step.params.ConfigureMission(params: list[ParamPrompt], *, title: str = 'Setup')

Bases: raccoon.ui.step.UIStep

Setup step: enter a game-table / side profile, then fill scoped params.

Prefer the configure_mission() factory over constructing this directly. At runtime it drives the full profile flow:

  1. shows one keypad screen where the operator types the table number and taps A / B (e.g. 1B),

  2. activates that (table, side) profile so every scoped parameter now resolves under it,

  3. if a complete set of saved values already exists for the profile, offers to reuse them (asking first) and skips the questions,

  4. otherwise asks for each parameter in turn and stores it under the profile.

Under --no-calibrate the UI is skipped entirely: the last persisted profile is restored (so scoped values read back from YAML) and no questions are asked.

step.params.configure_mission(params: list[ParamPrompt], *, title: str = 'Setup') step.base.Step

Enter a game-table / side profile, then fill the scoped parameters.

One-stop setup step for missions whose parameters differ per physical game table and starting side. It shows a single keypad screen where the operator types the table number and taps A / B (e.g. 1B), activates that profile (so every scoped=True parameter now reads and writes under it), and then either reuses a complete set of previously saved values (after confirming) or asks for each parameter in turn.

Prerequisites:

The parameters passed here should be declared with scoped=True (and usually persist=True so saved values survive a restart). Un-scoped parameters are unaffected and can still be asked with ask().

Parameters:
  • params(param, prompt) pairs — the scoped parameters to fill and the question shown for each.

  • title – Screen title for every screen in the flow. Defaults to "Setup".

Returns:

A ConfigureMission step, ready to drop into a seq([...]).

Example:

from raccoon import NumberParam, ParamSet, configure_mission


class P(ParamSet):
    cube_offset = NumberParam(default=0.0, unit="cm", scoped=True, persist=True)
    ramp_speed = NumberParam(default=0.5, scoped=True, persist=True)


seq(
    [
        configure_mission(
            params=[
                (P.cube_offset, "Cube-row offset"),
                (P.ramp_speed, "Ramp approach speed"),
            ],
        ),
    ]
)