mirror of
https://github.com/IS1DI/x0gp.git
synced 2026-07-17 04:57:55 +00:00
624 lines
22 KiB
Go
624 lines
22 KiB
Go
// Package seeddefs holds the canonical seed definitions for tracks and
|
||
// cars. It lives in its own package (no postgres dependencies) so both
|
||
// the catalog tests and the scripts/genseed SQL generator can consume
|
||
// the same source-of-truth data.
|
||
package seeddefs
|
||
|
||
import "math"
|
||
|
||
// Default tracks and cars shipped with the PoC. All entries are
|
||
// visibility=system and therefore immutable from the API.
|
||
//
|
||
// Tracks: five real F1 circuits (2024 calendar), simplified to fit a
|
||
// 4.5 × 3.2 m bounding box at 1/27 scale. Each track keeps a
|
||
// recognisable shape and characteristic corners.
|
||
//
|
||
// Cars: five real F1 2024 teams. Length / width / weight are real
|
||
// (1/24 scale), power figures are real (full-scale 1.6L V6 hybrid
|
||
// turbo, ~1000 hp combined MGU-K + ICE).
|
||
func DefaultTracks() []Track {
|
||
tracks := []Track{
|
||
Monaco(),
|
||
BarcelonaCatalunya(),
|
||
AustriaRedBullRing(),
|
||
GreatBritainSilverstone(),
|
||
BelgiumSpa(),
|
||
}
|
||
for i := range tracks {
|
||
for j := range tracks[i].Centerline {
|
||
tracks[i].Centerline[j].Y = -tracks[i].Centerline[j].Y
|
||
tracks[i].Centerline[j].HeadingRad = -tracks[i].Centerline[j].HeadingRad
|
||
}
|
||
tracks[i].Bounds = ComputeBounds(tracks[i].Centerline)
|
||
tracks[i].LengthM = tracks[i].Bounds.LengthM
|
||
tracks[i].WidthM = tracks[i].Bounds.WidthM
|
||
}
|
||
return tracks
|
||
}
|
||
|
||
func DefaultCars() []Car {
|
||
return []Car{
|
||
RedBullRB20(),
|
||
FerrariSF24(),
|
||
McLarenMCL38(),
|
||
MercedesW15(),
|
||
AstonMartinAMR24(),
|
||
}
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Tracks
|
||
//
|
||
// Coordinates: lower-left origin, metres. Bounds must fit inside a
|
||
// 4.5 × 3.2 m room.
|
||
//
|
||
// Conventions:
|
||
// - closed loop, samples[i] and samples[0] are not duplicated
|
||
// - speed_ms is a suggested cornering speed (lower = tighter)
|
||
// - curvature = 1/radius; 0 = straight
|
||
// ---------------------------------------------------------------------------
|
||
|
||
// Monaco — narrow street circuit, hairpins, tight corners.
|
||
// Length ≈ 3.337 km IRL → simplified to 4.0 × 2.6 m.
|
||
func Monaco() Track {
|
||
const (
|
||
lengthM = 4.0
|
||
widthM = 2.6
|
||
)
|
||
// Famous corners: Sainte-Devote, Casino Square, Mirabeau, Loews hairpin,
|
||
// Portier, Tunnel, Nouvelle chicane, Tabac, Swimming pool, Rascasse, Anthony Noghes.
|
||
// Lower speeds through Monaco → 1.8..3.2 m/s.
|
||
wps := []Waypoint{
|
||
// Start/finish straight (Sainte-Devote approach).
|
||
{X: 0.4, Y: 2.2, HeadingRad: 0, SpeedMs: 3.2, Curvature: 0},
|
||
{X: 1.1, Y: 2.2, HeadingRad: 0, SpeedMs: 3.0, Curvature: 0},
|
||
// Sainte-Devote (right kink).
|
||
{X: 1.5, Y: 2.05, HeadingRad: -0.4, SpeedMs: 2.4, Curvature: 1.2},
|
||
{X: 1.7, Y: 1.85, HeadingRad: -0.9, SpeedMs: 2.2, Curvature: 1.8},
|
||
// Up the hill to Casino / Massenet.
|
||
{X: 1.55, Y: 1.55, HeadingRad: -1.6, SpeedMs: 2.0, Curvature: 1.4},
|
||
{X: 1.25, Y: 1.4, HeadingRad: -2.4, SpeedMs: 2.1, Curvature: 1.2},
|
||
// Casino Square (slow left).
|
||
{X: 0.95, Y: 1.55, HeadingRad: math.Pi, SpeedMs: 1.9, Curvature: 1.6},
|
||
// Mirabeau hairpin (right).
|
||
{X: 0.75, Y: 1.85, HeadingRad: 2.0, SpeedMs: 1.8, Curvature: 2.2},
|
||
// Loews hairpin (tight left).
|
||
{X: 0.95, Y: 2.15, HeadingRad: 2.6, SpeedMs: 1.8, Curvature: 2.6},
|
||
{X: 1.2, Y: 2.4, HeadingRad: -2.8, SpeedMs: 2.1, Curvature: 1.4},
|
||
// Portier (right kink onto the straight).
|
||
{X: 1.6, Y: 2.5, HeadingRad: -3.0, SpeedMs: 2.4, Curvature: 1.0},
|
||
// Tunnel straight.
|
||
{X: 2.6, Y: 2.5, HeadingRad: 0, SpeedMs: 3.2, Curvature: 0},
|
||
{X: 3.4, Y: 2.5, HeadingRad: 0, SpeedMs: 3.2, Curvature: 0},
|
||
// Nouvelle chicane (fast left-right).
|
||
{X: 3.6, Y: 2.25, HeadingRad: -0.5, SpeedMs: 2.6, Curvature: 1.5},
|
||
{X: 3.55, Y: 1.95, HeadingRad: 0.4, SpeedMs: 2.6, Curvature: 1.5},
|
||
// Tabac (right).
|
||
{X: 3.3, Y: 1.75, HeadingRad: 1.0, SpeedMs: 2.3, Curvature: 1.4},
|
||
// Swimming pool (left-right-left).
|
||
{X: 2.95, Y: 1.55, HeadingRad: 1.7, SpeedMs: 2.1, Curvature: 1.8},
|
||
{X: 2.6, Y: 1.65, HeadingRad: 2.6, SpeedMs: 2.1, Curvature: 1.8},
|
||
{X: 2.4, Y: 1.9, HeadingRad: -2.7, SpeedMs: 2.3, Curvature: 1.4},
|
||
// Rascasse (left).
|
||
{X: 2.4, Y: 2.2, HeadingRad: -3.0, SpeedMs: 2.2, Curvature: 1.6},
|
||
// Anthony Noghes (left onto pit straight).
|
||
{X: 2.6, Y: 2.4, HeadingRad: 2.8, SpeedMs: 2.5, Curvature: 1.0},
|
||
// Pit straight (back to start).
|
||
{X: 3.2, Y: 2.4, HeadingRad: 0, SpeedMs: 3.2, Curvature: 0},
|
||
{X: 3.7, Y: 2.4, HeadingRad: 0, SpeedMs: 3.2, Curvature: 0},
|
||
{X: 3.95, Y: 2.3, HeadingRad: -0.3, SpeedMs: 2.8, Curvature: 0.8},
|
||
}
|
||
b := ComputeBounds(wps)
|
||
return Track{
|
||
ID: "monaco",
|
||
Name: "Monaco",
|
||
Description: "Circuit de Monaco. Narrow street circuit, tight hairpins, no room for error.",
|
||
AuthorID: "system",
|
||
Visibility: "system",
|
||
Scale: 27,
|
||
LengthM: b.LengthM,
|
||
WidthM: b.WidthM,
|
||
LaneWidthM: 0.18,
|
||
Surface: "tile",
|
||
Tags: []string{"f1", "street", "tight", "slow", "monaco"},
|
||
Centerline: wps,
|
||
Bounds: b,
|
||
BestLapHolder: "Verstappen",
|
||
}
|
||
}
|
||
|
||
// BarcelonaCatalunya — mix of high-speed and technical, used for testing.
|
||
// Length ≈ 4.675 km IRL → 4.3 × 2.9 m.
|
||
func BarcelonaCatalunya() Track {
|
||
const (
|
||
lengthM = 4.3
|
||
widthM = 2.9
|
||
)
|
||
wps := []Waypoint{
|
||
// Main straight.
|
||
{X: 0.3, Y: 0.4, HeadingRad: 0, SpeedMs: 4.5, Curvature: 0},
|
||
{X: 1.4, Y: 0.4, HeadingRad: 0, SpeedMs: 4.5, Curvature: 0},
|
||
// Turn 1 — Elf (right hairpin).
|
||
{X: 1.7, Y: 0.65, HeadingRad: math.Pi / 2, SpeedMs: 2.2, Curvature: 1.6},
|
||
{X: 1.5, Y: 0.95, HeadingRad: math.Pi, SpeedMs: 2.0, Curvature: 2.0},
|
||
// Turn 2-3 — right kink.
|
||
{X: 1.2, Y: 1.1, HeadingRad: math.Pi + 0.3, SpeedMs: 2.6, Curvature: 1.0},
|
||
{X: 0.95, Y: 1.0, HeadingRad: math.Pi + 0.9, SpeedMs: 2.6, Curvature: 1.0},
|
||
// Turn 4 — Repsol (left).
|
||
{X: 0.8, Y: 1.2, HeadingRad: -math.Pi / 2, SpeedMs: 2.4, Curvature: 1.2},
|
||
// Turn 5 — fast right.
|
||
{X: 1.05, Y: 1.5, HeadingRad: 0, SpeedMs: 3.2, Curvature: 0.7},
|
||
// Turn 7-8 — slow left-right.
|
||
{X: 1.3, Y: 1.65, HeadingRad: math.Pi / 2, SpeedMs: 2.4, Curvature: 1.0},
|
||
{X: 1.15, Y: 1.85, HeadingRad: math.Pi, SpeedMs: 2.4, Curvature: 1.0},
|
||
// Turn 9-10 — Campsa (fast right).
|
||
{X: 1.4, Y: 2.0, HeadingRad: math.Pi - 0.5, SpeedMs: 3.0, Curvature: 0.7},
|
||
{X: 1.7, Y: 2.0, HeadingRad: math.Pi - 1.0, SpeedMs: 3.0, Curvature: 0.7},
|
||
// Turn 12-13 — long right.
|
||
{X: 1.95, Y: 2.2, HeadingRad: math.Pi / 2, SpeedMs: 2.8, Curvature: 0.5},
|
||
{X: 1.65, Y: 2.5, HeadingRad: 0.3, SpeedMs: 2.8, Curvature: 0.5},
|
||
// Turn 14-15 — fast right curve.
|
||
{X: 2.2, Y: 2.7, HeadingRad: 0.7, SpeedMs: 3.2, Curvature: 0.5},
|
||
{X: 2.8, Y: 2.7, HeadingRad: 1.1, SpeedMs: 3.4, Curvature: 0.5},
|
||
// Turn 16 — La Caixa (right hairpin).
|
||
{X: 3.1, Y: 2.5, HeadingRad: math.Pi / 2 + 0.4, SpeedMs: 2.2, Curvature: 1.4},
|
||
{X: 2.95, Y: 2.2, HeadingRad: math.Pi + 0.4, SpeedMs: 2.2, Curvature: 1.4},
|
||
// Back straight.
|
||
{X: 3.3, Y: 1.9, HeadingRad: 0, SpeedMs: 4.0, Curvature: 0},
|
||
{X: 4.0, Y: 1.9, HeadingRad: 0, SpeedMs: 4.0, Curvature: 0},
|
||
// Turn 16bis-17 (chicane before start).
|
||
{X: 4.05, Y: 1.6, HeadingRad: -math.Pi / 2, SpeedMs: 2.4, Curvature: 1.4},
|
||
{X: 3.8, Y: 1.4, HeadingRad: math.Pi, SpeedMs: 2.4, Curvature: 1.4},
|
||
{X: 3.5, Y: 1.5, HeadingRad: math.Pi + math.Pi/4, SpeedMs: 2.6, Curvature: 1.0},
|
||
// Approach to start.
|
||
{X: 3.1, Y: 1.25, HeadingRad: -math.Pi / 2 - 0.4, SpeedMs: 3.0, Curvature: 0.7},
|
||
{X: 2.7, Y: 1.0, HeadingRad: -math.Pi / 2 - 0.9, SpeedMs: 3.0, Curvature: 0.7},
|
||
{X: 2.3, Y: 0.7, HeadingRad: math.Pi + 0.4, SpeedMs: 3.4, Curvature: 0.5},
|
||
{X: 1.6, Y: 0.5, HeadingRad: math.Pi + 0.2, SpeedMs: 3.6, Curvature: 0.4},
|
||
{X: 0.9, Y: 0.42, HeadingRad: 0, SpeedMs: 4.0, Curvature: 0},
|
||
}
|
||
b := ComputeBounds(wps)
|
||
return Track{
|
||
ID: "barcelona-catalunya",
|
||
Name: "Barcelona-Catalunya",
|
||
Description: "Circuit de Barcelona-Catalunya. Mix of high-speed corners and technical chicanes.",
|
||
AuthorID: "system",
|
||
Visibility: "system",
|
||
Scale: 27,
|
||
LengthM: b.LengthM,
|
||
WidthM: b.WidthM,
|
||
LaneWidthM: 0.18,
|
||
Surface: "tile",
|
||
Tags: []string{"f1", "balanced", "testing", "barcelona"},
|
||
Centerline: wps,
|
||
Bounds: b,
|
||
BestLapHolder: "Verstappen",
|
||
}
|
||
}
|
||
|
||
// AustriaRedBullRing — short, fast, lots of elevation IRL.
|
||
// Length ≈ 4.318 km IRL → 4.0 × 2.4 m.
|
||
func AustriaRedBullRing() Track {
|
||
const (
|
||
lengthM = 4.0
|
||
widthM = 2.4
|
||
)
|
||
wps := []Waypoint{
|
||
// Main straight.
|
||
{X: 0.3, Y: 0.4, HeadingRad: 0, SpeedMs: 4.8, Curvature: 0},
|
||
{X: 1.6, Y: 0.4, HeadingRad: 0, SpeedMs: 4.8, Curvature: 0},
|
||
// Turn 1 — right hairpin.
|
||
{X: 1.85, Y: 0.65, HeadingRad: math.Pi / 2, SpeedMs: 2.0, Curvature: 1.8},
|
||
{X: 1.6, Y: 0.95, HeadingRad: math.Pi, SpeedMs: 2.0, Curvature: 1.8},
|
||
// Turn 2 — fast right.
|
||
{X: 1.3, Y: 1.0, HeadingRad: math.Pi + 0.5, SpeedMs: 3.0, Curvature: 0.7},
|
||
// Turn 3 — left.
|
||
{X: 1.05, Y: 1.2, HeadingRad: -math.Pi / 2, SpeedMs: 2.6, Curvature: 1.0},
|
||
// Turn 4 — Remus (fast uphill right).
|
||
{X: 1.4, Y: 1.4, HeadingRad: 0, SpeedMs: 3.2, Curvature: 0.5},
|
||
{X: 1.85, Y: 1.4, HeadingRad: math.Pi / 2 + 0.2, SpeedMs: 3.2, Curvature: 0.5},
|
||
// Turn 5-6 — fast right-left.
|
||
{X: 2.1, Y: 1.65, HeadingRad: math.Pi, SpeedMs: 3.0, Curvature: 0.7},
|
||
{X: 2.0, Y: 1.85, HeadingRad: -math.Pi / 2, SpeedMs: 3.0, Curvature: 0.7},
|
||
// Turn 7 — right hairpin.
|
||
{X: 2.25, Y: 2.05, HeadingRad: 0, SpeedMs: 2.0, Curvature: 1.8},
|
||
{X: 2.4, Y: 2.0, HeadingRad: math.Pi / 2, SpeedMs: 2.0, Curvature: 1.8},
|
||
// Turn 8 — left hairpin.
|
||
{X: 2.4, Y: 2.2, HeadingRad: math.Pi, SpeedMs: 2.0, Curvature: 1.8},
|
||
{X: 2.2, Y: 2.3, HeadingRad: -math.Pi / 2, SpeedMs: 2.0, Curvature: 1.8},
|
||
// Turn 9 — back straight start (left kink).
|
||
{X: 2.55, Y: 2.4, HeadingRad: -0.2, SpeedMs: 3.4, Curvature: 0.4},
|
||
{X: 3.0, Y: 2.4, HeadingRad: 0, SpeedMs: 4.0, Curvature: 0},
|
||
{X: 3.5, Y: 2.4, HeadingRad: 0, SpeedMs: 4.5, Curvature: 0},
|
||
// Turn 10 — fast right.
|
||
{X: 3.85, Y: 2.15, HeadingRad: math.Pi / 2 + 0.2, SpeedMs: 3.4, Curvature: 0.6},
|
||
// Back to pit straight via Schumacher esses.
|
||
{X: 3.6, Y: 1.8, HeadingRad: math.Pi, SpeedMs: 3.0, Curvature: 0.8},
|
||
{X: 3.25, Y: 1.85, HeadingRad: math.Pi + 0.6, SpeedMs: 3.0, Curvature: 0.8},
|
||
{X: 3.0, Y: 1.6, HeadingRad: -math.Pi / 2 - 0.4, SpeedMs: 3.2, Curvature: 0.6},
|
||
{X: 2.6, Y: 1.4, HeadingRad: -math.Pi, SpeedMs: 3.4, Curvature: 0.5},
|
||
{X: 2.0, Y: 1.2, HeadingRad: math.Pi - 0.5, SpeedMs: 3.6, Curvature: 0.4},
|
||
{X: 1.4, Y: 0.8, HeadingRad: math.Pi - 0.2, SpeedMs: 3.8, Curvature: 0.3},
|
||
{X: 0.8, Y: 0.5, HeadingRad: 0, SpeedMs: 4.2, Curvature: 0.2},
|
||
}
|
||
b := ComputeBounds(wps)
|
||
return Track{
|
||
ID: "austria-redbull-ring",
|
||
Name: "Austria (Red Bull Ring)",
|
||
Description: "Red Bull Ring. Short, fast, three straights separated by hairpins.",
|
||
AuthorID: "system",
|
||
Visibility: "system",
|
||
Scale: 27,
|
||
LengthM: b.LengthM,
|
||
WidthM: b.WidthM,
|
||
LaneWidthM: 0.18,
|
||
Surface: "wood",
|
||
Tags: []string{"f1", "short", "fast", "austria"},
|
||
Centerline: wps,
|
||
Bounds: b,
|
||
BestLapHolder: "Verstappen",
|
||
}
|
||
}
|
||
|
||
// GreatBritainSilverstone — fast, flowing, essex corners.
|
||
// Length ≈ 5.891 km IRL → 4.4 × 3.0 m.
|
||
func GreatBritainSilverstone() Track {
|
||
const (
|
||
lengthM = 4.4
|
||
widthM = 3.0
|
||
)
|
||
wps := []Waypoint{
|
||
// Main straight (Wellington / pit straight), bottom of room.
|
||
{X: 0.3, Y: 0.3, HeadingRad: 0, SpeedMs: 5.0, Curvature: 0},
|
||
{X: 2.4, Y: 0.3, HeadingRad: 0, SpeedMs: 5.0, Curvature: 0},
|
||
// Turn 1 — Abbey (right).
|
||
{X: 2.8, Y: 0.45, HeadingRad: math.Pi / 2, SpeedMs: 3.0, Curvature: 0.8},
|
||
// Turn 2 — Farm Curve (left).
|
||
{X: 2.6, Y: 0.8, HeadingRad: math.Pi, SpeedMs: 3.4, Curvature: 0.6},
|
||
// Turn 3 — Village (right kink).
|
||
{X: 2.95, Y: 1.05, HeadingRad: math.Pi / 2 + 0.5, SpeedMs: 3.2, Curvature: 0.7},
|
||
// Turn 4 — The Loop (right hairpin).
|
||
{X: 3.3, Y: 1.3, HeadingRad: 0, SpeedMs: 2.2, Curvature: 1.6},
|
||
{X: 3.6, Y: 1.15, HeadingRad: -math.Pi / 2, SpeedMs: 2.2, Curvature: 1.6},
|
||
// Turn 5 — Aintree (fast left).
|
||
{X: 3.6, Y: 0.8, HeadingRad: -math.Pi, SpeedMs: 3.0, Curvature: 0.7},
|
||
// Turn 6 — Brooklands (right).
|
||
{X: 3.85, Y: 0.55, HeadingRad: -math.Pi / 2, SpeedMs: 2.6, Curvature: 1.0},
|
||
// Turn 7 — Luffield (long right).
|
||
{X: 3.55, Y: 0.25, HeadingRad: 0, SpeedMs: 2.4, Curvature: 1.0},
|
||
{X: 3.25, Y: 0.25, HeadingRad: math.Pi / 2, SpeedMs: 2.4, Curvature: 1.0},
|
||
// Turn 8-9 — Woodcote (right kink onto pit straight).
|
||
{X: 3.05, Y: 0.55, HeadingRad: math.Pi / 2 + 0.3, SpeedMs: 3.4, Curvature: 0.5},
|
||
{X: 2.85, Y: 0.45, HeadingRad: math.Pi, SpeedMs: 4.0, Curvature: 0.3},
|
||
// (mid-pit straight into Copse corner)
|
||
{X: 1.8, Y: 0.3, HeadingRad: 0, SpeedMs: 4.6, Curvature: 0},
|
||
// Copse (fast right).
|
||
{X: 1.2, Y: 0.45, HeadingRad: -math.Pi / 2 - 0.2, SpeedMs: 3.8, Curvature: 0.4},
|
||
// Maggotts (right curve, swept).
|
||
{X: 1.0, Y: 0.85, HeadingRad: -math.Pi - 0.4, SpeedMs: 3.6, Curvature: 0.5},
|
||
// Beckets (left hairpin-ish).
|
||
{X: 0.7, Y: 1.15, HeadingRad: math.Pi / 2 + 0.3, SpeedMs: 2.4, Curvature: 1.2},
|
||
{X: 0.5, Y: 0.9, HeadingRad: 0, SpeedMs: 2.6, Curvature: 0.9},
|
||
// Chapel (right kink).
|
||
{X: 0.7, Y: 0.65, HeadingRad: -math.Pi / 2, SpeedMs: 3.0, Curvature: 0.7},
|
||
// Hangar (left).
|
||
{X: 0.45, Y: 0.45, HeadingRad: -math.Pi - 0.4, SpeedMs: 2.6, Curvature: 1.0},
|
||
// Stowe (right).
|
||
{X: 0.75, Y: 1.0, HeadingRad: math.Pi / 2 - 0.4, SpeedMs: 2.4, Curvature: 1.0},
|
||
// Vale (left kink).
|
||
{X: 0.5, Y: 1.5, HeadingRad: -math.Pi - 0.4, SpeedMs: 2.6, Curvature: 0.8},
|
||
// Club (right kink onto Wellington).
|
||
{X: 0.3, Y: 1.15, HeadingRad: -math.Pi / 2, SpeedMs: 2.4, Curvature: 1.0},
|
||
// Top return straight + kink back to Wellington.
|
||
{X: 0.7, Y: 1.6, HeadingRad: math.Pi / 4, SpeedMs: 3.4, Curvature: 0.5},
|
||
{X: 2.0, Y: 1.95, HeadingRad: math.Pi / 2 + 0.4, SpeedMs: 3.0, Curvature: 0.7},
|
||
{X: 3.0, Y: 2.4, HeadingRad: math.Pi + 0.2, SpeedMs: 3.4, Curvature: 0.4},
|
||
{X: 2.4, Y: 2.65, HeadingRad: -math.Pi / 2 - 0.3, SpeedMs: 4.0, Curvature: 0.3},
|
||
{X: 1.4, Y: 2.6, HeadingRad: -math.Pi, SpeedMs: 4.4, Curvature: 0.2},
|
||
{X: 0.5, Y: 2.3, HeadingRad: -math.Pi + 0.3, SpeedMs: 4.8, Curvature: 0.2},
|
||
{X: 0.3, Y: 1.7, HeadingRad: -math.Pi / 2 + 0.3, SpeedMs: 5.0, Curvature: 0.1},
|
||
}
|
||
b := ComputeBounds(wps)
|
||
return Track{
|
||
ID: "great-britain-silverstone",
|
||
Name: "Great Britain (Silverstone)",
|
||
Description: "Silverstone Circuit. Fast, flowing corners; home of British motorsport.",
|
||
AuthorID: "system",
|
||
Visibility: "system",
|
||
Scale: 27,
|
||
LengthM: b.LengthM,
|
||
WidthM: b.WidthM,
|
||
LaneWidthM: 0.18,
|
||
Surface: "carpet",
|
||
Tags: []string{"f1", "fast", "flowing", "silverstone"},
|
||
Centerline: wps,
|
||
Bounds: b,
|
||
BestLapHolder: "Hamilton",
|
||
}
|
||
}
|
||
|
||
// BelgiumSpa — long, fast, Eau Rouge / Raidillon, varied weather.
|
||
// Length ≈ 7.004 km IRL → 4.5 × 3.0 m.
|
||
func BelgiumSpa() Track {
|
||
const (
|
||
lengthM = 4.5
|
||
widthM = 3.0
|
||
)
|
||
wps := []Waypoint{
|
||
// Main straight (La Source exit).
|
||
{X: 0.4, Y: 0.5, HeadingRad: 0, SpeedMs: 4.8, Curvature: 0},
|
||
{X: 1.6, Y: 0.5, HeadingRad: 0, SpeedMs: 4.8, Curvature: 0},
|
||
// Eau Rouge / Raidillon (fast uphill left-right-left).
|
||
{X: 1.95, Y: 0.6, HeadingRad: 0.3, SpeedMs: 4.2, Curvature: 0.5},
|
||
{X: 2.2, Y: 0.95, HeadingRad: 0.9, SpeedMs: 4.0, Curvature: 0.6},
|
||
{X: 2.1, Y: 1.3, HeadingRad: math.Pi + 0.4, SpeedMs: 4.0, Curvature: 0.6},
|
||
// Kemmel straight.
|
||
{X: 2.7, Y: 1.5, HeadingRad: 0, SpeedMs: 5.0, Curvature: 0},
|
||
{X: 3.5, Y: 1.5, HeadingRad: 0, SpeedMs: 5.0, Curvature: 0},
|
||
// Les Combes (chicane).
|
||
{X: 3.85, Y: 1.3, HeadingRad: -math.Pi / 2, SpeedMs: 2.4, Curvature: 1.4},
|
||
{X: 3.6, Y: 1.1, HeadingRad: math.Pi + 0.3, SpeedMs: 2.6, Curvature: 1.2},
|
||
{X: 3.3, Y: 1.25, HeadingRad: math.Pi + 0.9, SpeedMs: 2.6, Curvature: 1.2},
|
||
// Malmedy (fast right sweep).
|
||
{X: 3.5, Y: 1.55, HeadingRad: math.Pi / 2, SpeedMs: 3.6, Curvature: 0.4},
|
||
// Rivage (right hairpin).
|
||
{X: 3.25, Y: 1.85, HeadingRad: 0, SpeedMs: 2.2, Curvature: 1.6},
|
||
{X: 3.05, Y: 1.7, HeadingRad: -math.Pi / 2, SpeedMs: 2.4, Curvature: 1.4},
|
||
// Campus (left kink).
|
||
{X: 3.25, Y: 1.5, HeadingRad: 0, SpeedMs: 3.0, Curvature: 0.7},
|
||
// Stavelot (fast right).
|
||
{X: 3.7, Y: 1.65, HeadingRad: math.Pi / 2 + 0.3, SpeedMs: 3.4, Curvature: 0.5},
|
||
// Up to Blanchimont (long right).
|
||
{X: 4.1, Y: 2.0, HeadingRad: math.Pi / 2 + 0.6, SpeedMs: 4.0, Curvature: 0.3},
|
||
// Blanchimont (very fast right kink).
|
||
{X: 4.25, Y: 2.4, HeadingRad: math.Pi + 0.4, SpeedMs: 4.6, Curvature: 0.3},
|
||
// Bus stop chicane (slow left-right).
|
||
{X: 4.0, Y: 2.7, HeadingRad: -math.Pi / 2 - 0.3, SpeedMs: 2.4, Curvature: 1.4},
|
||
{X: 3.7, Y: 2.55, HeadingRad: math.Pi, SpeedMs: 2.4, Curvature: 1.4},
|
||
{X: 3.55, Y: 2.75, HeadingRad: math.Pi + 0.7, SpeedMs: 2.4, Curvature: 1.4},
|
||
// Back to La Source hairpin.
|
||
{X: 3.05, Y: 2.7, HeadingRad: -math.Pi / 2, SpeedMs: 2.0, Curvature: 1.8},
|
||
{X: 2.85, Y: 2.45, HeadingRad: 0, SpeedMs: 2.0, Curvature: 1.8},
|
||
// Pit straight.
|
||
{X: 2.0, Y: 2.2, HeadingRad: math.Pi - 0.3, SpeedMs: 3.6, Curvature: 0.3},
|
||
{X: 1.2, Y: 1.6, HeadingRad: math.Pi - 0.6, SpeedMs: 4.0, Curvature: 0.2},
|
||
{X: 0.6, Y: 1.05, HeadingRad: math.Pi - 0.3, SpeedMs: 4.4, Curvature: 0.2},
|
||
}
|
||
b := ComputeBounds(wps)
|
||
return Track{
|
||
ID: "belgium-spa",
|
||
Name: "Belgium (Spa-Francorchamps)",
|
||
Description: "Spa-Francorchamps. Long, fast, legendary Eau Rouge / Raidillon.",
|
||
AuthorID: "system",
|
||
Visibility: "system",
|
||
Scale: 27,
|
||
LengthM: b.LengthM,
|
||
WidthM: b.WidthM,
|
||
LaneWidthM: 0.18,
|
||
Surface: "carpet",
|
||
Tags: []string{"f1", "long", "fast", "spa"},
|
||
Centerline: wps,
|
||
Bounds: b,
|
||
BestLapHolder: "Verstappen",
|
||
}
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Cars — F1 2024 constructors
|
||
//
|
||
// Real cars are 1:1 with ~5.7 × 2.0 × 0.95 m, ~798 kg, ~1000 hp.
|
||
// We model them at 1/24 scale so the largest dimension (~238 mm) still
|
||
// fits the catalog physical-envelope check.
|
||
// ---------------------------------------------------------------------------
|
||
|
||
// RedBullRB20 — Oracle Red Bull Racing, Verstappen / Perez.
|
||
// 2024: 9 wins (last year of dominant era), Constructors' champion.
|
||
func RedBullRB20() Car {
|
||
return Car{
|
||
ID: "f1-2024-redbull-rb20",
|
||
Name: "Oracle Red Bull Racing RB20",
|
||
OwnerID: "system",
|
||
Visibility: "system",
|
||
Scale: 24,
|
||
LengthMm: 238, // 5.7m IRL
|
||
WidthMm: 83, // 2.0m IRL
|
||
HeightMm: 40, // 0.95m IRL
|
||
WeightG: 33, // 798 kg IRL
|
||
Chassis: Chassis{
|
||
Model: "RB20 carbon monocoque",
|
||
Material: "carbon-fibre",
|
||
Printed: false,
|
||
WheelbaseMm: 150, // 3.6m IRL
|
||
TrackMm: 75, // 1.8m IRL
|
||
},
|
||
Motor: Motor{
|
||
Kind: "turbo-hybrid",
|
||
Class: "F1 PU",
|
||
KV: 0,
|
||
PowerW: 746000, // ~1000 hp ICE + MGU-K, peak 746 kW
|
||
},
|
||
Battery: Battery{
|
||
VoltageV: 0,
|
||
CapacityMah: 0,
|
||
Cells: 0,
|
||
Chemistry: "li-ion MGU-K",
|
||
},
|
||
Drive: "2WD",
|
||
TopSpeedMs: 95, // ~340 km/h
|
||
ColorHex: "#1e3a8a", // dark blue + red/yellow accents
|
||
Active: true,
|
||
TotalRaces: 0,
|
||
TotalLaps: 0,
|
||
BestLapMs: 0,
|
||
}
|
||
}
|
||
|
||
// FerrariSF24 — Scuderia Ferrari, Leclerc / Sainz.
|
||
func FerrariSF24() Car {
|
||
return Car{
|
||
ID: "f1-2024-ferrari-sf24",
|
||
Name: "Scuderia Ferrari SF-24",
|
||
OwnerID: "system",
|
||
Visibility: "system",
|
||
Scale: 24,
|
||
LengthMm: 238,
|
||
WidthMm: 83,
|
||
HeightMm: 40,
|
||
WeightG: 33,
|
||
Chassis: Chassis{
|
||
Model: "SF-24 carbon monocoque",
|
||
Material: "carbon-fibre",
|
||
Printed: false,
|
||
WheelbaseMm: 150,
|
||
TrackMm: 75,
|
||
},
|
||
Motor: Motor{
|
||
Kind: "turbo-hybrid",
|
||
Class: "F1 PU 066/12",
|
||
KV: 0,
|
||
PowerW: 746000,
|
||
},
|
||
Battery: Battery{
|
||
Chemistry: "li-ion MGU-K",
|
||
},
|
||
Drive: "2WD",
|
||
TopSpeedMs: 94,
|
||
ColorHex: "#dc2626", // rosso corsa
|
||
Active: true,
|
||
}
|
||
}
|
||
|
||
// McLarenMCL38 — McLaren Racing, Norris / Piastri. 2024 Constructors' runner-up.
|
||
func McLarenMCL38() Car {
|
||
return Car{
|
||
ID: "f1-2024-mclaren-mcl38",
|
||
Name: "McLaren MCL38",
|
||
OwnerID: "system",
|
||
Visibility: "system",
|
||
Scale: 24,
|
||
LengthMm: 238,
|
||
WidthMm: 83,
|
||
HeightMm: 40,
|
||
WeightG: 33,
|
||
Chassis: Chassis{
|
||
Model: "MCL38 carbon monocoque",
|
||
Material: "carbon-fibre",
|
||
Printed: false,
|
||
WheelbaseMm: 150,
|
||
TrackMm: 75,
|
||
},
|
||
Motor: Motor{
|
||
Kind: "turbo-hybrid",
|
||
Class: "F1 PU Mercedes-derived",
|
||
KV: 0,
|
||
PowerW: 746000,
|
||
},
|
||
Battery: Battery{
|
||
Chemistry: "li-ion MGU-K",
|
||
},
|
||
Drive: "2WD",
|
||
TopSpeedMs: 94,
|
||
ColorHex: "#ea580c", // papaya orange
|
||
Active: true,
|
||
}
|
||
}
|
||
|
||
// MercedesW15 — Mercedes-AMG Petronas, Russell / Hamilton (last year of Hamilton at Mercedes).
|
||
func MercedesW15() Car {
|
||
return Car{
|
||
ID: "f1-2024-mercedes-w15",
|
||
Name: "Mercedes-AMG W15",
|
||
OwnerID: "system",
|
||
Visibility: "system",
|
||
Scale: 24,
|
||
LengthMm: 238,
|
||
WidthMm: 83,
|
||
HeightMm: 40,
|
||
WeightG: 33,
|
||
Chassis: Chassis{
|
||
Model: "W15 carbon monocoque",
|
||
Material: "carbon-fibre",
|
||
Printed: false,
|
||
WheelbaseMm: 150,
|
||
TrackMm: 75,
|
||
},
|
||
Motor: Motor{
|
||
Kind: "turbo-hybrid",
|
||
Class: "F1 PU M15",
|
||
KV: 0,
|
||
PowerW: 746000,
|
||
},
|
||
Battery: Battery{
|
||
Chemistry: "li-ion MGU-K",
|
||
},
|
||
Drive: "2WD",
|
||
TopSpeedMs: 94,
|
||
ColorHex: "#06b6d4", // silver-petronas cyan
|
||
Active: true,
|
||
}
|
||
}
|
||
|
||
// AstonMartinAMR24 — Aston Martin Aramco, Alonso / Stroll.
|
||
func AstonMartinAMR24() Car {
|
||
return Car{
|
||
ID: "f1-2024-astonmartin-amr24",
|
||
Name: "Aston Martin Aramco AMR24",
|
||
OwnerID: "system",
|
||
Visibility: "system",
|
||
Scale: 24,
|
||
LengthMm: 238,
|
||
WidthMm: 83,
|
||
HeightMm: 40,
|
||
WeightG: 33,
|
||
Chassis: Chassis{
|
||
Model: "AMR24 carbon monocoque",
|
||
Material: "carbon-fibre",
|
||
Printed: false,
|
||
WheelbaseMm: 150,
|
||
TrackMm: 75,
|
||
},
|
||
Motor: Motor{
|
||
Kind: "turbo-hybrid",
|
||
Class: "F1 PU Mercedes-derived",
|
||
KV: 0,
|
||
PowerW: 746000,
|
||
},
|
||
Battery: Battery{
|
||
Chemistry: "li-ion MGU-K",
|
||
},
|
||
Drive: "2WD",
|
||
TopSpeedMs: 94,
|
||
ColorHex: "#16a34a", // racing green
|
||
Active: true,
|
||
}
|
||
}
|
||
|
||
// ComputeBounds derives a bounding box from a closed loop of waypoints.
|
||
func ComputeBounds(loop []Waypoint) Bounds {
|
||
if len(loop) == 0 {
|
||
return Bounds{}
|
||
}
|
||
b := Bounds{
|
||
MinX: loop[0].X,
|
||
MinY: loop[0].Y,
|
||
MaxX: loop[0].X,
|
||
MaxY: loop[0].Y,
|
||
}
|
||
for _, p := range loop[1:] {
|
||
if p.X < b.MinX {
|
||
b.MinX = p.X
|
||
}
|
||
if p.Y < b.MinY {
|
||
b.MinY = p.Y
|
||
}
|
||
if p.X > b.MaxX {
|
||
b.MaxX = p.X
|
||
}
|
||
if p.Y > b.MaxY {
|
||
b.MaxY = p.Y
|
||
}
|
||
}
|
||
b.LengthM = b.MaxX - b.MinX
|
||
b.WidthM = b.MaxY - b.MinY
|
||
return b
|
||
} |