Add a track
You'll build mod.hockenheim, a track addon that adds Hockenheimring with its own world, AI data, names, lap count and fuel. Players see Hockenheim in every Grand Prix and Time Trial box it fits, and career calendars that race the German Grand Prix pick it through its venue, venue/hockenheimring.
The folder
The C++ and Without code versions share the track assets; choose one registration version.
mod.hockenheim/
├── swofty.json
├── registry.json
├── native/ # C++ version
│ ├── build.sh
│ ├── hockenheim.dll
│ └── src/
│ └── track.cpp
├── registry/ # Without code version
│ └── track.json
├── track/
│ └── hockenheim.erp
├── mipmaps/
│ └── hockenheim_d.tif.mipmaps
└── ui/
├── images.json
└── minimap.jsonThe world, textures, AI data and menu images are yours to make. This guide covers registering them.
1. Register Hockenheim
Write native/src/track.cpp, or use registry/track.json from Without code. Track inheritance carries Austria's data into Hockenheim and carries Hockenheim into each box's base track list.
#include <string>
#include <swofty/registry.hpp>
#include <swofty/systems/tracks.hpp>
#include <swofty/systems/ui.hpp>
using namespace swofty;
SWOFTY_REGISTER
{
ui::strings.add("lng_hockenheim_title", {
.text = { { "*", "Hockenheimring" } },
});
ui::strings.add("lng_hockenheim_gp", {
.text = { { "*", "German Grand Prix" } },
});
auto hockenheim = tracks::tracks.add("hockenheim", {
.code = "hockenheim",
.name = "Hockenheimring",
.base = tracks::tracks["game/austria"],
// No slot: allocate a new track row.
.provides = { tracks::venues["venue/hockenheimring"] },
.names = {
.title = Text::key("lng_hockenheim_title"),
.gp = Text::key("lng_hockenheim_gp"),
.acronym = "HOC",
},
.files = file("track/"),
.length_metres = 4574,
.corners = 17,
.latitude = 49.3278f,
.longitude = 8.5658f,
// stat_id stays automatic and is pinned for the install.
});
tracks::data.layer(
under<tracks::TrackData>("mod.hockenheim/hockenheim/"),
[](tracks::TrackData &d) {
// Menus read the names on TrackData.
d.names.title = Text::key("lng_hockenheim_title");
d.names.gp = Text::key("lng_hockenheim_gp");
d.names.acronym = "HOC";
d.laps = 67;
d.fuel.burn_per_lap = 1.62f;
d.pit.speed_limit = 80;
});
for (const char *set : {
"f1_2025", "sprint_f1_2025", "f1_2026", "sprint_f1_2026"
}) {
tracks::data.layer(
tracks::data[std::string("mod.hockenheim/hockenheim/") + set],
[](tracks::TrackData &d) {
d.tyres = {
.softer = "C4",
.medium = "C3",
.harder = "C2",
};
});
}
// Both modes use the supplied 22-slot, 11-garage world.
for (const char *mode : { "f1", "swofty_f1" }) {
tracks::layers.add(std::string("hockenheim_") + mode, {
.track = hockenheim,
.mode = mode,
.world = {
"environment_package/tracks/hockenheim",
"hockenheim_f1_26",
},
.grid = 22,
.garages = 11,
});
}
}
SWOFTY_ADDON()2. Name the addon and its requirement
Write swofty.json and registry.json. The manifest names the DLL, while the record requirement lets the launcher check Austria before Play.
{
"schema_version": 1,
"id": "mod.hockenheim",
"name": "Hockenheimring",
"version": "1.0.0",
"author": "you",
"category": "tracks",
"compat": {
"game": "f1_25",
"launcher": ">=1.6.0"
},
"operations": {
"native": [
{ "dll": "native/hockenheim.dll" }
]
}
}// registry.json
{
"requires": [
{ "record": "track:game/austria" }
]
}For Without code, remove operations and the native/ folder; the core discovers data providers in registry/.
3. Supply the world and AI data
The hockenheim_f1_26 world in step 1 says it has 22 grid slots and 11 garages. Those must be real objects in your world files: the numbers describe the world, they don't create anything. Worlds, slots and AI lines describes the records that pick the world.
Supply Hockenheim's baked AI data for the car packages you test, using ai/<package folder>/<asset_filename>/*.erp; AI packs determine which baked data each car reads.
An AI line lists its corner starts and ends. A line with an incomplete set, such as a work-in-progress line with a start that has no end, leaves some cars with no corner start ahead. The game reads that entry without a check and crashes in Ai_Job_UpdateCarsParallel; the core sends such a car down the game's own path for a line without corner data, and the car keeps driving the line. The first car logs once per session:
[ai] corner start: car 3 has no corner start ahead on this track's AI line (its corner starts and ends are incomplete); it drives the line without corner dataSWOFTY_AI_CORNER_GUARD=0 turns the guard off. Complete every corner's start and end on the line for the AI to brake and overtake at them.
A new track also needs the stack-built keys listed under track limits:
CareerHubFirstVisit_N
CareerHubLastVisit_N
TrackHighlights_N
TrackVideo_N4. Check the boxes that inherit it
Each box's track list offers Hockenheim wherever it fits: room for its grid, AI data for its cars, and Time Trial support where the box has Time Trial. A box where it doesn't fit leaves it out. To hide it from a box where it fits but you don't want it, set offered to false.
// Optional, inside SWOFTY_REGISTER: hide Hockenheim from one box.
tracks::per_package.layer(
tracks::per_package[
"swofty.classic-seasons-2016/f1_2016/hockenheim"
].optional(),
[](tracks::PackageTrack &t) {
t.offered = false;
});Test each box you care about:
- Grid size: it has enough grid slots and pit boxes for the session.
- AI data: every participating car resolves suitable baked data.
- Time Trial: entering, driving and finishing the session work.
The supplied world covers up to 22 grid slots and 11 garages; a My Team world needs its own assets with 24 slots and 12 garages.
Time Trial on added tracks can crash or freeze, and community tracks crash with more than 27 cars. Test both before you publish.
5. Let a career choose the venue
A career addon's calendar names venue/hockenheimring, and gets whichever enabled track provides that venue. When two tracks provide it, the later one in load order wins. This calendar round belongs in the career addon's calendar, outside the track addon.
// Entry in the calendar's .rounds list.
{
.round = 12,
.race = Text::key("lng_gp_germany"),
.venue = tracks::venues["venue/hockenheimring"].optional(),
},[registry] career.calendar swofty.classic-seasons-2016/f1_2016: round 12 venue/hockenheimring -> mod.hockenheim/hockenheim6. Make a track zip without C++
Package the assets with a root manifest.json, then import hockenheim.zip. The track importer creates mod.hockenheim, its file-placement operation and registry/track.json.
{
"track_code": "hockenheim",
"track_name": "Hockenheimring",
"version": "1.0.0",
"author_name": "you",
"erp": "hockenheim.erp",
"activation": {
"copy_from_track": "austria"
},
"copy_directories": [
{
"source": "mipmaps/",
"destination": "asset_groups/environment_package/tracks/hockenheim/mipmaps/"
}
],
"ui": {
"schema_version": 1,
"images_file": "ui/images.json",
"minimap_file": "ui/minimap.json"
}
}swofty mods import hockenheim.zip
swofty buildImport installs and enables the addon in the active profile. The imported package remains under track/, alongside ops/track.json; preserve the generated manifest and operations.
A track zip's display name doesn't supply menu strings, and the importer never guesses a venue from a track's code name. Add the Without code records from step 1 to the imported addon's registry/track.json, including "provides": ["venue/hockenheimring"], and keep any other records the importer wrote.
Try it
Build the C++ version from inside mod.hockenheim, then run the install commands from its parent folder. Skip the native build for Without code; an imported zip is already installed and enabled.
native/build.sh comes from the addon template.
# Inside mod.hockenheim, for the C++ version:
native/build.sh# From the folder containing mod.hockenheim:
swofty addons install ./mod.hockenheim
swofty addons enable mod.hockenheim
swofty run --applyOpen Grand Prix, choose a supported box and select Hockenheim. Check the displayed name, start a race, drive through the pit lane and confirm the AI completes laps.
Look for these lines in swofty/loader.log:
[registry] track mod.hockenheim/hockenheim: base game/austria, new row, provides venue/hockenheimring
[registry] applied at session load: track.data mod.hockenheim/hockenheim/f1_2026 (laps 67, fuel 1.62, tyres C4/C3/C2)The pinned-id line records the allocation for this install; its number and date vary:
[tracks] hockenheim: stat id 43 (pinned since 2026-10-27)These lines confirm registration and track-data application. Verify world loading, garages, AI and each supported mode in game.
When something's missing
A missing requires entry stops Play and names the requirement; a missing hard reference leaves its record unresolved and can hide or guard the affected content. An optional calendar venue drops that round when the career starts, as described under missing references.
[registry] career.calendar swofty.classic-seasons-2016/f1_2016: round 12 dropped (venue/hockenheimring: no
enabled track provides it; optional); round 16 races at mod.bahrain24/sepangOnce a career has raced Hockenheim, its dependency record retains the providing addon, resolved track and pinned stat id. The core stores it in the career's swofty_registry container and mirrors it to <game>/swofty/careers/index.json.
{
"addons": [
{
"id": "mod.hockenheim",
"version": "1.0.0",
"need": "^1.0.0",
"why": "calendar round 12: track mod.hockenheim/hockenheim"
}
],
"records": [
{
"ref": "track:venue/hockenheimring",
"resolved": "mod.hockenheim/hockenheim",
"stat_id": 43,
"used": "calendar round 12"
}
]
}This is an excerpt of career-owned data, not a file to ship. Removing or disabling mod.hockenheim makes the launcher warn that the career cannot load; the core's dependency guard stops Continue with a “Missing addons for this career” popup directing the player to restore the addon.
A career that skipped Hockenheim records it under dropped; enabling the track later does not make that career depend on it.
For a career identified only after loading from the save-slot list, the popup tells the player to quit without saving and restore the addon. The launcher's session backup protects recovery.
Publish
Package the tested addon and follow Publishing to publish mod.hockenheim.