ChronoCore Operator’s Guide

Welcome! This guide will help you run ChronoCore on race day. We’ll cover everything from starting up the system to handling common issues. If you’re looking for deep technical details, check out the Technical Reference Guide instead.


0. What’s New (2025-11-13)

Here’s what changed in the latest update:

1. Pre-Race Setup

1.1 Installing and Starting the Software (Python 3.12 Required)

ChronoCore provides several startup options depending on how you want to run the system.

Use this when you want to access the UI from multiple devices/browsers.

On Windows:

.\scripts\Run-Server.ps1

This script will:

Access the UIs:

Option 2: Desktop Application (Single Operator Station)

Use this for a standalone operator console with native window controls.

On Windows:

.\scripts\Run-Operator.ps1          # Normal mode
.\scripts\Run-Operator.ps1 -Debug   # With DevTools enabled

This script will:

Option 3: Remote Spectator Display

Use this to show race results on a separate display or machine.

On Windows:

.\scripts\Run-Spectator.ps1 -Server 192.168.1.100

On Linux (Debian/Ubuntu):

chmod +x scripts/Run-Spectator.sh
./scripts/Run-Spectator.sh 192.168.1.100

Opens the spectator UI in Chrome fullscreen mode (press F11 to exit).

Manual Installation (Advanced)

If you prefer to set up manually:

On Windows (PowerShell):

python -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -r backend/requirements.txt
python -m uvicorn backend.server:app --reload --port 8000

On Mac/Linux:

python3 -m venv .venv
source .venv/bin/activate
pip install -r backend/requirements.txt
python -m uvicorn backend.server:app --reload --port 8000

1.2 Pre-Race Checklist

Before the drivers arrive:

  1. Start the backend server (see commands above)
  2. Open the Operator Console at /ui/operator/index.html
  3. Make sure your timing hardware is connected and powered on
  4. Load your race session with the driver list and race ID

2. Pre-Race Mode (Flag = Pre)

When you first start up, the system is in PRE mode. This is your chance to make sure everything is working:

If a kart’s transponder isn’t showing up:


2. Pre-Race Mode (Flag = Pre)

If a kart’s tag is missing:


3. Race Start (Flag = Green)


4. During the Race

While the race is running, you can use different flags to manage the action:

What to watch for:


5. Ending the Race (Flag = Checkered)

5.1 Understanding Race Finish Modes

ChronoCore supports two different race finish behaviors:

Hard-End Mode (Traditional):

Soft-End Mode (Recommended for Most Races):

5.2 How Soft-End Works (Step by Step)

Let’s say you’re running a 10-lap race with soft-end enabled:

  1. Lap 9: Leader completes lap 9
  2. Lap 10: Leader crosses at lap 10
  3. During the 30-second window:
  4. After 30 seconds: Race freezes automatically

5.3 Why Use Soft-End?

Advantages:

When to Use Hard-End:

5.4 What You’ll See

During soft-end mode:

5.5 Manual Override

You can still manually throw flags during soft-end:

5.6 Configuration

Soft-end is configured per race mode in config/race_modes.yaml:

sprint_10_laps:
  label: "10 Lap Sprint"
  limit:
    type: laps
    value: 10
    soft_end: true              # Enable soft-end
    soft_end_timeout_s: 30      # 30 second window

Common timeout values:

Easy as that!

6. After the Race

Once the race is over, here’s what to do:


7. Troubleshooting Common Issues

7.1 Laps Aren’t Counting (But Transponder Reads Are Showing Up)

This is actually pretty common. Here’s what to check:

7.2 Other Common Problems

Driver not showing in standings? Check if they’re enabled and have a tag assigned.

Getting “Unknown” entries? The system saw a transponder it doesn’t recognize. Just assign it to the correct driver.

Race clock not moving? Make sure the flag is set to green.

Race ended too early? You might have accidentally hit the checkered flag button - easy to do when things get exciting!

8. Understanding Race Flags (2025-09-30)

The spectator screen (and your operator console) show different colored flags during the race. Here’s what they mean:

Flag Colors:

Changing Flags During a Race:

Normally, you’ll just click the flag buttons in the Operator Console. But if your UI isn’t responding or you need to change flags from a script, you can do it directly via the API.

Using PowerShell to change flags:

Invoke-RestMethod `
  -Method POST `
  -Uri "http://localhost:8000/engine/flag" `
  -Body (@{ flag = "green" } | ConvertTo-Json) `
  -ContentType "application/json"

Just replace "green" with whatever flag you need: "yellow", "red", "white", "checkered", or "blue".

8.1 How Flag Phases Work (2025-10-21)

Behind the scenes, the race goes through different phases, and only certain flags work in each phase. You don’t need to memorize this - the UI will gray out buttons that won’t work - but it’s helpful to understand what’s happening.

Race Phases:

Which Flags Work When:

Phase Available Flags Notes
pre PRE, GREEN You can arm the start or just send them green
countdown PRE only The timer will automatically go green when it expires, or you can abort to PRE
green All flags You can always get back to GREEN if you need to clear a caution
white All flags Same as green, just shows a white flag banner
checkered CHECKERED only Race is done - use the End/Reset controls to start a new session

A Few Things to Know:

Quick Test:

  1. While green, click Yellow - you’ll see the yellow flag appear
  2. Click Green again - back to green
  3. During a countdown, only PRE will work - use it if you need to abort the start
  4. Clicking the same flag twice is totally fine - it just confirms what’s already set

8.2 Lighting Integration with QLC+ (OSC Control)

ChronoCore can control professional lighting via QLC+ lighting software using OSC (Open Sound Control) protocol. This allows race flags to automatically trigger lighting cues, creating a synchronized visual experience for spectators.

How It Works

Bidirectional Communication:

What Gets Synchronized:

Configuration

Edit config/config.yaml to enable OSC lighting:

integrations:
  lighting:
    osc_out:
      enabled: true
      host: 192.168.1.101      # QLC+ computer IP
      port: 9000               # QLC+ OSC input port
      send_repeat:
        count: 2               # Send twice for UDP reliability
        interval_ms: 50
      addresses:
        flag:
          green:     "/ccrs/flag/green"
          yellow:    "/ccrs/flag/yellow"
          red:       "/ccrs/flag/red"
          white:     "/ccrs/flag/white"
          checkered: "/ccrs/flag/checkered"
          blue:      "/ccrs/flag/blue"
        blackout:    "/ccrs/blackout"
    
    osc_in:
      enabled: true
      host: 0.0.0.0            # Listen on all network interfaces
      port: 9010               # CCRS OSC input port
      paths:
        flag_prefix: "/ccrs/flag/"
        blackout: "/ccrs/blackout"
      threshold_on: 0.5        # Button value >= 0.5 = ON
      debounce_off_ms: 250     # Ignore OFF when switching flags

When Lights Trigger

Automatic Triggers (CCRS → QLC+):

Lighting Operator Control (QLC+ → CCRS):

Safety Features

The system prevents lighting operators from accidentally controlling race timing:

Example: If lighting operator clicks GREEN during pre-race, the system logs a warning and ignores it. Race Control must click “Start Race” to begin.

Troubleshooting Lighting

Lights not responding to flag changes?

Lighting triggering unwanted flag changes?

No blackout on reset?


9. Choosing and Using Timing Hardware

Your race timing system can work with several different hardware decoders. You only need one active at a time, and this is controlled in your configuration file.

How to Pick the Right Decoder:

  1. Open your config file
    Go to the config/ folder and open config.yaml

  2. Find the scanner.source section
    It’ll look something like this:
    scanner:
      source: ilap.serial
      decoder: ilap_serial
    
  3. Choose your hardware type
    Change the source: line to match what you’ve got:
  4. Set up the decoder details
    Under app.hardware.decoders, find your decoder type and adjust the settings:
    app:
      hardware:
        decoders:
          ilap_serial:
            port: COM3        # Windows: COM3, COM7, etc. | Linux: /dev/ttyUSB0
            baudrate: 9600
            init_7digit: true
    
  5. For USB/serial connections
  6. For network-based decoders
  7. Restart and check
    After saving the file, restart the backend so it picks up the changes.
    You can check the decoder status at /decoder/status to make sure everything connected properly.

10. Managing Transponder Tags (2025-10-04)

Why This Matters

You need to assign transponder tags to drivers so the system knows who’s who. ChronoCore makes sure that each tag only belongs to one active driver at a time. Disabled drivers can keep their old tags in the system for record-keeping, but those tags won’t interfere with active assignments.

How Tag Assignment Works


Common Tag Workflows

1) Assigning or Updating a Tag

  1. Pick the driver in the Operator UI
  2. Enter their transponder number (like 1234567) and save
  3. Even if the tag was already correct, you’ll see a success message - no harm done!

2) Fixing a Tag Conflict (409 Error)

When you try to assign a tag that’s already active on another driver:

  1. You’ll see a 409 Conflict error message (something like “Tag already assigned to another enabled entrant”)
  2. To fix it, you have three options:
  3. Once you’ve made the change, try the assignment again

3) Reloading After Making Changes

If you use the admin tools to bulk-edit drivers or import rosters, make sure to reload the session so the live race engine sees your changes.


Health Checks

You can quickly check if the system is working properly:


Common Problems and Solutions

“Entrant X not found” when assigning a tag The driver doesn’t exist in the database yet. Add them using the Entrants Admin page or roster import tool, then reload the session.

409 Conflict when enabling a driver or assigning a tag Another active driver has that tag. Clear their tag, change to a different tag, or disable the other driver if they’re not racing.

400 error on /engine/load
Your roster data has a problem (maybe a missing or invalid ID). Fix the roster file and try loading again.


Where the Database Lives

By default, ChronoCore stores everything in backend/db/laps.sqlite. If you need to change this, edit your config file:

app:
  engine:
    persistence:
      db_path: backend/db/laps.sqlite

Quick Reference for Common Tasks

What You’re Doing What Happens
Assign same tag to same driver 200 OK - nothing changes, system just confirms it’s already set
Assign tag used by enabled entrant 409 Conflict (nothing changes)
Clear a tag (send empty/whitespace) 200 OK, tag cleared
Reload session after admin edits Engine mirrors DB; changes take effect

11. Connecting to the Race Engine (2025 Update)

The Operator UI needs to know where your race engine is running. There are a few different setups:

How to Configure This

The engine connection settings are in your config/app.yaml file under app.client.engine:

app:
  client:
    engine:
      mode: fixed
      fixed_host: "10.77.0.10:8000"
      prefer_same_origin: true
      allow_client_override: false

What the Settings Mean

Checking Your Connection

Look at the footer of the Operator UI - it always shows the Effective Engine address it’s talking to:


12. Page Tour / Operator Workflow (2025 Update)

13. Race Control - Clarifications

Freeze vs Frozen

Demote (presentation aid)

Temporarily move an out-of-place entrant down the order for the display while you fix data (e.g., tag merge). Raw pass history remains intact.

13.1 Qualifying Races and Grid Freezing (2025-11-03)

ChronoCore supports a complete qualifying workflow where you can set the starting grid for races based on qualifying session results.

Running a Qualifying Session

  1. Set up the race - In Race Setup, select race type “qualifying”
  2. Set brake test flags - During or after qualifying, use the Race Control page to mark each driver’s brake test status (pass/fail) using the brake flag button
  3. Run the session - Let drivers complete their qualifying laps
  4. Throw checkered - When time expires, hit the checkered flag
  5. Freeze the grid - An orange “Freeze Grid Standings” button appears with a breathing animation

Scratch Pass Feature (Invalidating Laps)

During qualifying, you can invalidate an entrant’s current best lap using the Scratch button:

When to use:

How to use:

  1. Locate the driver’s row in the Race Control standings table
  2. Click the orange Scratch button next to their brake test indicator
  3. Confirm the action in the dialog
  4. The system will:

Example scenarios:

Note: The scratched lap is permanently excluded from best lap calculations but remains in the raw lap history for audit purposes. Drivers can continue qualifying and set new times after a scratch.

Freezing the Grid

When you click “Freeze Grid Standings”:

  1. The system captures:
  2. Auto-adopt unknown entrants (enabled by default):
  3. You choose a brake test failure policy:
  4. The grid is saved to the event config and persists across sessions

Configuration options (in Settings → Qualifying):

Applying the Frozen Grid

Once frozen, the grid automatically applies to subsequent races:

Grid Sorting Rules

When a qualifying grid is active and policy is “demote”:

  1. Enabled status - Enabled drivers always sort before disabled
  2. Passing brake test - Drivers with passing brake tests appear first, sorted by qualifying position (1, 2, 3…)
  3. Non-qualified drivers - Drivers who didn’t participate in qualifying appear next
  4. Failed brake test - Drivers who failed (or have null results) appear at the back, sorted by best lap time (fastest first)

Note: Null/missing brake test results are treated as failures per the policy setting.

Resetting or Updating the Grid

You have three options:

  1. Run another qualifying session - Freezing new results completely replaces the old grid
  2. Delete the qualifying race - In Results & Exports, delete the qualifying heat. The system automatically clears the frozen grid from the event config
  3. Manual config edit - Advanced users can edit events.config_json in the database directly

Persistence and Display

Troubleshooting

Grid not applying to new race:

Brake test badges not showing:

Grid positions show as blank:

14. Diagnostics / Live Sensors

15. Results & Exports

View Modes

CSV Exports

Deleting Races

Copy/Download

Buttons provide clipboard copy or file download for quick posting.


16. Moxie Board Scoring (2025-11-13)

The Moxie Board is a fun crowd engagement feature at Power Racing Series events. It’s a physical display board that shows which teams are getting the most “moxie points” from spectators and officials based on button presses.

What is Moxie Scoring?

Unlike race results which are based on laps and times, moxie scoring is pure button-press counting. Spectators and officials press buttons on wireless controllers to give moxie points to their favorite teams. The more button presses a team gets, the higher their moxie score.

Important: Moxie scoring is completely separate from race results. It’s about showmanship, creativity, and crowd appeal - not racing performance!

How It Works

Example: If Team A gets 50 button presses and Team B gets 30 presses out of 100 total:

Enabling Moxie Board

To turn on moxie board features:

  1. Open your config file (config/config.yaml)
  2. Find the moxie section under app.engine.scoring:
    app:
      engine:
        scoring:
          moxie:
            enabled: true              # Turn this on!
            auto_update: true          # Real-time updates
            total_points: 300          # Point pool to distribute
            board_positions: 20        # How many teams fit on your board
    
  3. Save and restart the backend server

Using the Moxie Board Page

Once enabled, you’ll see a Moxie Board button on the main operator page (between “Setup & Devices” and “Open Spectator View”).

Current Features:

Coming Soon:

Configuration Options

total_points - The point pool to distribute (typically 300)

board_positions - How many teams your physical board can show

Only the top N teams (by button presses) get displayed on the physical board, but the operator UI shows everyone’s scores.

Tips for Running Moxie

Troubleshooting

Moxie Board button doesn’t appear:

Scores not updating:


17. Appendix - Versioned Settings Summary

Relevant keys (see config/config.yaml):

app:
  engine:
    default_min_lap_s: 10          # global minimum lap threshold
    unknown_tags:
      allow: true                   # create provisional "Unknown" entrants
    persistence:
      enabled: true
      sqlite_path: backend/db/laps.sqlite
      journal_passes: true
      checkpoint_s: 15              # snapshot interval

scanner:
  source: ilap.serial               # ilap.serial | mock | ilap.udp
  decoder: ilap_serial              # references hardware decoder config
  min_tag_len: 7                    # reject shorter tags
  duplicate_window_sec: 0.5         # suppress duplicate reads
  rate_limit_per_sec: 20            # max passes per second

publisher:
  mode: http                        # how scanner publishes to backend

app:
  hardware:
    decoders:
      ilap_serial:
        port: COM3
        baudrate: 9600
        init_7digit: true
    
    pits:
      enabled: false
      receivers:
        pit_in: []
        pit_out: []

ui:
  theme: default-dark
  visible_rows: 16                  # standings viewport height

Last updated: 2025-11-13