Use the original handling, walls and projection from the executable

Decoded from SKIDMARKS_CD32's machine code:

- Car physics: the per-frame engine/grip/steering update, gravity and
  jumps, the hill effect on the throttle, and body tilt. Replaces the
  reconstructed handling model.
- The meaning of every .set value (separate drag, grip and power for
  human and computer drivers; steering, gravity, tow hitch), and the
  six handling presets, offered by a new Handling menu option.
- Walls: the road edges are quadratic B-splines through the gates'
  ends. They match the painted road edges.
- The real projection, which lifts points by their height; the height
  map is indexed by world cells, not screen pixels.
- Tracks have one more gate than the stored count: the last gate was
  being dropped on every track.

Computer drivers now follow the road between the walls and steer like
the original's. Vehicle assets store the values as `handling`; older
files still load. Docs describe the decoded logic.
This commit is contained in:
2026-10-20 07:32:46 +00:00
parent ee895e0455
commit 6226c88d4e
44 changed files with 1283 additions and 791 deletions
+25 -13
View File
@@ -28,32 +28,44 @@ give either to also gets the game data.
## What works
- All 24 tracks across the four track files, in their original palettes.
Each has its isometric height map and lap gates.
Each has its isometric height map and lap gates, and walls along the
road edges, curved the way the original curves them.
- All 9 cars (Mini, Beetle, Porsche, Formula 1, Midget, Truck, Police,
Cow, Camaro) with their drivers' names, each with 32 headings and 25
body-tilt frames for hills.
- The original handling: each car drives with the values from its `.set`
file, decoded from the executable, with separate profiles for human and
computer drivers. The menu's **Handling** option also offers the
original's six presets (Classic, Grunty, Zippy, Rippy, Slippery and Pedal
Car).
- The caravan: turn on **Caravans** in the menu and every car tows one.
- The original sound effects, and the original tune played by a built-in
OctaMED player. The menu has a **Music** option: menus, always, or off.
- The original title picture.
- 1 or 2 players (split screen) plus up to 7 computer drivers.
- Power-slide handling, slopes, car-to-car bumping, and skid marks burnt
into the track.
- Power-slides, jumps, hills that slow you going up and speed you up coming
down, car-to-car bumping, and skid marks burnt into the track.
- Lap and position tracking, lap times, and a results table.
- Engine note pitched with revs, tyre screech, crashes, backfires, a horn
and start beeps, all from the original samples.
## What's reconstructed
## What's from the original, and what isn't
The executable is on disk 1, but its game logic, a compiled Blitz Basic 2
program, hasn't been decoded. So the **handling, computer drivers and race
rules** are new code written to feel like the original, not a translation
of it.
The game logic is a compiled Blitz Basic 2 program on disk 1. These parts
were decoded from its machine code and work the same way here:
- the car physics: engine, grip, steering, gravity and jumps, and hills
- the meaning of every `.set` value, and the handling presets
- the walls along the road edges
- how the world is projected onto the screen, including height
Each car's original settings file has eleven handling numbers whose
meaning lives in that code. They're kept in the car's settings asset for
when it is decoded. [docs/FORMATS.md](docs/FORMATS.md) covers everything
reverse engineered so far.
The rest is new code written to feel like the original:
- how computer drivers choose their line (they steer and drive as the
original's do)
- car-to-car collisions
- lap counting and the rescue of stuck cars
[docs/FORMATS.md](docs/FORMATS.md) covers everything reverse engineered so
far.
## Running
@@ -88,7 +100,7 @@ Options: `--scale 1|2|4` (window size), `--mute`.
skids info --data DIR # list tracks and vehicles found
skids extract --data ADFS [--out assets] # convert original disks into editable assets
skids bench --data DIR --seconds 120 # race CPU cars on every track: laps and time on road
skids trace --data DIR --track 11 --out t.png # plot CPU racing lines
skids trace --data DIR --track 11 --out t.png # plot walls, gates and CPU racing lines
skids render --data DIR --track 3 --out shot.png # headless screenshot
skids music --out tune.wav --seconds 60 # render the tune to WAV
skids unpack disk.adf --out files/ # extract a disk or LHA archive as-is
+50 -21
View File
@@ -18,8 +18,9 @@ The track picture: 1024 × 512, a 16-colour indexed (palette) image.
- **Palette:** the PNG's palette is the track's palette. Change a palette
entry to recolour every pixel that uses it.
- **Road vs scenery:** colours 8–15 are road (full grip, full speed).
Colours 0–7 are scenery (less grip, slower).
- **Road vs scenery:** colours 8–15 are road; colours 0–7 are scenery,
where cars throw up dust. The picture doesn't affect handling. The
walls come from the gates (below).
- **Painting a new route:** paint it with the upper eight colours and
update the gates to match.
- **Saving:** keep the image indexed if your editor allows it. If it saves
@@ -32,16 +33,26 @@ Plain text; lines starting with `#` are comments.
- **`gate x1 y1 x2 y2`:** lap gates in race order.
- Gate 0 is the start/finish line, and the grid forms up behind it.
- Cars must cross every gate, in order, to complete a lap. Computer
drivers steer from one gate to the next.
- Cars must cross every gate, in order, to complete a lap.
- **Walls:** the gates' ends also shape the road. One edge is a smooth
curve through every gate's first end (`x1 y1`), the other through
every second end (`x2 y2`). Those curves are the walls, and computer
drivers follow the road between them. The curves pass halfway
between neighbouring gates' ends and round off the corners, as in
the original.
- Coordinates are on the isometric ground plane. To find where a point
sits in the picture:
`picture x = wx - wy + 512`, `picture y = (wx + wy) × 31/64 − 252`.
- `skids trace --data assets --track N --out t.png` draws the gates
(gate 0 in yellow) and the computer cars' paths over the picture.
- **`height`:** 64 rows of 64 numbers giving ground height × 256. Each
number covers a 16 × 8 pixel cell of the picture. Slopes push cars
downhill and tilt the car sprites.
on the ground sits in the picture, with `z` its height in pixels
(from `height`):
`picture x = wx - wy + 512`, `picture y = (wx + wy) / 2 − 156 − z`.
- `skids trace --data assets --track N --out t.png` draws the walls
(grey), the gates (gate 0 in yellow) and the computer cars' paths over
the picture.
- **`height`:** 64 rows of 64 numbers giving ground height in pixels × 64.
- **Layout:** the rows run along world y and the numbers within a row
along world x. Each covers 16 × 16 world units.
- **Effect:** climbing slows cars and descending speeds them up. Cars
leave the ground over crests that drop away faster than gravity, and
the car sprites tilt with the slope.
- **`set`:** the name of the set of tracks this one belongs to, shown under
the menu thumbnail.
- **`start`, `flags`:** values from the original data that the game
@@ -91,17 +102,35 @@ The display name, driver and handling:
| `name` | name shown in the menu |
| `driver` | driver's name, shown next to it |
| `trailer` | `yes` for something towed behind cars (the caravan) rather than driven |
| `original`| the 16 values from the original `.set` file, kept for reference; the game doesn't use them yet |
| `top` | top speed on tarmac |
| `accel` | acceleration |
| `brake` | braking |
| `grip` | sideways grip; higher means less sliding |
| `turn` | steering rate |
| `mass` | weight in collisions |
| `offroad` | speed and grip multiplier on scenery |
| `handling`| the 16 values from the original `.set` file, used as the original game uses them (below) |
| `mass` | weight when cars collide; not from the original data |
The handling values are a reconstruction, so tune them freely. To add a
car, add a new `<id>.png` / `<id>.txt` pair.
The `handling` values, counting from 1:
| Value | Meaning |
|-------|---------|
| 1, 2, 6 | computer drivers: drag, grip, power |
| 9, 10, 11 | human drivers: drag, grip, power |
| 3 | steering: 16 = 1/64 of a turn per frame at full lock |
| 5 | gravity: 8 = 1 pixel per frame² |
| 7 | tow hitch distance behind the car: 2 = 1 world unit |
| 4, 8, 12–16 | unused |
What the driving values do:
- **Drag** (below 64): how much of the engine's speed survives each frame.
Low drag gives fast pick-up and fast slowing.
- **Power:** what the throttle adds.
- **Top speed:** `power × drag / (64 − drag) / 32` world units per frame,
at 50 frames a second.
- **Grip:** how quickly the car's movement turns to follow where it's
pointing. Low grip means long slides.
The menu's **Handling** option can replace every car's values with one of
the original's presets.
Older files with `original`, `top`, `accel`, `brake`, `grip`, `turn` and
`offroad` lines still load: `original` is read as `handling`, and the
others are ignored. To add a car, add a new `<id>.png` / `<id>.txt` pair.
## Sounds: `sounds/*.wav`
+9 -5
View File
@@ -1,11 +1,14 @@
# Skids track TRACK1-1. Lines starting with # are comments.
#
# The picture is track.png: 1024x512, 16 colours. Its palette is the
# track's palette. Colours 8-15 are road (full grip), 0-7 are scenery.
# track's palette. Colours 8-15 are road, 0-7 are scenery (dusty).
#
# gate x1 y1 x2 y2: lap gates in race order, in world units. Gate 0 is
# the start/finish line; the grid forms up behind it. World to picture:
# picture x = wx - wy + 512, picture y = (wx + wy) * 31/64 - 252
# the start/finish line; the grid forms up behind it. The gates' ends
# also shape the walls: each edge of the road is a smooth curve
# through the matching ends of the gates. World to picture, for a
# point at height z (pixels):
# picture x = wx - wy + 512, picture y = (wx + wy) / 2 - 156 - z
gate 103.28125 584.9375 265.3125 631.09375
gate 135.53125 439.90625 280.96875 551
gate 271.90625 440.125 343.78125 550.96875
@@ -26,6 +29,7 @@ gate 602.125 632.875 489.125 633.375
gate 600.6875 768.8125 488.4375 696.3125
gate 599.25 904.75 487.75 759.25
gate 456.75 935 408.5 776.75
gate 276.5 753.46875 329.40625 696.40625
# Name of the set of tracks this one belongs to, shown in the menu.
set Racing In The Name Of
@@ -34,8 +38,8 @@ set Racing In The Name Of
start 458 107
flags 1
# height: 64 rows of 64 values, ground height x 256. Each value covers
# 16x8 pixels of the picture.
# height: 64 rows (world y) of 64 values (world x), ground height in
# pixels x 64. Each value covers 16 x 16 world units.
height
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
+9 -5
View File
@@ -1,11 +1,14 @@
# Skids track TRACK1-2. Lines starting with # are comments.
#
# The picture is track.png: 1024x512, 16 colours. Its palette is the
# track's palette. Colours 8-15 are road (full grip), 0-7 are scenery.
# track's palette. Colours 8-15 are road, 0-7 are scenery (dusty).
#
# gate x1 y1 x2 y2: lap gates in race order, in world units. Gate 0 is
# the start/finish line; the grid forms up behind it. World to picture:
# picture x = wx - wy + 512, picture y = (wx + wy) * 31/64 - 252
# the start/finish line; the grid forms up behind it. The gates' ends
# also shape the walls: each edge of the road is a smooth curve
# through the matching ends of the gates. World to picture, for a
# point at height z (pixels):
# picture x = wx - wy + 512, picture y = (wx + wy) / 2 - 156 - z
gate 514.25 827.25 587.71875 941.65625
gate 577 786 679.0625 854.1875
gate 585 721 681.25 727.75
@@ -35,6 +38,7 @@ gate 186 552 73.5 530.5
gate 253.125 618.375 144.34375 626.03125
gate 311.4375 676.8125 239.25 719.28125
gate 357.25 723.75 333.1875 815.5625
gate 438 802 452.875 928.625
# Name of the set of tracks this one belongs to, shown in the menu.
set Racing In The Name Of
@@ -43,8 +47,8 @@ set Racing In The Name Of
start 461 295
flags 0
# height: 64 rows of 64 values, ground height x 256. Each value covers
# 16x8 pixels of the picture.
# height: 64 rows (world y) of 64 values (world x), ground height in
# pixels x 64. Each value covers 16 x 16 world units.
height
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
+9 -5
View File
@@ -1,11 +1,14 @@
# Skids track TRACK1-3. Lines starting with # are comments.
#
# The picture is track.png: 1024x512, 16 colours. Its palette is the
# track's palette. Colours 8-15 are road (full grip), 0-7 are scenery.
# track's palette. Colours 8-15 are road, 0-7 are scenery (dusty).
#
# gate x1 y1 x2 y2: lap gates in race order, in world units. Gate 0 is
# the start/finish line; the grid forms up behind it. World to picture:
# picture x = wx - wy + 512, picture y = (wx + wy) * 31/64 - 252
# the start/finish line; the grid forms up behind it. The gates' ends
# also shape the walls: each edge of the road is a smooth curve
# through the matching ends of the gates. World to picture, for a
# point at height z (pixels):
# picture x = wx - wy + 512, picture y = (wx + wy) / 2 - 156 - z
gate 754 375 827.25 340
gate 686.25 304.25 692.875 209.25
gate 614.375 275.875 608.6875 199.875
@@ -30,6 +33,7 @@ gate 376.5 563.5 316.5 653.5
gate 487.75 549.25 491.5 648.5
gate 708 556 686.5 649.5
gate 766.5 529.5 861 616.5
gate 776 448.5 882.625 465.75
# Name of the set of tracks this one belongs to, shown in the menu.
set Racing In The Name Of
@@ -38,8 +42,8 @@ set Racing In The Name Of
start 608 305
flags 0
# height: 64 rows of 64 values, ground height x 256. Each value covers
# 16x8 pixels of the picture.
# height: 64 rows (world y) of 64 values (world x), ground height in
# pixels x 64. Each value covers 16 x 16 world units.
height
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
+9 -5
View File
@@ -1,11 +1,14 @@
# Skids track TRACK1-4. Lines starting with # are comments.
#
# The picture is track.png: 1024x512, 16 colours. Its palette is the
# track's palette. Colours 8-15 are road (full grip), 0-7 are scenery.
# track's palette. Colours 8-15 are road, 0-7 are scenery (dusty).
#
# gate x1 y1 x2 y2: lap gates in race order, in world units. Gate 0 is
# the start/finish line; the grid forms up behind it. World to picture:
# picture x = wx - wy + 512, picture y = (wx + wy) * 31/64 - 252
# the start/finish line; the grid forms up behind it. The gates' ends
# also shape the walls: each edge of the road is a smooth curve
# through the matching ends of the gates. World to picture, for a
# point at height z (pixels):
# picture x = wx - wy + 512, picture y = (wx + wy) / 2 - 156 - z
gate 545.875 864.625 548 968
gate 629.75 789.25 689.5 852.5
gate 673.90625 729.21875 768.1875 735.3125
@@ -30,6 +33,7 @@ gate 906.25 678.75 828.75 569.25
gate 761 667 688.5 575.5
gate 645 675 623 573
gate 550.4375 729.8125 495.75 655.25
gate 465.40625 784.96875 375.375 789.125
# Name of the set of tracks this one belongs to, shown in the menu.
set Racing In The Name Of
@@ -38,8 +42,8 @@ set Racing In The Name Of
start 608 305
flags 0
# height: 64 rows of 64 values, ground height x 256. Each value covers
# 16x8 pixels of the picture.
# height: 64 rows (world y) of 64 values (world x), ground height in
# pixels x 64. Each value covers 16 x 16 world units.
height
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6432 6700 6444 7135 6400 6400 6400 6692 6924 8802 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
+9 -5
View File
@@ -1,11 +1,14 @@
# Skids track TRACK1-5. Lines starting with # are comments.
#
# The picture is track.png: 1024x512, 16 colours. Its palette is the
# track's palette. Colours 8-15 are road (full grip), 0-7 are scenery.
# track's palette. Colours 8-15 are road, 0-7 are scenery (dusty).
#
# gate x1 y1 x2 y2: lap gates in race order, in world units. Gate 0 is
# the start/finish line; the grid forms up behind it. World to picture:
# picture x = wx - wy + 512, picture y = (wx + wy) * 31/64 - 252
# the start/finish line; the grid forms up behind it. The gates' ends
# also shape the walls: each edge of the road is a smooth curve
# through the matching ends of the gates. World to picture, for a
# point at height z (pixels):
# picture x = wx - wy + 512, picture y = (wx + wy) / 2 - 156 - z
gate 456.53125 333.8125 497.53125 236.8125
gate 361.375 315.15625 308.875 219.65625
gate 367.4375 414.6875 278.6875 347.4375
@@ -31,6 +34,7 @@ gate 333.03125 801.21875 431.53125 800.71875
gate 381.40625 687.25 449.78125 749.875
gate 472.28125 690.78125 551.53125 741.53125
gate 468.5625 611.34375 549.5625 610.34375
gate 473.71875 458.5 555.71875 434.5
# Name of the set of tracks this one belongs to, shown in the menu.
set Racing In The Name Of
@@ -39,8 +43,8 @@ set Racing In The Name Of
start 301 79
flags 0
# height: 64 rows of 64 values, ground height x 256. Each value covers
# 16x8 pixels of the picture.
# height: 64 rows (world y) of 64 values (world x), ground height in
# pixels x 64. Each value covers 16 x 16 world units.
height
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
+9 -5
View File
@@ -1,11 +1,14 @@
# Skids track TRACK1-6. Lines starting with # are comments.
#
# The picture is track.png: 1024x512, 16 colours. Its palette is the
# track's palette. Colours 8-15 are road (full grip), 0-7 are scenery.
# track's palette. Colours 8-15 are road, 0-7 are scenery (dusty).
#
# gate x1 y1 x2 y2: lap gates in race order, in world units. Gate 0 is
# the start/finish line; the grid forms up behind it. World to picture:
# picture x = wx - wy + 512, picture y = (wx + wy) * 31/64 - 252
# the start/finish line; the grid forms up behind it. The gates' ends
# also shape the walls: each edge of the road is a smooth curve
# through the matching ends of the gates. World to picture, for a
# point at height z (pixels):
# picture x = wx - wy + 512, picture y = (wx + wy) / 2 - 156 - z
gate 826.15625 521.3125 744.65625 537.3125
gate 826.0625 647.40625 745.0625 616.65625
gate 825 776.5 727 680.5
@@ -47,6 +50,7 @@ gate 539.1875 318.125 439.9375 263.375
gate 535.65625 389.6875 437.78125 406.5625
gate 576.625 424.75 529.125 502.25
gate 646.96875 425.4375 648.46875 504.4375
gate 741.8125 425.625 705.8125 502.625
# Name of the set of tracks this one belongs to, shown in the menu.
set Racing In The Name Of
@@ -55,8 +59,8 @@ set Racing In The Name Of
start 434 354
flags 1
# height: 64 rows of 64 values, ground height x 256. Each value covers
# 16x8 pixels of the picture.
# height: 64 rows (world y) of 64 values (world x), ground height in
# pixels x 64. Each value covers 16 x 16 world units.
height
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
+9 -5
View File
@@ -1,11 +1,14 @@
# Skids track TRACK2-1. Lines starting with # are comments.
#
# The picture is track.png: 1024x512, 16 colours. Its palette is the
# track's palette. Colours 8-15 are road (full grip), 0-7 are scenery.
# track's palette. Colours 8-15 are road, 0-7 are scenery (dusty).
#
# gate x1 y1 x2 y2: lap gates in race order, in world units. Gate 0 is
# the start/finish line; the grid forms up behind it. World to picture:
# picture x = wx - wy + 512, picture y = (wx + wy) * 31/64 - 252
# the start/finish line; the grid forms up behind it. The gates' ends
# also shape the walls: each edge of the road is a smooth curve
# through the matching ends of the gates. World to picture, for a
# point at height z (pixels):
# picture x = wx - wy + 512, picture y = (wx + wy) / 2 - 156 - z
gate 169.5 489.0625 266.625 584.9375
gate 297.90625 488.4375 298.03125 583.625
gate 393.09375 489.125 394.71875 584.96875
@@ -27,6 +30,7 @@ gate 489.96875 839.53125 456.15625 757.59375
gate 360.5 837.5 380 756
gate 222.9375 708.375 281.25 659.65625
gate 80.25 559.03125 229.5625 610.21875
gate 97.875 487.03125 229.84375 585.8125
# Name of the set of tracks this one belongs to, shown in the menu.
set Even Flow
@@ -35,8 +39,8 @@ set Even Flow
start 474 167
flags 1
# height: 64 rows of 64 values, ground height x 256. Each value covers
# 16x8 pixels of the picture.
# height: 64 rows (world y) of 64 values (world x), ground height in
# pixels x 64. Each value covers 16 x 16 world units.
height
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
+9 -5
View File
@@ -1,11 +1,14 @@
# Skids track TRACK2-2. Lines starting with # are comments.
#
# The picture is track.png: 1024x512, 16 colours. Its palette is the
# track's palette. Colours 8-15 are road (full grip), 0-7 are scenery.
# track's palette. Colours 8-15 are road, 0-7 are scenery (dusty).
#
# gate x1 y1 x2 y2: lap gates in race order, in world units. Gate 0 is
# the start/finish line; the grid forms up behind it. World to picture:
# picture x = wx - wy + 512, picture y = (wx + wy) * 31/64 - 252
# the start/finish line; the grid forms up behind it. The gates' ends
# also shape the walls: each edge of the road is a smooth curve
# through the matching ends of the gates. World to picture, for a
# point at height z (pixels):
# picture x = wx - wy + 512, picture y = (wx + wy) / 2 - 156 - z
gate 177.09375 661.0625 280.84375 667.78125
gate 94.03125 579.53125 231.53125 622.75
gate 133.53125 502.1875 237.1875 597
@@ -32,6 +35,7 @@ gate 671 856 590 781
gate 622.375 910.5 544.6875 822.6875
gate 508.6875 934.5 481.71875 820.71875
gate 418 903.5 421.75 805.75
gate 296.78125 781.03125 363.28125 749.75
# Name of the set of tracks this one belongs to, shown in the menu.
set Even Flow
@@ -40,8 +44,8 @@ set Even Flow
start 339 117
flags 1
# height: 64 rows of 64 values, ground height x 256. Each value covers
# 16x8 pixels of the picture.
# height: 64 rows (world y) of 64 values (world x), ground height in
# pixels x 64. Each value covers 16 x 16 world units.
height
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
+9 -5
View File
@@ -1,11 +1,14 @@
# Skids track TRACK2-3. Lines starting with # are comments.
#
# The picture is track.png: 1024x512, 16 colours. Its palette is the
# track's palette. Colours 8-15 are road (full grip), 0-7 are scenery.
# track's palette. Colours 8-15 are road, 0-7 are scenery (dusty).
#
# gate x1 y1 x2 y2: lap gates in race order, in world units. Gate 0 is
# the start/finish line; the grid forms up behind it. World to picture:
# picture x = wx - wy + 512, picture y = (wx + wy) * 31/64 - 252
# the start/finish line; the grid forms up behind it. The gates' ends
# also shape the walls: each edge of the road is a smooth curve
# through the matching ends of the gates. World to picture, for a
# point at height z (pixels):
# picture x = wx - wy + 512, picture y = (wx + wy) / 2 - 156 - z
gate 444.875 209.125 371.875 150.125
gate 449.84375 275.15625 378.09375 263.90625
gate 440.8125 388.65625 373.4375 396.53125
@@ -39,6 +42,7 @@ gate 514.25 270.75 628.25 356.75
gate 610 280 715.5 340.5
gate 627.25 212.75 703.5 208.5
gate 565.5 156.5 587 89
gate 480 148 466 70
# Name of the set of tracks this one belongs to, shown in the menu.
set Even Flow
@@ -47,8 +51,8 @@ set Even Flow
start 420 97
flags 0
# height: 64 rows of 64 values, ground height x 256. Each value covers
# 16x8 pixels of the picture.
# height: 64 rows (world y) of 64 values (world x), ground height in
# pixels x 64. Each value covers 16 x 16 world units.
height
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
+9 -5
View File
@@ -1,11 +1,14 @@
# Skids track TRACK2-4. Lines starting with # are comments.
#
# The picture is track.png: 1024x512, 16 colours. Its palette is the
# track's palette. Colours 8-15 are road (full grip), 0-7 are scenery.
# track's palette. Colours 8-15 are road, 0-7 are scenery (dusty).
#
# gate x1 y1 x2 y2: lap gates in race order, in world units. Gate 0 is
# the start/finish line; the grid forms up behind it. World to picture:
# picture x = wx - wy + 512, picture y = (wx + wy) * 31/64 - 252
# the start/finish line; the grid forms up behind it. The gates' ends
# also shape the walls: each edge of the road is a smooth curve
# through the matching ends of the gates. World to picture, for a
# point at height z (pixels):
# picture x = wx - wy + 512, picture y = (wx + wy) / 2 - 156 - z
gate 439.5 408.5 359.21875 465.90625
gate 489 427 455.5 518.5
gate 552 426 553 519
@@ -46,6 +49,7 @@ gate 584.0625 247.5625 537.125 136.6875
gate 551.3125 264.84375 488.125 183.375
gate 504.84375 311.25 424.9375 247.21875
gate 465.90625 350.15625 376.78125 298.09375
gate 426 376 306.9375 363.3125
# Name of the set of tracks this one belongs to, shown in the menu.
set Even Flow
@@ -54,8 +58,8 @@ set Even Flow
start 689 201
flags 1
# height: 64 rows of 64 values, ground height x 256. Each value covers
# 16x8 pixels of the picture.
# height: 64 rows (world y) of 64 values (world x), ground height in
# pixels x 64. Each value covers 16 x 16 world units.
height
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
+9 -5
View File
@@ -1,11 +1,14 @@
# Skids track TRACK2-5. Lines starting with # are comments.
#
# The picture is track.png: 1024x512, 16 colours. Its palette is the
# track's palette. Colours 8-15 are road (full grip), 0-7 are scenery.
# track's palette. Colours 8-15 are road, 0-7 are scenery (dusty).
#
# gate x1 y1 x2 y2: lap gates in race order, in world units. Gate 0 is
# the start/finish line; the grid forms up behind it. World to picture:
# picture x = wx - wy + 512, picture y = (wx + wy) * 31/64 - 252
# the start/finish line; the grid forms up behind it. The gates' ends
# also shape the walls: each edge of the road is a smooth curve
# through the matching ends of the gates. World to picture, for a
# point at height z (pixels):
# picture x = wx - wy + 512, picture y = (wx + wy) / 2 - 156 - z
gate 327.5 333.375 262 304.875
gate 326.6875 383.96875 265.1875 378.46875
gate 327.8125 521.375 262.8125 516.375
@@ -50,6 +53,7 @@ gate 664.96875 296.4375 609.71875 230.6875
gate 598.9375 295.90625 581.4375 233.40625
gate 486.375 293.34375 450.375 235.34375
gate 350.8125 298.75 320.0625 235.5
gate 327.75 299.6875 267.75 237.6875
# Name of the set of tracks this one belongs to, shown in the menu.
set Even Flow
@@ -58,8 +62,8 @@ set Even Flow
start 353 47
flags 0
# height: 64 rows of 64 values, ground height x 256. Each value covers
# 16x8 pixels of the picture.
# height: 64 rows (world y) of 64 values (world x), ground height in
# pixels x 64. Each value covers 16 x 16 world units.
height
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
+9 -5
View File
@@ -1,11 +1,14 @@
# Skids track TRACK2-6. Lines starting with # are comments.
#
# The picture is track.png: 1024x512, 16 colours. Its palette is the
# track's palette. Colours 8-15 are road (full grip), 0-7 are scenery.
# track's palette. Colours 8-15 are road, 0-7 are scenery (dusty).
#
# gate x1 y1 x2 y2: lap gates in race order, in world units. Gate 0 is
# the start/finish line; the grid forms up behind it. World to picture:
# picture x = wx - wy + 512, picture y = (wx + wy) * 31/64 - 252
# the start/finish line; the grid forms up behind it. The gates' ends
# also shape the walls: each edge of the road is a smooth curve
# through the matching ends of the gates. World to picture, for a
# point at height z (pixels):
# picture x = wx - wy + 512, picture y = (wx + wy) / 2 - 156 - z
gate 361.125 706.8125 424.75 670.6875
gate 277 621 365 609
gate 279.6875 502.3125 364.6875 525.3125
@@ -53,6 +56,7 @@ gate 732.4375 701.46875 647.6875 702.21875
gate 722.125 791.0625 640.125 753.0625
gate 659.5 864.25 598.5 787.25
gate 556.375 884.4375 560.75 793.5625
gate 474.75 821.125 516.5 762.375
# Name of the set of tracks this one belongs to, shown in the menu.
set Even Flow
@@ -61,8 +65,8 @@ set Even Flow
start 353 47
flags 0
# height: 64 rows of 64 values, ground height x 256. Each value covers
# 16x8 pixels of the picture.
# height: 64 rows (world y) of 64 values (world x), ground height in
# pixels x 64. Each value covers 16 x 16 world units.
height
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
+9 -5
View File
@@ -1,11 +1,14 @@
# Skids track TRACK3-1. Lines starting with # are comments.
#
# The picture is track.png: 1024x512, 16 colours. Its palette is the
# track's palette. Colours 8-15 are road (full grip), 0-7 are scenery.
# track's palette. Colours 8-15 are road, 0-7 are scenery (dusty).
#
# gate x1 y1 x2 y2: lap gates in race order, in world units. Gate 0 is
# the start/finish line; the grid forms up behind it. World to picture:
# picture x = wx - wy + 512, picture y = (wx + wy) * 31/64 - 252
# the start/finish line; the grid forms up behind it. The gates' ends
# also shape the walls: each edge of the road is a smooth curve
# through the matching ends of the gates. World to picture, for a
# point at height z (pixels):
# picture x = wx - wy + 512, picture y = (wx + wy) / 2 - 156 - z
gate 556.09375 597.4375 540.59375 694.9375
gate 643.5625 601.125 638.8125 697.875
gate 696.53125 599.34375 791.03125 692.84375
@@ -43,6 +46,7 @@ gate 282 602.75 277.375 692.65625
gate 307.5 604.875 307.6875 694.3125
gate 358.5 603.5 386 696
gate 437.34375 599.28125 412.75 695.25
gate 448.1875 597.0625 481.5 696.5
# Name of the set of tracks this one belongs to, shown in the menu.
set Surf 'n' Slam
@@ -51,8 +55,8 @@ set Surf 'n' Slam
start 641 315
flags 0
# height: 64 rows of 64 values, ground height x 256. Each value covers
# 16x8 pixels of the picture.
# height: 64 rows (world y) of 64 values (world x), ground height in
# pixels x 64. Each value covers 16 x 16 world units.
height
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
+9 -5
View File
@@ -1,11 +1,14 @@
# Skids track TRACK3-2. Lines starting with # are comments.
#
# The picture is track.png: 1024x512, 16 colours. Its palette is the
# track's palette. Colours 8-15 are road (full grip), 0-7 are scenery.
# track's palette. Colours 8-15 are road, 0-7 are scenery (dusty).
#
# gate x1 y1 x2 y2: lap gates in race order, in world units. Gate 0 is
# the start/finish line; the grid forms up behind it. World to picture:
# picture x = wx - wy + 512, picture y = (wx + wy) * 31/64 - 252
# the start/finish line; the grid forms up behind it. The gates' ends
# also shape the walls: each edge of the road is a smooth curve
# through the matching ends of the gates. World to picture, for a
# point at height z (pixels):
# picture x = wx - wy + 512, picture y = (wx + wy) / 2 - 156 - z
gate 463.875 679.71875 550.09375 653.59375
gate 393.6875 588.65625 498.75 595.40625
gate 371.5 487.625 481.90625 548.71875
@@ -29,6 +32,7 @@ gate 420.46875 755.125 419 867.0625
gate 498.8125 807.25 578.75 940.28125
gate 566.5 820.5 690.75 890.25
gate 597.75 790.3125 707.8125 768.96875
gate 550.0625 735.75 637.21875 701.03125
# Name of the set of tracks this one belongs to, shown in the menu.
set Surf 'n' Slam
@@ -37,8 +41,8 @@ set Surf 'n' Slam
start 475 288
flags 0
# height: 64 rows of 64 values, ground height x 256. Each value covers
# 16x8 pixels of the picture.
# height: 64 rows (world y) of 64 values (world x), ground height in
# pixels x 64. Each value covers 16 x 16 world units.
height
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
+9 -5
View File
@@ -1,11 +1,14 @@
# Skids track TRACK3-3. Lines starting with # are comments.
#
# The picture is track.png: 1024x512, 16 colours. Its palette is the
# track's palette. Colours 8-15 are road (full grip), 0-7 are scenery.
# track's palette. Colours 8-15 are road, 0-7 are scenery (dusty).
#
# gate x1 y1 x2 y2: lap gates in race order, in world units. Gate 0 is
# the start/finish line; the grid forms up behind it. World to picture:
# picture x = wx - wy + 512, picture y = (wx + wy) * 31/64 - 252
# the start/finish line; the grid forms up behind it. The gates' ends
# also shape the walls: each edge of the road is a smooth curve
# through the matching ends of the gates. World to picture, for a
# point at height z (pixels):
# picture x = wx - wy + 512, picture y = (wx + wy) / 2 - 156 - z
gate 810 502 905 589
gate 806 456 904.5 475.5
gate 787.5 406.5 859.5 378.5
@@ -37,6 +40,7 @@ gate 565.375 326.125 574.3125 437.6875
gate 635 363 636.75 487.25
gate 689.25 419.75 707.625 558.375
gate 741.4375 471.8125 785.84375 638.65625
gate 782.125 507.375 850.5625 636.4375
# Name of the set of tracks this one belongs to, shown in the menu.
set Surf 'n' Slam
@@ -45,8 +49,8 @@ set Surf 'n' Slam
start 809 244
flags 0
# height: 64 rows of 64 values, ground height x 256. Each value covers
# 16x8 pixels of the picture.
# height: 64 rows (world y) of 64 values (world x), ground height in
# pixels x 64. Each value covers 16 x 16 world units.
height
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
+9 -5
View File
@@ -1,11 +1,14 @@
# Skids track TRACK3-4. Lines starting with # are comments.
#
# The picture is track.png: 1024x512, 16 colours. Its palette is the
# track's palette. Colours 8-15 are road (full grip), 0-7 are scenery.
# track's palette. Colours 8-15 are road, 0-7 are scenery (dusty).
#
# gate x1 y1 x2 y2: lap gates in race order, in world units. Gate 0 is
# the start/finish line; the grid forms up behind it. World to picture:
# picture x = wx - wy + 512, picture y = (wx + wy) * 31/64 - 252
# the start/finish line; the grid forms up behind it. The gates' ends
# also shape the walls: each edge of the road is a smooth curve
# through the matching ends of the gates. World to picture, for a
# point at height z (pixels):
# picture x = wx - wy + 512, picture y = (wx + wy) / 2 - 156 - z
gate 562.5 134.625 626 267.125
gate 669.125 175.03125 665.875 301.28125
gate 835.25 345.9375 720.75 356.4375
@@ -38,6 +41,7 @@ gate 597.0625 448.21875 736.5625 554.71875
gate 573.59375 394.84375 716.59375 427.84375
gate 483.625 301.96875 614.125 331.46875
gate 450.3125 217.03125 589.3125 298.03125
gate 485.90625 155.3125 589.15625 269.0625
# Name of the set of tracks this one belongs to, shown in the menu.
set Surf 'n' Slam
@@ -46,8 +50,8 @@ set Surf 'n' Slam
start 796 294
flags 0
# height: 64 rows of 64 values, ground height x 256. Each value covers
# 16x8 pixels of the picture.
# height: 64 rows (world y) of 64 values (world x), ground height in
# pixels x 64. Each value covers 16 x 16 world units.
height
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
+9 -5
View File
@@ -1,11 +1,14 @@
# Skids track TRACK3-5. Lines starting with # are comments.
#
# The picture is track.png: 1024x512, 16 colours. Its palette is the
# track's palette. Colours 8-15 are road (full grip), 0-7 are scenery.
# track's palette. Colours 8-15 are road, 0-7 are scenery (dusty).
#
# gate x1 y1 x2 y2: lap gates in race order, in world units. Gate 0 is
# the start/finish line; the grid forms up behind it. World to picture:
# picture x = wx - wy + 512, picture y = (wx + wy) * 31/64 - 252
# the start/finish line; the grid forms up behind it. The gates' ends
# also shape the walls: each edge of the road is a smooth curve
# through the matching ends of the gates. World to picture, for a
# point at height z (pixels):
# picture x = wx - wy + 512, picture y = (wx + wy) / 2 - 156 - z
gate 522.3125 64.1875 567.8125 186.6875
gate 734.5 221.5 638 256
gate 792 413.6875 637.5 300.1875
@@ -35,6 +38,7 @@ gate 308.625 399.375 423.625 431.375
gate 304.5 269.5 405 337
gate 335.75 207.5 415.75 287.5
gate 386.125 161.5 465.375 240.25
gate 432.5 111.5 512.5 191.5
# Name of the set of tracks this one belongs to, shown in the menu.
set Surf 'n' Slam
@@ -43,8 +47,8 @@ set Surf 'n' Slam
start 744 137
flags 0
# height: 64 rows of 64 values, ground height x 256. Each value covers
# 16x8 pixels of the picture.
# height: 64 rows (world y) of 64 values (world x), ground height in
# pixels x 64. Each value covers 16 x 16 world units.
height
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
+9 -5
View File
@@ -1,11 +1,14 @@
# Skids track TRACK3-6. Lines starting with # are comments.
#
# The picture is track.png: 1024x512, 16 colours. Its palette is the
# track's palette. Colours 8-15 are road (full grip), 0-7 are scenery.
# track's palette. Colours 8-15 are road, 0-7 are scenery (dusty).
#
# gate x1 y1 x2 y2: lap gates in race order, in world units. Gate 0 is
# the start/finish line; the grid forms up behind it. World to picture:
# picture x = wx - wy + 512, picture y = (wx + wy) * 31/64 - 252
# the start/finish line; the grid forms up behind it. The gates' ends
# also shape the walls: each edge of the road is a smooth curve
# through the matching ends of the gates. World to picture, for a
# point at height z (pixels):
# picture x = wx - wy + 512, picture y = (wx + wy) / 2 - 156 - z
gate 486.125 379.375 471.1875 273.8125
gate 441.75 391.25 369.125 274.875
gate 404.875 417.625 314.8125 321.1875
@@ -46,6 +49,7 @@ gate 785.40625 579.625 944.625 631.8125
gate 796.78125 544.59375 896.84375 523.4375
gate 760.125 503.53125 816.78125 446.34375
gate 658.5 417.5 705.75 366.25
gate 585.5625 383.1875 589.46875 297.03125
# Name of the set of tracks this one belongs to, shown in the menu.
set Surf 'n' Slam
@@ -54,8 +58,8 @@ set Surf 'n' Slam
start 356 188
flags 0
# height: 64 rows of 64 values, ground height x 256. Each value covers
# 16x8 pixels of the picture.
# height: 64 rows (world y) of 64 values (world x), ground height in
# pixels x 64. Each value covers 16 x 16 world units.
height
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
+9 -5
View File
@@ -1,11 +1,14 @@
# Skids track TRACK4-1. Lines starting with # are comments.
#
# The picture is track.png: 1024x512, 16 colours. Its palette is the
# track's palette. Colours 8-15 are road (full grip), 0-7 are scenery.
# track's palette. Colours 8-15 are road, 0-7 are scenery (dusty).
#
# gate x1 y1 x2 y2: lap gates in race order, in world units. Gate 0 is
# the start/finish line; the grid forms up behind it. World to picture:
# picture x = wx - wy + 512, picture y = (wx + wy) * 31/64 - 252
# the start/finish line; the grid forms up behind it. The gates' ends
# also shape the walls: each edge of the road is a smooth curve
# through the matching ends of the gates. World to picture, for a
# point at height z (pixels):
# picture x = wx - wy + 512, picture y = (wx + wy) / 2 - 156 - z
gate 310.09375 599.75 182.5 594.25
gate 306.09375 660.3125 203 706.375
gate 341.625 703.375 328.5 831.5
@@ -36,6 +39,7 @@ gate 402 504 402 378
gate 402 504 402 378
gate 342.53125 501.71875 264 374.5
gate 316.5625 513.03125 192.25 387
gate 310.09375 542.65625 184 495.5
# Name of the set of tracks this one belongs to, shown in the menu.
set Sounf Of Speed
@@ -44,8 +48,8 @@ set Sounf Of Speed
start 213 275
flags 1
# height: 64 rows of 64 values, ground height x 256. Each value covers
# 16x8 pixels of the picture.
# height: 64 rows (world y) of 64 values (world x), ground height in
# pixels x 64. Each value covers 16 x 16 world units.
height
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
+9 -5
View File
@@ -1,11 +1,14 @@
# Skids track TRACK4-2. Lines starting with # are comments.
#
# The picture is track.png: 1024x512, 16 colours. Its palette is the
# track's palette. Colours 8-15 are road (full grip), 0-7 are scenery.
# track's palette. Colours 8-15 are road, 0-7 are scenery (dusty).
#
# gate x1 y1 x2 y2: lap gates in race order, in world units. Gate 0 is
# the start/finish line; the grid forms up behind it. World to picture:
# picture x = wx - wy + 512, picture y = (wx + wy) * 31/64 - 252
# the start/finish line; the grid forms up behind it. The gates' ends
# also shape the walls: each edge of the road is a smooth curve
# through the matching ends of the gates. World to picture, for a
# point at height z (pixels):
# picture x = wx - wy + 512, picture y = (wx + wy) / 2 - 156 - z
gate 495.78125 950.1875 540.1875 849.78125
gate 400.03125 867.9375 513.3125 821.65625
gate 374.78125 805.1875 508.4375 791.53125
@@ -46,6 +49,7 @@ gate 711.6875 750.40625 583.65625 688.46875
gate 715.375 842.65625 586.40625 755
gate 701.84375 891.1875 585.65625 814.03125
gate 663.3125 939.1875 581.0625 837.78125
gate 608.78125 967.1875 571.4375 852.53125
# Name of the set of tracks this one belongs to, shown in the menu.
set Sounf Of Speed
@@ -54,8 +58,8 @@ set Sounf Of Speed
start 122 216
flags 0
# height: 64 rows of 64 values, ground height x 256. Each value covers
# 16x8 pixels of the picture.
# height: 64 rows (world y) of 64 values (world x), ground height in
# pixels x 64. Each value covers 16 x 16 world units.
height
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
+9 -5
View File
@@ -1,11 +1,14 @@
# Skids track TRACK4-3. Lines starting with # are comments.
#
# The picture is track.png: 1024x512, 16 colours. Its palette is the
# track's palette. Colours 8-15 are road (full grip), 0-7 are scenery.
# track's palette. Colours 8-15 are road, 0-7 are scenery (dusty).
#
# gate x1 y1 x2 y2: lap gates in race order, in world units. Gate 0 is
# the start/finish line; the grid forms up behind it. World to picture:
# picture x = wx - wy + 512, picture y = (wx + wy) * 31/64 - 252
# the start/finish line; the grid forms up behind it. The gates' ends
# also shape the walls: each edge of the road is a smooth curve
# through the matching ends of the gates. World to picture, for a
# point at height z (pixels):
# picture x = wx - wy + 512, picture y = (wx + wy) / 2 - 156 - z
gate 503.90625 560.59375 573.40625 679.09375
gate 582.71875 565.78125 651.21875 689.28125
gate 699.375 589.34375 743.375 713.34375
@@ -39,6 +42,7 @@ gate 346.25 489.65625 296.0625 570.84375
gate 372.1875 581.84375 301.1875 652.34375
gate 402.625 626.75 392.625 759.25
gate 435.0625 628.59375 502.1875 772.34375
gate 460.5 581.40625 544.75 708.40625
# Name of the set of tracks this one belongs to, shown in the menu.
set Sounf Of Speed
@@ -47,8 +51,8 @@ set Sounf Of Speed
start 672 360
flags 0
# height: 64 rows of 64 values, ground height x 256. Each value covers
# 16x8 pixels of the picture.
# height: 64 rows (world y) of 64 values (world x), ground height in
# pixels x 64. Each value covers 16 x 16 world units.
height
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
+9 -5
View File
@@ -1,11 +1,14 @@
# Skids track TRACK4-4. Lines starting with # are comments.
#
# The picture is track.png: 1024x512, 16 colours. Its palette is the
# track's palette. Colours 8-15 are road (full grip), 0-7 are scenery.
# track's palette. Colours 8-15 are road, 0-7 are scenery (dusty).
#
# gate x1 y1 x2 y2: lap gates in race order, in world units. Gate 0 is
# the start/finish line; the grid forms up behind it. World to picture:
# picture x = wx - wy + 512, picture y = (wx + wy) * 31/64 - 252
# the start/finish line; the grid forms up behind it. The gates' ends
# also shape the walls: each edge of the road is a smooth curve
# through the matching ends of the gates. World to picture, for a
# point at height z (pixels):
# picture x = wx - wy + 512, picture y = (wx + wy) / 2 - 156 - z
gate 633.9375 408.65625 639.0625 527.6875
gate 714.9375 491.65625 738.0625 620.6875
gate 773.125 529.625 824.1875 651.21875
@@ -42,6 +45,7 @@ gate 298.375 350.65625 413.6875 398.3125
gate 293.90625 281.8125 414.1875 352
gate 347.40625 233.625 448.21875 353.125
gate 423.4375 212.9375 488.71875 377.71875
gate 564.25 334.1875 561.15625 443.375
# Name of the set of tracks this one belongs to, shown in the menu.
set Sounf Of Speed
@@ -50,8 +54,8 @@ set Sounf Of Speed
start 800 350
flags 0
# height: 64 rows of 64 values, ground height x 256. Each value covers
# 16x8 pixels of the picture.
# height: 64 rows (world y) of 64 values (world x), ground height in
# pixels x 64. Each value covers 16 x 16 world units.
height
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
+9 -5
View File
@@ -1,11 +1,14 @@
# Skids track TRACK4-5. Lines starting with # are comments.
#
# The picture is track.png: 1024x512, 16 colours. Its palette is the
# track's palette. Colours 8-15 are road (full grip), 0-7 are scenery.
# track's palette. Colours 8-15 are road, 0-7 are scenery (dusty).
#
# gate x1 y1 x2 y2: lap gates in race order, in world units. Gate 0 is
# the start/finish line; the grid forms up behind it. World to picture:
# picture x = wx - wy + 512, picture y = (wx + wy) * 31/64 - 252
# the start/finish line; the grid forms up behind it. The gates' ends
# also shape the walls: each edge of the road is a smooth curve
# through the matching ends of the gates. World to picture, for a
# point at height z (pixels):
# picture x = wx - wy + 512, picture y = (wx + wy) / 2 - 156 - z
gate 361 644.5 356 731.5
gate 430.75 680.5 500.25 773
gate 490.1875 665 569.1875 744
@@ -46,6 +49,7 @@ gate 344.875 359.8125 261.375 281.3125
gate 265.75 441.875 165.75 375.875
gate 247.84375 489.21875 133.09375 438.96875
gate 259.9375 536.59375 154.9375 525.59375
gate 295.96875 583.03125 220.96875 624.03125
# Name of the set of tracks this one belongs to, shown in the menu.
set Sounf Of Speed
@@ -54,8 +58,8 @@ set Sounf Of Speed
start 362 264
flags 0
# height: 64 rows of 64 values, ground height x 256. Each value covers
# 16x8 pixels of the picture.
# height: 64 rows (world y) of 64 values (world x), ground height in
# pixels x 64. Each value covers 16 x 16 world units.
height
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
+9 -5
View File
@@ -1,11 +1,14 @@
# Skids track TRACK4-6. Lines starting with # are comments.
#
# The picture is track.png: 1024x512, 16 colours. Its palette is the
# track's palette. Colours 8-15 are road (full grip), 0-7 are scenery.
# track's palette. Colours 8-15 are road, 0-7 are scenery (dusty).
#
# gate x1 y1 x2 y2: lap gates in race order, in world units. Gate 0 is
# the start/finish line; the grid forms up behind it. World to picture:
# picture x = wx - wy + 512, picture y = (wx + wy) * 31/64 - 252
# the start/finish line; the grid forms up behind it. The gates' ends
# also shape the walls: each edge of the road is a smooth curve
# through the matching ends of the gates. World to picture, for a
# point at height z (pixels):
# picture x = wx - wy + 512, picture y = (wx + wy) / 2 - 156 - z
gate 833.125 731.5 782.625 620
gate 679.4375 694.5 660.6875 577.25
gate 522.59375 598 507.96875 476.625
@@ -41,6 +44,7 @@ gate 497.5625 440.6875 597.5625 484.6875
gate 506.1875 355.09375 607.4375 450.84375
gate 628.3125 300 668.3125 426
gate 841.875 457.5 766.875 502.5
gate 919.96875 630.5 800.96875 579.5
# Name of the set of tracks this one belongs to, shown in the menu.
set Sounf Of Speed
@@ -49,8 +53,8 @@ set Sounf Of Speed
start 501 153
flags 0
# height: 64 rows of 64 values, ground height x 256. Each value covers
# 16x8 pixels of the picture.
# height: 64 rows (world y) of 64 values (world x), ground height in
# pixels x 64. Each value covers 16 x 16 world units.
height
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400 6400
+10 -16
View File
@@ -4,20 +4,14 @@ name CAMARO
driver ROD
# yes = towed behind cars (Caravans option) instead of driven
trailer no
# The 16 values from the original .set file. Their meaning is not
# decoded yet, so the game does not use them; the handling below is
# a reconstruction.
original 54 35 32 8 7 22 22 16 49 14 53 0 0 0 0 0
# top speed on tarmac, world pixels per second
top 200
# acceleration and braking, world pixels per second squared
accel 170
brake 320
# sideways grip (higher = less sliding)
grip 5
# steering rate, radians per second
turn 3.6
# weight in collisions
#
# handling: the 16 values from the original .set file, used as the
# original game does. Counting from 1:
# 1 2 6 computer drivers: drag, grip, power
# 9 10 11 human drivers: drag, grip, power
# 3 steering, 5 gravity, 7 tow hitch distance; the rest are unused.
# Top speed = power * drag / (64 - drag) / 32 world units per frame,
# 50 frames a second (drag below 64). Higher grip, less sliding.
handling 54 35 32 8 7 22 22 16 49 14 53 0 0 0 0 0
# weight when cars collide (not from the original data)
mass 1
# speed and grip multiplier off the road
offroad 0.55
+10 -16
View File
@@ -4,20 +4,14 @@ name CARAVAN
driver SIMON
# yes = towed behind cars (Caravans option) instead of driven
trailer yes
# The 16 values from the original .set file. Their meaning is not
# decoded yet, so the game does not use them; the handling below is
# a reconstruction.
original 2 65532 65532 1 12 10 16 14 0 0 0 0 0 0 0 0
# top speed on tarmac, world pixels per second
top 200
# acceleration and braking, world pixels per second squared
accel 170
brake 320
# sideways grip (higher = less sliding)
grip 5
# steering rate, radians per second
turn 3.6
# weight in collisions
#
# handling: the 16 values from the original .set file, used as the
# original game does. Counting from 1:
# 1 2 6 computer drivers: drag, grip, power
# 9 10 11 human drivers: drag, grip, power
# 3 steering, 5 gravity, 7 tow hitch distance; the rest are unused.
# Top speed = power * drag / (64 - drag) / 32 world units per frame,
# 50 frames a second (drag below 64). Higher grip, less sliding.
handling 2 65532 65532 1 12 10 16 14 0 0 0 0 0 0 0 0
# weight when cars collide (not from the original data)
mass 1
# speed and grip multiplier off the road
offroad 0.55
+10 -16
View File
@@ -4,20 +4,14 @@ name COW
driver ROB
# yes = towed behind cars (Caravans option) instead of driven
trailer no
# The 16 values from the original .set file. Their meaning is not
# decoded yet, so the game does not use them; the handling below is
# a reconstruction.
original 15 14 34 8 4 598 15 12 13 4 2003 0 0 0 0 0
# top speed on tarmac, world pixels per second
top 170
# acceleration and braking, world pixels per second squared
accel 150
brake 320
# sideways grip (higher = less sliding)
grip 7
# steering rate, radians per second
turn 4.6
# weight in collisions
#
# handling: the 16 values from the original .set file, used as the
# original game does. Counting from 1:
# 1 2 6 computer drivers: drag, grip, power
# 9 10 11 human drivers: drag, grip, power
# 3 steering, 5 gravity, 7 tow hitch distance; the rest are unused.
# Top speed = power * drag / (64 - drag) / 32 world units per frame,
# 50 frames a second (drag below 64). Higher grip, less sliding.
handling 15 14 34 8 4 598 15 12 13 4 2003 0 0 0 0 0
# weight when cars collide (not from the original data)
mass 1.4
# speed and grip multiplier off the road
offroad 0.9
+10 -16
View File
@@ -4,20 +4,14 @@ name FORMULA 1
driver SIMON
# yes = towed behind cars (Caravans option) instead of driven
trailer no
# The 16 values from the original .set file. Their meaning is not
# decoded yet, so the game does not use them; the handling below is
# a reconstruction.
original 60 71 30 0 6 16 24 12 58 42 29 0 0 0 0 0
# top speed on tarmac, world pixels per second
top 240
# acceleration and braking, world pixels per second squared
accel 210
brake 320
# sideways grip (higher = less sliding)
grip 6.5
# steering rate, radians per second
turn 3.7
# weight in collisions
#
# handling: the 16 values from the original .set file, used as the
# original game does. Counting from 1:
# 1 2 6 computer drivers: drag, grip, power
# 9 10 11 human drivers: drag, grip, power
# 3 steering, 5 gravity, 7 tow hitch distance; the rest are unused.
# Top speed = power * drag / (64 - drag) / 32 world units per frame,
# 50 frames a second (drag below 64). Higher grip, less sliding.
handling 60 71 30 0 6 16 24 12 58 42 29 0 0 0 0 0
# weight when cars collide (not from the original data)
mass 0.9
# speed and grip multiplier off the road
offroad 0.45
+10 -16
View File
@@ -4,20 +4,14 @@ name MIDGET
driver ROB
# yes = towed behind cars (Caravans option) instead of driven
trailer no
# The 16 values from the original .set file. Their meaning is not
# decoded yet, so the game does not use them; the handling below is
# a reconstruction.
original 51 21 30 16 3 45 16 13 44 13 93 0 0 0 0 0
# top speed on tarmac, world pixels per second
top 205
# acceleration and braking, world pixels per second squared
accel 185
brake 320
# sideways grip (higher = less sliding)
grip 4
# steering rate, radians per second
turn 4
# weight in collisions
#
# handling: the 16 values from the original .set file, used as the
# original game does. Counting from 1:
# 1 2 6 computer drivers: drag, grip, power
# 9 10 11 human drivers: drag, grip, power
# 3 steering, 5 gravity, 7 tow hitch distance; the rest are unused.
# Top speed = power * drag / (64 - drag) / 32 world units per frame,
# 50 frames a second (drag below 64). Higher grip, less sliding.
handling 51 21 30 16 3 45 16 13 44 13 93 0 0 0 0 0
# weight when cars collide (not from the original data)
mass 0.85
# speed and grip multiplier off the road
offroad 0.6
+10 -16
View File
@@ -4,20 +4,14 @@ name MINI
driver ROB
# yes = towed behind cars (Caravans option) instead of driven
trailer no
# The 16 values from the original .set file. Their meaning is not
# decoded yet, so the game does not use them; the handling below is
# a reconstruction.
original 56 24 30 12 3 24 20 12 46 10 100 0 0 0 0 0
# top speed on tarmac, world pixels per second
top 190
# acceleration and braking, world pixels per second squared
accel 200
brake 320
# sideways grip (higher = less sliding)
grip 5.8
# steering rate, radians per second
turn 4.4
# weight in collisions
#
# handling: the 16 values from the original .set file, used as the
# original game does. Counting from 1:
# 1 2 6 computer drivers: drag, grip, power
# 9 10 11 human drivers: drag, grip, power
# 3 steering, 5 gravity, 7 tow hitch distance; the rest are unused.
# Top speed = power * drag / (64 - drag) / 32 world units per frame,
# 50 frames a second (drag below 64). Higher grip, less sliding.
handling 56 24 30 12 3 24 20 12 46 10 100 0 0 0 0 0
# weight when cars collide (not from the original data)
mass 0.8
# speed and grip multiplier off the road
offroad 0.55
+10 -16
View File
@@ -4,20 +4,14 @@ name POLICE
driver SIMON
# yes = towed behind cars (Caravans option) instead of driven
trailer no
# The 16 values from the original .set file. Their meaning is not
# decoded yet, so the game does not use them; the handling below is
# a reconstruction.
original 56 34 22 6 2 15 19 11 49 14 44 0 0 0 0 0
# top speed on tarmac, world pixels per second
top 215
# acceleration and braking, world pixels per second squared
accel 180
brake 320
# sideways grip (higher = less sliding)
grip 5
# steering rate, radians per second
turn 3.5
# weight in collisions
#
# handling: the 16 values from the original .set file, used as the
# original game does. Counting from 1:
# 1 2 6 computer drivers: drag, grip, power
# 9 10 11 human drivers: drag, grip, power
# 3 steering, 5 gravity, 7 tow hitch distance; the rest are unused.
# Top speed = power * drag / (64 - drag) / 32 world units per frame,
# 50 frames a second (drag below 64). Higher grip, less sliding.
handling 56 34 22 6 2 15 19 11 49 14 44 0 0 0 0 0
# weight when cars collide (not from the original data)
mass 1.2
# speed and grip multiplier off the road
offroad 0.55
+10 -16
View File
@@ -4,20 +4,14 @@ name PORSCHE
driver IMAGINE
# yes = towed behind cars (Caravans option) instead of driven
trailer no
# The 16 values from the original .set file. Their meaning is not
# decoded yet, so the game does not use them; the handling below is
# a reconstruction.
original 56 34 22 6 2 15 21 12 49 14 44 0 0 0 0 0
# top speed on tarmac, world pixels per second
top 225
# acceleration and braking, world pixels per second squared
accel 190
brake 320
# sideways grip (higher = less sliding)
grip 5.2
# steering rate, radians per second
turn 3.6
# weight in collisions
#
# handling: the 16 values from the original .set file, used as the
# original game does. Counting from 1:
# 1 2 6 computer drivers: drag, grip, power
# 9 10 11 human drivers: drag, grip, power
# 3 steering, 5 gravity, 7 tow hitch distance; the rest are unused.
# Top speed = power * drag / (64 - drag) / 32 world units per frame,
# 50 frames a second (drag below 64). Higher grip, less sliding.
handling 56 34 22 6 2 15 21 12 49 14 44 0 0 0 0 0
# weight when cars collide (not from the original data)
mass 1
# speed and grip multiplier off the road
offroad 0.55
+10 -16
View File
@@ -4,20 +4,14 @@ name TRUCK
driver ROD
# yes = towed behind cars (Caravans option) instead of driven
trailer no
# The 16 values from the original .set file. Their meaning is not
# decoded yet, so the game does not use them; the handling below is
# a reconstruction.
original 55 26 28 18 2 29 20 17 54 19 35 0 0 0 0 0
# top speed on tarmac, world pixels per second
top 195
# acceleration and braking, world pixels per second squared
accel 140
brake 320
# sideways grip (higher = less sliding)
grip 4.2
# steering rate, radians per second
turn 3
# weight in collisions
#
# handling: the 16 values from the original .set file, used as the
# original game does. Counting from 1:
# 1 2 6 computer drivers: drag, grip, power
# 9 10 11 human drivers: drag, grip, power
# 3 steering, 5 gravity, 7 tow hitch distance; the rest are unused.
# Top speed = power * drag / (64 - drag) / 32 world units per frame,
# 50 frames a second (drag below 64). Higher grip, less sliding.
handling 55 26 28 18 2 29 20 17 54 19 35 0 0 0 0 0
# weight when cars collide (not from the original data)
mass 1.8
# speed and grip multiplier off the road
offroad 0.75
+10 -16
View File
@@ -4,20 +4,14 @@ name BEETLE
driver ROB
# yes = towed behind cars (Caravans option) instead of driven
trailer no
# The 16 values from the original .set file. Their meaning is not
# decoded yet, so the game does not use them; the handling below is
# a reconstruction.
original 57 28 32 12 3 29 20 14 51 15 63 0 0 0 0 0
# top speed on tarmac, world pixels per second
top 185
# acceleration and braking, world pixels per second squared
accel 160
brake 320
# sideways grip (higher = less sliding)
grip 4.6
# steering rate, radians per second
turn 3.6
# weight in collisions
#
# handling: the 16 values from the original .set file, used as the
# original game does. Counting from 1:
# 1 2 6 computer drivers: drag, grip, power
# 9 10 11 human drivers: drag, grip, power
# 3 steering, 5 gravity, 7 tow hitch distance; the rest are unused.
# Top speed = power * drag / (64 - drag) / 32 world units per frame,
# 50 frames a second (drag below 64). Higher grip, less sliding.
handling 57 28 32 12 3 29 20 14 51 15 63 0 0 0 0 0
# weight when cars collide (not from the original data)
mass 1
# speed and grip multiplier off the road
offroad 0.55
+175 -29
View File
@@ -1,8 +1,8 @@
# Super Skidmarks data formats
Notes from reverse engineering the AGA/CD32 release of *Super Skidmarks*
(Acid Software, 1995), worked out from all six disks. The one thing not
yet decoded is the executable's game logic. See [Gaps](#gaps).
(Acid Software, 1995), worked out from all six disks and from the main
executable's machine code. See [Gaps](#gaps) for what's still unknown.
All multi-byte values are big-endian unless noted.
@@ -70,7 +70,7 @@ The last 142 bytes of section 0 hold:
|--------|------|----------|
| 0 | 2 | File number (1–4) |
| 2 | 96 | The palette shared by the file's six tracks: 16 colours × R, G, B, each a `u16` from 0 to 15 (Amiga 12-bit colour) |
| 98 | 12 | Unknown |
| 98 | 12 | Six `u16`, one per track, meaning unknown. The values are 0–5, some with bit 6 (`0x40`) set, plus one `0x3F`. |
| 110 | … | The name of the set of tracks, NUL-terminated |
The first 20480 bytes of section 0 are the track-selector thumbnails:
@@ -88,38 +88,63 @@ Each track section unpacks to 281240 (`0x44A98`) bytes:
| Offset | Size | Contents |
|----------|--------|----------|
| `0x0000` | 8192 | Height map: 64 × 64 `u16`, 8.8 fixed point, row-major. Each cell covers 16 × 8 screen pixels. |
| `0x0000` | 8192 | Height map: 64 rows (world y) × 64 `u16` (world x). Each cell is one unit of the game's world; the ground there is value / 64 pixels high. |
| `0x2000` | 8192 | Unused, zero on every track. |
| `0x4000` | 8 | `u16` gate count, `u16` start x, `u16` start y, `u16` flags. |
| `0x4008` | 8 × n | Gates: `x1 y1 x2 y2`, world coordinates × 32. The list repeats after the last gate. |
| … | | Unknown data, mostly a bit pattern, up to `0x4A98`. |
| `0x4000` | 8 | `u16` index of the last gate (so the track has one more gate than this), `u16` start x, `u16` start y, `u16` flags. |
| `0x4008` | 70 × 8 | Gate slots: `x1 y1 x2 y2`, world coordinates × 32 (× 512 in the game's own units). Slots after the last gate repeat the list from the start. |
| `0x4238` | 2144 | Unknown. 88 bytes of mostly doubled bytes, then what looks like leftover picture data. |
| `0x4A98` | 4 × 65536 | Four non-interleaved 1024 × 512 bitplanes, giving 16 colours. |
The executable replaces the start point and flags of two tracks with
fixed values: (844, 50, flags 1) for file 1's track 2 and (187, 119, flags 0)
for file 2's track 4. It isn't clear whether those track numbers count from
0 or 1.
### Track geometry
Gates are line segments across the road and run in race order; gate 0 is
the start/finish line. They're defined on an isometric ground plane rotated
45° to the picture. The projection that best fits the data is:
45° to the picture.
The rest of this section uses this port's world units, which are 1/16 of
the game's own unit (the 16.16 fixed-point values the executable works in).
So a height-map cell is 16 × 16 units.
The executable projects a point `z` pixels above the ground plane like
this:
```
screen x = wx - wy + 512
screen y = (wx + wy) × 31/64 - 252
screen y = (wx + wy) / 2 - 156 - z
```
With these constants, 96% of points sampled along the gates land on road
pixels across all 24 tracks (92% minimum). A plain screen-aligned 2:1 squash
gives 40–70%. Adding a height term doesn't improve the fit, so the height
map is used only for slope physics and body tilt.
The car sprite position uses 182 in place of 156, putting the sprite's top
left 26 pixels higher. With `z` set to the ground height, 98% of points
sampled along the gates land on road pixels across all 24 tracks.
**Walls.** The gates are also the control points of the road edges:
- **The curves:** routine `$24670` evaluates a uniform quadratic B-spline
through the gates' first ends for one edge, and their second ends for
the other. At parameter `i + f` that is
`(1-f)²/2 · G[i] + (1/2 + f - f²) · G[i+1] + f²/2 · G[i+2]`.
- **Zones:** at set-up the game cuts the road into zones, two per gate,
between consecutive cross-sections of the two curves. Each zone's four
sides are tested against the car (routine `$25b2c`).
- **Collisions:** crossing a side edge is a wall hit; crossing the front
or back moves the car on to the next or previous zone.
- **Fit:** points 5% in from either edge are on road pixels 95% of the
time, and points 15% outside only 25% of the time. So the curves follow
the painted road edge.
The stored start point is on the road for 14 of the 24 tracks read as screen
coordinates, and 10 read as world coordinates. It isn't used; the grid forms
up behind gate 0.
coordinates, and 10 read as world coordinates. The game seems to use it for
the starting camera position. This port forms the grid up behind gate 0.
### Road and scenery
Colours 8–15 are the driving surface on every track, so the fourth
bitplane is effectively a road mask. Off the road means less grip and
lower speed.
bitplane is effectively a road mask. The game's physics don't read the
picture; the walls keep cars on the road.
## Vehicles
@@ -153,15 +178,56 @@ intact when read directly from the disk image.
- **Header:** the car's name and its driver's name, each followed by a
newline.
- **Settings:** 16 `u16` values. Eleven are used by the cars; the rest are
zero. The caravan's values differ in kind; two of them are −4.
- **Settings:** 16 `u16` values (see below). The caravan's values aren't
driving values; two of them are −4.
- **Menu picture:** 512 bytes. A 64 × 32 picture in two bitplanes, plane 0
(256 bytes) then plane 1.
The settings are clearly handling values. The first is smallest for the
Cow, the second largest for the F1, and the Police car and Porsche are
nearly identical. What each one means lives in the executable and isn't
decoded yet.
Routine `$332a` copies the settings into each car, as 16.16 fixed point
with these shifts. Word `k` is the `k`th `u16`, counting from 0:
| Word | Car field | Value | Meaning |
|------|-----------|-------|---------|
| 0 | `$bc` | w / 64 | drag (computer drivers) |
| 1 | `$e2` | w / 512 | grip (computer drivers) |
| 2 | `$e6` | w / 16 | steering, 64ths of a turn per frame |
| 3 | `$c0` | −w / 64 | stored but never read |
| 4 | `$106` | w / 8 | gravity, pixels per frame² |
| 5 | `$b8` | w / 512 | power (computer drivers) |
| 6 | (towing) | w / 32 | tow hitch distance behind the car, in game units |
| 8 | `$bc` | w / 64 | drag (human drivers) |
| 9 | `$e2` | w / 512 | grip (human drivers) |
| 10 | `$b8` | w / 512 | power (human drivers) |
Words 7 and 11–15 aren't read. [Game logic](#game-logic) shows how drag,
grip and power work together. In short, top speed is
`power × drag / (1 − drag)` game units per frame. For the Camaro that is
271 of this port's units a second for human drivers and 186 for computer
ones.
### Handling presets
The race menu's handling option is CUSTOM (each car's own settings, as
above) or one of six presets that replace them for every car. The table
is at `$21dc6`, 8 bytes each. Bytes 0–2 are for human drivers, 3–5 for
computer drivers, and byte 6 is steering for both:
| Preset | Human | Computer | Steering |
|--------|-------|----------|----------|
| CLASSIC | 98 26 24 | 98 24 24 | 44 |
| GRUNTY | 92 40 30 | 108 33 25 | 60 |
| ZIPPY | 102 61 47 | 115 59 45 | 64 |
| RIPPY | 100 80 60 | 110 50 60 | 72 |
| SLIPPERY | 25 127 101 | 29 45 122 | 68 |
| PEDAL CAR | 117 23 17 | 104 17 10 | 30 |
For a preset `(a, t, g)`:
- drag = a/128 × 0.98 + 0.01
- p = t/16 × (1/drag − 1), and power = p/8
- grip = g/256 / p / drag / 8
- steering = byte 6 / 32, gravity = 0.5
So top speed is simply t/128 game units per frame.
## Sounds (`sfx/*.iff`, `gfx/*.iff`)
@@ -208,6 +274,82 @@ The song uses these commands:
module second by second through the whole song (correlation 0.996 over the
first minute), and it loops at the same point.
## Game logic
`SKIDMARKS_CD³²` is one code hunk of 222,032 bytes, compiled from Blitz
Basic 2. Addresses below are offsets into it. All arithmetic is 16.16
fixed point (multiply at `$2ceda`, divide at `$2e122`).
### Car record
Each car is a 266-byte (`$10a`) record:
| Offset | Field |
|--------|-------|
| `$0`, `$4`, `$8` | x, y, height z |
| `$c`, `$10`, `$14` | velocity x, y, vertical |
| `$18`, `$1c` | this frame's velocity change |
| `$58`, `$5c` | heading (64 per turn) and turn rate |
| `$60`, `$64` | heading vector, from sine/cosine tables of 64 entries |
| `$68` | computer steering, −1, 0 or 1 |
| `$6c`, `$6e` | pitch and roll notch, 0–4 |
| `$76` | controller: 0–7 for joysticks and keyboards, 8 for computer |
| `$78` | sprite frame: roll × 160 + pitch × 32 + heading / 2 |
| `$7a` | airborne |
| `$7e` | state: negative while racing, 1 while warping |
| `$a6` | engine speed |
| `$aa` | forward speed |
| `$b8`, `$bc`, `$e2`, `$e6`, `$106` | handling (above) |
| `$d2`, `$d6` | links to the car towing this one, and the one it tows |
| `$ea` | zone (half-gate) progress |
### One frame
The race loop runs at 50 Hz. Each car gets steering (`$1c618`), drawing
(`$1cafa`), and moving (`$1982c`) in turn. While drawing, a car sprite
is skipped if the beam is already past a given line, so a frame always
fits in one field. Then `$247ac` integrates every car.
1. **Steering:**
- Input is −1, 0 or 1, from the joystick or the computer driver.
- turn = (turn + input × steering) / 2. Airborne cars can't steer, and
their turn rate is limited to ±1.
2. **Height:**
- vertical −= gravity.
- The car looks two frames ahead: if `z + 2 × vertical` is still above
the ground at `pos + 2 × velocity`, it's airborne.
- Otherwise vertical = (ground there − z) / 2.
- Pitch and roll each step one notch a frame towards
`floor(rise + 2.5)`, clamped to 0–4. The rise is the ground height
change over 0.2 units along world y (pitch) or world −x (roll).
- heading += turn.
3. **Engine and grip**, skipped while airborne:
- throttle is −1, 0 or 1. Half the vertical speed is taken off it
going forwards (added going backwards), so hills slow cars down and
speed them up.
- target = (engine speed + throttle × power) × drag
- change = grip × (heading vector × target − velocity)
- engine speed += (target − engine speed) / 4
4. **Walls:**
- The zone test runs on `pos + 4 × velocity + 10 × change`.
- On a wall the velocity points back off it. The car is held at the
contact point and loses 1.5× its velocity into the wall, so it
bounces back at half speed.
- Crossing a zone's front or back moves `$ea` on, and counts the laps.
5. **Integrate:** velocity += change, position += velocity, z += vertical.
A warping car (state 1) instead flies straight at a point on the track:
its velocity is the distance to it / 16.
### Computer drivers
Computer cars use words 0, 1 and 5 for handling rather than 8, 9 and 10.
They steer with the sign of the cross product between their heading and
the direction to a target point, as if holding a joystick hard over. The
targets come from a table of points built along the road. The driver
picks one side of the road or the other depending on which way the road
bends, and the throttle comes from fixed values.
## Executables
- **`SKIDMARKS_CD³²`** (237 KB): a single-hunk compiled Blitz Basic 2
@@ -217,12 +359,16 @@ first minute), and it loops at the same point.
## Gaps
- **Game logic.** The handling, AI and rules in the executable aren't
decoded; this port's versions are reconstructions. Decoding the `.set`
values would be the first step towards the original handling.
- **Unknown data:** the 12 bytes after each track file's palette, the bit
pattern after the gates in each track section, and the colours of
`ball.spr` (probably set by the executable).
- **Game logic still to decode:**
- car-to-car collisions
- exactly how computer drivers pick target points and throttle
- the warp rules
- the towing physics, beyond the hitch distance
- the race rules and scoring
- **Unknown data:**
- the six per-track words after each track file's palette
- the 2144 bytes after the gate slots in each track section
- the colours of `ball.spr`, probably set by the executable
The engine's own editable formats, which `skids extract` converts all of
the above into, are described in [assets/README.md](../assets/README.md).
+19 -3
View File
@@ -60,6 +60,8 @@ pub struct Settings {
pub opponents: usize,
/// Every car tows a caravan.
pub caravans: bool,
/// 0 = each car's own handling, else a preset (`vehicle::PRESETS`).
pub handling: usize,
pub music: Music,
}
@@ -73,6 +75,7 @@ enum Item {
Car2,
Opponents,
Caravans,
Handling,
Music,
Quit,
}
@@ -150,6 +153,7 @@ impl App {
cars,
opponents: 3,
caravans: false,
handling: 0,
music,
},
screen: Screen::Menu { cursor: 0 },
@@ -179,6 +183,7 @@ impl App {
if self.data.vehicles.iter().any(|v| v.trailer) {
v.push(Item::Caravans);
}
v.push(Item::Handling);
if self.audio.has_music() {
v.push(Item::Music);
}
@@ -209,6 +214,7 @@ impl App {
team: p,
name: format!("PLAYER {}", p + 1),
trailer,
preset: self.settings.handling,
});
}
let mut rng = seed.wrapping_mul(2654435761) | 1;
@@ -222,6 +228,7 @@ impl App {
team: humans + i,
name: CPU_NAMES[(i + seed as usize) % CPU_NAMES.len()].to_string(),
trailer,
preset: self.settings.handling,
});
}
// Humans start at the back, as is tradition.
@@ -347,6 +354,7 @@ impl App {
}
Item::Opponents => s.opponents = wrap(s.opponents, 8 - s.players + 1, delta),
Item::Caravans => s.caravans = !s.caravans,
Item::Handling => s.handling = wrap(s.handling, vehicle::PRESETS.len() + 1, delta),
Item::Music => {
const ORDER: [Music; 3] = [Music::Menus, Music::Always, Music::Off];
let at = ORDER.iter().position(|&m| m == s.music).unwrap_or(0);
@@ -370,6 +378,7 @@ impl App {
| Item::Laps
| Item::Players
| Item::Caravans
| Item::Handling
| Item::Music
| Item::Opponents => {
let s = &self.settings;
@@ -505,6 +514,13 @@ impl App {
Item::Caravans => {
format!("CARAVANS < {} >", if s.caravans { "ON" } else { "OFF" })
}
Item::Handling => format!(
"HANDLING < {} >",
match s.handling {
0 => "CUSTOM",
n => vehicle::PRESETS[n - 1].0,
}
),
Item::Music => format!(
"MUSIC < {} >",
match s.music {
@@ -729,7 +745,7 @@ pub fn draw_race(
};
let e = &race.entrants[ei];
// Look ahead in the direction of travel.
let target = track::to_screen(e.car.x + e.car.vx * 0.3, e.car.y + e.car.vy * 0.3);
let target = track::project(e.car.x + e.car.vx * 0.3, e.car.y + e.car.vy * 0.3, e.car.z);
cams[p].0 += (target.0 - cams[p].0) * 0.12;
cams[p].1 += (target.1 - cams[p].1) * 0.12;
draw_world(f, data, race, *view, &mut cams[p], false, 1.0);
@@ -774,7 +790,7 @@ pub fn draw_world(
for (i, e) in race.entrants.iter().enumerate() {
order.push((e.car.screen_pos().1, i, false));
if let Some(t) = e.trailer {
order.push((track::to_screen(t.x, t.y).1, i, true));
order.push((race.track.to_screen(t.x, t.y).1, i, true));
}
}
order.sort_by(|a, b| a.0.total_cmp(&b.0));
@@ -784,7 +800,7 @@ pub fn draw_world(
let pal = palette::car_palette(c.team);
if is_trailer {
let t = e.trailer.unwrap();
let (sx, sy) = track::to_screen(t.x, t.y);
let (sx, sy) = race.track.to_screen(t.x, t.y);
let x = view.x + sx as i32 - cx - (vehicle::SPR_W as i32 / 2);
let y = view.y + sy as i32 - cy - (vehicle::SPR_H as i32 / 2);
let pix =
+124 -35
View File
@@ -13,18 +13,27 @@
//! name of the set of tracks, NUL-terminated.
//!
//! Each track section (281240 bytes once decrunched):
//! 0x0000 height map, 64x64 BE16 8.8 fixed point, one cell per 16x8 pixels
//! 0x0000 height map, 64x64 BE16, row-major by world y then x. Each cell
//! is one unit of the original's world (16 of ours); the ground
//! stands value / 64 pixels above the plane
//! 0x2000 8192 bytes, unused (always zero on the retail disks)
//! 0x4000 BE16 gate count, start x, start y (screen pixels), flags
//! 0x4008 gates: x1 y1 x2 y2 BE16, world coordinates in 1/32 units
//! 0x4000 BE16 last gate index (one less than the gate count), start x,
//! start y (screen pixels), flags
//! 0x4008 70 gate slots: x1 y1 x2 y2 BE16, world coordinates in 1/32
//! units. Slots past the last gate repeat the list.
//! 0x4A98 four 1024x512 bitplanes (65536 bytes each), giving 16 colours
//!
//! The world is an isometric ground plane rotated 45 degrees to the picture:
//! screen x = wx - wy + 512, screen y = (wx + wy) * 31/64 - 252. The constants
//! were fitted so that 96% of gate samples over all 24 tracks land on road
//! pixels (against 40-70% for a plain 2:1 squash). The stored start point
//! only lands on the road on 14 of the 24 tracks, so the grid is placed from
//! the gates instead.
//! The world is an isometric ground plane rotated 45 degrees to the picture.
//! The executable projects a point at height z (pixels) with
//! screen x = wx - wy + 512, screen y = (wx + wy) / 2 - 156 - z, which puts
//! 98% of gate samples over all 24 tracks on road pixels once z is the
//! ground height. The stored start point only lands on the road on 14 of
//! the 24 tracks, so the grid is placed from the gates instead.
//!
//! The gates are also the control points of the road edges: the executable
//! runs a uniform quadratic B-spline through the gates' first ends for one
//! edge and their second ends for the other, and those curves are the
//! walls. They follow the painted road edge closely (see `Track::edges`).
use super::image;
use super::palette::{self, Rgb};
@@ -35,19 +44,24 @@ pub const W: usize = 1024;
pub const H: usize = 512;
pub const HM_W: usize = 64;
pub const HM_H: usize = 64;
pub const CELL_W: f32 = (W / HM_W) as f32;
pub const CELL_H: f32 = (H / HM_H) as f32;
/// World units per height-map cell: one unit of the original's world.
pub const CELL: f32 = 16.0;
/// Samples per gate along the road edges.
pub const EDGE_STEPS: usize = 8;
const SECTION_SIZE: usize = 0x44a98;
const GATES_AT: usize = 0x4000;
/// Room for gates in a track section; slots past the last gate repeat the
/// list from the start.
const GATE_SLOTS: usize = 70;
const PLANES_AT: usize = 0x4a98;
const PLANE_SIZE: usize = W * H / 8;
const SECTIONS: usize = 7;
const ISO_X0: f32 = 512.0;
const ISO_Y_SCALE: f32 = 31.0 / 64.0;
const ISO_Y0: f32 = -252.0;
const ISO_Y0: f32 = -156.0;
pub fn to_screen(wx: f32, wy: f32) -> (f32, f32) {
(wx - wy + ISO_X0, (wx + wy) * ISO_Y_SCALE + ISO_Y0)
/// Screen position of a world point `z` pixels above the ground plane.
pub fn project(wx: f32, wy: f32, z: f32) -> (f32, f32) {
(wx - wy + ISO_X0, (wx + wy) * 0.5 + ISO_Y0 - z)
}
pub fn on_screen(sx: f32, sy: f32, margin: f32) -> bool {
@@ -89,10 +103,13 @@ pub struct Track {
/// Palette indices, W x H.
pub pixels: Vec<u8>,
pub palette: [Rgb; 16],
/// Height samples in 8.8 fixed point.
/// Height samples; the ground is value / 64 pixels high.
pub height: Vec<u16>,
/// Gates in world coordinates.
pub gates: Vec<Gate>,
/// The road edges sampled EDGE_STEPS times per gate, as a closed loop
/// of cross-sections (one point on each edge). Built from the gates.
pub edges: Vec<Gate>,
/// Start position and flags as stored. Kept for reference: the start
/// point does not line up with the road reliably, and the flags' meaning
/// is unknown.
@@ -201,10 +218,11 @@ impl Track {
}
let height = (0..HM_W * HM_H).map(|i| be16(d, i * 2)).collect();
let count = be16(d, GATES_AT) as usize;
// The stored value is the index of the last gate.
let count = be16(d, GATES_AT) as usize + 1;
let start = (be16(d, GATES_AT + 2) as f32, be16(d, GATES_AT + 4) as f32);
let flags = be16(d, GATES_AT + 6);
if count == 0 || GATES_AT + 8 + count * 8 > PLANES_AT {
if count > GATE_SLOTS {
return Err(format!("implausible gate count {count}"));
}
let gates = (0..count)
@@ -242,28 +260,35 @@ impl Track {
thumbnail: None,
height,
gates,
edges: Vec::new(),
start,
flags,
})
}
.with_edges())
}
/// Ground height (in height units) at a screen position, bilinear.
pub fn height_at(&self, sx: f32, sy: f32) -> f32 {
let fx = (sx / CELL_W - 0.5).clamp(0.0, (HM_W - 1) as f32);
let fy = (sy / CELL_H - 0.5).clamp(0.0, (HM_H - 1) as f32);
fn with_edges(mut self) -> Track {
self.edges = edges(&self.gates);
self
}
/// Ground height in pixels at a world position, bilinear between cells
/// as the executable does.
pub fn ground(&self, wx: f32, wy: f32) -> f32 {
let fx = (wx / CELL).clamp(0.0, (HM_W - 1) as f32);
let fy = (wy / CELL).clamp(0.0, (HM_H - 1) as f32);
let (x0, y0) = (fx as usize, fy as usize);
let (x1, y1) = ((x0 + 1).min(HM_W - 1), (y0 + 1).min(HM_H - 1));
let (tx, ty) = (fx - x0 as f32, fy - y0 as f32);
let h = |x: usize, y: usize| self.height[y * HM_W + x] as f32 / 256.0;
let h = |x: usize, y: usize| self.height[y * HM_W + x] as f32 / 64.0;
let top = h(x0, y0) * (1.0 - tx) + h(x1, y0) * tx;
let bot = h(x0, y1) * (1.0 - tx) + h(x1, y1) * tx;
top * (1.0 - ty) + bot * ty
}
/// Height at a world position.
pub fn height_world(&self, wx: f32, wy: f32) -> f32 {
let (sx, sy) = to_screen(wx, wy);
self.height_at(sx, sy)
/// Screen position of a point on the ground.
pub fn to_screen(&self, wx: f32, wy: f32) -> (f32, f32) {
project(wx, wy, self.ground(wx, wy))
}
pub fn pixel(&self, sx: f32, sy: f32) -> u8 {
@@ -279,7 +304,7 @@ impl Track {
}
pub fn on_road_world(&self, wx: f32, wy: f32) -> bool {
let (sx, sy) = to_screen(wx, wy);
let (sx, sy) = self.to_screen(wx, wy);
on_screen(sx, sy, 0.0) && self.on_road(sx, sy)
}
@@ -382,9 +407,11 @@ impl Track {
thumbnail: None,
height,
gates,
edges: Vec::new(),
start,
flags,
})
}
.with_edges())
}
/// The thumbnail as an indexed PNG in the track's palette.
@@ -414,11 +441,14 @@ impl Track {
"# Skids track {}. Lines starting with # are comments.\n\
#\n\
# The picture is track.png: 1024x512, 16 colours. Its palette is the\n\
# track's palette. Colours 8-15 are road (full grip), 0-7 are scenery.\n\
# track's palette. Colours 8-15 are road, 0-7 are scenery (dusty).\n\
#\n\
# gate x1 y1 x2 y2: lap gates in race order, in world units. Gate 0 is\n\
# the start/finish line; the grid forms up behind it. World to picture:\n\
# picture x = wx - wy + 512, picture y = (wx + wy) * 31/64 - 252\n",
# the start/finish line; the grid forms up behind it. The gates' ends\n\
# also shape the walls: each edge of the road is a smooth curve\n\
# through the matching ends of the gates. World to picture, for a\n\
# point at height z (pixels):\n\
# picture x = wx - wy + 512, picture y = (wx + wy) / 2 - 156 - z\n",
self.name
);
for g in &self.gates {
@@ -427,8 +457,8 @@ impl Track {
t.push_str(&format!(
"\n# Name of the set of tracks this one belongs to, shown in the menu.\nset {}\n\n\
# As stored in the original data; not used by the game.\nstart {} {}\nflags {}\n\n\
# height: 64 rows of 64 values, ground height x 256. Each value covers\n\
# 16x8 pixels of the picture.\nheight\n",
# height: 64 rows (world y) of 64 values (world x), ground height in\n\
# pixels x 64. Each value covers 16 x 16 world units.\nheight\n",
self.set_name, self.start.0, self.start.1, self.flags
));
for row in self.height.chunks(HM_W) {
@@ -444,3 +474,62 @@ impl Track {
self.pixels.iter().map(|&i| pal[i as usize]).collect()
}
}
/// The road edges: a uniform quadratic B-spline through the gates' ends,
/// sampled EDGE_STEPS times per gate. Sample k * EDGE_STEPS lies halfway
/// between gates k and k + 1, as in the executable.
pub fn edges(gates: &[Gate]) -> Vec<Gate> {
let n = gates.len();
if n < 3 {
return gates.to_vec();
}
let mut out = Vec::with_capacity(n * EDGE_STEPS);
for i in 0..n {
let (p0, p1, p2) = (gates[i], gates[(i + 1) % n], gates[(i + 2) % n]);
for s in 0..EDGE_STEPS {
let f = s as f32 / EDGE_STEPS as f32;
let (w0, w2) = ((1.0 - f) * (1.0 - f) * 0.5, f * f * 0.5);
let w1 = 1.0 - w0 - w2;
let mix = |a: (f32, f32), b: (f32, f32), c: (f32, f32)| {
(
a.0 * w0 + b.0 * w1 + c.0 * w2,
a.1 * w0 + b.1 * w1 + c.1 * w2,
)
};
out.push(Gate {
a: mix(p0.a, p1.a, p2.a),
b: mix(p0.b, p1.b, p2.b),
});
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn edges_run_between_gate_midpoints() {
let g = |x: f32| Gate {
a: (x, 0.0),
b: (x, 10.0),
};
let gates = [g(0.0), g(10.0), g(30.0), g(60.0)];
let e = edges(&gates);
assert_eq!(e.len(), gates.len() * EDGE_STEPS);
// Each gate's run of samples starts halfway between it and the next.
assert_eq!(e[0].a, (5.0, 0.0));
assert_eq!(e[EDGE_STEPS].b, (20.0, 10.0));
// In between, the curve stays inside the control polygon.
assert!(e[EDGE_STEPS / 2].a.0 > 5.0 && e[EDGE_STEPS / 2].a.0 < 20.0);
}
#[test]
fn projection_lifts_by_height() {
let (x0, y0) = project(100.0, 50.0, 0.0);
let (x1, y1) = project(100.0, 50.0, 10.0);
assert_eq!((x0, y0), (562.0, -81.0));
assert_eq!((x1, y1), (x0, y0 - 10.0));
}
}
+196 -167
View File
@@ -7,9 +7,9 @@
//! file holds the display name and handling.
//!
//! Settings (`vehicles/*.set`, disk 1): car name and driver name, each
//! ending in a newline, then 544 bytes: 16 BE16 values (11 used; their
//! meaning lives in the executable and is not decoded yet) and a 64x32
//! two-colour-plane picture for the menu, plane 0 then plane 1.
//! ending in a newline, then 544 bytes: 16 BE16 handling values (see
//! `SetValues`) and a 64x32 two-colour-plane picture for the menu, plane 0
//! then plane 1.
//!
//! Original data (`vehicles/*.aga`):
//! 409600 bytes once decrunched: 25 tilt blocks x 32 headings x 512 bytes.
@@ -35,105 +35,135 @@ const BANK_BYTES: usize = TILTS * HEADINGS * FRAME_BYTES;
pub const SHEET_W: usize = SPR_W * HEADINGS;
pub const SHEET_H: usize = SPR_H * TILTS;
/// Handling. The retail executable is not available, so these values are a
/// reconstruction; edit them in the vehicle's `.txt` asset.
/// The 16 handling values of a `.set` file. Their meaning, from the
/// executable's set-up code (word k is `set[k]`):
///
/// | Word | Use |
/// |------|-----|
/// | 0, 1, 5 | computer drivers' drag, grip and power |
/// | 2 | steering rate |
/// | 3 | read into the car but never used |
/// | 4 | gravity |
/// | 6 | tow hitch distance behind the car |
/// | 8, 9, 10 | human drivers' drag, grip and power |
///
/// Words 7 and 11-15 aren't read.
pub type SetValues = [u16; 16];
/// The Camaro's values, for vehicles that come without any.
pub const DEFAULT_SET: SetValues = [54, 35, 32, 8, 7, 22, 22, 16, 49, 14, 53, 0, 0, 0, 0, 0];
/// One driver profile. The original's per-frame update (in its own units,
/// where one world unit is a height-map cell) is:
///
/// ```text
/// target = (speed + throttle * power) * drag
/// velocity += grip * (heading * target - velocity)
/// speed += (target - speed) / 4
/// ```
///
/// so top speed is `power * drag / (1 - drag)` per frame, and `grip` is
/// the fraction of the gap between where the car points and where it is
/// going that closes each frame.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Spec {
/// Top speed on tarmac, world pixels per second.
pub top: f32,
pub accel: f32,
pub brake: f32,
/// Lateral grip: how quickly sideways motion is killed (1/s).
pub struct Handling {
pub drag: f32,
pub grip: f32,
/// Steering rate at speed (rad/s).
pub turn: f32,
pub mass: f32,
/// Speed and grip multiplier off the road.
pub offroad: f32,
pub power: f32,
}
pub const BASE_SPEC: Spec = Spec {
top: 200.0,
accel: 170.0,
brake: 320.0,
grip: 5.0,
turn: 3.6,
mass: 1.0,
offroad: 0.55,
};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Spec {
pub player: Handling,
pub computer: Handling,
/// Steering at full lock, in 64ths of a turn per frame.
pub turn: f32,
/// Pixels per frame per frame.
pub gravity: f32,
/// Tow hitch distance behind the car's centre, world units.
pub hitch: f32,
/// Weight in collisions between cars. Not from the original data.
pub mass: f32,
}
/// Starting values used when importing the original data.
pub fn default_spec(id: &str) -> Spec {
let b = BASE_SPEC;
impl Spec {
/// Decodes the values as the executable does (fixed-point shifts).
pub fn from_set(v: &SetValues, mass: f32) -> Spec {
let f = |k: usize, shift: u32| v[k] as i16 as f32 / (1u32 << shift) as f32;
Spec {
player: Handling {
drag: f(8, 6),
grip: f(9, 9),
power: f(10, 9),
},
computer: Handling {
drag: f(0, 6),
grip: f(1, 9),
power: f(5, 9),
},
turn: f(2, 4),
gravity: f(4, 3),
hitch: f(6, 5) * super::track::CELL,
mass,
}
}
}
/// The original's handling presets, which replace every car's own values
/// (its "CUSTOM" setting). Bytes 0-2 are drag, top speed and grip for
/// human drivers, 3-5 the same for computer drivers, and 6 is steering.
pub const PRESETS: [(&str, [u8; 7]); 6] = [
("CLASSIC", [98, 26, 24, 98, 24, 24, 44]),
("GRUNTY", [92, 40, 30, 108, 33, 25, 60]),
("ZIPPY", [102, 61, 47, 115, 59, 45, 64]),
("RIPPY", [100, 80, 60, 110, 50, 60, 72]),
("SLIPPERY", [25, 127, 101, 29, 45, 122, 68]),
("PEDAL CAR", [117, 23, 17, 104, 17, 10, 30]),
];
impl Handling {
/// Decodes a preset's three bytes as the executable does: top speed
/// comes out at `top / 128` world units per frame.
fn from_preset(drag: u8, top: u8, grip: u8) -> Handling {
let drag = drag as f32 / 128.0 * 0.98 + 0.01;
let top = top as f32 / 16.0;
let power = top * (1.0 / drag - 1.0);
Handling {
drag,
grip: grip as f32 / 256.0 / power / drag / 8.0,
power: power / 8.0,
}
}
}
impl Spec {
/// The spec with preset `n` (1-based, as in the original's menu; 0
/// keeps the car's own values).
pub fn with_preset(self, n: usize) -> Spec {
let Some((_, b)) = n.checked_sub(1).and_then(|i| PRESETS.get(i)) else {
return self;
};
Spec {
player: Handling::from_preset(b[0], b[1], b[2]),
computer: Handling::from_preset(b[3], b[4], b[5]),
turn: b[6] as f32 / 32.0,
gravity: 0.5,
..self
}
}
}
/// Collision weight. The original's car-to-car physics isn't decoded, so
/// these are guesses.
pub fn default_mass(id: &str) -> f32 {
match id {
"f1" => Spec {
top: 240.0,
accel: 210.0,
grip: 6.5,
turn: 3.7,
mass: 0.9,
offroad: 0.45,
..b
},
"porsche" => Spec {
top: 225.0,
accel: 190.0,
grip: 5.2,
turn: 3.6,
..b
},
"mini" => Spec {
top: 190.0,
accel: 200.0,
grip: 5.8,
turn: 4.4,
mass: 0.8,
..b
},
"vw" => Spec {
top: 185.0,
accel: 160.0,
grip: 4.6,
turn: 3.6,
mass: 1.0,
..b
},
"midget" => Spec {
top: 205.0,
accel: 185.0,
grip: 4.0,
turn: 4.0,
mass: 0.85,
offroad: 0.6,
..b
},
"truck" => Spec {
top: 195.0,
accel: 140.0,
grip: 4.2,
turn: 3.0,
mass: 1.8,
offroad: 0.75,
..b
},
"police" => Spec {
top: 215.0,
accel: 180.0,
grip: 5.0,
turn: 3.5,
mass: 1.2,
..b
},
"cow" => Spec {
top: 170.0,
accel: 150.0,
grip: 7.0,
turn: 4.6,
mass: 1.4,
offroad: 0.9,
..b
},
_ => b,
"f1" => 0.9,
"mini" => 0.8,
"midget" => 0.85,
"truck" => 1.8,
"police" => 1.2,
"cow" => 1.4,
_ => 1.0,
}
}
@@ -143,8 +173,8 @@ pub struct Vehicle {
pub driver: String,
/// Towed behind cars rather than driven (the caravan).
pub trailer: bool,
/// The 16 values from the original `.set` file, kept for reference.
pub original: Vec<u16>,
/// The 16 handling values from the original `.set` file.
pub set: SetValues,
/// 64x32 menu picture (indices 0-3), from the `.set` file.
pub menu_picture: Option<Vec<u8>>,
pub spec: Spec,
@@ -180,9 +210,9 @@ impl Vehicle {
name: display_name(&id),
driver: String::new(),
trailer: id == "caravan",
original: Vec::new(),
set: DEFAULT_SET,
menu_picture: None,
spec: default_spec(&id),
spec: Spec::from_set(&DEFAULT_SET, default_mass(&id)),
id,
frames,
}))
@@ -202,7 +232,10 @@ impl Vehicle {
self.name = name;
}
self.driver = crate::formats::latin1(&d[a + 1..b]).trim().to_uppercase();
self.original = (0..16).map(|i| crate::formats::be16(body, i * 2)).collect();
for (i, v) in self.set.iter_mut().enumerate() {
*v = crate::formats::be16(body, i * 2);
}
self.spec = Spec::from_set(&self.set, self.spec.mass);
let pic = &body[32..32 + 512];
let mut px = vec![0u8; SPR_W * SPR_H];
for (i, p) in px.iter_mut().enumerate() {
@@ -258,9 +291,9 @@ impl Vehicle {
name: display_name(id),
driver: String::new(),
trailer: id == "caravan",
original: Vec::new(),
set: DEFAULT_SET,
menu_picture: None,
spec: default_spec(id),
spec: Spec::from_set(&DEFAULT_SET, default_mass(id)),
frames,
};
if let Some(txt) = txt {
@@ -284,7 +317,6 @@ impl Vehicle {
}
pub fn settings_text(&self) -> String {
let s = &self.spec;
format!(
"# Skids vehicle settings. Lines starting with # are comments.\n\
# The sprite sheet is {id}.png: 32 headings across, 25 tilts down.\n\
@@ -292,40 +324,28 @@ impl Vehicle {
driver {driver}\n\
# yes = towed behind cars (Caravans option) instead of driven\n\
trailer {trailer}\n\
# The 16 values from the original .set file. Their meaning is not\n\
# decoded yet, so the game does not use them; the handling below is\n\
# a reconstruction.\n\
original {original}\n\
# top speed on tarmac, world pixels per second\n\
top {top}\n\
# acceleration and braking, world pixels per second squared\n\
accel {accel}\n\
brake {brake}\n\
# sideways grip (higher = less sliding)\n\
grip {grip}\n\
# steering rate, radians per second\n\
turn {turn}\n\
# weight in collisions\n\
mass {mass}\n\
# speed and grip multiplier off the road\n\
offroad {offroad}\n",
#\n\
# handling: the 16 values from the original .set file, used as the\n\
# original game does. Counting from 1:\n\
# 1 2 6 computer drivers: drag, grip, power\n\
# 9 10 11 human drivers: drag, grip, power\n\
# 3 steering, 5 gravity, 7 tow hitch distance; the rest are unused.\n\
# Top speed = power * drag / (64 - drag) / 32 world units per frame,\n\
# 50 frames a second (drag below 64). Higher grip, less sliding.\n\
handling {set}\n\
# weight when cars collide (not from the original data)\n\
mass {mass}\n",
id = self.id,
name = self.name,
driver = self.driver,
trailer = if self.trailer { "yes" } else { "no" },
original = self
.original
set = self
.set
.iter()
.map(|v| v.to_string())
.collect::<Vec<_>>()
.join(" "),
top = s.top,
accel = s.accel,
brake = s.brake,
grip = s.grip,
turn = s.turn,
mass = s.mass,
offroad = s.offroad
mass = self.spec.mass,
)
}
@@ -337,43 +357,36 @@ impl Vehicle {
}
let (key, val) = line.split_once(char::is_whitespace).unwrap_or((line, ""));
let val = val.trim();
let bad = |what: &str| format!("line {}: {what}", n + 1);
match key {
"name" => {
self.name = val.to_uppercase();
continue;
}
"driver" => {
self.driver = val.to_uppercase();
continue;
}
"trailer" => {
self.trailer = matches!(val, "yes" | "true" | "1");
continue;
}
"original" => {
self.original = val
"name" => self.name = val.to_uppercase(),
"driver" => self.driver = val.to_uppercase(),
"trailer" => self.trailer = matches!(val, "yes" | "true" | "1"),
// `original` is what older asset files called it.
"handling" | "original" => {
let vals: Vec<u16> = val
.split_whitespace()
.filter_map(|x| x.parse().ok())
.collect();
continue;
.map(|x| x.parse::<u16>())
.collect::<Result<_, _>>()
.map_err(|_| bad("handling values must be whole numbers 0-65535"))?;
if vals.len() > 16 {
return Err(bad("handling has more than 16 values"));
}
_ => {}
self.set = [0; 16];
self.set[..vals.len()].copy_from_slice(&vals);
}
let num: f32 = val
.parse()
.map_err(|_| format!("line {}: `{val}` is not a number", n + 1))?;
let s = &mut self.spec;
match key {
"top" => s.top = num,
"accel" => s.accel = num,
"brake" => s.brake = num,
"grip" => s.grip = num,
"turn" => s.turn = num,
"mass" => s.mass = num.max(0.05),
"offroad" => s.offroad = num,
_ => return Err(format!("line {}: unknown setting `{key}`", n + 1)),
"mass" => {
self.spec.mass = val
.parse::<f32>()
.map_err(|_| bad(&format!("`{val}` is not a number")))?
.max(0.05)
}
// Settings from before the original handling was decoded.
"top" | "accel" | "brake" | "grip" | "turn" | "offroad" => {}
_ => return Err(bad(&format!("unknown setting `{key}`"))),
}
}
self.spec = Spec::from_set(&self.set, self.spec.mass);
Ok(())
}
@@ -436,15 +449,15 @@ mod tests {
name: String::new(),
driver: String::new(),
trailer: false,
original: Vec::new(),
set: DEFAULT_SET,
menu_picture: None,
spec: BASE_SPEC,
spec: Spec::from_set(&DEFAULT_SET, 1.0),
frames,
};
let mut v = blank(vec![0; TILTS * HEADINGS * SPR_W * SPR_H]);
v.driver = "SIMON".into();
v.original = vec![60, 71, 30];
v.spec.grip = 6.25;
v.set = [60, 71, 30, 0, 6, 16, 24, 12, 58, 42, 29, 0, 0, 0, 0, 0];
v.spec = Spec::from_set(&v.set, 0.9);
v.name = "FORMULA 1".into();
let txt = v.settings_text();
let mut w = blank(Vec::new());
@@ -452,8 +465,24 @@ mod tests {
assert_eq!(w.spec, v.spec);
assert_eq!(w.name, "FORMULA 1");
assert_eq!(w.driver, "SIMON");
assert_eq!(w.original, v.original);
assert!(w.apply_settings("grip fast").is_err());
assert_eq!(w.set, v.set);
assert!(w.apply_settings("mass heavy").is_err());
// Older files with reconstructed handling still load.
assert!(w.apply_settings("original 1 2 3\ntop 200").is_ok());
assert_eq!(&w.set[..4], &[1, 2, 3, 0]);
}
#[test]
fn decodes_set_values() {
let s = Spec::from_set(&DEFAULT_SET, 1.0);
assert_eq!(s.player.drag, 49.0 / 64.0);
assert_eq!(s.computer.power, 22.0 / 512.0);
assert_eq!(s.turn, 2.0);
assert_eq!(s.hitch, 11.0);
let p = s.with_preset(1);
let top = p.player.power * p.player.drag / (1.0 - p.player.drag);
assert!((top - 26.0 / 128.0).abs() < 1e-5);
assert_eq!(s.with_preset(0), s);
}
#[test]
+47 -113
View File
@@ -1,24 +1,25 @@
//! Computer drivers.
//!
//! Each driver follows a line through the gates (at its own offset across
//! the track) by pure pursuit: aim at a point a speed-dependent distance
//! further along the line. Short look-ahead rays nudge the chosen heading
//! away from the grass, and a driver that stops moving backs out.
//! As in the executable, a computer car steers like a joystick: full lock
//! towards a target point ahead, or straight on when it is dead ahead, and
//! drives flat out. The cars' computer handling (`vehicle::Handling`) is
//! what makes them take bends at that. The target is a point a little way
//! along the road (`Track::edges`) at the driver's own line across it. A
//! driver that stops moving backs out.
use super::car::{angle_diff, Car, Controls};
use crate::data::track::{Gate, Track};
use crate::data::track::Track;
const RAYS: i32 = 7;
const RAY_STEP: f32 = 0.13;
/// Heading error, in radians, below which the driver doesn't steer.
const DEAD_AHEAD: f32 = 0.06;
#[derive(Clone, Debug)]
pub struct Ai {
/// Where across each gate this driver aims (0 = end a, 1 = end b).
/// Where across the road this driver aims (0 = one edge, 1 = the other).
pub line: f32,
/// Fraction of the car's top speed this driver uses.
/// Fraction of full throttle this driver uses.
pub pace: f32,
stuck_for: f32,
blocked_for: f32,
reversing: f32,
}
@@ -28,83 +29,59 @@ impl Ai {
line,
pace,
stuck_for: 0.0,
blocked_for: 0.0,
reversing: 0.0,
}
}
fn point(&self, g: &Gate) -> (f32, f32) {
/// The point `dist` along the road from the car's cross-section, at
/// this driver's line.
pub fn target(&self, car: &Car, track: &Track, dist: f32) -> (f32, f32) {
let e = &track.edges;
let m = e.len();
let at = |k: usize| {
let g = e[k % m];
(
g.a.0 + (g.b.0 - g.a.0) * self.line,
g.a.1 + (g.b.1 - g.a.1) * self.line,
)
};
let mut k = car.section + 1;
let mut p = at(k);
let mut left = dist - (p.0 - car.x).hypot(p.1 - car.y);
while left > 0.0 && k < car.section + m {
let q = at(k + 1);
let len = (q.0 - p.0).hypot(q.1 - p.1);
if len >= left {
let f = left / len;
return (p.0 + (q.0 - p.0) * f, p.1 + (q.1 - p.1) * f);
}
left -= len;
p = q;
k += 1;
}
p
}
/// Point `dist` along the driving line from the car's projection onto it.
fn pursue(&mut self, car: &Car, gates: &[Gate], next_gate: usize, dist: f32) -> (f32, f32) {
let n = gates.len();
// Only the stretch into the gate the lap counter needs next, or the
// one after it (crossing counts with one gate skipped), so a car
// knocked backwards turns round for the gate it missed.
let seg = next_gate;
let mut best = (f32::MAX, seg, 0.0f32);
for k in 0..2 {
let i = (seg + k) % n;
let a = self.point(&gates[(i + n - 1) % n]);
let b = self.point(&gates[i]);
let (dx, dy) = (b.0 - a.0, b.1 - a.1);
let len2 = (dx * dx + dy * dy).max(1e-3);
let t = (((car.x - a.0) * dx + (car.y - a.1) * dy) / len2).clamp(0.0, 1.0);
let d = (a.0 + dx * t - car.x).hypot(a.1 + dy * t - car.y);
if d < best.0 - 1.0 {
best = (d, i, t);
}
}
let (_, mut i, t) = best;
let mut a = self.point(&gates[(i + n - 1) % n]);
let mut b = self.point(&gates[i]);
let mut remaining = dist + (b.0 - a.0).hypot(b.1 - a.1) * t;
for _ in 0..n {
let len = (b.0 - a.0).hypot(b.1 - a.1);
if remaining <= len {
let k = remaining / len.max(1e-3);
return (a.0 + (b.0 - a.0) * k, a.1 + (b.1 - a.1) * k);
}
remaining -= len;
i = (i + 1) % n;
a = b;
b = self.point(&gates[i]);
}
b
}
pub fn drive(
&mut self,
car: &Car,
track: &Track,
next_gate: usize,
dt: f32,
racing: bool,
) -> Controls {
if !racing {
pub fn drive(&mut self, car: &Car, track: &Track, dt: f32, racing: bool) -> Controls {
if !racing || track.edges.len() < 3 {
return Controls::default();
}
let gates = &track.gates;
let speed = car.speed();
let look = 34.0 + speed * 0.32;
let target = self.pursue(car, gates, next_gate, look);
let far = self.pursue(car, gates, next_gate, look * 2.2);
let want = (target.1 - car.y).atan2(target.0 - car.x);
let look = 30.0 + speed * 0.2;
let (tx, ty) = self.target(car, track, look);
let diff = angle_diff((ty - car.y).atan2(tx - car.x), car.heading);
let steer = if diff.abs() < DEAD_AHEAD {
0.0
} else {
diff.signum()
};
if self.reversing > 0.0 {
self.reversing -= dt;
let diff = angle_diff(want, car.heading);
return Controls {
throttle: 0.0,
brake: 1.0,
steer: -diff.signum(),
steer: -steer,
};
}
if speed < 10.0 {
@@ -116,49 +93,6 @@ impl Ai {
} else {
self.stuck_for = 0.0;
}
// Pick the heading closest to the target that keeps us on the road.
let ray_len = 18.0 + speed * 0.25;
let mut best = (f32::MAX, want);
for k in -RAYS..=RAYS {
let cand = want + k as f32 * RAY_STEP;
let mut grass = 0.0;
for step in 1..=4 {
let d = ray_len * step as f32 / 4.0;
if !track.on_road_world(car.x + cand.cos() * d, car.y + cand.sin() * d) {
grass += 1.0 / step as f32;
}
}
let cost = (k as f32 * RAY_STEP).abs() + grass * 0.9;
if cost < best.0 {
best = (cost, cand);
}
}
let diff = angle_diff(best.1, car.heading);
let steer = (diff * 3.0).clamp(-1.0, 1.0);
// Boxed in (nose in the scenery or against the edge): back out.
if best.0 > 1.6 && speed < 30.0 {
self.blocked_for += dt;
if self.blocked_for > 0.5 {
self.blocked_for = 0.0;
self.reversing = 0.7;
}
} else {
self.blocked_for = 0.0;
}
// Slow for what is coming: compare heading with the far probe.
let bend = angle_diff((far.1 - car.y).atan2(far.0 - car.x), car.heading).abs();
let corner_speed = car.spec.top * self.pace * (1.0 - (bend / 1.3).min(0.75));
let (mut throttle, mut brake) = (1.0, 0.0);
if speed > corner_speed {
throttle = 0.0;
if speed > corner_speed + 25.0 {
brake = 0.7;
}
} else if diff.abs() > 0.8 {
throttle = 0.6;
}
if diff.abs() > 2.3 && speed < 35.0 {
// Facing the wrong way: reverse-turn round.
return Controls {
@@ -168,8 +102,8 @@ impl Ai {
};
}
Controls {
throttle,
brake,
throttle: self.pace,
brake: 0.0,
steer,
}
}
+225 -86
View File
@@ -1,19 +1,36 @@
//! Vehicle handling.
//! Vehicle handling, as decoded from the executable.
//!
//! The retail executable is not available, so handling is a reconstruction
//! (per-car values live in the vehicle assets): an arcade model with separate forward and lateral friction, which gives
//! the long power-slides the series is known for. Physics runs on the
//! isometric ground plane (see `data::track::to_screen`).
//! The original runs one update per 50 Hz frame in its own units, where one
//! world unit is a height-map cell (`track::CELL` of ours). Each frame:
//!
//! - **Steering:** spin = (spin + steer * turn) / 2, then the heading
//! turns by spin (64ths of a turn). Steering is digital, as the
//! joystick is.
//! - **Height:** vertical speed loses `gravity`. If the car would still be
//! above the ground two frames ahead it's airborne; otherwise its
//! vertical speed is half the climb to that point. Body tilt steps one
//! notch a frame towards the slope under the car.
//! - **Engine and grip:** see `vehicle::Handling`. Climbing takes half the
//! vertical speed off the throttle; descending adds it.
//! - **Move:** velocity is added to the position. Airborne cars keep
//! their velocity and engine speed.
//! - **Walls:** the road edges (`Track::edges`) are walls. A car that
//! reaches one loses the part of its velocity into the wall and bounces
//! back with half of it.
//!
//! Human and computer drivers use separate handling from the vehicle's
//! `.set` values.
use crate::data::track::{self, Track};
use crate::data::vehicle::{self, Spec};
use super::DT;
use crate::data::track::{self, Gate, Track, CELL};
use crate::data::vehicle::{self, Handling, Spec};
use std::f32::consts::{PI, TAU};
pub const RADIUS: f32 = 11.0;
const SLOPE_GRAVITY: f32 = 110.0;
/// Cap on slope acceleration so the steep banking on some tracks cannot pin
/// a car against the outside of a bend.
const SLOPE_MAX: f32 = 45.0;
/// How close the car's centre may come to a wall, in world units.
const WALL_MARGIN: f32 = 4.0;
/// The executable samples the slope 0.2 cells from the car.
const TILT_PROBE: f32 = 0.2 * CELL;
#[derive(Clone, Copy, Default, Debug)]
pub struct Controls {
@@ -28,21 +45,39 @@ pub struct Car {
pub vehicle: usize,
pub spec: Spec,
pub team: usize,
/// Uses the vehicle's computer-driver handling.
pub computer: bool,
pub x: f32,
pub y: f32,
/// Height above the ground plane, pixels.
pub z: f32,
/// World units per second.
pub vx: f32,
pub vy: f32,
/// Pixels per frame.
pub vz: f32,
/// World angle, 0 = +x, increasing clockwise on screen.
pub heading: f32,
/// Turn per frame, 64ths of a turn.
pub spin: f32,
/// The engine's speed, in the original's units per frame.
pub drive: f32,
pub airborne: bool,
pub controls: Controls,
/// Sideways speed this frame, used for skid marks and sound.
pub slip: f32,
pub on_road: bool,
/// Tilt notches 0-4 (2 = level): pitch follows the slope along world y
/// and roll the slope along world x, as in the executable.
pub pitch: i32,
pub roll: i32,
pub tilt: usize,
/// Throttle/rpm proxy for the engine sound, 0..1.
pub rpm: f32,
/// Strength of the last collision, decays each frame.
pub bump: f32,
/// The road cross-section (`Track::edges`) the car is past.
pub section: usize,
}
impl Car {
@@ -51,22 +86,44 @@ impl Car {
vehicle,
spec,
team,
computer: false,
x,
y,
z: 0.0,
vx: 0.0,
vy: 0.0,
vz: 0.0,
heading,
spin: 0.0,
drive: 0.0,
airborne: false,
controls: Controls::default(),
slip: 0.0,
on_road: true,
pitch: 2,
roll: 2,
tilt: vehicle::LEVEL,
rpm: 0.0,
bump: 0.0,
section: 0,
}
}
/// Puts the car on the ground at its position, stopped, and finds its
/// place along the road.
pub fn place(&mut self, track: &Track) {
self.z = track.ground(self.x, self.y);
self.vx = 0.0;
self.vy = 0.0;
self.vz = 0.0;
self.spin = 0.0;
self.drive = 0.0;
self.airborne = false;
self.section = nearest_section(&track.edges, (self.x, self.y));
}
pub fn screen_pos(&self) -> (f32, f32) {
track::to_screen(self.x, self.y)
track::project(self.x, self.y, self.z)
}
pub fn speed(&self) -> f32 {
@@ -77,84 +134,147 @@ impl Car {
(self.heading.cos(), self.heading.sin())
}
pub fn step(&mut self, track: &Track, dt: f32, top_scale: f32) {
self.on_road = track.on_road_world(self.x, self.y);
let surf = if self.on_road { 1.0 } else { self.spec.offroad };
pub fn handling(&self) -> Handling {
if self.computer {
self.spec.computer
} else {
self.spec.player
}
}
/// Top speed on the flat, world units per second.
pub fn top_speed(&self) -> f32 {
let h = self.handling();
if h.drag >= 1.0 {
return f32::INFINITY;
}
h.power * h.drag / (1.0 - h.drag) * CELL / DT
}
/// One frame. `pace` scales the throttle (finished cars cruise).
pub fn step(&mut self, track: &Track, pace: f32) {
let c = self.controls;
let h = self.handling();
// Velocity in world units per frame.
let (mut vx, mut vy) = (self.vx * DT, self.vy * DT);
if self.airborne {
self.spin = self.spin.clamp(-1.0, 1.0);
} else {
let steer = c.steer.clamp(-1.0, 1.0);
self.spin = (self.spin + steer * self.spec.turn) * 0.5;
}
self.vz -= self.spec.gravity;
let (ax, ay) = (self.x + 2.0 * vx, self.y + 2.0 * vy);
let ground = track.ground(ax, ay);
if self.z + 2.0 * self.vz > ground {
self.airborne = true;
} else {
self.airborne = false;
self.vz = (ground - self.z) * 0.5;
let notch = |probe: f32| ((probe - ground + 2.5).floor() as i32).clamp(0, 4);
let pitch = notch(track.ground(ax, ay + TILT_PROBE));
let roll = notch(track.ground(ax - TILT_PROBE, ay));
self.pitch += (pitch - self.pitch).signum();
self.roll += (roll - self.roll).signum();
}
self.tilt = vehicle::tilt_block(self.pitch - 2, self.roll - 2);
self.heading = (self.heading + self.spin * TAU / 64.0).rem_euclid(TAU);
let (fx, fy) = self.forward();
let (rx, ry) = (-fy, fx);
let mut vf = self.vx * fx + self.vy * fy;
let mut vr = self.vx * rx + self.vy * ry;
let top = self.spec.top * surf * top_scale;
if c.throttle > 0.0 && vf < top {
let headroom = (1.0 - (vf / top).max(0.0).powi(2)).max(0.05);
vf += self.spec.accel * c.throttle * headroom * dt;
if !self.airborne {
// The original's units: world cells per frame.
let (cx, cy) = (vx / CELL, vy / CELL);
let along = cx * fx + cy * fy;
let mut throttle = (c.throttle - c.brake).clamp(-1.0, 1.0) * pace;
throttle += if along < 0.0 { self.vz } else { -self.vz } * 0.5;
let target = (self.drive + throttle * h.power) * h.drag;
let (sx, sy) = (h.grip * (fx * target - cx), h.grip * (fy * target - cy));
self.drive += (target - self.drive) * 0.25;
vx += sx * CELL;
vy += sy * CELL;
}
if c.brake > 0.0 {
if vf > 4.0 {
vf = (vf - self.spec.brake * c.brake * dt).max(0.0);
} else if vf > -top * 0.35 {
vf -= self.spec.accel * 0.6 * c.brake * dt;
}
}
let drag = if self.on_road { 0.35 } else { 1.6 };
vf -= vf * drag * dt;
if c.throttle == 0.0 && c.brake == 0.0 {
vf -= vf.signum() * (30.0 * dt).min(vf.abs());
}
// Sideways friction: grip drops as the slide gets faster, so once a
// car breaks away it keeps sliding.
let grip = self.spec.grip * surf.max(0.6) / (1.0 + vr.abs() / 120.0);
vr *= (-grip * dt).exp();
self.slip = vr.abs();
let steer_auth = (vf.abs() / 45.0).min(1.0) * vf.signum();
let turn = self.spec.turn * (1.0 - (vf.abs() / (top * 2.2)).min(0.35));
self.heading = (self.heading + c.steer * turn * steer_auth * dt).rem_euclid(TAU);
self.vx = fx * vf + rx * vr;
self.vy = fy * vf + ry * vr;
// Hills: the height map is sampled in screen space.
let (gx, gy) = gradient(track, self.x, self.y);
let (mut ax, mut ay) = (-gx * SLOPE_GRAVITY, -gy * SLOPE_GRAVITY);
let a = ax.hypot(ay);
if a > SLOPE_MAX {
ax *= SLOPE_MAX / a;
ay *= SLOPE_MAX / a;
}
self.vx += ax * dt;
self.vy += ay * dt;
let (ox, oy) = (self.x, self.y);
self.x += self.vx * dt;
self.y += self.vy * dt;
self.keep_in_bounds(ox, oy);
// Body tilt from the slope along and across the car.
let (fx, fy) = self.forward();
let along = gx * fx + gy * fy;
let across = gx * -fy + gy * fx;
let q = |s: f32| (s / 0.035).round() as i32;
self.tilt = vehicle::tilt_block(-q(along), q(across));
self.x += vx;
self.y += vy;
self.z += self.vz;
self.vx = vx / DT;
self.vy = vy / DT;
self.walls(track);
self.keep_in_bounds(track, ox, oy);
self.on_road = track.on_road_world(self.x, self.y);
self.slip = (self.vy * fx - self.vx * fy).abs();
let top = self.top_speed().max(1.0);
let fwd = (self.vx * fx + self.vy * fy).abs();
let target_rpm = if c.throttle > 0.0 {
0.35 + 0.65 * (vf.abs() / top).min(1.0)
0.35 + 0.65 * (fwd / top).min(1.0)
} else {
(vf.abs() / top).min(1.0) * 0.7
(fwd / top).min(1.0) * 0.7
};
self.rpm += (target_rpm - self.rpm) * (1.0 - (-6.0 * dt).exp());
self.bump *= (-8.0 * dt).exp();
self.rpm += (target_rpm - self.rpm) * (1.0 - (-6.0 * DT).exp());
self.bump *= (-8.0 * DT).exp();
}
/// The picture edge is a wall. Slide along it by keeping whichever axis
/// of the move stays inside, so cars never get pinned in a corner.
fn keep_in_bounds(&mut self, ox: f32, oy: f32) {
/// Keeps the car between the road edges of the section it is in, and
/// moves `section` along as the car crosses from one to the next.
fn walls(&mut self, track: &Track) {
let e = &track.edges;
let m = e.len();
if m < 3 {
return;
}
let p = (self.x, self.y);
for _ in 0..m {
let next = (self.section + 1) % m;
if !past(e, next, p) {
break;
}
self.section = next;
}
for _ in 0..m {
if past(e, self.section, p) {
break;
}
self.section = (self.section + m - 1) % m;
}
let (s0, s1) = (e[self.section], e[(self.section + 1) % m]);
for (p0, p1, inside) in [(s0.a, s1.a, s0.b), (s0.b, s1.b, s0.a)] {
let (dx, dy) = (p1.0 - p0.0, p1.1 - p0.1);
let len = dx.hypot(dy);
if len < 1e-3 {
continue;
}
// Unit normal pointing into the road.
let (mut nx, mut ny) = (-dy / len, dx / len);
if nx * (inside.0 - p0.0) + ny * (inside.1 - p0.1) < 0.0 {
nx = -nx;
ny = -ny;
}
let dist = nx * (self.x - p0.0) + ny * (self.y - p0.1);
if dist >= WALL_MARGIN {
continue;
}
self.x += nx * (WALL_MARGIN - dist);
self.y += ny * (WALL_MARGIN - dist);
let vn = self.vx * nx + self.vy * ny;
if vn < 0.0 {
self.vx -= 1.5 * vn * nx;
self.vy -= 1.5 * vn * ny;
self.bump = self.bump.max((-vn / 200.0).min(0.6));
}
}
}
/// The picture edge is a wall too, for tracks whose road edges run off
/// it. Slide along it by keeping whichever axis of the move stays
/// inside, so cars never get pinned in a corner.
fn keep_in_bounds(&mut self, track: &Track, ox: f32, oy: f32) {
const MARGIN: f32 = 3.0;
let inside = |x: f32, y: f32| {
let (sx, sy) = track::to_screen(x, y);
let (sx, sy) = track.to_screen(x, y);
track::on_screen(sx, sy, MARGIN)
};
if inside(self.x, self.y) {
@@ -177,23 +297,42 @@ impl Car {
}
/// Rear wheel contact points in screen space, for skid marks.
pub fn rear_wheels(&self) -> [(f32, f32); 2] {
pub fn rear_wheels(&self, track: &Track) -> [(f32, f32); 2] {
let (fx, fy) = self.forward();
let (rx, ry) = (-fy, fx);
let (bx, by) = (self.x - fx * 10.0, self.y - fy * 10.0);
[
track::to_screen(bx + rx * 6.0, by + ry * 6.0),
track::to_screen(bx - rx * 6.0, by - ry * 6.0),
track.to_screen(bx + rx * 6.0, by + ry * 6.0),
track.to_screen(bx - rx * 6.0, by - ry * 6.0),
]
}
}
/// Height change per world unit along world x and y.
fn gradient(track: &Track, wx: f32, wy: f32) -> (f32, f32) {
let d = 6.0;
let gx = (track.height_world(wx + d, wy) - track.height_world(wx - d, wy)) / (2.0 * d);
let gy = (track.height_world(wx, wy + d) - track.height_world(wx, wy - d)) / (2.0 * d);
(gx, gy)
/// Whether `p` is past cross-section `k`, going round the road.
fn past(e: &[Gate], k: usize, p: (f32, f32)) -> bool {
let m = e.len();
let (a, b) = (e[k].a, e[k].b);
let side = |q: (f32, f32)| (b.0 - a.0) * (q.1 - a.1) - (b.1 - a.1) * (q.0 - a.0);
side(p) * side(e[(k + 1) % m].mid()) > 0.0
}
/// The cross-section just behind the point closest to `p`.
pub fn nearest_section(e: &[Gate], p: (f32, f32)) -> usize {
let mut best = (f32::MAX, 0);
for (k, g) in e.iter().enumerate() {
let (mx, my) = g.mid();
let d = (mx - p.0).hypot(my - p.1);
if d < best.0 {
best = (d, k);
}
}
let k = best.1;
let m = e.len().max(1);
if past(e, k, p) || e.len() < 3 {
k
} else {
(k + m - 1) % m
}
}
/// Pushes overlapping cars apart and exchanges momentum.
+58 -23
View File
@@ -46,15 +46,15 @@ pub struct Trailer {
pub heading: f32,
}
/// Hitch distance behind the car's centre, and tow bar length to the
/// trailer's centre, in world units.
const HITCH: f32 = 12.0;
/// Tow bar length from the car's hitch (`Spec::hitch`) to the trailer's
/// centre, in world units.
const TOW_BAR: f32 = 20.0;
const TRAILER_RADIUS: f32 = 10.0;
/// Seconds without crossing a gate before a computer car is rescued.
const RESCUE_AFTER: f32 = 8.0;
fn rescue(e: &mut Entrant, gates: &[Gate]) {
fn rescue(e: &mut Entrant, track: &Track) {
let gates = &track.gates;
let n = gates.len();
let from = gates[(e.next_gate + n - 1) % n].mid();
let to = gates[e.next_gate].mid();
@@ -62,11 +62,10 @@ fn rescue(e: &mut Entrant, gates: &[Gate]) {
let c = &mut e.car;
c.x = from.0;
c.y = from.1;
c.vx = 0.0;
c.vy = 0.0;
c.heading = heading.rem_euclid(std::f32::consts::TAU);
c.place(track);
if let Some(t) = &mut e.trailer {
let back = HITCH + TOW_BAR;
let back = c.spec.hitch + TOW_BAR;
t.x = from.0 - heading.cos() * back;
t.y = from.1 - heading.sin() * back;
t.heading = heading;
@@ -78,7 +77,7 @@ impl Trailer {
/// hitch and swings to point at it.
fn follow(&mut self, car: &Car) {
let (fx, fy) = car.forward();
let (hx, hy) = (car.x - fx * HITCH, car.y - fy * HITCH);
let (hx, hy) = (car.x - fx * car.spec.hitch, car.y - fy * car.spec.hitch);
let (dx, dy) = (self.x - hx, self.y - hy);
let d = dx.hypot(dy).max(1e-3);
self.x = hx + dx / d * TOW_BAR;
@@ -130,6 +129,8 @@ pub struct EntrantSetup {
pub name: String,
/// Vehicle index of a caravan to tow, if any.
pub trailer: Option<usize>,
/// Handling preset (`Spec::with_preset`); 0 = the car's own.
pub preset: usize,
}
impl Race {
@@ -153,15 +154,18 @@ impl Race {
let row = (i / 2) as f32;
let side = if i % 2 == 0 { -1.0 } else { 1.0 };
// Leave room for a caravan between rows when towing.
let spec = vehicles[s.vehicle].spec.with_preset(s.preset);
let spacing = if s.trailer.is_some() {
30.0 + HITCH + TOW_BAR + TRAILER_RADIUS
30.0 + spec.hitch + TOW_BAR + TRAILER_RADIUS
} else {
30.0
};
let ((px, py), heading) = behind(&path, 18.0 + row * spacing);
let (rx, ry) = (-heading.sin(), heading.cos());
let (x, y) = (px + rx * side * 12.0, py + ry * side * 12.0);
let car = Car::new(s.vehicle, vehicles[s.vehicle].spec, s.team, x, y, heading);
let mut car = Car::new(s.vehicle, spec, s.team, x, y, heading);
car.computer = s.driver == Driver::Cpu;
car.place(&track);
let line = 0.3 + 0.4 * rand01(&mut rng);
let pace = 0.86 + 0.12 * rand01(&mut rng);
entrants.push(Entrant {
@@ -178,7 +182,7 @@ impl Race {
progress_at: 0.0,
trailer: s.trailer.map(|t| {
let (fx, fy) = (heading.cos(), heading.sin());
let back = HITCH + TOW_BAR;
let back = spec.hitch + TOW_BAR;
Trailer {
vehicle: t,
x: x - fx * back,
@@ -187,11 +191,6 @@ impl Race {
}
}),
});
if let Some(e) = entrants.last_mut().filter(|e| e.trailer.is_some()) {
// Towing costs some speed and pick-up.
e.car.spec.top *= 0.9;
e.car.spec.accel *= 0.8;
}
}
let ground = track.rgb();
Race {
@@ -236,15 +235,15 @@ impl Race {
for e in self.entrants.iter_mut() {
let controls = match (e.driver, e.finished) {
(_, Some(_)) => e.ai.drive(&e.car, &self.track, e.next_gate, DT, racing),
(_, Some(_)) => e.ai.drive(&e.car, &self.track, DT, racing),
(Driver::Human(p), None) if racing => human.get(p).copied().unwrap_or_default(),
(Driver::Human(_), None) => Controls::default(),
(Driver::Cpu, None) => e.ai.drive(&e.car, &self.track, e.next_gate, DT, racing),
(Driver::Cpu, None) => e.ai.drive(&e.car, &self.track, DT, racing),
};
e.car.controls = controls;
// Finished cars cruise round on autopilot at reduced pace.
let pace = if e.finished.is_some() { 0.6 } else { 1.0 };
e.car.step(&self.track, DT, pace);
e.car.step(&self.track, pace);
}
for i in 0..self.entrants.len() {
@@ -298,7 +297,7 @@ impl Race {
&& e.finished.is_none()
&& rt - e.progress_at > RESCUE_AFTER
{
rescue(e, &self.track.gates);
rescue(e, &self.track);
e.progress_at = rt;
}
}
@@ -326,14 +325,14 @@ impl Race {
for e in &self.entrants {
let c = &e.car;
let speed = c.speed();
if c.slip > 55.0 && speed > 40.0 && c.on_road {
if c.slip > 55.0 && speed > 40.0 && c.on_road && !c.airborne {
let strength = ((c.slip - 55.0) / 120.0).clamp(0.08, 0.35);
for (wx, wy) in c.rear_wheels() {
for (wx, wy) in c.rear_wheels(&self.track) {
burn(&mut self.ground, wx, wy, strength);
}
}
if !c.on_road && speed > 50.0 {
spawn.push((c.rear_wheels()[0], c.track_colour(&self.track)));
spawn.push((c.rear_wheels(&self.track)[0], c.track_colour(&self.track)));
}
}
for ((x, y), colour) in spawn {
@@ -487,4 +486,40 @@ mod tests {
let ((x, y), _) = behind(&path, 130.0);
assert!((x - 30.0).abs() < 1e-3 && (y - 100.0).abs() < 1e-3);
}
#[test]
fn walls_keep_cars_on_the_road() {
let gates: Vec<Gate> = (0..12)
.map(|i| {
let a = i as f32 / 12.0 * std::f32::consts::TAU;
let (c, s) = (a.cos(), a.sin());
Gate {
a: (512.0 + c * 150.0, 512.0 + s * 150.0),
b: (512.0 + c * 210.0, 512.0 + s * 210.0),
}
})
.collect();
let track = Track {
name: "ring".into(),
set_name: String::new(),
thumbnail: None,
pixels: vec![8; crate::data::track::W * crate::data::track::H],
palette: crate::data::palette::TRACK_DEFAULT,
height: vec![0; 64 * 64],
edges: crate::data::track::edges(&gates),
gates,
start: (0.0, 0.0),
flags: 0,
};
let spec = crate::data::vehicle::Spec::from_set(&crate::data::vehicle::DEFAULT_SET, 1.0);
let mut c = Car::new(0, spec, 0, 512.0 + 180.0, 512.0, 0.0);
c.place(&track);
// Flat out in a straight line: the outer wall turns the car back.
c.controls.throttle = 1.0;
for _ in 0..500 {
c.step(&track, 1.0);
let r = (c.x - 512.0).hypot(c.y - 512.0);
assert!(r > 140.0 && r < 215.0, "left the road at radius {r}");
}
}
}
+35 -8
View File
@@ -39,12 +39,12 @@ USAGE:
skids unpack FILE.adf|FILE.lha --out DIR extract every file from a disk image or archive
skids music [--data DIR] --out FILE.wav [--seconds S]
render the tune to a WAV file
skids render [--data DIR] --out FILE.png [--track N] [--seconds S] [--players 1|2] [--menu] [--caravans]
skids render [--data DIR] --out FILE.png [--track N] [--seconds S] [--players 1|2] [--menu] [--caravans] [--handling N]
simulate a race (or the menu) and save a frame
skids bench [--data DIR] [--seconds S] [--caravans]
skids bench [--data DIR] [--seconds S] [--caravans] [--handling N]
race computer cars on every track
skids trace [--data DIR] --out FILE.png [--track N] [--seconds S]
draw the computer cars' paths on a track
skids trace [--data DIR] --out FILE.png [--track N] [--seconds S] [--handling N]
draw the walls, gates and computer cars' paths
The game data is built into the executable, so no disks are needed. --data
adds to or overrides it with ADF images, LHA archives, or extracted track/,
@@ -52,6 +52,9 @@ vehicles/ and palettes/ folders, or an assets folder of edits. Without
--data, a folder like that in the current directory or next to the
executable is picked up automatically.
--handling picks the original's handling presets: 0 = each car's own
(CUSTOM), 1-6 = CLASSIC, GRUNTY, ZIPPY, RIPPY, SLIPPERY, PEDAL CAR.
CONTROLS:
Player 1: arrow keys (or W A S D in one-player games)
Player 2: W A S D
@@ -68,6 +71,7 @@ struct Args {
players: usize,
menu: bool,
caravans: bool,
handling: usize,
}
fn parse_args() -> Result<Args, String> {
@@ -82,6 +86,7 @@ fn parse_args() -> Result<Args, String> {
players: 1,
menu: false,
caravans: false,
handling: 0,
};
let mut it = std::env::args().skip(1);
while let Some(arg) = it.next() {
@@ -97,6 +102,12 @@ fn parse_args() -> Result<Args, String> {
"--mute" => a.mute = true,
"--menu" => a.menu = true,
"--caravans" => a.caravans = true,
"--handling" => {
a.handling = val("--handling")?.parse().map_err(|_| "bad --handling")?;
if a.handling > data::vehicle::PRESETS.len() {
return Err("--handling must be 0-6".into());
}
}
"info" | "extract" | "render" | "bench" | "trace" | "music" | "play" => a.cmd = arg,
"unpack" => {
a.cmd = arg;
@@ -370,6 +381,7 @@ fn render_shot(data: GameData, args: &Args) -> Result<(), String> {
App::new(data, audio)
};
app.settings.caravans = args.caravans;
app.settings.handling = args.handling;
if args.menu {
for _ in 0..(args.seconds / game::DT) as usize {
app.update(&Input::default());
@@ -399,7 +411,7 @@ fn render_shot(data: GameData, args: &Args) -> Result<(), String> {
.entrants
.iter_mut()
.filter(|e| matches!(e.driver, game::Driver::Human(_)))
.map(|e| e.ai.drive(&e.car, &race.track, e.next_gate, game::DT, true))
.map(|e| e.ai.drive(&e.car, &race.track, game::DT, true))
.collect();
race.step(&controls);
app::draw_race(&mut frame, &app.data, &race, &mut cams, players);
@@ -428,6 +440,7 @@ fn bench(data: GameData, args: &Args) -> Result<(), String> {
App::new(data, audio)
};
app.settings.caravans = args.caravans;
app.settings.handling = args.handling;
let frames = (args.seconds / game::DT) as usize;
out!(
"{:<10} {:>5} {:>8} {:>9} {:>8}",
@@ -537,6 +550,7 @@ fn trace(data: GameData, args: &Args) -> Result<(), String> {
App::new(data, audio)
};
app.settings.caravans = args.caravans;
app.settings.handling = args.handling;
// Same seed as `bench` uses for this track, so problems seen there can be traced.
let mut race = app.build_race(
args.track.saturating_sub(1),
@@ -547,18 +561,31 @@ fn trace(data: GameData, args: &Args) -> Result<(), String> {
args.track.max(1) as u32,
)?;
let mut img = race.track.rgb();
let plot = |img: &mut Vec<u32>, wx: f32, wy: f32, c: u32| {
let (x, y) = track::to_screen(wx, wy);
let plot = |img: &mut Vec<u32>, t: &track::Track, wx: f32, wy: f32, c: u32| {
let (x, y) = t.to_screen(wx, wy);
if track::on_screen(x, y, 0.0) {
img[y as usize * track::W + x as usize] = c;
}
};
// The walls: both road edges.
let edges = &race.track.edges;
for (k, g) in edges.iter().enumerate() {
let h = edges[(k + 1) % edges.len()];
for i in 0..8 {
let f = i as f32 / 8.0;
for (p, q) in [(g.a, h.a), (g.b, h.b)] {
let (x, y) = (p.0 + (q.0 - p.0) * f, p.1 + (q.1 - p.1) * f);
plot(&mut img, &race.track, x, y, 0xc0c0c0);
}
}
}
for (gi, g) in race.track.gates.iter().enumerate() {
let c = if gi == 0 { 0xffff00 } else { 0xff2020 };
for i in 0..=64 {
let k = i as f32 / 64.0;
plot(
&mut img,
&race.track,
g.a.0 + (g.b.0 - g.a.0) * k,
g.a.1 + (g.b.1 - g.a.1) * k,
c,
@@ -569,7 +596,7 @@ fn trace(data: GameData, args: &Args) -> Result<(), String> {
for _ in 0..(args.seconds / game::DT) as usize {
race.step(&[]);
for (i, e) in race.entrants.iter().enumerate() {
plot(&mut img, e.car.x, e.car.y, TRAIL[i % 4]);
plot(&mut img, &race.track, e.car.x, e.car.y, TRAIL[i % 4]);
}
}
let n = race.track.gates.len();