Skip to content

The front end ​

Reference: POD 2.8 in English (AGENTS.md). Everything below was traced on the first edition and checked against its frames (now history, docs/versions.md); the addresses are the first edition's unless marked POD. Against POD's English captures (derived/ui/pod/, 2026-10-24) every capture of a ported screen matches exactly, the pointer included: the main menu (two), the one-player menu, the options screen, the track screen (pages 1 and 2), the car screen, the pause menu and its quit warning, the records, the car swap (opened and with a slot picked), the garage (but for one field, below) and the options after the difficulty and damage buttons (normal with sector damage, as the port's rules give). The options handler is traced in POD (0x4385b8, crates/pod-ui/src/screens/options.rs), and so are the track screen's page arrows for POD's 32 circuits (0x4a4e18, 0x4a4fc4); POD's list values (0x4fda60) go through a pointer table (0x4fdb2c) that is the identity up to index 38 and permuted after. The track screen's handler and page fill are traced in POD (below). POD's default player name is the text table's INFOS/PLAYER and the number ("Player 1"). On some captures the pointer matches the glyph at no placement while the screen around it matches: inferred, the pixels draw_pointer saves under it and restores on its own schedule (not ported).

How WINPOD.EXE runs its menus, as Un-POD's crates/pod-ui reimplements them. The menus are data: 33 menus and 503 items in SCRIPTS/BETAMNU.BIN (docs/formats/lists.md), each with its pictures in MIMG/<language>/<menu>.IMG (docs/formats/images.md). One engine draws and drives them all. Each menu can add a handler, run on entering it, and each item an action. The engine is ported in full; the screens one at a time (the table at the end).

Evidence labels: observed (read in the code or data), measured (run in the emulated PC or under Unicorn), inferred, unknown.

The menu tree in memory ​

load_menus (FUN_00435d74) reads the file at start-up and stores each menu's address at 0x502dcc + 4 × number (observed). It replaces the handler index (+0x46) and every item's action index (+0x68, and each list entry's action) with the address from the function table at 0x4f640c (FUN_0043be00; 265 entries) (observed). The list items' value index (+0x7c) becomes a pointer into the values at 0x506e10 + 4 n, which FUN_00435a14 sets up (observed).

Menu fieldMeaningRead by
+0x46Handler, run by goto_menu after the menu becomes currentFUN_00438168
+0x48Parent: where Esc and "back" lead; set by a link to the menu that followed itFUN_0043a344, FUN_0043e9cc
+0x4cThe background picture's slotFUN_0043b4d4
Item fieldMeaningRead by
+0x00Top, bottom, left, right; the mouse test is strict on all four sidesFUN_00436658
+0x10Text, 0x40 bytes (text items)FUN_00439c44
+0x51, +0x52, +0x53Text alignment (0 left + 5, 1 centred, 2 right − 5), size shift, fontFUN_00439c44
+0x58Sound played when the item is usedFUN_0043a344
+0x60, +0x62, +0x64, +0x66The item reached by Up, Down, Left, Right; 999 = the item selected beforeFUN_00439e4c
+0x68ActionFUN_0043a344
+0x6cNon-zero: holding Select repeats the action every 50 ms after 500 msFUN_00436658
+0x70State: −1 hidden, −2 drawn but never selected, 0 drawn, 1 and 2 selectableeverywhere
+0x74Type, belowFUN_00439464
+0x78..+0x84Picture slots by state; type 1 links to menu +0x78, type 11 to +0x84FUN_00439464, FUN_0043a344
+0x88The text of field items (7) and labelled pictures (12)FUN_00439464
+0x80, +0xa8, +0xd4Lists (type 10): each entry's value, picture and action; +0xd0 the picture shownFUN_004383bc

Types (observed in draw_item, FUN_00439464): 0 text; 1 text that links; 4 a choice (lit at +0x7c); 6 a text list; 7 an editable text field; 8 text from a function; 9 a picture button; 10 a picture list; 11 a picture button that links; 12 a picture with text.

What the states draw in POD (observed in POD's draw_menu 0x42d898 and draw_item 0x42b9b0; ported in crates/pod-ui/src/render/item.rs). draw_menu skips items at −1, and draw_item itself returns for any state below 0 but −2: a hidden item draws nothing, so whatever the background picture has in its box shows. "Pressed" below means selected (0x4f9b48), Select down (0x4f9b62), and Select from the keyboard or the mouse inside the box.

Type−2012
9 button (table 0x42b964)+0x78+0x78, pressed +0x7c+0x7c, pressed +0x80nothing, pressed +0x80
11 link (0x42ba27)as 0+0x78, pressed +0x7cas 0nothing, pressed +0x7c
4 choice (0x42bb6d), lit (+0x7c ≠ 0)+0x84+0x84, pressed +0x80as 0+0x84, pressed nothing
4 choice, unlit+0x80+0x80, pressed +0x84as 0nothing, pressed +0x84
12 labelled (0x42c21a)picture, then the fieldpicture, then the fieldas 0the field only

The labelled picture is +0xac when +0xb0 is set or the item is selected, else +0xa8 (menu 53's items 19 to 26 use it; pod-ui drew +0x80, which is 0 there, and so showed the background's first slot: fixed). Choices on menu 14's items 10 to 13 draw nothing when unlit. Text types (0, 1, 6, 7, 8, 13) draw the same at every state from −2 up.

Two items change at run time in a way the file does not show (measured by reading the running game's tree, menupeek.py): the main menu's item 3 ("Ubi Online") is selectable and its item 6, a picture over "Multi joueurs", is hidden. The code that sets them is not found (unknown); pod-ui sets them in the main menu's handler (a Deviation:).

Input ​

The menus read twelve buttons that poll_menu_buttons (FUN_004a2364) makes from the DirectInput key table at 0x60bbe8 (observed): Home, Backspace, Tab, Shift+Tab, Up, Down, Left, Right, Select (Enter, keypad Enter or the left mouse button), Esc, F1, Space; the joystick is ORed into some of them. In the menus' mode (0x60b0e0 = 0) a button is pressed from the poll it goes down until the menu code reads it once, and again only after it has been let go (observed). Select remembers what pressed it: 1 the keyboard, 2 the mouse.

A key tapped for less than a frame can fall between two polls and never be seen (measured: one of the catalogue's taps was lost; tests/frames.rs, MISSED).

The menu loop ​

front_end_loop (FUN_00436658, state 1) runs once a frame (observed):

  1. While Select is held on the selected item, the item shows its pressed picture; on release the item is used if the keyboard pressed it, or the mouse pressed it and is still inside it. Using an item (activate_item, FUN_0043a344) plays its sound, runs its action, and for types 1 and 11 goes to the linked menu, making the current one its parent unless the link leads to the root menu.
  2. Esc: on the root menu, the quit confirmation (menu 1, the file's second word 0x502ed4); elsewhere menu_back (FUN_0043e9cc; POD 0x430368, called from Esc at 0x42d040 behind the key latch 0x4fdf3c and as action 5), which does the menu's clean-up and goes to its parent (see "The track screen's mirror and menu_back").
  3. When the mouse has moved, the selectable item under it is selected.
  4. Tab and Shift+Tab select the next or previous selectable item round the menu.
  5. The arrows follow the item's neighbour (move_selection, FUN_00439e4c) until a selectable item; after a whole loop (or 90 steps), the first selectable item. A neighbour of 999 goes back to the item selected before, with corrections that depend on the direction: on the main menu Up, Down and Right go to item 3 and Left to item 2 when that item was 4 or later; on menu 10 Up goes to 16 (item 10 or before) and Left to 8 (item 10 or after); on menu 17 Up goes to 11 (item 12 or after) (observed, measured on the main menu's captures).
  6. F1 loads 61.IMG, draws its picture (slot 0x12b) until a menu key or the mouse button is held, then waits for them all to be let go and reloads the menu's pictures. The test is "held", not "pressed": an F1 still down when the help is first checked closes it at once, so a long F1 shows no help (observed in FUN_004a2bf0; measured: some captures show the help, some not). The multi-player menu has no help.

goto_menu (FUN_00438168) loads the menu's pictures, makes it current, runs its handler, selects its first item of state 1 or 2 (menu 17: item 28, or 43 in two modes) and draws it (observed).

Pictures ​

load_menu_images (FUN_0047a454) puts each entry of a menu's file into the picture table at 0x502f1c (16 bytes a slot: pixels, width, height, palette) at the slot its entry names plus a base: 0x172 for menus 4 to 7, 10, 11, 13, 20, 25, 26, 35, 45, 51, 53, 54 and 60, else 0 (observed). draw_picture (FUN_00434fb0) adds the same base, and also for menu 3: menu 3 loads at 0 but draws from 0x172, showing the pictures menu 7 left there (observed, kept). Pictures are copied whole, with no transparent colour (FUN_004a1a10, observed).

Colours go through the table at 0x57925c (FUN_0047a3c0). On a 16-bit RGB565 screen (mode 1 of 0x60b314, the emulated PC's) an RGB555 colour becomes (c & 0x1f) + ((c & 0xffe0) << 1): green's lowest bit stays 0 (observed, measured: the captures match).

draw_menu (FUN_0043b4d4) draws the background (+0x4c, plus the base), every item that is not hidden, the outline, then the menu's own drawing function (0x502f0c, then the one it returns at 0x502f08). The background and items are redrawn only when the menu, the selection or the Select button changed, or after a timer (FUN_004a1894); a menu's drawing function gets that "redrawn" flag and draws only then (observed). pod-ui draws the whole screen every frame, which gives the same picture.

The selection outline ​

draw_outline (FUN_0043873c) corrects the selected item's box by menu and item and calls outline_box (FUN_004a1388) with a style (observed). The corrections sit in a jump table the decompiler merged; they are measured by running the function under Unicorn for every item of every menu (measure/menu_outline.py, 503 items, 79 with corrections).

StyleDrawingColours
0, 1, 4A two-pixel box whose colour runs round it (FUN_00492070): each pixel blends two colours by a phase (0x5c5370) that steps by one and bounces between 0 and a period from the box's size (w + h + 3 below 70, w + h − 5 below 220, else w + h − 11)0: black to green; 1: black to red; 4: 0x4696f9 to black
2, 3Two one-pixel boxes, one colour blending in 16 steps that bounce (0x60a9b0) (FUN_00491e3c)2: 0x3f00 to 0xc000; 3: red to yellow
5FUN_00493368 (menus 10 and 51)not ported

The animation keeps its phase from frame to frame, so a capture shows the outline at one phase. The frame test accepts any colour of the outline's own gradient on its pixels.

Text and the pointer ​

The menu fonts are POLICES/FNT_MENU.BP3's three glyph tables (load_menu_fonts, FUN_0046e7bc; read_glyph_table, FUN_0046e0e0) (observed). Text is measured by text_size (FUN_0048f2cc) and drawn glyph by glyph with each glyph's bottom on the baseline (FUN_0048ed10, blitter FUN_004dda63): index 0 is transparent, the others go through the page palette in the screen's format (observed; the palette's conversion is measured on the pointer). A text item's baseline is top + (bottom − top − h) / 2 + h (observed).

The mouse pointer is not the system's cursor (the window's cursor resource is empty) but glyph # of font 2, drawn by draw_pointer (FUN_004acfa0) with its left at the mouse's x and its bottom 40 pixels below its y (observed; measured: found exactly in every capture).

Screens ​

MenuScreenHandler, actionsState in pod-uiFrames matched
0Main menu43 FUN_0043ffa2Ported; difficulty list; "Un joueur" (36) sets a single race30, with its help frames
1Quit confirmation25 FUN_0043e814; "Oui" 1 FUN_0043fa7c quitsPorted6
—Help (F1)61.IMG, slot 0x12bPortedcounted with each menu
35Records242 (POD 0x42e2d0), 243, 244, fill 0x42dfc4, drawing 0x43c5c0Ported, the files read ("Best times"); the mirrored circuits' names not drawn12
10OptionsPOD: 199 0x4385b8; difficulty 200 0x438b84, damage 201 0x438c90, music and sound effects 202 0x438d4c / 203 0x438f3c, volume field 207 0x4391f8, Dolby ON/OFF 208 0x4393b8 / 209 0x43944c, intro list 204 0x43912c / 205 0x439190, stereo list 210 0x4394dc / 211 0x439548, names 212 0x4395b8 / 213 0x4396d0, volume arrows 214 0x4397e8 / 215 0x439880, OK 216 0x439918, opponents 206 0x42f154 / 67 0x42f0e0, credits 251 0x42df88Handler and actions ported (screens/options.rs, below), the typed fields through the editor (below); not ported: the cheat flag 0x5001ec1
16Race mode257; "Course simple" 13 FUN_0043e870Ported: the single race, the championships (below) and time attack (item 6, action 154, below)13
17Track choicePOD: 76 0x4a509c, page fill 0x4a4148, arrows 252 0x4a4e18 and 253 0x4a4fc4, 189 0x4a47b8; first edition: 76 FUN_004a99c0, 189 FUN_004a91ac, OK 159 FUN_004aa498, drawing 0x44e82c, map FUN_00450838Single race: digits, names, arrows (selectable past 16 circuits), the neighbour patches, the title, km and laps text, the map picture (load_menu_images case 200), OK (Player1DefaultCircuit); the map's squash animation and the championship branches not ported13
24Car choice42 FUN_004405dd, drawing 0x44b640, arrows 56, 57, OK 49 FUN_0043c71cHandler (item states measured in the running game), the car picture (case 0xc9), the garage figures from the car files, the car's and player's names, Player1DefaultCar from Ui::set_ini; OK gives Effect::StartRace; the arrows round the list (inferred)9 (three Down captures unexplained)
32Pause78 FUN_0044196c; 3, 160 FUN_00441b70, OUI 161 FUN_00441d74, NON 162 FUN_00441ed8Ui::pause (state 2: Esc resumes), restart only in championship and time attack, the confirmation (box, OUI/NON, the question per choice, NON selected; measured in the running game)14
25Car swap (POD)44 0x4308ac; 220 OK 0x43115c, 221 slot 0x430b68, 222/223 arrows 0x430c54/0x430c9c, 224 swap 0x430ce8, 225 reset 0x430f1c; drawing hook 0x43c844Ported (screens/car_swap.rs, below); OK writes VOITURES.BIN and VOITURE2.BIN (Effect::File); the window message, the per-slot flags 0x4ffd68 and the model reload not ported— (none captured)
26GaragePOD: 178 0x436b10 (via 255 0x436ae4 for the second player), 179 to 186 (0x4371f4, 0x4374f4, 0x43762c, 0x437708, 0x4378e0, 0x437bac, 0x4383d0, 0x437dd4), chart 0x435e54Ported for one player (below): the settings, the star chart, typed values, load and save of Params\<car>.prb; the test drive not ported1 (item 28 open)
4Single race endPOD: 146 0x4330f4, 150 0x43328c, pages 0x4c0dc8, 0x4c4facUi::race_over, the ranking and the best races pages (below)2
18Time attack (POD)193 0x4a6f90; 15, 14, 77, 256, 192, 68, 247, 155, 156, 194, 195Ported, with the "Top 5" ghost panel, its import (194) and export (195) but for their best times (below)—
7, 3, 6, 5Time attack race ends (POD)149 0x433528, 148 0x433214, 147 0x433114; 153, 259, 152, 151, 197, 196Page turning and the way back ported; every page 0 and page 10 drawn; their pictures not (below)—
18Time attack (POD)193 0x4a6f90; 15, 14, 77, 256, 192, 68, 247, 155, 156, 194, 195Ported, with the "Top 5" ghost panel but for its import (194) and export (195) (below)—
7, 3, 6, 5Time attack race ends (POD)149 0x433528, 148 0x433214, 147 0x433114; 153, 259, 152, 151, 197, 196Page turning and the way back ported; every page 0 and page 10 drawn, with the pages' backgrounds and the ghost page's lap pictures (below)—
60, 20Championship race end, next trackPOD: 170 0x434650, pages 0x433f58, 163 to 168, 172, 250; drawing 0x43f74c, 0x43f83cPages 0 to 5's states, pages 1 and 2 drawn, menu 20's map and titles (below); team pages not ported—
21First track's view (POD)254 0x43c17c; go 169; drawing 0x44046c; reached from action 49 0x42e7b0Ported (below, "Menus 21, 41 and 44"); 41 unreachable, 44 absent—
60, 20Championship race end, next trackPOD: 170 0x434650, pages 0x433f58, 163 to 168, 172, 250; drawing 0x43f74c, 0x43f83cPages 0 to 5's states, pages 1 to 4 drawn (3 and 4 the team pages), menu 20's map and titles (below)—
othersMulti-playerEngine only—

The frames come from the original in the emulated PC (run/vm/uicap.sh, an isolated game on display :3), with the key steps that reached each (derived/ui/seq/), and are compared in crates/pod-ui/tests/frames.rs. Files the screens write (Ubi.ini, .BRT, Params\<car>.prb) come with the screens that write them.

How the captures show a frame (measured, pod_ui::check::presented). POD's captures do not show the game's frame quite as drawn: row 478 repeats row 477, and columns 636 to 638 show columns 635 to 637 (column 635 twice); row 479 and column 639 are the window's border. It shows where the pictures differ there (the car screen's and the records' backgrounds, and the pointer over the bottom edge), and nowhere else. Inferred: the emulated PC's display scaling the window, since the game's drawing does not depend on the row. The comparison applies it to our frame; the pointer is drawn as the original clips it (its height loses the overflow past row 479).

The track screen in POD (observed, handler 76 0x4a509c, page fill 0x4a4148). The first edition's states differ, and POD's captures follow POD's:

  • The handler hides the title and map items (41, 42; -2), sets PREVIOUS (43) and OK (28) to 1, and leaves the arrows (24, 25) to the page fill. It sets no state on 189's OK: choosing a circuit (0x4a47b8) only lights its digit and fills the page again.
  • The page fill's single-race branch (0x4a439f) gives each shown circuit's name state 1 and its digit state 2 with +0x80 and +0x84 both the digit's picture (the table at 0x4eb492: 237 to 248, 267 to 270); past the last circuit the digits and names are -2, the digits with the blank pictures 0x10f and 0x110 and the names emptied. The left arrow is 1 on a page after the first, the right arrow while circuits follow; otherwise -2, which still draws the button's +0x78 picture. It loads the chosen circuit's map (case 200) and makes item 59 selectable when list value 48 (0x4fdb20) is 0.
  • With +0x80 equal to +0x84, a lit digit in state 2 is drawn (the lit branch) and an unlit one is not: a Choice item's state 2 hides the unlit branch's picture, whatever its slot.

The options (POD, menu 10) ​

Observed in POD's WINPOD.EXE; Ubi.ini is written only when the game has one (0x4ea8c0), which pod-ui reports as Effect::Ini each time.

  • Difficulty (200) steps easy → normal → hard → easy, and sets the damage with it: normal brings global damage, hard sector damage, easy none. It writes Difficulty (Easy, Normal, Hard) and Damage (Off, Global, Sector).
  • Damage (201) steps none → global → sector; from sector it goes back to what the difficulty allows (none on easy, global on normal, sector kept on hard), or to none with the cheat flag 0x5001ec. It writes Damage.
  • Music (202) and sound effects (203) choose which volume (0x4ffe78) the field (13) and its arrows (19, 20) show and change, and show them while that sound is available; the arrows (214, 215) step it within 0 to 100 and hand it to the sound (0x4ba00c, 0x4ba0e0; Effect::Volume). OK (216) writes MusicVolume and SoundFxVolume and goes back.
  • The lists: the intro list's entries (204, 205) write PlayIntroAtStart; the stereo list's (210, 211) set reversed or normal stereo (0x4bfafc) and write Stereo. Each shows the other entry afterwards (values 43 and 42).
  • Dolby (208, 209) lights ON or OFF, switches it in the sound (0x4bfb5c) and writes Dolby.
  • Opponents (206, 67) step the count round 0 to 7 into item 23's text; NbConc is not written here.
  • Credits (251) plays credits.avi (Effect::Video).

The garage (POD, menu 26) ​

Observed in POD's WINPOD.EXE; ported in crates/pod-ui/src/screens/garage.rs for one player. The car screen's "Settings" (item 1) leads here.

  • The settings. Five values at 0x4ffe64 to 0x4ffe74 (acceleration, brakes, grip, steering, speed), 0 to 100 each, sharing 300 points (0x4e9f50). Fields 15 to 19 show them, 20 the budget and 21 the points left (0x435d0c).
  • The kept set-up. The handler (178, 0x436b10) takes the values from the car file (the car block's +0x628, 0x62d87c + car × 0x63c), or from the car's set-up when the garage has made one (0x4ffd68[car] set; 0x434dcc on 0x4fe2e8 + car × 0x1a8). After every change setup_defaults (0x4354b0) makes the set-up (from the car the first time, setup_from_car), marks the car kept and puts the values in. The race takes the kept set-up (0x43d089); pod-ui hands the five values on in RaceSetup::settings. The car swap drops a slot's kept set-up when its car changes.
  • + and − (180, 181) step a value by one: + only while the five add up to less than 300 and the value is below 100, − down to 0.
  • A typed value (179) is read with atoi. If it is 0 or more it becomes the value, less what takes the five over 300, then held within 0 to 100.
  • The car's own settings (182) drops the kept set-up and takes the car file's again.
  • Load (183) and save (184) turn the screen to choosing a slot (0x4ffec4: save): items 0 to 3 and the buttons hidden, the values drawn, the slots selectable (only the used ones to load), the first selected. A slot (186) loads its values, or, to save, has its name typed (cancelled or empty: the old name back), takes the values, is marked used, and the five set-ups are written (0x472668, docs/formats/lists.md). Then the screen comes back with item 3 selected.
  • The editor starts with the cursor at 0 and keeps the old text, so typing "FAST" over an unused slot's ... gives "FAST..." unless the dots are deleted, as in the original.
  • The star chart (0x435e54, drawn each frame on menu 26): the star's ten triangles cleared in dark red (0x4a9e88 of 0x140101), then ten triangles that grow with the settings (each coordinate base + (setting × k) / 100, the setting held to 100) in green (0x118821), all filled by 0x43567c on odd screen rows only, which hatches them. The fill sorts the corners into top, middle and bottom and fills flat-topped, flat-bottomed or two-half triangles between truncated edge positions (screens/garage_chart.rs).
  • Against POD's capture pod-garage the screen matches pixel for pixel, chart and pointer included. Item 28 (a field of the menu file's "100") is one of the time items that 0x431eb8 hides outside a network race; the car screen's handler runs it before the garage.
  • Not ported: the test drive (185, 0x4383d0: it puts the race's flags and the damage aside and returns 1), the second player's garage (255), the network branches, and the check that quits when a car's file settings exceed 300 points (0x4c7769).

Typing into a field ​

edit_field (0x42eb80) runs its own loop until Enter or Esc. pod-ui makes it a state of the front end (crates/pod-ui/src/edit.rs): while it is open the menu loop does not run, and the action that opened it finishes when it closes. The field's +0x78 is the longest text, +0x7c = 2 picks the second character map (0x42e9f0 fills both: letters, digits, the space and !"$%&'()*+,-./<=>?; the first adds #:;[\]_|~ and turns @ into 0xe0, the second adds `). The key queue gives characters and the codes 0x100 Delete, 0x101 Backspace, 0x103 Enter, 0x104 Left, 0x107 Right, 0x108 Esc, 0x109 End, 0x10a Home. A character goes in at the cursor, the rest shifting right and the last falling off; the cursor starts at 0. Esc puts the field back and sets 0x4fde5d. Fields may take any character, digits only, or hex digits. The options' volume field (207) starts from the sound effects volume when that is available, else the music's, and a typed 0 to 100 sets every volume whose button is available. The names (212, 213) start empty; an empty name or the other player's puts the old one back, and PlayerNName is written either way. Not ported: the field's cursor drawing.

Controls and graphics (POD, menus 11, 13, 14) ​

Observed in POD's WINPOD.EXE; crates/pod-ui/src/screens/controls.rs and graphics.rs. All three match POD's captures pixel for pixel.

  • Menu 11 (handler 227 0x439f48, layout 0x4399cc): each player's device (0x4fdf7c, 0x4fdf7d: 0 keyboard, 1 pad, 2 wheel, 3 joystick) and joystick number (0x4fdf80, 0x4fdf84), kept for cancel (235). The game devices show for player 1 only when DirectInput found a joystick (0x6211b1, 0x6211e5, the host's Ui::set_joysticks); for player 2 they hide when none was found or player 1 takes the only one, and the keyboard hides when player 1 has it. A player (229) or a device (228: wheel 2, pad 1, joystick 3, keyboard 0; a game device takes the joystick there is) chosen, then menu 13.
  • Menu 13 (handler 230 0x43a7ec), for the keyboard: the analogue items hidden, the keyboard's picture drawn, each action's key named: accelerate, brake, gear up, gear down, left, right (0x621938, 0x621939, 0x62193f, 0x621940, 0x62193a, 0x62193b). Names (0x4327b4): special keys from the menus' text section KIBOARD, F1 to F12 written out, others the one character of the keyboard layout's table (0x621a50, built at start-up; a US layout assumed). The defaults Up, Down, A, Z, Left, Right are measured on the capture of a fresh install.
  • Choosing a key (236 to 241, the keyboard's 0x432f24, 0x432f98, 0x43300c, 0x433080, 0x432e3c, 0x432eb0): the game loops until a key is held (0x46c8ec, the lowest scancode first); pod-ui waits the same way, a frame at a time (Ui::waiting_key). A refused key (0x4326c4: one another action has, F1 to F12, the digits, E, Insert, Home, Page Up/Down, Delete, End, keypad − and +, Esc and the lock keys) leaves the binding; any other goes into the copy being edited (0x4fdee8) and is named. Back (231) and OK (232) both apply the copy (0x4326a0) and write KeyboardAccelerate, KeyBoardBrake, KeyboardLeft, KeyboardRight, KeyboardGearUp, KeyboardGearDown (decimal scancodes); back then returns to menu 11 for the other player, OK to the options. Not ported: the pad, wheel and joystick layouts and settings (233, 234 and both actions' other writes), and handing the joysticks to the players (0x431650).
  • Menu 14 (handler 261 0x43c1e0): LevelView (default 3), ComputeView, LetterBox and DisplayMode (0) from Ubi.ini; the level's slider position lit and only its preview drawn; the three options lit as set; 800 x 600 hidden, 640 x 400 and 512 x 384 selectable when DirectDraw lists them in 16-bit colour (0x4a1854, 0x4a17f4). A resolution (262) sets DisplayMode = code × 2 + every other line (640 x 480 0, 640 x 400 1, 512 x 384 2, 800 x 600 3); 263 switches letterbox, every other line or auto visibility; 264 the level. Going back writes the four (LevelView observed, the others inferred). Not ported: the drawing hook 0x440908 and applying the level (0x46f368).

The time items (POD, 0x431eb8) ​

Run by the car screen's handler (42) and the race end's (146). Outside a network race (0x63331c & 4 clear; earlier notes called this bit time attack, which is the mode's 0x633320 & 4) it hides the time items: menu 24's item 8, the garage's 28, menu 20's 3, menu 60's 15 and the race end's 1 (observed; ported as screens::car::time_items, which tests Game::race_kind). In a network race it shows them with the time (0x4e9ad0, default 30) and sets the drawing functions: the car screen 0x43d03c, menu 60 0x43f74c, the race end 0x43f338 (not ported).

Start-up and the championship (POD): what is read so far ​

Observed in POD's WINPOD.EXE.

  • Start-up (0x49c73c, unless video is off, 0x5829e0): logo.AVI, then intropod.AVI when PlayIntroAtStart is on. Ported: Ui::start gives them as Effect::Video.
  • A championship's end (0x49c4c4): finpod.AVI then credits.AVI when 0x49c494 is true (inferred: a won championship), else finpod2.AVI (docs/formats/video.md).
  • Choosing one (menu 16): action 19 (0x42f1c8) the standard championship: mode 10 (0x633320), 3 laps, the race number (0x633344) 0, the tracks (0x634594, their count 0x633340, -1 past the last) the circuits whose +0x7c is set, in order, the car menu's parent 40; action 20 (0x42f2a0) the custom one (mode 0x8a, menu 19 picks the tracks); action 21 (0x42f3a8) a random one (mode 0x180a, four random circuits).
  • Menu 40 (handler 174 0x43486c): sixteen save slots, those with a saved championship (0x46c2d8) selectable; a slot (175) picks a save, resume (188) loads it (below, "Championship saves"). Item 16 (list value 45, 0x4fdb14) starts: 0 runs 246 (0x42f514: the standings cleared, 0x43c138, then the car screen), 10 runs 245 (0x42f4e8: the standings cleared, then the track screen).
  • Points (0x4734b4, after each race, from the race end 0x473e18): each car's race time added to its total (0x58b690) and its points (0x474084: the cars in the race less its place, plus 1; nothing unless finished) to its points (0x58b6b0). A car that did not finish adds the penalty: the last finisher's time (the finishers by time at 0x4f9364, their count 0x4f938c) plus 60 s (0x3c0000). Then 0x473798 sorts the race order (0x58b6d8, the racers by place) and 0x4736d8 the standings (0x58b6e0).
  • The standings (0x4736d8, observed): in a points championship (0x633320 & 8, the standard one's mode 10) by most points, then least total time (0x473638); otherwise by least time, then most points (0x473684); equal racers in their order in the last standings (0x4735f0; after 0x43c138 cleared it, racer 0 first). The sort is Watcom's qsort (0x4c8e22, not traced; the port keeps still-equal racers in racer order, inferred). The leader is 0x58b6e0[0].
  • Ported (crates/pod-ui/src/screens/championship.rs, tests/flow.rschampionship_end_to_end): the standard championship for one player: action 19, menu 40 (no saved slots) and its start (246), each race on the next track with 3 laps, the points after each race, menu 60's first page (handler 170: 0x433f58 page 0's states; save selectable after every fourth race but the last; quit only drawn after the last race when the player leads), next (171: the following race's view, menu 20; after the last race the ending videos and the main menu, by writing the link of item 7 before it is followed), menu 20 (173) and its go (169). The standings' leader (0x58b6e0) is the most points, then the least time (inferred: the sorts 0x4787dc, 0x47875c are not traced).
  • Not ported yet: menu 60's other pages and the drawing of menus 60 and 20 (0x43f74c, 0x43f83c), and a won championship's prizes (0x434670, 0x4317f4). Time attack has its own section.
  • The menu loop reads an item's link after its action (an action may rewrite it, as 171 does) and does not follow it when the action changed the menu (inferred).

Custom and random championships (POD) ​

Observed in POD's WINPOD.EXE unless marked; ported in crates/pod-ui/src/screens/championship.rs (actions 20, 21, 245), screens/custom.rs (the track screen's championship list), screens/menu19.rs and src/rand.rs; tested by crates/pod-ui/tests/championships.rs (custom and random championships to the ending, the random mirror draws, menu 19).

  • The model (one place each, after the merge of three branches): the circuits, race number, points, totals, standings and menu 60's page belong to Championship; the laps (0x633330, Game::race_laps), the mirror mask (0x4fdf16, Game::mirror_mask, bit k for race k, tested as 1 << (k & 31)) and the mirror prizes (0x6345d4 .. 0x6345d6, Game::mirror_unlocked) are game globals, because the custom championship and the saves keep them across championships. The standings' clear 0x46c3e8 is a memset of 0x68 bytes from 0x58b690: the totals, the points and the orders (the standings 0x58b6e0 among them).
  • Laps: action 19 writes 3 to 0x633330, but the race set-up 0x4284a8 writes the circuit's laps (+0x84) over it outside time attack (0x633320 & 4; time attack takes 3, or 1 when 0x633321 & 2: see "Time attack"), so every championship race runs its circuit's laps and a save holds the last set-up's laps.
  • Mirror: 0x4284a8 sets the race's mirror flag 0x633350 from the race's bit of 0x4fdf16 in a championship (0x633320 & 2), from any bit in a single race. Action 19 does not write the mask (0x42f1c8), so a standard championship keeps the mask it finds; its other writers are the random draws, the custom list's add and remove, the save reader 0x46bc60, back from menu 17 (0x430368, clears it and 0x4fdf18), the mirror item's action 66 and 0x44d398 (the network championship's list; below).
  • Custom (action 20, 0x42f2a0): the car menu's parent 17, menu 17's parent 40, menu 40's start value 10 (list value 47 → slot 0x4fdb14), the sixteen entries -1 and menu 17's first sixteen lights out, the list empty (0x633340), mode 0x8a, race 0; laps and mask left as they were. Menu 40's start on 10 runs action 245 (0x42f4e8): the standings and race cleared (0x43c138), the parents set again, then the track screen. In a championship mode (0x633320 & 0x80, or & 0x1040 = 0x1000) the track screen's handler 76 (0x4a509c) and page (0x4a4148) show the list: the first digit row and the names are the entries (a mirrored entry's name reversed with strrev 0x4c840f), the second row picks a circuit (189, 0x4a47b8), "add" (191, 0x4a4d10) appends it (up to 16, or 4 in the 0x1000 modes; the next entry inherits the mirror bit), "remove" (190, 0x4a4adc) takes the lit entry out (the mirror bits after it move down). OK is selectable from two entries (exactly four in the 0x1000 modes). The mirror item 59 is offered with the difficulty's prize (0x42df3c); item 59 and its flags are in the next section. Not ported: the loads of the circuit's records in action 190 and 0x4a4654 ("Best times") and the drawing function 0x43fedc in these modes.
  • Random (action 21, 0x42f3a8): srand(time(NULL)) (0x4c7f5b, 0x4c81e3; the port takes the time from the host, Game::now), mode 0x180a, four races, the car menu's parent the current menu (16, straight to the car screen), start value 0, race 0, the standings cleared; sixteen draws rand() % circuits (0x62d22c), one per entry, the first four raced; then 0x42f334: the mask cleared and, with the difficulty's prize, one draw rand() % 2 per race whose circuit has +0x7c & 2, added at the race's bit. rand is Watcom's LCG (0x4c7f37: state × 0x41c64e6d + 0x3039, bits 16 to 30).
  • Menu 19 ("Choix Circuits pour Championnat Custom"): handler 27 (0x432678), actions 22 (0x4314f4, append a circuit), 23 (0x431578, go), 24 (0x4315b0, unselect), drawing 0x43d514. Inferred unreachable: only menu 41 links to it, nothing links to 41, and no code holds its pointer 0x4f9a68 or passes 19 to goto_menu. Ported for completeness.

The track screen's mirror and menu_back (POD) ​

Observed in POD's WINPOD.EXE unless marked; ported in crates/pod-ui/src/screens/mirror.rs, screens/mirror_map.rs, screens/track_name.rs, src/codes.rs and actions::leave; tested by crates/pod-ui/tests/championships.rs (custom_mirror_item, single_race_mirror_item, garage_back_while_choosing), tests/track_mirror.rs and tests/time_attack.rs.

  • Item 59 of menu 17 is a picture list (type 10) on list value 48 (0x4fdb20), three entries in BETAMNU.BIN: value 0 → picture 275, action 66; 1 → 276, action 0 (the pressed look); 10 → 277, action 0 (prize not won). Handler 76's tail (0x4a558e) clears 0x4fdef9; in a single race, a custom list mode or time attack (0x633320 & 1, & 0x80, & 0x1000, & 4) it offers the mirror by the difficulty's prize (1, 2, 4 → 0x6345d4, d5, d6; another difficulty leaves it): won → value 48 = 0, item 59 state 1, item 27's right and item 43's left neighbour 59; not won (and in any other mode) → 10, state 0, neighbours 0x2b and 0x1b. Then 0x4fdf18 = 1, 0x440ff8(2) (not ported) and 0x500af4 = 1.
  • Action 66 (0x4a5a88): nothing unless the chosen circuit's row (0x633354[0x6a7290]) has +0x7c & 2 (every POD circuit has: +0x7c = 3 in all 32 rows of the disc's list, observed). In a list mode or time attack it turns over bit 0x633340 (the list's length, shift count masked to 5 bits): set → taken off, 0x500af5 = 0x6a729d = 0; clear → added, both 1. In a single race the mask goes 0 → 1 (both flags 1) or anything → 0 (both 0). Every branch sets 0x500af6 = 1, then the track block 0x6a87dc (0x38 bytes) is cleared and load_records (0x4ad118) runs with 0x6a729d (ported, "Best times"). sets 0x500af6 = 1, then the records buffer 0x6a87dc (0x38 bytes) is cleared and load_records (0x4ad118) runs with 0x6a729d (the records: not ported).
  • The flags: 0x500af5 draws the map mirrored (read by 0x440f0c, 0x4414a8, 0x441a74), 0x6a729d picks the mirrored circuit's records, 0x500af6 asks 0x441f98 to redraw the map (below). Handler 76's start (0x4a5103): on its first run (0x4eb428, 1 in the data) both flags 0; afterwards in time attack or a single race both = (mask ≠ 0), in other modes 0. Action 189's tail (0x4a48d8): with an item selected, items 12 to 23 clear 0x6a729d; another item showing entry n (0x4a4058) sets it from bit n − 1 of the mask; then 0x500af5 = 0x6a729d. 0x4a45bc (called at the end of action 65's single-race branch) clears the mirror of a circuit without +0x7c & 2: in a list mode or time attack bit 0x633340 (0x500af5 0), in a single race the whole mask (0x500af5 1). 0x4fdf18 is written (handler 76: 1; 159 and back from 17: 0) and, observed, never read by address. 0x4fdef9 only matters after handler 76 has emptied the list, so it changes nothing.
  • menu_back (0x430368), in order: menu 24 with 0x633320 & 0x80 sets 0x4fdef9; menu 17 clears 0x4fdf18 and the mask; menu 20 decrements the race (0x633344); menu 24 in a network race (0x63331c & 4) with 0x633321 & 0x10 returns; menu 26 while choosing a set-up (0x4fdf22) sets items 0, 1 and 5 to 19 to 1, 2 and 3 to 1 (−1 without Ubi.ini), runs 0x430274, sets 20 to 27 to 0, selects item 3, clears 0x4fdf22 and returns; menu 3 on page 100 (0x4fdaf8) turns to page 10 and returns (so Esc from time attack's best races stays on menu 3); menu 14 writes the graphics keys; menus 28 to 30 (network) and 24, 51 with 0x5ab324 & 0xe go elsewhere, 51 and 54 call 0x489fc8 (not ported); menu 60 returns; any other goes to its parent (+0x48). Deviation in the port: menu 20's race stops at 0.
  • 0x44d398 is the network championship's default list, called by the multi-player screen's actions 90 (0x44fc30, menu 51 item 1, a picture list on value 13) and 98 (0x44ff28; unknown which item carries it). It sets 0x633320 and its network copy 0x5aafc8 to (bits | 0xa) & ~1, clears the race (0x633344), 0x5aafd8 and 0x4fda74; for each of the first min(circuits, 16) rows with +0x7c ≠ 0 the entry k is 0x543228 + k (also at 0x5aaff8) and item 37 + k's +0x7c and +0x80 are 0x102 + 3k; then 0x5aafdc and the list length 0x633340 are 16, the mask 0. Not ported: the multi-player menus are engine-only.
  • The mirror's typed code (0x4a5bb0, called at 0x46ce1a): observed, this is not a won championship's prize (that is action 171's, 0x4346e4 → 0x434670, not ported) but a cheat code. It sets the difficulty's prize (0x633334 1, 2, 4 → 0x6345d4, d5, d6; another difficulty none) and list value 48 to 0 in every case. Ported as mirror::code_typed, run from crate::codes.
  • Keys typed as words (crate::codes): the DirectInput keyboard read (0x4a16b1, its buffered events) hands each key press to 0x46ccb0 (0x4a1790), skipped while Left Alt is held (0x621980 = the held-key table 0x621948 + 0x38), the four arrows as 0. 0x46ccb0 ignores keys with bit 0x80 and a key equal to the newest recorded (so a doubled letter counts once); otherwise the 256-byte history 0x58d5b0 moves up, the key XOR 0x96 goes first and the new-key flag 0x58d5a8 is set. 0x46ccf8 (from draw_menu 0x42d8ab, when 0x62103c is clear, and from the race loop 0x461212) runs only with that flag, clears it and compares the newest bytes with seven codes in turn: two static (0x4ea7f8, 8 bytes; 0x4ea800, 6) and five built by 0x46c9d0 (0x58d560, 7 bytes; 0x58d590, 0x58d580, 0x58d588, 0x58d5a0, 5 each); the fourth, 0x58d590, calls 0x4a5bb0. It is built from the template 0x4ea868 with its last byte the layout's M (0x58d525 = 0x58d50c XOR 0x96): the keys M, I, R, O, R by position (scan codes 0x32 or 0x27, 0x17, 0x13, 0x18, 0x13). The layout comes from 0x46c400: GetKeyNameTextA of scan code 0x10 named "A" means AZERTY (M at 0x27), of 0x15 named "Z" QWERTZ, else QWERTY (M at 0x32). Not ported: the other six codes (0x46d614, 0x46d630, 0x46d650, 0x46d0fc, 0x46d73c, 0x44d268). Deviation: the port sees keys once a frame; keys going down together are taken in scan-code order.
  • The track screen's map (crate::screens::mirror_map): draw_picture (0x427bf0) hands menu 17's item 42 to 0x441f98 with mode 2 (menu 51's item 77 gets mode 0, the animated path; mode -1 is a plain draw). Mode 2: with 0x500af4 set it is cleared and 0x44193c sets the map up at once (the picture into the buffer 0x5173f0 and the target 0x5242b0, mirrored when the records' flag 0x6a729d is set, its palette to 0x522fd0); with 0x500af6 set it is cleared and the squash starts (0x500b28 = 1); 0x441bb4 runs one frame of it (close 15 rows a frame, then 50 columns, load the picture squashed with 0x441a74(1), open 50 columns, then 0x441a74(0) into the target and 15 rows a frame, and at the end the target copied into the buffer); the buffer is drawn with 0x49da90 centred in the 288 × 167 box by its current size (0x500b30 × 0x500b2c). 0x441a74 mirrors by 0x500af5 at that time. Every mirror (0x440f0c, 0x44193c, 0x441a74(0)) reads each row from its start backwards: pixel 0 stays, pixel c is the previous row's 288 − c. (0x441a74(1)'s mirrored branch adds the source row's index, not its offset: an original bug in the squashed frames, not ported.) Writers of the redraw 0x500af6: action 66 (every branch past the circuit test), 65 in a single race (and 0x4a45bc), 189's end (0x4a4948: 0 while handler 76 runs it, 0x6a729c, else 1), 252 (0x4a4e4c) and 253 (0x4a5023). Of the set-up 0x500af4: handler 76's tail (0x4a5624) and 0x44f908 (another menu, not ported). Deviation: the port shows the settled map at once, from the picture and the flag at the request (mirror_map::map_frame, run at the end of each menu frame); the squash is not ported. Unknown: how POD's map item follows the chosen circuit: only handler 76 writes item 42's +0x78 (0x4a5470, the only + 0x273 besides the loaders and menu 20); the port sets it on each choice, which matches pod-tracks-page2.
  • The title (0x43fedc, the track screen's drawing function, POD's form of the port's draw_track_info): the circuit's name (+0x14) reversed with strrev when 0x6a729d is set, in every mode (both branches at 0x43ff20 test it). Not ported: the record times (0x6a8f54/0x6a8c20 in time attack, 0x6a8b60/0x6a882c otherwise), which need the records.
  • Action 65 in a single race or time attack (0x4a4672, screens/track_name.rs): the entry n the selected name shows (0x4a4058) and its digit: the word at 0x4eb432 + 2 (n mod 16), the digits table for 1 to 15 and for the 16th name the high half of the lit item 0x4eb430 (−1 when none is lit, else 0: item 0, the first digit; observed, an original slip). For 1 ≤ n ≤ the circuits: circuit n − 1, OK state 1, 0x633340 = 1, the lit digit put out and this one lit, OK selected, records reloaded (not ported), the page filled, and when the digit shows an entry entry n's digit (0x4a4010) lit again. Then always 0x500af6 = 1 and 0x4a45bc(n), which reads the row 0x633350 + 4n (circuit n − 1). Deviation: an entry outside the circuits changes no mirror (the original reads a row outside the table).
  • No capture of POD shows the mirrored map or a reversed title (the walks were made on EASY without the prize, so item 59 shows picture 277). tests/track_mirror.rs checks the code (doubled keys, a key between, an arrow, Left Alt, AZERTY, the difficulties), the map's pixels against the mirror of the slot's picture, the reversed title and action 65; tests/frames.rs's pod-single-race-tracks-map and pod-tracks-page2 still match.

Championship saves (POD) ​

Observed in POD's WINPOD.EXE; ported for one player in crates/pod-ui/src/screens/championship_saves.rs, tested by crates/pod-ui/tests/championship_saves.rs (save after the fourth race, resume in a new session: the standings, the race and the next track as saved). The file is docs/formats/chship.md.

  • Saving: menu 60's item 9 (a link, list value 45, selectable after every fourth race but the last) leads to menu 45 ("Selection des slots pour la sauvegarde d'un fichier championnat"). Its handler 177 (0x434b04) sets 0x4ffd98 (cleared by handler 170; no reader found by an address scan), makes the sixteen slots (items 0 to 15, fields of up to 8 characters, the second character map) selectable and reads the saved names into their fields (0x46c2d8 with 0; no save, an empty field). Item 16 is back (action 5).
  • Action 176 (0x43497c), a slot: the field editor (0x42eb80, EDX, EBX, ECX 0) starts from the field when it holds 1 to 8 characters, else empty. Esc or an empty name: the cancel flag cleared, back (16) selected. Else the field takes the name and 0x46b70c writes POD<slot>.<kind>; when that fails the field is emptied (0x4e1ee1, ""); back is selected either way.
  • Resuming: menu 40's handler 174 clears the sixteen slots' text and states, reads the names into the text (0x46c2d8 with 1), makes each slot with a name selectable (from 15 down) and draws resume (17). Action 175 (0x434934) keeps the slot (0x4fdf14) and probes its save (0x46bc60 with DL 0: read, then the state put back); when it reads, resume is selected (0x42cbc4) and made selectable. Action 188 (0x4348f0) draws resume (state 0), loads the kept slot (0x46bc60 with DL 1); when it loads, sorts the standings (0x473e74: 0x473798 on 0x58b6d8, 0x4736d8 on 0x58b6e0, then 0x473c52 in team mode) and goes to menu 60 (goto_menu), whose handler 170 shows the standings with the saved race number: next (171) leads to the following track.
  • Menus 42 and 43 are not on this path: no item of the menu file links to them and no goto_menu call names them (a scan of every call to 0x42a628). Menu 42 ("Champ qcq, Continue/Stats/Save/Previous": text links to 24, 43, 43 with action 59, and to 2 with action 12) has handler 58; 58 and 59 are both 0x42f0dc, which returns 0; action 12 (0x43023c) sets bit 0 of 0x634da9 and 0x4f9b40 = 3. Menu 43 (stats) has handler 60 (0x431e98: the drawing function 0x43cdf0). Not ported.
  • What the port keeps (inferred as enough for the menus): the save's header (tracks, race, laps, mode, difficulty, damage, racer counts), each racer's name, points and time, player 1's car and gearbox. Deviation: the garage set-ups, kept flags, race records, computer drivers' lists and the racer entries' other fields are written as zeros and not read back. The mirror mask (0x4fdf16, the file's last word) is written from the game's mask and read back into it; a non-zero mask without the difficulty's prize fails the load and clears the mask; the probe's restore (0x46b3cc) puts back the laps (0x633330) but not the mask, so a probe that reads leaves the save's mask (observed). Not ported: load_records for the next track and 0x421bb0 after a load; the probe's partial restore (the original restores only the header and the first two racers, so the computers' standings stay loaded; the start (246) clears them and resume loads them again, so nothing differs on screen, inferred); what sets the mirror flags 0x6345d4 to 0x6345d6.

Observed in POD's WINPOD.EXE; crates/pod-ui/src/screens/championship_end.rs, championship_pages.rs, next_track.rs; tested in crates/pod-ui/tests/championship_end.rs. No POD capture of these screens exists yet: the item states follow the code, and the drawing is checked only for running and for a few positions the code gives (a capture has been asked for).

  • Items of menu 60: 0, 6, 8 pictures; 1 the race again (163); 2 to 5 the pages 1 to 4 (164 to 167); 7 next (171); 9 save (a link to menu 45); 10 quit (172); 11 back (168); 12 the confirmation's box, 13 yes and 14 no (both 250); 15 the time; 16, 17 pictures.
  • Page selector 0x433f58 (page in 0x4fdc38; jump tables 0x433f28 for the page number, 0x433f40 for the states). Page 0 (0x433f81): 0 drawn, 1 selectable outside a network race (0x63331c & 4; earlier read as time attack), 2 and 3 selectable, 4 and 5 selectable in a team championship (0x633320 & 0x20) else drawn, 6 and 8 drawn, 7 selectable, 9 as before (every fourth race but the last; not in time attack, with 0x633321 & 0x18 or split screen; 0x4ea8c0 set), 10 selectable, 11 to 14 hidden; after the last race 10 is only drawn when the player leads; next (7) selected. Pages 1 to 4 (0x43423c, 0x434371, the same states): all hidden but 11. Page 5 (0x4344a6): all hidden but 12 (drawn), 13 and 14 (selectable). Then 0x433eec hides 16 and 17 for every page.
  • On entering, goto_menu (0x42a628) selects the first selectable item after the handler, so menu 60 opens with item 1 selected, not 7.
  • Buttons: 164 to 167 set the page and select the first selectable item (0x42cb84, here 11); 168 page 0; 172 page 5 and no (14) selected; 250 tells yes from no by the selected item: no is page 0 with 7 selected, yes the front end's state 6, which leaves the menu loop, runs 0x46dd48 and resumes the menus (the confirmation reads "really quit to main menu?"; the port goes to the main menu and ends the championship, inferred). 163 takes the race's points and times back (0x473534), sorts again (0x473e74) and sets the state 5, which runs the same race again (the state machine's case 5).
  • Drawing 0x43f74c (installed for menu 60 by 0x431eb8 in every mode): page 1 0x4c1404, 2 0x4c1c8c, 3 0x4c2130 (TEAMRAN), 4 0x4c2b80 (TEAMGEN), 0 and 5 nothing (table 0x43f738). Text as on the race end's pages: font 0x4f9b30, "0" measured for w and h, centred texts at x − (len × w) / 2.
  • Page 1 (0x4c1404, this race): PILORAN centred at y 70, TRACK with the title (reversed for a mirrored race, 0x4c840f) a line below; two lines further the headings RANK (centred at 56), NAME (140), TIME (306), BEST (436) and, in a points championship, POINTS (550); h + 10 further one line per car in the race order (up to 8): %u) centred at 56, the name from x 88, the time text (0x4c0a68: the time when finished, else DESERTD, OVRTIME or nothing) centred at 306, the best lap (0x4731dc) at 436, the race's points (%d) at 550. The player's line is on the blue bar from 56 − w to 436 + 4w (550 + 2w in a points championship); a time whose place in the track's best races (0x4ad628 on 0x6a8818) is 1 sits on a red bar (0xff2222) of its own width; a best lap first in the best laps (0x6a8c0c) on a red bar, else, when it is the race's best lap (0x4f93a0), on a green one (0xeeffee). Two lines below the last car, RECLAP at x 50 and the track's record lap (0x6a8c20) at 436; a line further, when a car did not finish, PENALTY and the penalty.
  • Page 2 (0x4c1c8c, the standings): PILOGEN centred at y 70; a line below RANKX1 after the first race, else RANKX2, with the races run; the headings RANK, NAME, POINTS (points championship, at 306) and TIME (436); one line per racer in the standings: %u), the name, the points, the total time; the player's line on the blue bar from 56 − w to 436 + 4w.
  • Both pages read the bar's x and end from 0x6d46f8 before measuring "0" again, so from the last measurement (a Deviation only on a page's first frame).
  • The bar 0x4c0d70 → 0x4c0cc0: each pixel widened to 24 bits, ANDed with the colour and ORed with 0x66 (0x2222ff), 0xaa0000 (0xff2222), 0x7700 (0xeeffee) or 0x666666 (any other), narrowed back.
  • 0x4ad628 (the record test): counting back from the table's last row past every row slower than the time, the place of the row it stops on when equal, else 0 (and 0 with 0x6a8814 set). The tables are the track's best times after the race ("Best times").
  • Menu 20 (0x43f83c, installed by handler 173): on its first call the next circuit's map is loaded (load_menu_images case 200, slot 0x273 + 3 t; 0x5006ec = 0x634594[race]) and for a mirrored race (bit race of 0x4fdf16) mirrored by 0x440f0c: for each of 167 rows of 288 pixels it reads from the row's start backwards, so pixel 0 stays and pixel c is the previous row's 288 − c (row 0 reads before the picture), and its palette is copied to 0x522fd0. Every frame: the map centred in item 1's box (halves rounded toward zero), drawn opaque (0x49da90); each race run so far, its circuit's title at x 30, y 100 + 20 k (reversed when mirrored); the next circuit's title at x 320, y 80 (not centred: 0x5006f4, 640 − width − 20, is computed and not used).
  • The championship's tracks are at 0x634594 + 4 k (action 19 writes 0x634590 + 4 (k + 1)).
  • Not ported: the team pages 3 and 4 (the teams, 0x473d24), split screen's second player (0x58dd8c), time attack's branches, the network's points (0x5ab588 = 2), the palette copy of a mirrored map.
  • Not ported: split screen's second player (0x58dd8c), time attack's branches, the network's points (0x5ab588 = 2), reading the track's records for the record bars, the palette copy of a mirrored map.

Observed in POD's WINPOD.EXE and SCRIPTS/BETAMNU.BIN unless marked; crates/pod-ui/src/screens/first_track.rs, screens/race.rs (car_ok); tested in crates/pod-ui/tests/championships.rs (first_track_view, first_track_view_random) and on the championship paths of tests/flow.rs, championships.rs, championship_end.rs and championship_saves.rs, which now pass through menu 21. No POD capture of menu 21 exists.

  • Menu 21 ("Ecran Visu du circuit suivant pour Mode_Jeu&RANDOM_qqeChose", parent 24, background slot 1, handler 254): item 0 go (button, action 169, as menu 20's), 1 the map's box (165, 151 to 480, 328), 2 a link to the garage (menu 26), 3 back (action 5).
  • How it is reached: the car screen's OK, action 49 (car_ok, 0x42e7b0), the only goto_menu call with 21 (0x42e9dc; a scan of every call to 0x42a628). After the set-up and Player1DefaultCar, with one human (0x633324 = 1) and a championship mode (0x633320 & 0x1802: the standard 10, custom 0x8a and random 0x180a all have bit 2) it goes to menu 21 and returns 0, so the race does not start; otherwise 0x4301d4 starts it. So every one-player championship's first race (and a race after back from 21 to the car screen) passes through menu 21; the later races go through menu 20. Before that, in the modes with 0x633321 & 0x10 (0x1000, the random championship), car_ok makes the car screen's items 2 (back) and 3 (gearbox) selectable (0x42e925).
  • Handler 254 (0x43c17c): the drawing function 0x44046c installed (0x4f9b5c); item 2 state 1, or 0 with 0x633321 & 0x10; the garage menu's parent (+0x48 of menu 26, 0x4f9a84) the current menu; 0x4fde5a cleared when set (a garage flag also read by handler 178 and cleared by 173, tied to split screen's second player 0x4f9b60; not ported). It does not set item 1's state (the file's 0) nor 0x4fdef8 (173 does both).
  • Drawing 0x44046c: menu 20's (0x43f83c) with its own globals (0x500988 to 0x5009b0) and two differences: the map (load_menu_images case 200, circuit 0x634594[0x633344]) is not mirrored for a mirrored race (no call to 0x440f0c), and the races run so far start at y 80 (0x50 + 20 k; menu 20: 100). The race's title is at x 320 (0x621220 / 2), y 80, reversed when mirrored; 0x5009a4 (640 − width − 20) is computed and not used.
  • The garage's states (0x430274, 0x437056) test for a parent of 20 or 21 in split screen (0x63331c & 3); not ported.
  • Menu 41 ("Choix Nouveau ou Reprise Champ Custom", parent 0, no handler): text links "New Custom Champ" to menu 19, "Load Champ" to 44, "Previous Menu" to 44 with action 5. Observed unreachable: no item links to 41, no menu has 41 as its parent, no goto_menu call passes 0x29 and no code holds its table entry 0x4f9ac0. Not ported (nothing to run: no handler, no actions but back).
  • Menu 44 is not in BETAMNU.BIN (the file holds menus 0, 1, 3 to 8, 10 to 14, 16 to 21, 24 to 30, 32, 35, 40 to 43, 45, 51, 53, 54, 60), and nothing but menu 41 names it. goto_menu (0x42a628) reads 0x4f9a1c[n] and its +0x44 without a test, so following menu 41's links would read through an empty entry (inferred: a crash). Not ported.

The team championship's sorts and pages (POD) ​

Observed in POD's WINPOD.EXE unless marked; crates/pod-ui/src/screens/championship_teams.rs, tested in crates/pod-ui/tests/championship_end.rs (team_pages, team_sort_by_time). No POD capture of these pages exists.

  • How it is reached: the team bit 0x633320 & 0x20 is set only by 0x43e014 (five or writes at 0x43e1fc to 0x43e39a), called from the multi-player menu 51's code (0x43ec37), and by the network's mode writes; so a team championship needs the multi-player menus, which the port does not run (engine only). A team's racers are those whose racer entry +0xc8 (0x6339bc + r × 0xdc) holds its number; the teams' count is 0x633338; the pages show 0x633324 / 0x633338 racers a team (the humans over the teams; with no team, nothing). Who writes +0xc8 and 0x633338 for teams is unknown (the multi-player code, not traced). The port keeps them in Championship::team and teams, set by a host.
  • When: 0x473e74 (after each race, from 0x473e18; and from action 163 and the save's resume 188) sorts the race order and the standings, then in team mode jumps to 0x473c52. 0x43c138 and 0x46c3e8 clear the two team orders with the others.
  • This race by team (0x473b38 on 0x58b6e8): two eight-entry tables on the stack (zeroed, 0x4c76de), each racer's race points (0x474084) and race time (finished: 0x4f8d94; else the penalty, the last finisher plus 60 s) added at its team; the racers 0..n (humans and computers) sorted by qsort (0x4c8e22) with 0x473970 (points championship) or 0x473a54: same team, by place (the high nibble of 0x4f8d88); else team points (more first) then team time (less first), or time then points; still equal, by the last team standings (0x4735f0 on 0x58b6f0, which returns −1 or 1 for whichever racer it meets first, 0 for neither) only after the first race (0x633344 > 0).
  • Standings by team (0x473c52's rest, on 0x58b6f0): the championship points (0x58b6b0) and totals (0x58b690) summed per team; comparator 0x4737d0 or 0x473880: same team, the standings' own comparators (0x473638, 0x473684, whose last tie is the standings just sorted); else team points then time, or time then points, then the last team standings, with no race test. The port's sort is stable (inferred, as for the standings).
  • 0x473d24 (eax racer, edx order): the racer's place in the first n entries, from 1; 0 when absent.
  • Page 3 (0x4c2130): TEAMRAN centred at y 70, TRACK with the title (reversed when 0x633350) a line below; two lines further RANK (56), TEAM (140), PILRANK (238), TIME (316), BEST (446) and in a points championship POINTS (550), all centred; h + 10 further, team by team: %u) with k + 1 centred at 56 on the team's first line, then each racer of 0x58b6e8 from k × size: the player's line (0x58dd88, or 0x58dd8c in split screen) on the blue bar from x 88 to 446 + 4w (515 + 3w in a points championship; the end read from 0x6d46f8 before "0" is measured, as on page 1); the name at 88; its race place (0x473d24 on 0x58b6d8, %u) centred at 238; the time text (0x4c0a68, which returns 1 when finished, else 0) centred at 316 on a red bar when 0x4ad628 on 0x6a8818 gives 1; the best lap (0x4731dc) at 446, red when first in 0x6a8c0c, else green when it is the race's best (0x4f93a0); in a points championship the race points left-aligned at 515 as %3d , on the team's last racer %3d %3d with the team's sum. One line a racer, 5 more after a team. Then 2 lines down RECLAP at 50 and the record lap (0x6a8c20) centred at 446; when a shown racer's time text returned 0, a line further PENALTY and the penalty.
  • Page 4 (0x4c2b80): TEAMGEN centred at y 70; RANKX1 (first race) or RANKX2 with the race number + 1 a line below; the headings as page 3's without BEST; team by team, %u) at 56, then each racer of 0x58b6f0 from k × size: the bar as on page 3, the name at 88, its standings place (0x473d24 on 0x58b6e0) at 238, its total time (0x58b690) at 316; in a points championship its points at 515 as %3u , on the team's last racer %3u %3u with the team's points; otherwise on the team's last racer the team's total time centred at 446. 5 more after a team; no bottom lines.
  • Deviations: a team number past 7 is taken as 7 (the original indexes past its stack tables). Not ported: split screen's second player's bar, the teams' set-up, and the team sort after a save's resume (action 188; the port does not keep the teams in a save).

The race end in POD (menu 4) ​

Observed in POD's WINPOD.EXE; crates/pod-ui/src/screens/race_end_pages.rs. Both pages match POD's captures of the Azthec race (pod-results-azthec, pod-best-5-laps-azthec) pixel for pixel, pointer included.

  • Handler 146 (0x4330f4) runs the time items (0x431eb8), starts on page 0 (0x4fdaec) and marks the pages drawn (0x4fde59); action 150 (0x43328c) turns to page 10, then ends the screen. The drawing function 0x43f338 draws page 0 with 0x4c0dc8 and page 10 with 0x4c4fac in a single race.
  • The text is draw_text (0x48ba20, baseline at y) in the menus' font 2. Each page measures "0" (0x48c054): its width w places text as if every character were that wide (centred at 320 − len × w / 2, right-aligned at 560 − len × w), its height h is the line step.
  • Ranking (0x4c0dc8): ENDRACE CLASST centred at y 70; TRACK ("TRACK : %s") a line below; two lines further, one line per car (up to 8): %ld) %s at x 50 and the winner's time or the others' gap right-aligned at 560; three lines further BESTLAP + " : " + the holder and the time; a line below, when the player does not hold it, YOURBST and the player's best lap. A car that did not finish shows DESERTD or OVRTIME (not ported).
  • The gap (0x479050): "+ " (or "- ") and format_time, the minutes cut when they are "00" ("+ 05"85"); zero gives + 00"00. Not ported: "+ 1 lap" and "+ n laps".
  • The bar (0x4c0d70 with 0x2222ff) behind the player's line: from (x − 2, y − h − 2), w + 2 by h + 1, each pixel ANDed with 0x2222ff and ORed with 0x66 in 24 bits (0x4c0cc0; in 16 bits red keeps bit 2, green bit 3, blue gains bits 2 and 3), then a white outline of four lines that leave the far corner out (0x48eb4c; the white measured).
  • Best races (0x4c4fac): RBESTXL with the race's laps centred at y 70, the track a line below, then from two lines further one row per record (0x6a8818, 0x38 bytes a row): %2u) %s %s (place, time, name) at x 50; the player's row (same total and name, 0x4c805c) on the bar, as wide as its text. The table is the track's best times after the race ("Best times" below): on a fresh install, as in the capture, no .BRM file exists, so it holds this race's finishers.
  • The host names the drivers (Ui::set_racer_names); without names, racer 0 is player 1.

Best times (POD) ​

Observed in POD's WINPOD.EXE (register arguments read from the assembly) unless marked; ported in crates/pod-ui/src/best_times/ (mod.rs the table and the files, insert.rs the three insertion functions, race.rs the race's crossings). The track's best times are the 0xE5C-byte image of a RECORDS file at 0x6a8818 (layout: docs/formats/records.md).

  • The file name (0x4ad09c; EAX the output, EDX the circuit's name, EBX the mirror flag): 0x4a9b98 copies the name and replaces the text after its last . (one that follows the last / or \) with BRx, or appends .BRx; it ignores the ECX = 8 its callers pass. The last character then becomes T in time attack (0x633320 & 4), else E for difficulty (0x633334) 1, H for 4, M otherwise; with the mirror flag the first character becomes -. So a single race on Beltane at medium is BELTANE.BRM, which the disc does not ship: POD's disc has only the 16 .BRT files (measured).
  • Load (load_records, 0x4ad118; EAX the name, EDX the mirror): 0x6a8814 cleared, the file opened (block 0x1000, key 0xEAFA) and 0xE5C bytes read to 0x6a8818. A missing file calls default_records (0x4acce0), which fills the tables only in time attack and when the track's 0x38-byte block (0x6a87dc, read from the track file by 0x46a1bc) has non-zero words at +0 and +0xc; otherwise the tables keep whatever was in memory (zero at start-up, else the last file loaded or the last race's insertions). The port keeps this as it is, 1:1 (the user's decision, 2026-10-25): do not clear the tables or mark it as a deviation. The race loads in race_reset (0x41eb5e, the name 0x62c628 from the track file, the mirror 0x633350 > 0); the track screen (handler 76, actions 189 to 191, 66), the records screen (0x42e0ac, 0x42e0f5, 0x42e242) and 0x4a7949 load too.
  • Insert a race or lap (0x4ad220; EAX the time, EDX the words, EBX their count, ECX the name, then on the stack the table and "keep a repeat"; returns the place from 1, or 0): a negative time, or 0x6a8814 set, does nothing. The place n is found counting back from the last row past every slower row. Past the last row: appended when fewer than 15 rows, else nothing. Otherwise, without "keep a repeat", the same time and name (strcmp) on the row above means it is there already; for n < 14 memmove (0x4c771b) moves 14 − n rows down one, whatever the count, and the count goes up to at most 15; n = 14 writes over the last row. The row gets the name by strcpy (bytes after its zero stay), the time at +0x14, the words at +0x18, and the table's byte +0x349 the word count.
  • Insert a split (0x4ad438; EAX the time, EDX the name, EBX the sector table, ECX keep a repeat): the same on 0x18-byte rows with the count at +0x168, name and time only.
  • The best race's splits (0x4ad7b8; EAX the race time, EDX, EBX, ECX the splits of laps 1, 2, 3): when the race table is empty or the time beats row 1 (0x6a882c), the first 0x63333c words of each lap go to 0x6a8b7c, 0x6a8bb4, 0x6a8bec. The word copy is f2 a5 (repne movsd, which runs as rep; capstone prints it as a plain movsd).
  • Who inserts (the race's macro handlers, for every racer, always keeping repeats, the name from the racer table 0x6338f4 + racer × 0xdc): "P-Chrono Inter" (0x41ee24) each counted split into sector table k = the checkpoint (0x41ef70); "P-Chrono Tour" (0x41f17c, the entry in the macro table at 0x4805ee; the old label 0x41f629 is inside it), at each lap-line crossing after the first, the lap's last split into table 0x63333c − 1 (0x41f40b) and the lap time with that lap's splits (+0x3c + 0x10 (lap − 1), 0x63333c words) into the best laps (0x41f479); at the finish (crossings past 0x633330) the best splits (0x41f931, the clock 0x4f9384, record +0x3c, +0x4c, +0x5c) and the race time +0x10 with the laps from +0x1c (0x633330 words) into the best races (0x41f995). The network's finish (0x41fddc, 0x41fa5c) and the ghost loader (0x4b3f58) insert too.
  • Save (save_records, 0x4ad194; EAX the name): unless 0x6a8814 is set, the file is created with the mirror 0x633350 > 0 and the 0xE5C bytes written (block 0x1000, key 0xEAFA). race_end (0x473e18) calls it with 0x634db0 (the race's circuit name, copied from 0x633354[race] by 0x4284d2) when 0x4ea8c0 is set (1 in the data); time attack calls it again at 0x4a7c89.
  • The records screen (fill_records_page, 0x42dfc4, observed): for each row of the page the circuit (6 × page + row) % 0x62d22c, mirrored past the circuit count (the list doubles with the difficulty's mirror prize, 0x42df3c); 0x6a87dc cleared, then with 0x633320 set to 1 the single race's file (.BRE/.BRM/.BRH by the difficulty) gives column 0 row 0 of the best races; set to 4 the .BRT gives column 1 the best race and column 2 the best lap; 0x633320 is put back. From time attack (0x4fda5c = 1) the three columns are the .BRT's three best laps. As a missing file leaves the tables, a missing single-race file shows the previous row's time attack record (every row but the first on POD's disc, which ships no .BR[EMH]). Ported in screens/records.rs::fill; tested by tests/best_times.rs (the_records_screen_reads_the_files). The frame test's capture (pod-records) has an empty grid: the test drops the RECORDS files, as that install had none.
  • The track screen (observed): actions 189 (0x4a493c, single race and time attack), 252 (0x4a4ea9), 253 (0x4a5078) and 66 (0x4a5b19) clear 0x6a87dc and load the chosen circuit's file (0x633354[0x6a7290], mirror 0x6a729d, the current mode and difficulty); handler 76 does not, so the screen first shows the tables as they are. The drawing function (0x43fedc's state at 0x440176) draws, outside time attack, the best race's time (0x6a882c) centred in the fourth box when 0x6a8b60 is non-zero, and in every mode the best lap's (0x6a8c20) in the fifth when 0x6a8f54 is (else empty texts). Ported (best_times::load_track, race::draw_track_info); tested by the_track_screen_loads_the_chosen_circuit. Not ported: the loads of action 190 (0x4a4b19) and 0x4a4654 (0x4a4745), the championship lists' paths.
  • In the port: the race's crossings are inserted when the race is over (best_times::race_over), rebuilt from the race records and sorted by race clock, then by racer (inferred: the step moves the cars in racer order). The file is loaded there too, and written as Effect::File to RECORDS/<NAME>; later loads in the session read the written file first. The end pages (race end, championship, time attack) read their tables from this image. Deviations and gaps: the load at the race's end instead of its start; not ported: default_records, the one-lap type's inserts, the network's and the ghost's, the championship lists' and time attack menus' loaders (0x4a4b19, 0x4a4745, 0x4a7949), the second save.
  • Measured (2026-10-25): measure/records_insert.py runs POD's 0x4ad220, 0x4ad438 and 0x4ad7b8 under Unicorn (with strcmp as is; Watcom's memmove done in Python, as its push es/pop es trips the emulator) on the 65 operations of crates/pod-ui/tests/data/records_ops.tsv: from BELTANE.BRT (a full table: a new first row, a repeat refused and kept, a time past the last row, the last row written over by a shorter name, a 25-character name, sector inserts, the splits kept and not) and from an empty image (appends, memmove over unused rows, the appending branch taking a repeat that the moving branch refuses, filling past 15 rows). crates/pod-ui/tests/best_times.rs compares the place returned and all 0xE5C bytes after every operation: all 65 equal. The same test reads and writes back the 16 disc files byte for byte, and checks a single race's RECORDS/<STEM>.BRM being written and read again by the next race.

The car swap (POD, menu 25) ​

Observed in POD's WINPOD.EXE. The car screen (24) offers only the cars of VOITURES.BIN (0x63418c, count 0x62d228): its arrows go round those. Its item 7 ("Load new car") links to menu 25, where the player trades one of them for a car of VOITURE2.BIN (the pool: 0x4fc8a4, count 0x4fc8a0, read by read_car_list 0x472020 into a 0x1000-byte buffer). Added cars therefore reach the race through this screen.

  • Handler 44 reads the pool, loads every listed car's pictures (0x428764: case 0xcc of load_menu_images 0x474f88, car n's MIMG/<name>.IMG at slot 0x258 + 3 n) and the first pool car's (slot 0x270), and lays out the eight car buttons (items 5 to 12, entries 1 and 2 of each car's file). Swap (2) starts hidden; reset (3) shows when a slot's car (the record's first field) differs from VOIRESET.BIN's. With an empty pool only OK works.
  • 221 picks a slot (its button shows entry 1) or unpicks it; swap shows while one is picked (0x5001e8, -1 none).
  • 222, 223 page the pool round (0x4fc89c); the hook 0x43c844 draws the offered car's entry 2 at (260, 330).
  • 224 exchanges the picked slot's two pictures with the offered car's (pixels copied through 0x5433d0) and the two 0x80-byte records.
  • 225 puts VOIRESET.BIN's eight back. It rewrites the pool from its start: the listed cars not in the reset list, then those of the old pool's first records not in it either. It looks at as many old records as the reset list has (8), whatever the pool's count, so with a pool longer than 8 records past the rewrite stay as they were.
  • 220 (OK) writes VOITURES.BIN and VOITURE2.BIN (write_car_list 0x47209c: the count and the counted records, so POD's 15 uncounted VOITURE2.BIN records are dropped the first time), clears 0x4ffd68[i] for each slot whose car changed from the file on disk, reloads the cars' models and the players' car names, then goes to the parent menu.

tests/flow.rs (car_swap) checks that OK with no change writes VOITURES.BIN byte for byte and the counted part of VOITURE2.BIN, that a swap exchanges the two cars, and that reset restores VOIRESET.BIN without losing a car. With POD_EXTRAS set to a content folder (pod-extras install), car_swap_extras checks that every added car has its pictures and figures (.BV4 when there is no .BV3). No capture of the screen exists yet.

Time attack (POD) ​

This section covers menu 18 and the time-attack race ends (menus 7, 3, 6, 5) in POD's WINPOD.EXE. Everything here is observed in its code (static reading of the assembly and Ghidra's export) unless marked. It is ported in crates/pod-ui/src/screens/time_attack.rs and time_attack_end.rs and tested by crates/pod-ui/tests/time_attack.rs, which drives each type from the main menu to its race end and back. No POD capture of these screens exists, so the drawing is not compared.

  • The mode bits. Time attack is the mode's bit 0x633320 & 4. The bit 0x63331c & 4, which earlier notes in this file took for time attack, marks a network race (Game::race_kind).
  • The way in. Menu 16's item 6 runs action 154 (0x4a7054) and links to the track screen. Action 154 puts out "Top 5" and the lit type, empties the row texts (20 to 29), turns the panel off and hides it. It draws the title (4) with state -2, makes the records (3) and back (9) selectable, hides "Top 5" (5), the panel's buttons (6, 7) and go (8), clears every row's choice and ghost, sets the row pictures (0x4a5cec) and writes the mode 4. The track screen's OK (0x4a59b0) in time attack runs 0x4a7054 again and goes to menu 18. The car menu's parent stays as it was. Player1DefaultCircuit is written after either goto_menu. In the port a championship under way ends here, because the port keeps it as Game::championship where POD's mode becomes 4.
  • Menu 18's items: 0 to 2 the types (actions 15, 14, 77); 3 the records (a link to menu 35, action 247); 4 the circuit's title; 5 "Top 5" (155); 6 and 7 the panel's buttons (194, 195); 8 go (256); 9 back (192); 10 to 19 the panel's rows (the odd ones choose a ghost, 156); 20 to 29 their texts; 30 and 31 the choices of action 68.
  • Menu 18's hidden buttons. The background (18.IMG slot 7, +0x4c, bank 0) already has the frames and words of "Top 5", "Add", "Export" and "Race" in it. After action 154 the four items are hidden (−1) and draw nothing, so POD shows the background's frames there, as pod-ui does (observed: the two drawing functions above and the picture). The items' own pictures (342/195, 337, 339, 341) differ from the background's in nearly every pixel (measured), so only lit, pressed or −2 items change what is seen: "Top 5" lit by 155 shows +0x84 (195); unlit at state 2 it draws nothing; the panel's buttons at state 2 draw only while pressed. tests/time_attack_pictures.rs checks this.
  • Handler 193 (0x4a6f90) titles the menu with the chosen circuit (0x633354[0x633344] + 0x14), reversed when any mirror bit of 0x4fdf16 is set. With the panel off it hides both choices.
  • The types (the lit type in 0x4eb6c0, -1 none). Each puts out the lit type (0x4a6dfc) and writes one circuit (0x633340) first.
ActionAddressTypeMode 0x633320Laps 0x633330Choices
150x4a73d8three laps0x14330 selectable, 31 hidden
140x4a7310one lap0x2141both hidden; panel off
770x4a74a0ghost race0x104330 hidden, 31 selectable; one human (0x633324), no computers (0x633328, 0x63332c)

All three then light the type (0x4a6ed0), make go selectable and select it (0x42cbc4), put out both choices and clear the mode's bit 0x10000 (0x633322 & 1). Lighting the ghost type also makes "Top 5" selectable and readies the panel (0x4a6728: 0x6a72c8 = 1, the mode's 0x8000 and 0x2000 set, 0x4000 cleared). Before its writes, action 77 sends message 0x432 to the game's window (0x620fbc). When the answer is 0 the game posts WM_CLOSE and sleeps for ever. The port goes on as when it answers (unknown: what the window procedure answers).

  • Go (256, 0x4a75c0): readies the panel when it is off, or when it shows ghosts with none chosen. It clears 0x4fdf23 and sets 0x4fdf24 for the three-lap type (cleared otherwise), then goes to the car screen (24). Back (192, 0x4a72f4): the panel off (0x4a6754) and hidden (0x4a6ab8), then menu_back to the track screen. Action 68 (0x4a7644, choices 30 and 31): an unlit choice lights, sets the bit 0x10000 and selects go when go is selectable. A lit choice is put out and the bit cleared. 247 (0x42dfa4) sets the records screen's source 0x4fda5c to 1, so its "previous" leads back to menu 18. The main menu's records (248, 0x42dfb4) set it to 0.
  • The race set-up's laps (0x4284a8, at 0x428527 and 0x42861d): in time attack 1 when 0x633321 & 2 (the one-lap type), else 3. Outside time attack it uses the circuit's laps (+0x84). Time attack races its circuit as a single race does.
  • The race end (the front end's state 4, 0x4295b4 at 0x429af2): time attack is tested before the single race and the championship. The one-lap type (0x633321 & 2) opens menu 6, the ghost race (0x633321 & 1) menu 5, and three laps menu 7, whose item 0 links to menu 3. Their pages are 0, 10 and 100 (0x4fdaf8, 0x4fdaf4, 0x4fdaf0; list values 38, 37, 36).
MenuHandlerActions
7, 3 (three laps)149 0x433528: drawing 0x43f338; on menu 7 page 10 back to 0, 100 to 10153 0x433580 (menu 7's item 0 and menu 3's list): page 0, 10, 100, then the pages not drawn, state 1 and menu 18. 259 0x4335f0 (menu 7's item 1): state 1, the race again
6 (one lap)148 0x433214: page 100 back to 10, others to 0; 0x42deb4152 0x4334b8: page 0, 10, 100, then the menu loop ends (returns 1) and the main menu follows (0x42a76c)
5 (ghost)147 0x433114: on page 0, 10 or 1000 page 0, view buttons 1 to 4 selectable, view 0x4fdc4c 1; page 100 back to 10151 0x4332d4: page 0 hides the view buttons and turns to 10, then 100, then 1000; from there with item 1 selected the race again (0x4301d4), else menu 18 with go selected and 0x6afb68 1. 197 0x433410 (item 4): the race again
  • Drawing (0x43f338 in time attack): page 100 is the best laps (0x4c4e24) for one lap and the best races (0x4c4fac) for the other two. The best laps page draws ENDRACE/RBESTL centred at y 70, TRACK with the title a line below, then one row per record (0x6a8c0c, 0x38 bytes a row, count 0x6a8f54) at x 50, without a bar. The table is inferred as each racer's best completed lap, sorted, at most 15 rows. Reading the track's RECORDS file would confirm it.
  • The "Top 5" panel (ported in screens/time_attack_panel.rs, the ghost slots in screens/ghosts.rs; the files in docs/formats/ghost.md). Its state is 0x6a72c8 (0 off, 1 ready, 2 the ghosts shown). Action 155 (0x4a6dc4): off or at 2, the panel is hidden and readied (0x4a6d9c); ready, 0x4a6db0 turns it off (0x4a6754) and shows the ghosts (0x4a6780). Showing them lights "Top 5" (0x42df08), sorts the circuit's five ghost slots by time (ta_ghost_list, 0x4b39c8), clears the mode's bits 0x2000 and 0x8000 and sets 0x4000, makes the row buttons (11 to 19, odd) and texts (20 to 29) selectable and the first panel button (6) state 2. The second (7) is state 2 when a ghost is chosen (0x6a72d0 ≥ 1), else hidden. It fills the rows (0x4a5d70), then gives the even row items (10 to 18) state 0, the buttons 1 and the texts −2, and sets the panel to 2.
  • The rows (0x4a5d70): row k (0 to 4) shows ghost slot k + 1. Texts 20 + k and 25 + k are emptied, then the slot is read. When the file reads, text 25 + k becomes the best of its three laps (0x4b3cec, 0x4b3c60). A negative time means no ghost (0x6a72b4[k] = 0); a chosen row is then unchosen: its pictures go back (0x4a5cec), the chosen count 0x6a72cc drops by one and 0x6a72a0[k] is cleared. The chosen ghosts 0x6a72d0 would drop only if the row held a ghost, but it was just marked empty, so they never do. With a ghost, text 20 + k is the total, two spaces and the ghost's name (the first entry's second string of its sequence, 0x4b3e40). The times print as hh:mm"ss'cc (0x4b5860) from the fourth character on.
  • Action 156 (0x4a6654), a row button (0x4a5c00 maps items 11, 13, .., 19 to rows 0 to 4 through 0x4eb6c4; other items do nothing): a chosen row is unchosen (pictures back, 0x4a5cec), any other is chosen and lit (0x4a5c5c: +0x7c and +0x80 0xc9, 0xcc, 0xcf, 0xd2, 0xd5). The chosen count follows, and so do the chosen ghosts when the row holds one. Then button 7 is state 2 with a ghost chosen, else hidden.
  • Handler 193's ghost branch: with the panel at 2 the slots are sorted again and the rows filled.
  • Actions 194 and 195 (0x4a76e0, 0x4a7d9c; ported in screens/time_attack_panel.rs, tested in tests/time_attack/ghost_io.rs): import Scope\Load\<title>[M].ght and export the chosen ghosts to Scope\Export\<title>[M].ght (docs/formats/ghost.md). Both open their files through 0x4afda0, which passes folder 11 to open_data_file; folder_path's folder 11 is Scope\ (0x4e5f88) after the root at 0x58e1b0 (observed; that the root is Directory is inferred). Each first sends message 0x433 (circuit & 3) or 0x431 (chosen count & 3) to the game's window and, on 0, posts WM_CLOSE and sleeps for ever, as action 77 does. All observed in the code:
    • 194 sorts the slots, then marks as kept each row that holds a ghost and is not chosen ([esp + 0x314]; it does not stop when such a row exists, which this file said before). A missing file, a version other than 1 (0x4eb6d8), a short header or a wrong magic word end it. Then it reads the best times (load_records, the circuit's row and 1 when the race's mirror flag 0x633350 is above 0) and, for ghosts 1 to the count (signed) while nothing failed: reads the time and size (a short read passes the ghost over); copies the sequence in pieces of at most 10,000 bytes (0x2710, a buffer from 0x4b63b8) into Load.seq in the ghost folder (0x4e7e2a, 0x4eb9a8; a piece that reads nothing fails the import, leaving that file); picks the slot from the back (0x4a7ac0: 5, else the last below whose row is not kept, else none and the ghost is passed over); deletes it (0x4afdec, DeleteFileA, 0 when deleted) and, when deleted, moves the kept marks and the rows' ghosts (0x6a72b4) after it down one, the last cleared; sorts; finds the work file's place (0x4b3690, its name in EAX) and, up to 5, inserts it (0x4b377c, -11 when the work file is missing), moving marks and ghosts from the place up and marking the place kept and holding a ghost; counts the chosen ghosts again (0x4a7bed). A ghost read without failure is merged into the best times (0x4b3f58: the slot whose number is the ghost's number in the bundle, read through 0x4b5404 and 0x4ae7e4, its first entry's second string as the name, merged with 0x4ad438, 0x4ad220, 0x4ad7b8; observed that it takes the slot by the bundle's number, not the slot the ghost went into). After one or more, with an Ubi.ini (0x4ea8c0), save_records. Then the bundle is closed, the slots sorted, button 7 state 2 with a ghost chosen (else -1) and the rows filled (0x4a5d70). The chosen marks (0x6a72a0) do not move with the slots. A ghost that finds no slot leaves Load.seq in the ghost folder (the sort deletes only FantBuf).
    • 195 does nothing without a ghost chosen. It creates the file, writes the version and 16 bytes of its uninitialised header buffer, then for each slot 1 to 5 whose row is chosen (== 1) and holds a ghost: notes the position, writes the time (0x4b3d80; not above 0, the slot is passed over), the size (the slot file's length) and the sequence in pieces of 10,000; a failed write seeks back to the noted position (0x4afef8) and goes on. At the end it writes the header at offset 4: the magic words, the count written and the circuit's number (0x633344). With no ghost written (or a failed header) it deletes the file. It changes no state and draws nothing.
    • Ported: both, but for the best times (load_records, 0x4b3f58, save_records: the port has no store of best times yet; noted as not ported when reached) and the messages. The bundle comes from Game::ght_load and goes out as Effect::File (Deviation: the host reads and writes the files). Deviation: an export with no ghost sends no effect, where the original deletes the file it created and with it any earlier export of that name.
  • Action 68's race writes (ported): lighting a choice also sets view record 0's first word 0 (0x634b58) and view record 1's 1 (0x634c2c), racers 0 and 1 humans (+0xd0 = 1, 0x6339c4, 0x633aa0) and 0x6afb68 1. The race's ghost set-up 0x4b23c8 (not ported) reads the last: with the mode's bit 0x10000 it saves the racer table, starts the racers' rotation 0x6b5d88 from 0 when 0x6afb68 is 1 (and clears it), else moves it on, and copies that racer over racer 0. It also marks humans 1 and computers 2 in +0xd0, and counts the chosen rows that hold a ghost.
  • The racer table (ported in crates/pod-ui/src/racers.rs): racers 0 and 1 (0x6338f4, 0xdc bytes each: name, two more texts at +0x40 and +0x80, the car +0xc0, the gearbox byte +0xc4, +0xc8 to +0xd8). The car screen's OK (0x42e7b0), once 0x4301d4 has returned 1, clears 0x4fdf24 when set and saves them (0x42e640, to 0x633fd4, with 0x4fdefa set). The save copies the texts with strcpy and the fields one by one, so the bytes +0xc5 to +0xc7 are not kept. The restore (0x42e4c0) runs only with 0x4fdefa set, and clears it. Action 153 sets 0x4fdefa before it restores, so the table always comes back on the way to menu 18. Action 259 with the bit 0x10000 does the same, then toggles 0x4fdf23 (go clears it) and copies that racer's whole entry over racer 0 (0x37 dwords), so the two players take turns. Action 197 with the bit restores only what was saved. Action 151's way back sets 0x6afb68 too.
  • The end pages' backgrounds (observed; ported in time_attack_end::background). 0x4fb28c and 0x4fb298 are the pixels and palette of picture slot 0x172 (0x4f9b6c + 0x172 × 16), the background of menus 5 and 6. 0x474f4c (menu_entry_pixels) returns the pixels of entry n of the menu file read last (0x5a35b4 plus the entry's offset at 0x59e7b8 + 16 n); 0x59f7b0 and 0x59f9b0 are that file's palettes 0 and 1. The width and height stay. Entry 0 with palette 0 is the file's slot 0; entry 1 with palette 1 is 6.IMG's slot 3 and 5.IMG's slot 10, both 640 × 480 (measured): the "statistics" background of pages 10 and 100. Entry 1 goes in on page 0 → 10 (actions 152 and 151) and on handler 147's page 100 → 10. Entry 0 comes back on handler 147's pages 0, 10 and 1000, and on handler 148's page 10 → 0. POD's goto_menu (0x42a628) does not read 5.IMG while 0x4fdaf0 is 10 or 100, nor 6.IMG while 0x4fdaf4 is not 0, so the swapped picture stays. pod-ui tested two fields that nothing set; it now tests values 36 and 37. Action 151's turn to page 10 also restores the racer table when 0x633322 & 1 (0x42e4c0), ported. Inferred: the file read last is the current menu's own; it is whenever goto_menu read it. tests/time_attack_pictures.rs checks both menus' pages.
  • Not ported: 0x4b6394. 0x633348, set to 1 at each step, whose readers are in the race. The test drive's return (0x4fde5e). The ghost files' own file-system layer (0x4adfac): the port keeps them in Game::fantome (a Deviation).
  • Tests: crates/pod-ui/tests/time_attack.rs with time_attack/panel.rs (slot names, the sort, the time text, 155 and 156, handler 193 with a ghost removed) and time_attack/racers.rs (save at the car screen's OK, two players' turns through 259, the restore of 153, the bytes the save leaves).
  • Page 0 of the three-lap type (0x4c3264; crates/pod-ui/src/screens/time_attack_pages.rs, data in time_attack_times.rs). It measures "0" (w, h) and draws, in font 2:
    • the circuit's title (reversed when mirrored, 0x633350) centred at x 225, y 74, and the player's name (0x6338f4 + 0x58dd88 × 0xdc) a line below;
    • the player's splits (0x4f8db0 + player × 0xbc + 0x10 lap + 4 k: record word 0x3c/4 + 4 (lap − 1) + k), one row per checkpoint (0x63333c, at most 4) from y 198, 40 apart; one column per lap (the race's laps 0x633330, at most 3) centred at 0x4ed414's 150, 280, 415 plus 60. Each split is format_time with "00'" blanked (strncmp with "00" over 2 characters, then strncpy of three spaces);
    • the lap times (record words LAPS + lap) on y 353 in the same columns, unblanked;
    • the race time centred at x 487 on y 410 (435 − 25). The time is +0x10 when the finished bit is set, else the last finisher's time plus 60 s (0x4f9360[0x4f938c] +0x10 + 0x3c0000), or 0 without finishers.
    • The bars and places. A split whose place in the track's split records (0x4ad5c8 on 0x6a8f58 + k × 0x16c) is 1 sits on a red bar (0xff2222) as wide as its text. Otherwise, when it equals the value the checkpoint's best-split pointer points at (0x4f93a4 + 4 k), it sits on a green one (0xeeffee). Laps work the same way with 0x4ad628 on the best laps (0x6a8c0c), and green when the best-lap pointer 0x4f93a0 is that very word. The race time is red with the best races (0x6a8818).
    • A non-zero place is printed as "(%u)" at draw_text's scale 1 (half size, blitter 0x4d6857: every second pixel of every second row), from centre − len × w / 4, half a line below the time. The race time's place is half a line below y 435, which puts it 25 pixels lower than the time's own baseline.
    • The page draws no headings: the labels come from the page's picture (not ported).
    • The split records, best laps and races are the track's best times after the race ("Best times"). The port reads the race's records and best pointers through Ui::race_over_with_times (RaceTimes::of). Without them it rebuilds the laps and totals from the results and draws no split rows (Deviation).
    • tests/time_attack_pages.rs draws the page from fixed records and compares it pixel for pixel with the strings and positions above. No POD capture of it exists yet.
  • Page 0 of the one-lap type (0x4c3a9c, same files). It draws the title and name centred at x 225 from y 76. Then one row per checkpoint (at most 4) from y 198, 40 apart, with three words of the player's record at +0x4c, +0x5c and +0x6c + 4 k:
    • the first, when not 0: format_time with "00'" removed (strncmp, then memmove0x4c771b), left-aligned at x 156;
    • the second, when not 0: the same at x 295, except that when the minutes are not "00" it is cut to 5 characters ("mm'ss"). When it is 0 the row ends there;
    • the third, when it is below 100 (signed): "L%u" at x 395.
    • The two times get a red bar of their width when their place in the checkpoint's split records (0x4ad5c8) is 1. A non-zero place is printed as "(%u)" at half size right after the text (x + len × w), on the row's own baseline (not half a line lower, unlike the three-lap page).
    • Then the lap time at +0x20, when not 0: centred at x 211 (156 + 55) on y 355, red when it is the best lap (0x4ad628 on 0x6a8c0c), with its place half a line below.
    • Unknown: what the race writes into these words in the one-lap type. The generic split and lap writers would put lap 2's splits at +0x4c and lap 2's time at +0x20, and a race of one lap ends before lap 2. So in the port, whose race has only those writers, the page shows just its title and name. The split table for this page is inferred: each racer's smaller non-zero time of the two.
    • tests/time_attack_pages.rs checks this page the same way as the three-lap page. No capture of it exists.
  • Page 10 of every type, the hall of fame (0x4c5414, time_attack_pages::hall_of_fame). It reads the track's records, which sit at 0x6a8818 as an image of RECORDS/<file>.BRT (pod_formats::records): the best races at +0 (count +0x348, laps a row +0x349), the best laps at 0x6a8c0c (count 0x6a8f54, sectors a row 0x6a8f55) and the sector tables at 0x6a8f58 + k × 0x16c. The layout:
    • TA_HOF centred at y 70, then TRACK with the title a line below, both on the screen's middle (0x621220 / 2). Call y1 = 70 + 3h.
    • Left column, centred at x 170 (50 + 120). BESTLAP on a blue bar on y1. A line below, "%s %s": the best lap's time and its holder (row 0). From y1 + 3h, 0x4c52d0 draws one line per sector of that row: "P%u %s", the time with "00'" removed, from x 170 − (len("PX XX.XX") × w) / 2. After each line comes its place in sector table i at half size; the place is 0x4ad748: the first row with the holder's name and that time.
    • Right column, centred at x 420 (300 + 120). BESTINT on a blue bar on y1 + h. From y1 + 3h, one line per checkpoint of the race (0x63333c): "P%u %s %s" from x 300, with row 0 of sector table k (its time with "00'" removed, then its holder). After each line comes, at half size, the place in the best laps of the lap that holds it (0x4ad6e4: the first row with that name whose sector k is that time).
    • Below the right column, at y2 = y1 + (checkpoints + 5) h: VIRT1, and a line below it VIRT2, both on blue bars at x 420. Five pixels and a line further comes the sum of the sector records.
    • Three laps only (the page returns first for one lap and for the ghost race): BEST3L on a blue bar at x 170 on y2. A line below, the best race's time and holder. Then, from 5 pixels and a line further, one line per lap of that row (0x6a8b61): the lap's time centred at 170. Its place in the best laps (0x4ad68c: down the rows, 0 at the first slower one, else the first row with the same time and name) is drawn at half size from x 50.
    • Every place is drawn on the line's own baseline.
    • The tables are the track's best times after the race ("Best times": the circuit's .BRT loaded, the race's crossings inserted). The emulated PC's install has no Data\Records (measured on the records screen), so its pages show the race alone; tests/time_attack_pages.rs starts with no RECORDS files for the same reason.
    • tests/time_attack_pages.rs checks the page against the strings and positions above. No capture of it exists.
  • Page 0 of the ghost race (0x4c40ec, crates/pod-ui/src/screens/time_attack_ghost.rs). 0x43f338 passes it the view (0x4fdc4c: 1 to 3, set to 1 by handler 147). Action 196 (0x43343c, view buttons 1 to 3) sets the view to the selected item's index and lights that button alone (0x4fdb08, 0x4fdb0c, 0x4fdb10).
    • Records. The ghost's record is the one after the racers (0x633324 + 0x633328). The record after the ghost's holds gaps, which the page shows as "kept" gaps.
    • Columns. The player's times are centred at x 286 and the ghost's at 402. Their gap is centred at 520 in font 0x4f9b2c, using that font's own "0" width. The port takes this font for font 1 (inferred from the order in which 0x49f358 makes the three fonts). The kept gaps start at x 522 in the same font.
    • Hiding the ghost. The ghost's columns and both gap columns are skipped when the mode has bit 0x8000 without 0x4000 (0x633321 & 0x80, & 0x40). Go's readying of the panel leaves exactly that state. Unknown: what the race does with these bits.
    • Name. The player's name is centred at 286 on y 56.
    • Lap rows on y 92, 284 and 324 (0x4ed420) show laps (v − 1 + i) mod 3 + 1, so the viewed lap comes first.
      • The player's lap is red when its place in the best laps is 1, and green when it is the race's best lap (0x4f93a0). Its place is drawn at half size half a line below.
      • The ghost's lap is red when it is a record, with its place.
      • The gap goes 8 pixels above the row. For laps after the first, the kept gap of the lap's last split goes 4 pixels below.
      • The gap text is format_gap with 0x4c4094's change. A " at position 4 becomes ' ("+ 05'85"). A minutes gap becomes "+ mm"ss" instead. When both pairs are "00" the sign becomes a space.
    • Split rows, one per checkpoint from y 126, 38 apart, for lap v. The player's and the ghost's splits have "00'" removed. Each is red when its place in the split records (0x4ad5c8) is 1, with its place. The gap goes 12 pixels above the row. The kept gap goes on the row itself, except for lap 1's first split.
    • Total row on y 362. The player's race time (the penalty when not finished) is red as a race record, with its place. The ghost's laps 1 to 3 are summed with no bar. Their gap goes 4 pixels above.
    • Pictures. 0x43f338 then blits three pictures with blit_paletted: the picture table 0x4f9b6c has 16-byte slots, so 0x4fb2bc, 0x4fb2cc and 0x4fb2dc are slots 0x175 to 0x177, 5.IMG's slots 3, 4 and 5 at the bank 0x172 (14 × 25, the laps' numbers). They go at (187, 74), (188, 272) and (188, 307) (0x4ea060), lap v's first, then the next two in turn, so they follow the lap rows. A view outside 1 to 3 draws none. Ported (time_attack_end::lap_pictures).
    • tests/time_attack_pages.rs checks the page with the ghost's columns shown and hidden. No capture of it exists. The port's race has no ghost, so outside the test the ghost's columns read zeros.
  • Not ported: the ghost import's best times (load_records, 0x4b3f58, save_records in 194 0x4a76e0). 0x4b6394. 0x633348, set to 1 at each step, whose readers are in the race. What action 68's lighting writes for the race (0x634b58, 0x634c2c, racers' +0xd0, 0x6afb68). The racer table's save (0x42e640, from the car screen's OK 0x42e7b0 when 0x4fdf24 is set), its restore (0x42e4c0 when 0x4fdefa is set) and the racer swap (0x4fdf23) of 259 and 197. The test drive's return (0x4fde5e).
  • Unknown: what the race does with the mode's bits 0x2000, 0x4000 and 0x8000.

The single race, end to end ​

From the main menu: "Un joueur" (action 36) sets one player and a single race; "Course simple" (13) sets the mode (0x61d5b8 = 9) and the parents; the track screen's handler lays the circuits out; a digit (189) chooses one and selects OK; OK (159) goes to the car screen and writes Player1DefaultCircuit; the car screen's OK is its list item, whose action is the shown entry's (49 when 0x506e10 is 10, as "Un joueur" leaves it); it writes Player1DefaultCar and starts the race. pod-ui reports this as Effect::StartRace with a RaceSetup (circuit, file, laps, difficulty, mode flags, cars) and the Ubi.ini writes as Effect::Ini; tests/flow.rs drives it from the main menu.

The catalogue ​

derived/ui/catalogue.md (the catalogue agent's, not in git: it holds the captures' names) describes every screen reached, with its keys and mouse behaviour, and lists what was not: the loading screen (too short to capture), the race-end menus and the results (no race was finished), menus 25, 54, 42, 43, 45 and 60. Menus 8, 12, 19 and 41 never appear and are probably unused (inferred). No action wrote a file in the emulated PC: the game runs from the CD and its Data\ folders are the install's, which has none (measured), so the files the screens write (.BRT, Params\<car>.prb) are still to be seen being written. F1 showed no help on the reliable instance's main menu, quit and records captures, as the held-key test predicts for an F1 still down.

Open questions ​

  • Why Down from the car screen's right arrow stays on it in the original, then goes to "Charger voiture": the neighbours read from the running game say otherwise.

  • Which code makes the main menu's items 3 and 6 differ from the file.

  • Answered: the records grid stays empty in the emulated PC because fill_records_page (0x42dfc4) clears the track block 0x6a87dc before each load_records, so default_records fills nothing, and the install has no Data\Records ("Best times").

  • The palette of the menu fonts: converted at load by the mode-8 texture reader (FUN_0046a7bc); measured to match screen_colour's rounding, not traced.

  • The key bindings: the options screens (11, 13) and the Ubi.ini reader own the player's key map at 0x60bbdc (DirectInput codes; slot 0 accelerate, 1 brake, 2 and 3 steer, 7 and 8 the two buttons; 4 to 6 not read by the controller) and the device table at 0x60b428, which pod-ai takes as pod_ai::human::Controls (observed by the controllers session).

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