Skip to content

The track editor ​

2026-10-24, first session.

The editor in the viewer: a face of Alderon picked, its sector given another texture page, and the panel with the edit in its history

The editor in the browser build of the viewer, on ALDERON.BL3. The picked face is outlined in yellow, its sector in faint orange. The panel shows what can be changed, and the history holds the one edit made.

The viewer has drawn every track in 3D for some days. Today it became an editor, the first tool of the layer the plan calls "tools and mods on top". The task had four parts: edit props, textures, vertices and lights with undo and save; drive every edit from the command line too; prove that saving changes nothing it should not; and load an edited track in the original game. All four are done. The road there was not straight.

Decisions ​

The editor is a build option. The original game has no editor, so the editor is an addition and stays apart from the rest (working rule 5): cargo build -p pod-viewer --features editor. Without the option the viewer builds as before, with no new warnings.

One kind of edit. Every change is one value of one type, Edit. It covers moving a prop, turning a prop, a sector's texture page, a face's texture, moving a vertex, moving a sector, raising the ground, moving a light, a light's word and the global light. The window makes these values when you click its buttons. The command line makes the same values from options. So an agent or a test can do anything the window can:

sh
pod-viewer edit BELTANE.BL3 --sector-page 35 1 --raise 601.5 48.2 35 6 --out BELTANE.BL3
pod-viewer edit ALDERON.BL3 --list props

Write over fields, never rebuild. The viewer reads positions as 32-bit floats. The file stores them as 16.16 fixed point. A float keeps 24 bits, and Beltane's coordinates reach 2,000 units, which needs 27. Writing a track back from the floats would change bytes that nobody edited. So the reader now records where each field it reads sits in the file: a face's page word and normal, an object's vertices, a prop's position, a light's record. An edit writes new bytes at those places, and a move is added to the stored integer. Nothing changes size. Undo puts back the bytes an edit replaced. After each edit the track is read again from the bytes, so the view always shows what will be saved.

The check. A file saved with no edits must be the original. It is, byte for byte, on all 36 tracks (measured, a test over the player's own disc). Each edit has a test that reads the saved file back and finds the change: eight tests in all.

What moving the road needs ​

Two things in a track depend on its vertices. Before moving any, we looked for them.

  • Each sector ends with its bounds. The six words after each sector were an open question. They are the lowest and then the highest corner of the sector's vertices, on every sector of every track (measured). The editor writes them again when a vertex moves.
  • Each face has a normal. Three ways of computing it were tried against the disc. Newell's method over all corners gives the stored normal on 97.9 % (Beltane) to 99.6 % of each track's faces. The cross product of the first two edges matches only 74 %, and so does the last two. The faces left over are quads that are not quite flat (measured). The editor writes the normal again for every face that touches a moved vertex.

Which way is up ​

The first "raise" in the editor lowered the road. The viewer's code said the height axis grows downwards, and the editor followed it.

The game says otherwise. The race recordings hold the car's position in the game's own coordinates. On three stretches of Beltane we looked for every track face under the car: there is exactly one each time, the road, and the car is 0.45 above it (measured: road z 15.0 and car 15.45 at the start; 0.0 and 0.45 at the bottom of the hill; a slope from 54.8 to 58.8 with the car at 56.7). Two more checks agree: a road raised in +z lifts the cars in the game (below), and the line follower's Left key turns the car anticlockwise in (x, y), as seen from +z. On a track, axis 2 is the height and grows upwards.

So the viewer had drawn every track upside down since its first 3D picture. Its projection was written for cars, whose models do have z growing downwards (their decks and grilles face minus z; inferred from drawings). A track drawn upside down puts its tunnel roofs under the road, so a view from above showed every road clearly, and nothing looked wrong. Now the viewer turns cars over and draws tracks as they are, and a test checks that +z is up the screen.

Alderon before and after the fix: on the left the city is upside down, on the right its tower stands up

ALDERON.BL3 in the viewer, before (left) and after (right). The tall block at the top right was hanging below the city; now it stands on it.

Beltane before and after the fix: the column under the city had been drawn as a tower above it

BELTANE.BL3, the city at the end of the long straight. Before (left), a column seemed to rise through the roads. After (right), it hangs below the city, and the roofs cover the tunnels.

In the game ​

Each edit went into the emulated PC as a mod, a file under C:\POD\Data\Binary\Circuits\ where the game looks before the CD. Each race was compared with a stock race driven the same way.

What went wrong first ​

The window would not open for the agent. Computer use works with application bundles, and the viewer is a plain binary. So the editor's window was checked in the browser build, with the same code and features except saving.

The first race went nowhere useful. The edits were at (906, −314), a point on a recorded racing line, but the player starts at the back of the grid near (394, 51), and the line starts somewhere else. The line follower also sent no keys: sudo had dropped the DISPLAY setting that xdotool needs. The car rolled straight into a wall (left, below).

Two failed drives: the car against Beltane's wall with no steering, and wedged against a wall on Alderon

Left: Beltane, the follower without a display, the car into the first wall. Right: Alderon, the follower with a recorded line shorter than a lap, the car wedged at 0 km/h.

On Alderon the follower wedged the car twice. Its line came from a computer car over 60 seconds, which is less than one lap (a computer car takes about 70 s). At the line's end, the aim jumped back to its start, across the city. A full lap, cut where the car returns to its first point (70.6 s), fixed that.

When timeout stopped the follower, its keys stayed held. The next race's car accelerated with no input from us, which first looked like a feature of the game. The follower now releases the keys however it ends.

The menus failed one time in three. The game draws its own cursor from DirectInput, so a click first moves that cursor, and the press only counts once it has arrived. The same script picked Alderon once and Beltane twice.

The track screen

The track screen, where the scripted click sometimes missed Alderon. Each step now moves the cursor, waits a second, presses, and is checked with a screenshot.

Beltane has no placed props: its environment is NEANT. So the props were moved on Alderon. The first placement put them 100 units behind the grid, where the race only passes at the end of a lap. The second used the recorded lap to put them 40 and 60 units after the grid.

The results ​

Beltane after the grid: the stock road on the left, the edited one on the right with texture page 1 on two sectors

A changed texture (measured, screenshots). BELTANE.BL3 stock (left, 11.4 s) and with --sector-page 35 1 --sector-page 36 1 (right, 10.9 s). Every textured face of the two sectors after the grid shows page 1, a page of tanks, pipes and lamps, which the game draws in red and orange on the road.

Beltane's long downhill: the stock view on the left, the edited one tilting over the raised road on the right

A raised road (measured). --raise 601.5 48.2 35 6 puts a smooth bump six units high on the downhill straight. At 222 km/h the car crests it and the camera tilts (right, 12.6 s). The stock race is level there (left, 13.1 s).

The bump is more than a picture. The race recorder reads every car's position from the game's memory ten times a second. Over the edited stretch all eight cars ride higher than in the stock race. At x ≈ 596 the player's car is at height 55.2 against 52.3, and at x ≈ 613 at 54.7 against 49.4. Past the crest the cars stay above the old road until x ≈ 650, beyond the end of the bump at 636: they leave the ground and land. The game's physics takes the new road from the same geometry it draws, with the normals and bounds the editor wrote.

The cars' heights along the straight: stock in grey, edited in orange, lifted over the bump and flying past its end

Height against distance for all eight cars, from racerec.py recordings of a stock race and of the edited one (first pass along the straight).

Alderon, the same bend in a stock race and with the moved props

The bend enlarged: empty in the stock race, two signs and three cones in the edited one

Moved props (measured, screenshots). ALDERON.BL3 stock (top, 15.8 s) and with five --move-prop edits (bottom, 16.3 s): two signs and three cones stand on the bend.

One thing was mistaken for a prop on the way. On one drive a red X sign floated in front of the car. It was the wrong-way sign, an animation the game plays when the car faces backwards. The props were confirmed only by the stock run at the same moment.

Not done ​

  • A light edit was made and read back in tests, but not seen in the game. The meaning of most of a light's record is still open (docs/formats/tracks.md).
  • The browser build edits but cannot save.
  • Props can be moved, not added or removed. The animation library's instances (the wrong-way sign is one) and the race paths and beacons are still kept as bytes. A road moved far would need its racing line moved too.

Next ​

  • Add and remove props; edit the animation instances.
  • Write the race paths and beacons as fields.
  • Save from the browser build.
  • A fly camera, now that the tunnels have roofs again.

Prose CC0 1.0, code Apache 2.0. POD: Planet of Death is © Ubi Soft; none of its files are on this site.