Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ A demo of SimTrace in action showing the (toggleable) braking x steering phase p
| Assetto Corsa Competizione | |
| Automobilista 2 | [Enable shared memory](#automobilista-2) |
| iRacing | Support is currently experimental |
| EA SPORTS F1 25 | [UDP 2025 and 2026 Season Pack](docs/F1_UDP.md) |

## Installation

Expand All @@ -42,6 +43,10 @@ The overlay fades out when your cursor leaves it and reappears on hover.

Enable shared memory: (from the main menu) `Options (top right) > System > Shared Memory` and setting it to `Project CARS 2`

## EA SPORTS F1 25

SimTrace accepts native UDP from both F1 25 and the 2026 Season Pack. The listen address and port are saved with the other settings. See the [F1 UDP setup guide](docs/F1_UDP.md) for direct and forwarded configurations.

## Building from source

Requires [Rust](https://rustup.rs/) (stable).
Expand Down
12 changes: 12 additions & 0 deletions THIRD_PARTY_LICENSES/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Third-party font assets

## Saira Semi Condensed SemiBold

- File: `assets/fonts/SairaSemiCondensed-SemiBold.ttf`
- Copyright: 2016 The Saira Project Authors (omnibus.type@gmail.com)
- Source: https://github.com/google/fonts/tree/main/ofl/sairasemicondensed
- License: SIL Open Font License 1.1
- License text: `THIRD_PARTY_LICENSES/SairaSemiCondensed-OFL.txt`

The OFL permits the unmodified font to be bundled and redistributed with this
application when its copyright notice and license accompany it.
93 changes: 93 additions & 0 deletions THIRD_PARTY_LICENSES/SairaSemiCondensed-OFL.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
Copyright 2016 The Saira Project Authors (omnibus.type@gmail.com), with reserved font name "Saira".

This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL


-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------

PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.

The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.

DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.

"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).

"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).

"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.

"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.

PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:

1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.

2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.

3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.

4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.

5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.

TERMINATION
This license becomes null and void if any of the above conditions are
not met.

DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
Binary file added assets/fonts/SairaSemiCondensed-SemiBold.ttf
Binary file not shown.
49 changes: 49 additions & 0 deletions docs/F1_UDP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# EA SPORTS F1 UDP setup

SimTrace uses one UDP listener and automatically selects a decoder from the packet format identifier. F1 25 UDP Format 2025 and the F1 25: 2026 Season Pack UDP Format 2026 are separate decoders with their own packet sizes and vehicle-array layouts. Native 2026 Season Pack output is supported; the game does not need to use its legacy F1 25 format.

EA's 2026 structure document labels the 2026 header game-year value as `26`. A live F1 25: 2026 Season Pack build was observed sending packet format `2026` with game year `25` and game version `1.26`, while its packet IDs and lengths matched the documented 2026 layouts. The 2026 decoder therefore accepts game year `25` or `26` only after packet format `2026` selects that decoder. This compatibility allowance does not alter any packet layout or offset.

The implementation follows EA's current official [F1 25 and 2026 Season Pack UDP specification page](https://forums.ea.com/blog/f1-games-game-info-hub-en/ea-sports%E2%84%A2-f1%C2%AE25-2026-season-pack-udp-specification/12187347), [F1 25 v3 specification](https://forums.ea.com/t5/s/tghpe58374/attachments/tghpe58374/f1-games-game-info-hub-en/61/4/Data%20Output%20from%20F1%2025%20v3.pdf), [F1 25 structures](https://forums.ea.com/t5/s/tghpe58374/attachments/tghpe58374/f1-games-game-info-hub-en/61/5/F1%2025%20Telemetry%20Output%20Structures.txt), and [2026 Season Pack structures](https://forums.ea.com/t5/s/tghpe58374/attachments/tghpe58374/f1-games-game-info-hub-en/61/8/2026%20Season%20Pack%20Telemetry%20Output%20Structures%20(1).txt).

## Direct game output

1. In SimTrace, open settings and select **EA SPORTS F1 (UDP 2025/2026)**.
2. Set **Listen address** to `127.0.0.1` when the game and SimTrace run on this PC. Use `0.0.0.0` when SimTrace must accept packets on any network interface.
3. Choose an unused **UDP port**. `20777` is EA's usual default, but SimTrace does not require it.
4. Save the SimTrace settings.
5. In F1 25 telemetry settings, turn UDP telemetry on, set the UDP IP address to `127.0.0.1`, and enter the same port.
6. For the first 2026 Season Pack test, select its native UDP Format 2026. A 60 Hz send rate is suitable.

SimTrace accepts unicast or broadcast datagrams. The sender address is not used to distinguish direct game packets from forwarded packets.

## Sharing telemetry with SimHub or Project Aeternum

Two applications on the same Windows PC should not both depend on owning the same unicast UDP endpoint. Give one application the game-facing port and forward a copy to a different SimTrace port.

Example:

1. Let SimHub or Project Aeternum receive the game's UDP stream on `20777`.
2. Enable that application's UDP forwarding to `127.0.0.1:20778`.
3. Set SimTrace to listen on `127.0.0.1` and port `20778`, then save.
4. Keep the forwarded packet payload unchanged. SimTrace handles it exactly like a direct game datagram.

SimHub documents its forwarding setup in [Sharing UDP data with other applications](https://github.com/SHWotever/SimHub/wiki/Sharing-UDP-data-with-other-applications/3d41406eaa9474eed8e10dec14ebd3d66a9d2fee). SimTrace does not forward packets in this phase.

## First live test

With the 2026 Season Pack sending native Format 2026, verify that:

- speed, RPM, gear, throttle, brake, clutch, and normalized steering respond for the player's car;
- trace history continues moving without mouse or window events;
- the selected 10-second or 30-second trace window remains bounded;
- opening and closing the Phase Plot does not interrupt collection;
- restarting SimTrace retains the listen address, port, trace window, and display refresh rate.

### Live diagnostic log

On Windows, receive and decoder diagnostics are written to `%APPDATA%\simtrace\simtrace.log.YYYY-MM-DD`. The first few distinct packet headers include the sender, datagram length, format/year, game major/minor version, packet version, packet ID/type, and player-car index. A summary is emitted every five seconds while packets arrive, with counters for protocol detection, validation, telemetry/status decoding, normalized frames, and categorized rejections. Each rejection category logs at most its first three examples.

For a failed live test, preserve the lines containing `F1 UDP datagram header observed`, `F1 UDP datagram rejected`, and `F1 UDP diagnostic summary`. These values should be compared with the current EA specification before any decoder layout is changed.

F1 car status packets expose the configured ABS and traction-control assistance modes. They do not identify moment-by-moment intervention, so SimTrace does not invent ABS-active or TC-active samples. The initial decoder also leaves physical wheel angle and wheel slip unsupported until a later packet integration provides those channels correctly.
2 changes: 1 addition & 1 deletion src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

pub mod settings;

pub use settings::{AppSettings, GraphSettings, ParsedColors};
pub use settings::{AppSettings, GraphSettings, ParsedColors, UiLayoutMode};
63 changes: 63 additions & 0 deletions src/config/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,26 @@ pub struct AppSettings {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CollectorConfig {
pub plugin: String,
#[serde(default = "default_f1_bind_address")]
pub f1_bind_address: String,
#[serde(default = "default_f1_udp_port")]
pub f1_udp_port: u16,
}

fn default_f1_bind_address() -> String {
"0.0.0.0".to_owned()
}

fn default_f1_udp_port() -> u16 {
20777
}

/// Graph visualization settings.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GraphSettings {
/// Selects the stable renderer or an experimental presentation layer.
#[serde(default)]
pub layout_mode: UiLayoutMode,
/// Seconds of history shown in the trace graph.
pub window_seconds: f64,
pub show_grid: bool,
Expand Down Expand Up @@ -54,6 +69,23 @@ pub struct GraphSettings {
pub show_speed: bool,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum UiLayoutMode {
#[default]
Classic,
F1OpenHudBeta,
}

impl UiLayoutMode {
pub const fn display_name(self) -> &'static str {
match self {
Self::Classic => "Classic",
Self::F1OpenHudBeta => "F1 Open HUD (Beta)",
}
}
}

fn default_true() -> bool {
true
}
Expand Down Expand Up @@ -157,8 +189,11 @@ impl Default for AppSettings {
Self {
collector: CollectorConfig {
plugin: "mock".to_string(),
f1_bind_address: default_f1_bind_address(),
f1_udp_port: default_f1_udp_port(),
},
graph: GraphSettings {
layout_mode: UiLayoutMode::Classic,
window_seconds: 10.0,
show_grid: true,
show_legend: true,
Expand Down Expand Up @@ -270,6 +305,7 @@ mod tests {
assert_eq!(s.collector.plugin, "mock");
assert_eq!(s.overlay.opacity, 1.0);
assert_eq!(s.graph.window_seconds, 10.0);
assert_eq!(s.graph.layout_mode, UiLayoutMode::Classic);
}

#[test]
Expand Down Expand Up @@ -332,6 +368,7 @@ mod tests {
let s: AppSettings = toml::from_str(toml_str).unwrap();
assert_eq!(s.colors.clutch, default_clutch_color());
assert!(!s.graph.speed_mph);
assert_eq!(s.graph.layout_mode, UiLayoutMode::Classic);
}

#[test]
Expand Down Expand Up @@ -359,4 +396,30 @@ mod tests {
egui::Color32::WHITE
);
}

#[test]
fn legacy_ui_fps_field_is_ignored() {
let mut value = toml::Value::try_from(AppSettings::default()).unwrap();
value
.get_mut("graph")
.and_then(toml::Value::as_table_mut)
.unwrap()
.insert("ui_fps".to_owned(), toml::Value::Integer(120));

let restored: AppSettings = value.try_into().unwrap();
let serialized = toml::to_string_pretty(&restored).unwrap();
assert!(!serialized.contains("ui_fps"));
}

#[test]
fn f1_open_hud_layout_round_trips() {
let mut settings = AppSettings::default();
settings.graph.layout_mode = UiLayoutMode::F1OpenHudBeta;

let serialized = toml::to_string_pretty(&settings).unwrap();
let restored: AppSettings = toml::from_str(&serialized).unwrap();

assert_eq!(restored.graph.layout_mode, UiLayoutMode::F1OpenHudBeta);
assert!(serialized.contains("layout_mode = \"f1_open_hud_beta\""));
}
}
Loading