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 field | Meaning | Read by |
|---|---|---|
+0x46 | Handler, run by goto_menu after the menu becomes current | FUN_00438168 |
+0x48 | Parent: where Esc and "back" lead; set by a link to the menu that followed it | FUN_0043a344, FUN_0043e9cc |
+0x4c | The background picture's slot | FUN_0043b4d4 |
| Item field | Meaning | Read by |
|---|---|---|
+0x00 | Top, bottom, left, right; the mouse test is strict on all four sides | FUN_00436658 |
+0x10 | Text, 0x40 bytes (text items) | FUN_00439c44 |
+0x51, +0x52, +0x53 | Text alignment (0 left + 5, 1 centred, 2 right − 5), size shift, font | FUN_00439c44 |
+0x58 | Sound played when the item is used | FUN_0043a344 |
+0x60, +0x62, +0x64, +0x66 | The item reached by Up, Down, Left, Right; 999 = the item selected before | FUN_00439e4c |
+0x68 | Action | FUN_0043a344 |
+0x6c | Non-zero: holding Select repeats the action every 50 ms after 500 ms | FUN_00436658 |
+0x70 | State: −1 hidden, −2 drawn but never selected, 0 drawn, 1 and 2 selectable | everywhere |
+0x74 | Type, below | FUN_00439464 |
+0x78..+0x84 | Picture slots by state; type 1 links to menu +0x78, type 11 to +0x84 | FUN_00439464, FUN_0043a344 |
+0x88 | The text of field items (7) and labelled pictures (12) | FUN_00439464 |
+0x80, +0xa8, +0xd4 | Lists (type 10): each entry's value, picture and action; +0xd0 the picture shown | FUN_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 | −2 | 0 | 1 | 2 |
|---|---|---|---|---|
9 button (table 0x42b964) | +0x78 | +0x78, pressed +0x7c | +0x7c, pressed +0x80 | nothing, pressed +0x80 |
11 link (0x42ba27) | as 0 | +0x78, pressed +0x7c | as 0 | nothing, pressed +0x7c |
4 choice (0x42bb6d), lit (+0x7c ≠ 0) | +0x84 | +0x84, pressed +0x80 | as 0 | +0x84, pressed nothing |
| 4 choice, unlit | +0x80 | +0x80, pressed +0x84 | as 0 | nothing, pressed +0x84 |
12 labelled (0x42c21a) | picture, then the field | picture, then the field | as 0 | the 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):
- 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. - Esc: on the root menu, the quit confirmation (menu 1, the file's second word
0x502ed4); elsewheremenu_back(FUN_0043e9cc; POD0x430368, called from Esc at0x42d040behind the key latch0x4fdf3cand as action 5), which does the menu's clean-up and goes to its parent (see "The track screen's mirror andmenu_back"). - When the mouse has moved, the selectable item under it is selected.
- Tab and Shift+Tab select the next or previous selectable item round the menu.
- 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). - 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 inFUN_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).
| Style | Drawing | Colours |
|---|---|---|
| 0, 1, 4 | A 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, 3 | Two one-pixel boxes, one colour blending in 16 steps that bounce (0x60a9b0) (FUN_00491e3c) | 2: 0x3f00 to 0xc000; 3: red to yellow |
| 5 | FUN_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
| Menu | Screen | Handler, actions | State in pod-ui | Frames matched |
|---|---|---|---|---|
| 0 | Main menu | 43 FUN_0043ffa2 | Ported; difficulty list; "Un joueur" (36) sets a single race | 30, with its help frames |
| 1 | Quit confirmation | 25 FUN_0043e814; "Oui" 1 FUN_0043fa7c quits | Ported | 6 |
| — | Help (F1) | 61.IMG, slot 0x12b | Ported | counted with each menu |
| 35 | Records | 242 (POD 0x42e2d0), 243, 244, fill 0x42dfc4, drawing 0x43c5c0 | Ported, the files read ("Best times"); the mirrored circuits' names not drawn | 12 |
| 10 | Options | POD: 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 0x42df88 | Handler and actions ported (screens/options.rs, below), the typed fields through the editor (below); not ported: the cheat flag 0x5001ec | 1 |
| 16 | Race mode | 257; "Course simple" 13 FUN_0043e870 | Ported: the single race, the championships (below) and time attack (item 6, action 154, below) | 13 |
| 17 | Track choice | POD: 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_00450838 | Single 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 ported | 13 |
| 24 | Car choice | 42 FUN_004405dd, drawing 0x44b640, arrows 56, 57, OK 49 FUN_0043c71c | Handler (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) |
| 32 | Pause | 78 FUN_0044196c; 3, 160 FUN_00441b70, OUI 161 FUN_00441d74, NON 162 FUN_00441ed8 | Ui::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 |
| 25 | Car swap (POD) | 44 0x4308ac; 220 OK 0x43115c, 221 slot 0x430b68, 222/223 arrows 0x430c54/0x430c9c, 224 swap 0x430ce8, 225 reset 0x430f1c; drawing hook 0x43c844 | Ported (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) |
| 26 | Garage | POD: 178 0x436b10 (via 255 0x436ae4 for the second player), 179 to 186 (0x4371f4, 0x4374f4, 0x43762c, 0x437708, 0x4378e0, 0x437bac, 0x4383d0, 0x437dd4), chart 0x435e54 | Ported for one player (below): the settings, the star chart, typed values, load and save of Params\<car>.prb; the test drive not ported | 1 (item 28 open) |
| 4 | Single race end | POD: 146 0x4330f4, 150 0x43328c, pages 0x4c0dc8, 0x4c4fac | Ui::race_over, the ranking and the best races pages (below) | 2 |
| 18 | Time attack (POD) | 193 0x4a6f90; 15, 14, 77, 256, 192, 68, 247, 155, 156, 194, 195 | Ported, with the "Top 5" ghost panel, its import (194) and export (195) but for their best times (below) | — |
| 7, 3, 6, 5 | Time attack race ends (POD) | 149 0x433528, 148 0x433214, 147 0x433114; 153, 259, 152, 151, 197, 196 | Page turning and the way back ported; every page 0 and page 10 drawn; their pictures not (below) | — |
| 18 | Time attack (POD) | 193 0x4a6f90; 15, 14, 77, 256, 192, 68, 247, 155, 156, 194, 195 | Ported, with the "Top 5" ghost panel but for its import (194) and export (195) (below) | — |
| 7, 3, 6, 5 | Time attack race ends (POD) | 149 0x433528, 148 0x433214, 147 0x433114; 153, 259, 152, 151, 197, 196 | Page 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, 20 | Championship race end, next track | POD: 170 0x434650, pages 0x433f58, 163 to 168, 172, 250; drawing 0x43f74c, 0x43f83c | Pages 0 to 5's states, pages 1 and 2 drawn, menu 20's map and titles (below); team pages not ported | — |
| 21 | First track's view (POD) | 254 0x43c17c; go 169; drawing 0x44046c; reached from action 49 0x42e7b0 | Ported (below, "Menus 21, 41 and 44"); 41 unreachable, 44 absent | — |
| 60, 20 | Championship race end, next track | POD: 170 0x434650, pages 0x433f58, 163 to 168, 172, 250; drawing 0x43f74c, 0x43f83c | Pages 0 to 5's states, pages 1 to 4 drawn (3 and 4 the team pages), menu 20's map and titles (below) | — |
| others | Multi-player | Engine 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+0x80and+0x84both the digit's picture (the table at0x4eb492: 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+0x78picture. It loads the chosen circuit's map (case 200) and makes item 59 selectable when list value 48 (0x4fdb20) is 0. - With
+0x80equal 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) andDamage(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 writesDamage. - 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) writesMusicVolumeandSoundFxVolumeand 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 writeStereo. Each shows the other entry afterwards (values 43 and 42). - Dolby (208, 209) lights ON or OFF, switches it in the sound (
0x4bfb5c) and writesDolby. - Opponents (206, 67) step the count round 0 to 7 into item 23's text;
NbConcis 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
0x4ffe64to0x4ffe74(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;0x434dccon0x4fe2e8 + car × 0x1a8). After every changesetup_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-uihands the five values on inRaceSetup::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 (0x4a9e88of 0x140101), then ten triangles that grow with the settings (each coordinatebase + (setting × k) / 100, the setting held to 100) in green (0x118821), all filled by0x43567con 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-garagethe 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 that0x431eb8hides 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, layout0x4399cc): 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'sUi::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 sectionKIBOARD, 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-uiwaits 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 writeKeyboardAccelerate,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,LetterBoxandDisplayMode(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) setsDisplayMode= 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 (LevelViewobserved, the others inferred). Not ported: the drawing hook0x440908and 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, thenintropod.AVIwhenPlayIntroAtStartis on. Ported:Ui::startgives them asEffect::Video. - A championship's end (
0x49c4c4):finpod.AVIthencredits.AVIwhen0x49c494is true (inferred: a won championship), elsefinpod2.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 count0x633340, -1 past the last) the circuits whose+0x7cis 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 end0x473e18): 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 at0x4f9364, their count0x4f938c) plus 60 s (0x3c0000). Then0x473798sorts the race order (0x58b6d8, the racers by place) and0x4736d8the 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; after0x43c138cleared it, racer 0 first). The sort is Watcom'sqsort(0x4c8e22, not traced; the port keeps still-equal racers in racer order, inferred). The leader is0x58b6e0[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:0x433f58page 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 sorts0x4787dc,0x47875care 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 as1 << (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' clear0x46c3e8is a memset of 0x68 bytes from0x58b690: the totals, the points and the orders (the standings0x58b6e0among them). - Laps: action 19 writes 3 to
0x633330, but the race set-up0x4284a8writes the circuit's laps (+0x84) over it outside time attack (0x633320 & 4; time attack takes 3, or 1 when0x633321 & 2: see "Time attack"), so every championship race runs its circuit's laps and a save holds the last set-up's laps. - Mirror:
0x4284a8sets the race's mirror flag0x633350from the race's bit of0x4fdf16in 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 reader0x46bc60, back from menu 17 (0x430368, clears it and0x4fdf18), the mirror item's action 66 and0x44d398(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 → slot0x4fdb14), 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 withstrrev0x4c840f), 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 and0x4a4654("Best times") and the drawing function0x43fedcin 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 drawsrand() % circuits(0x62d22c), one per entry, the first four raced; then0x42f334: the mask cleared and, with the difficulty's prize, one drawrand() % 2per race whose circuit has+0x7c & 2, added at the race's bit.randis 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), drawing0x43d514. Inferred unreachable: only menu 41 links to it, nothing links to 41, and no code holds its pointer0x4f9a68or passes 19 togoto_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 inBETAMNU.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) clears0x4fdef9; 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. Then0x4fdf18= 1,0x440ff8(2)(not ported) and0x500af4= 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 bit0x633340(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 sets0x500af6= 1, then the track block0x6a87dc(0x38 bytes) is cleared andload_records(0x4ad118) runs with0x6a729d(ported, "Best times"). sets0x500af6= 1, then the records buffer0x6a87dc(0x38 bytes) is cleared andload_records(0x4ad118) runs with0x6a729d(the records: not ported). - The flags:
0x500af5draws the map mirrored (read by0x440f0c,0x4414a8,0x441a74),0x6a729dpicks the mirrored circuit's records,0x500af6asks0x441f98to 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 clear0x6a729d; another item showing entry n (0x4a4058) sets it from bit n − 1 of the mask; then0x500af5=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 bit0x633340(0x500af50), in a single race the whole mask (0x500af51).0x4fdf18is written (handler 76: 1; 159 and back from 17: 0) and, observed, never read by address.0x4fdef9only matters after handler 76 has emptied the list, so it changes nothing. menu_back(0x430368), in order: menu 24 with0x633320 & 0x80sets0x4fdef9; menu 17 clears0x4fdf18and the mask; menu 20 decrements the race (0x633344); menu 24 in a network race (0x63331c & 4) with0x633321 & 0x10returns; 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), runs0x430274, sets 20 to 27 to 0, selects item 3, clears0x4fdf22and 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 with0x5ab324 & 0xego elsewhere, 51 and 54 call0x489fc8(not ported); menu 60 returns; any other goes to its parent (+0x48). Deviation in the port: menu 20's race stops at 0.0x44d398is 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 sets0x633320and its network copy0x5aafc8to (bits | 0xa) & ~1, clears the race (0x633344),0x5aafd8and0x4fda74; for each of the first min(circuits, 16) rows with+0x7c≠ 0 the entry k is0x543228 + k(also at0x5aaff8) and item 37 + k's+0x7cand+0x80are 0x102 + 3k; then0x5aafdcand the list length0x633340are 16, the mask 0. Not ported: the multi-player menus are engine-only.- The mirror's typed code (
0x4a5bb0, called at0x46ce1a): 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 (0x6333341, 2, 4 →0x6345d4,d5,d6; another difficulty none) and list value 48 to 0 in every case. Ported asmirror::code_typed, run fromcrate::codes. - Keys typed as words (
crate::codes): the DirectInput keyboard read (0x4a16b1, its buffered events) hands each key press to0x46ccb0(0x4a1790), skipped while Left Alt is held (0x621980= the held-key table0x621948+ 0x38), the four arrows as 0.0x46ccb0ignores keys with bit 0x80 and a key equal to the newest recorded (so a doubled letter counts once); otherwise the 256-byte history0x58d5b0moves up, the key XOR 0x96 goes first and the new-key flag0x58d5a8is set.0x46ccf8(fromdraw_menu0x42d8ab, when0x62103cis clear, and from the race loop0x461212) 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 by0x46c9d0(0x58d560, 7 bytes;0x58d590,0x58d580,0x58d588,0x58d5a0, 5 each); the fourth,0x58d590, calls0x4a5bb0. It is built from the template0x4ea868with its last byte the layout's M (0x58d525=0x58d50cXOR 0x96): the keys M, I, R, O, R by position (scan codes 0x32 or 0x27, 0x17, 0x13, 0x18, 0x13). The layout comes from0x46c400:GetKeyNameTextAof 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 to0x441f98with mode 2 (menu 51's item 77 gets mode 0, the animated path; mode -1 is a plain draw). Mode 2: with0x500af4set it is cleared and0x44193csets the map up at once (the picture into the buffer0x5173f0and the target0x5242b0, mirrored when the records' flag0x6a729dis set, its palette to0x522fd0); with0x500af6set it is cleared and the squash starts (0x500b28= 1);0x441bb4runs one frame of it (close 15 rows a frame, then 50 columns, load the picture squashed with0x441a74(1), open 50 columns, then0x441a74(0) into the target and 15 rows a frame, and at the end the target copied into the buffer); the buffer is drawn with0x49da90centred in the 288 × 167 box by its current size (0x500b30×0x500b2c).0x441a74mirrors by0x500af5at 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 redraw0x500af6: action 66 (every branch past the circuit test), 65 in a single race (and0x4a45bc), 189's end (0x4a4948: 0 while handler 76 runs it,0x6a729c, else 1), 252 (0x4a4e4c) and 253 (0x4a5023). Of the set-up0x500af4: handler 76's tail (0x4a5624) and0x44f908(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+ 0x273besides the loaders and menu 20); the port sets it on each choice, which matchespod-tracks-page2. - The title (
0x43fedc, the track screen's drawing function, POD's form of the port'sdraw_track_info): the circuit's name (+0x14) reversed withstrrevwhen0x6a729dis set, in every mode (both branches at0x43ff20test it). Not ported: the record times (0x6a8f54/0x6a8c20in time attack,0x6a8b60/0x6a882cotherwise), 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 at0x4eb432 + 2 (n mod 16), the digits table for 1 to 15 and for the 16th name the high half of the lit item0x4eb430(−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 always0x500af6= 1 and0x4a45bc(n), which reads the row0x633350 + 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.rschecks 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'spod-single-race-tracks-mapandpod-tracks-page2still 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) sets0x4ffd98(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 (0x46c2d8with 0; no save, an empty field). Item 16 is back (action 5). - Action 176 (
0x43497c), a slot: the field editor (0x42eb80,EDX,EBX,ECX0) 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 and0x46b70cwritesPOD<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 (
0x46c2d8with 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 (0x46bc60withDL0: 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 (0x46bc60withDL1); when it loads, sorts the standings (0x473e74:0x473798on0x58b6d8,0x4736d8on0x58b6e0, then0x473c52in 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_menucall names them (a scan of every call to0x42a628). 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 both0x42f0dc, which returns 0; action 12 (0x43023c) sets bit 0 of0x634da9and0x4f9b40= 3. Menu 43 (stats) has handler 60 (0x431e98: the drawing function0x43cdf0). 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_recordsfor the next track and0x421bb0after 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 flags0x6345d4to0x6345d6.
Menu 60 and menu 20 in POD (the championship's race end and next track)
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 in0x4fdc38; jump tables0x433f28for the page number,0x433f40for 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, with0x633321 & 0x18or split screen;0x4ea8c0set), 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). Then0x433eechides 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, runs0x46dd48and 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 by0x431eb8in every mode): page 10x4c1404, 20x4c1c8c, 30x4c2130(TEAMRAN), 40x4c2b80(TEAMGEN), 0 and 5 nothing (table0x43f738). Text as on the race end's pages: font0x4f9b30, "0" measured forwandh, centred texts at x − (len × w) / 2. - Page 1 (
0x4c1404, this race):PILORANcentred at y 70,TRACKwith the title (reversed for a mirrored race,0x4c840f) a line below; two lines further the headingsRANK(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, elseDESERTD,OVRTIMEor 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 (0x4ad628on0x6a8818) 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,RECLAPat x 50 and the track's record lap (0x6a8c20) at 436; a line further, when a car did not finish,PENALTYand the penalty. - Page 2 (
0x4c1c8c, the standings):PILOGENcentred at y 70; a line belowRANKX1after the first race, elseRANKX2, with the races run; the headingsRANK,NAME,POINTS(points championship, at 306) andTIME(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
0x6d46f8before 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 with0x6a8814set). 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_imagescase 200, slot 0x273 + 3 t;0x5006ec=0x634594[race]) and for a mirrored race (bit race of0x4fdf16) mirrored by0x440f0c: 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 to0x522fd0. 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 writes0x634590 + 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.
Menus 21, 41 and 44 in POD (the first track's view and two dead menus)
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 onlygoto_menucall with 21 (0x42e9dc; a scan of every call to0x42a628). After the set-up andPlayer1DefaultCar, 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; otherwise0x4301d4starts 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 with0x633321 & 0x10(0x1000, the random championship),car_okmakes the car screen's items 2 (back) and 3 (gearbox) selectable (0x42e925). - Handler 254 (
0x43c17c): the drawing function0x44046cinstalled (0x4f9b5c); item 2 state 1, or 0 with0x633321 & 0x10; the garage menu's parent (+0x48of menu 26,0x4f9a84) the current menu;0x4fde5acleared when set (a garage flag also read by handler 178 and cleared by 173, tied to split screen's second player0x4f9b60; not ported). It does not set item 1's state (the file's 0) nor0x4fdef8(173 does both). - Drawing
0x44046c: menu 20's (0x43f83c) with its own globals (0x500988to0x5009b0) and two differences: the map (load_menu_imagescase 200, circuit0x634594[0x633344]) is not mirrored for a mirrored race (no call to0x440f0c), 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_menucall passes 0x29 and no code holds its table entry0x4f9ac0. 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) reads0x4f9a1c[n]and its+0x44without 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 & 0x20is set only by0x43e014(fiveorwrites at0x43e1fcto0x43e39a), 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 is0x633338; the pages show0x633324 / 0x633338racers a team (the humans over the teams; with no team, nothing). Who writes+0xc8and0x633338for teams is unknown (the multi-player code, not traced). The port keeps them inChampionship::teamandteams, set by a host. - When:
0x473e74(after each race, from0x473e18; and from action 163 and the save's resume 188) sorts the race order and the standings, then in team mode jumps to0x473c52.0x43c138and0x46c3e8clear the two team orders with the others. - This race by team (
0x473b38on0x58b6e8): 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 byqsort(0x4c8e22) with0x473970(points championship) or0x473a54: same team, by place (the high nibble of0x4f8d88); else team points (more first) then team time (less first), or time then points; still equal, by the last team standings (0x4735f0on0x58b6f0, 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, on0x58b6f0): the championship points (0x58b6b0) and totals (0x58b690) summed per team; comparator0x4737d0or0x473880: 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):TEAMRANcentred at y 70,TRACKwith the title (reversed when0x633350) a line below; two lines furtherRANK(56),TEAM(140),PILRANK(238),TIME(316),BEST(446) and in a points championshipPOINTS(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 of0x58b6e8from k × size: the player's line (0x58dd88, or0x58dd8cin split screen) on the blue bar from x 88 to 446 + 4w (515 + 3w in a points championship; the end read from0x6d46f8before "0" is measured, as on page 1); the name at 88; its race place (0x473d24on0x58b6d8,%u) centred at 238; the time text (0x4c0a68, which returns 1 when finished, else 0) centred at 316 on a red bar when0x4ad628on0x6a8818gives 1; the best lap (0x4731dc) at 446, red when first in0x6a8c0c, 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 %3dwith the team's sum. One line a racer, 5 more after a team. Then 2 lines downRECLAPat 50 and the record lap (0x6a8c20) centred at 446; when a shown racer's time text returned 0, a line furtherPENALTYand the penalty. - Page 4 (
0x4c2b80):TEAMGENcentred at y 70;RANKX1(first race) orRANKX2with the race number + 1 a line below; the headings as page 3's withoutBEST; team by team,%u)at 56, then each racer of0x58b6f0from k × size: the bar as on page 3, the name at 88, its standings place (0x473d24on0x58b6e0) 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 %3uwith 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 function0x43f338draws page 0 with0x4c0dc8and page 10 with0x4c4facin a single race. - The text is
draw_text(0x48ba20, baseline at y) in the menus' font 2. Each page measures "0" (0x48c054): its widthwplaces text as if every character were that wide (centred at 320 − len × w / 2, right-aligned at 560 − len × w), its heighthis the line step. - Ranking (
0x4c0dc8):ENDRACE CLASSTcentred at y 70;TRACK("TRACK : %s") a line below; two lines further, one line per car (up to 8):%ld) %sat x 50 and the winner's time or the others' gap right-aligned at 560; three lines furtherBESTLAP+ " : " + the holder and the time; a line below, when the player does not hold it,YOURBSTand the player's best lap. A car that did not finish showsDESERTDorOVRTIME(not ported). - The gap (
0x479050): "+ " (or "- ") andformat_time, the minutes cut when they are "00" ("+ 05"85"); zero gives+ 00"00. Not ported: "+ 1 lap" and "+ n laps". - The bar (
0x4c0d70with 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):RBESTXLwith 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.BRMfile 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):0x4a9b98copies the name and replaces the text after its last.(one that follows the last/or\) withBRx, or appends.BRx; it ignores the ECX = 8 its callers pass. The last character then becomesTin time attack (0x633320 & 4), elseEfor difficulty (0x633334) 1,Hfor 4,Motherwise; with the mirror flag the first character becomes-. So a single race on Beltane at medium isBELTANE.BRM, which the disc does not ship: POD's disc has only the 16.BRTfiles (measured). - Load (
load_records,0x4ad118; EAX the name, EDX the mirror):0x6a8814cleared, the file opened (block 0x1000, key 0xEAFA) and 0xE5C bytes read to0x6a8818. A missing file callsdefault_records(0x4acce0), which fills the tables only in time attack and when the track's 0x38-byte block (0x6a87dc, read from the track file by0x46a1bc) has non-zero words at+0and+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 inrace_reset(0x41eb5e, the name0x62c628from the track file, the mirror0x633350 > 0); the track screen (handler 76, actions 189 to 191, 66), the records screen (0x42e0ac,0x42e0f5,0x42e242) and0x4a7949load 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, or0x6a8814set, 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 < 14memmove(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 bystrcpy(bytes after its zero stay), the time at+0x14, the words at+0x18, and the table's byte+0x349the 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 first0x63333cwords of each lap go to0x6a8b7c,0x6a8bb4,0x6a8bec. The word copy isf2 a5(repne movsd, which runs asrep; capstone prints it as a plainmovsd). - 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 at0x4805ee; the old label0x41f629is inside it), at each lap-line crossing after the first, the lap's last split into table0x63333c− 1 (0x41f40b) and the lap time with that lap's splits (+0x3c + 0x10 (lap − 1),0x63333cwords) into the best laps (0x41f479); at the finish (crossings past0x633330) the best splits (0x41f931, the clock0x4f9384, record+0x3c,+0x4c,+0x5c) and the race time+0x10with the laps from+0x1c(0x633330words) 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): unless0x6a8814is set, the file is created with the mirror0x633350 > 0and the 0xE5C bytes written (block 0x1000, key 0xEAFA).race_end(0x473e18) calls it with0x634db0(the race's circuit name, copied from0x633354[race]by0x4284d2) when0x4ea8c0is set (1 in the data); time attack calls it again at0x4a7c89. - 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);0x6a87dccleared, then with0x633320set to 1 the single race's file (.BRE/.BRM/.BRHby the difficulty) gives column 0 row 0 of the best races; set to 4 the.BRTgives column 1 the best race and column 2 the best lap;0x633320is 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 inscreens/records.rs::fill; tested bytests/best_times.rs(the_records_screen_reads_the_files). The frame test's capture (pod-records) has an empty grid: the test drops theRECORDSfiles, as that install had none. - The track screen (observed): actions 189 (
0x4a493c, single race and time attack), 252 (0x4a4ea9), 253 (0x4a5078) and 66 (0x4a5b19) clear0x6a87dcand load the chosen circuit's file (0x633354[0x6a7290], mirror0x6a729d, 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 at0x440176) draws, outside time attack, the best race's time (0x6a882c) centred in the fourth box when0x6a8b60is non-zero, and in every mode the best lap's (0x6a8c20) in the fifth when0x6a8f54is (else empty texts). Ported (best_times::load_track,race::draw_track_info); tested bythe_track_screen_loads_the_chosen_circuit. Not ported: the loads of action 190 (0x4a4b19) and0x4a4654(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 asEffect::FiletoRECORDS/<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.pyruns POD's0x4ad220,0x4ad438and0x4ad7b8under Unicorn (withstrcmpas is; Watcom'smemmovedone in Python, as itspush es/pop estrips the emulator) on the 65 operations ofcrates/pod-ui/tests/data/records_ops.tsv: fromBELTANE.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,memmoveover unused rows, the appending branch taking a repeat that the moving branch refuses, filling past 15 rows).crates/pod-ui/tests/best_times.rscompares 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'sRECORDS/<STEM>.BRMbeing 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 ofload_menu_images0x474f88, car n'sMIMG/<name>.IMGat slot0x258 + 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 fromVOIRESET.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 hook0x43c844draws 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.BINandVOITURE2.BIN(write_car_list0x47209c: the count and the counted records, so POD's 15 uncountedVOITURE2.BINrecords are dropped the first time), clears0x4ffd68[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 bit0x63331c & 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 runs0x4a7054again and goes to menu 18. The car menu's parent stays as it was.Player1DefaultCircuitis written after eithergoto_menu. In the port a championship under way ends here, because the port keeps it asGame::championshipwhere 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, aspod-uidoes (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.rschecks this. - Handler 193 (
0x4a6f90) titles the menu with the chosen circuit (0x633354[0x633344] + 0x14), reversed when any mirror bit of0x4fdf16is 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.
| Action | Address | Type | Mode 0x633320 | Laps 0x633330 | Choices |
|---|---|---|---|---|---|
| 15 | 0x4a73d8 | three laps | 0x14 | 3 | 30 selectable, 31 hidden |
| 14 | 0x4a7310 | one lap | 0x214 | 1 | both hidden; panel off |
| 77 | 0x4a74a0 | ghost race | 0x104 | 3 | 30 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 clears0x4fdf23and sets0x4fdf24for the three-lap type (cleared otherwise), then goes to the car screen (24). Back (192,0x4a72f4): the panel off (0x4a6754) and hidden (0x4a6ab8), thenmenu_backto 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 source0x4fda5cto 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, at0x428527and0x42861d): in time attack 1 when0x633321 & 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,
0x4295b4at0x429af2): 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).
| Menu | Handler | Actions |
|---|---|---|
| 7, 3 (three laps) | 149 0x433528: drawing 0x43f338; on menu 7 page 10 back to 0, 100 to 10 | 153 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; 0x42deb4 | 152 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 10 | 151 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 (
0x43f338in 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 drawsENDRACE/RBESTLcentred at y 70,TRACKwith the title a line below, then one row per record (0x6a8c0c, 0x38 bytes a row, count0x6a8f54) 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'sRECORDSfile would confirm it. - The "Top 5" panel (ported in
screens/time_attack_panel.rs, the ghost slots inscreens/ghosts.rs; the files indocs/formats/ghost.md). Its state is0x6a72c8(0 off, 1 ready, 2 the ghosts shown). Action 155 (0x4a6dc4): off or at 2, the panel is hidden and readied (0x4a6d9c); ready,0x4a6db0turns 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 count0x6a72ccdrops by one and0x6a72a0[k]is cleared. The chosen ghosts0x6a72d0would 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 ashh:mm"ss'cc(0x4b5860) from the fourth character on. - Action 156 (
0x4a6654), a row button (0x4a5c00maps items 11, 13, .., 19 to rows 0 to 4 through0x4eb6c4; other items do nothing): a chosen row is unchosen (pictures back,0x4a5cec), any other is chosen and lit (0x4a5c5c:+0x7cand+0x800xc9, 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 inscreens/time_attack_panel.rs, tested intests/time_attack/ghost_io.rs): importScope\Load\<title>[M].ghtand export the chosen ghosts toScope\Export\<title>[M].ght(docs/formats/ghost.md). Both open their files through0x4afda0, which passes folder 11 toopen_data_file;folder_path's folder 11 isScope\(0x4e5f88) after the root at0x58e1b0(observed; that the root isDirectoryis inferred). Each first sends message 0x433 (circuit & 3) or 0x431 (chosen count & 3) to the game's window and, on 0, postsWM_CLOSEand 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 flag0x633350is 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 from0x4b63b8) intoLoad.seqin 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 inEAX) 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 through0x4b5404and0x4ae7e4, its first entry's second string as the name, merged with0x4ad438,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 leavesLoad.seqin the ghost folder (the sort deletes onlyFantBuf). - 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 fromGame::ght_loadand goes out asEffect::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.
- 194 sorts the slots, then marks as kept each row that holds a ghost and is not chosen (
- 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) and0x6afb681. The race's ghost set-up0x4b23c8(not ported) reads the last: with the mode's bit 0x10000 it saves the racer table, starts the racers' rotation0x6b5d88from 0 when0x6afb68is 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+0x40and+0x80, the car+0xc0, the gearbox byte+0xc4,+0xc8to+0xd8). The car screen's OK (0x42e7b0), once0x4301d4has returned 1, clears0x4fdf24when set and saves them (0x42e640, to0x633fd4, with0x4fdefaset). The save copies the texts withstrcpyand the fields one by one, so the bytes+0xc5to+0xc7are not kept. The restore (0x42e4c0) runs only with0x4fdefaset, and clears it. Action 153 sets0x4fdefabefore it restores, so the table always comes back on the way to menu 18. Action 259 with the bit 0x10000 does the same, then toggles0x4fdf23(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 sets0x6afb68too. - The end pages' backgrounds (observed; ported in
time_attack_end::background).0x4fb28cand0x4fb298are 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 (0x5a35b4plus the entry's offset at0x59e7b8+ 16 n);0x59f7b0and0x59f9b0are 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'sgoto_menu(0x42a628) does not read 5.IMG while0x4fdaf0is 10 or 100, nor 6.IMG while0x4fdaf4is not 0, so the swapped picture stays.pod-uitested two fields that nothing set; it now tests values 36 and 37. Action 151's turn to page 10 also restores the racer table when0x633322 & 1(0x42e4c0), ported. Inferred: the file read last is the current menu's own; it is whenevergoto_menuread it.tests/time_attack_pictures.rschecks 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 inGame::fantome(a Deviation). - Tests:
crates/pod-ui/tests/time_attack.rswithtime_attack/panel.rs(slot names, the sort, the time text, 155 and 156, handler 193 with a ghost removed) andtime_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 intime_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 word0x3c/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 laps0x633330, at most 3) centred at0x4ed414's 150, 280, 415 plus 60. Each split isformat_timewith "00'" blanked (strncmpwith "00" over 2 characters, thenstrncpyof 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
+0x10when 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 (
0x4ad5c8on0x6a8f58 + 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 with0x4ad628on the best laps (0x6a8c0c), and green when the best-lap pointer0x4f93a0is 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, blitter0x4d6857: 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.rsdraws the page from fixed records and compares it pixel for pixel with the strings and positions above. No POD capture of it exists yet.
- the circuit's title (reversed when mirrored,
- 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,+0x5cand+0x6c+ 4 k:- the first, when not 0:
format_timewith "00'" removed (strncmp, thenmemmove0x4c771b), 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 (0x4ad628on0x6a8c0c), 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
+0x4cand 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.rschecks this page the same way as the three-lap page. No capture of it exists.
- the first, when not 0:
- Page 10 of every type, the hall of fame (
0x4c5414,time_attack_pages::hall_of_fame). It reads the track's records, which sit at0x6a8818as an image ofRECORDS/<file>.BRT(pod_formats::records): the best races at+0(count+0x348, laps a row+0x349), the best laps at0x6a8c0c(count0x6a8f54, sectors a row0x6a8f55) and the sector tables at0x6a8f58 + k × 0x16c. The layout:TA_HOFcentred at y 70, thenTRACKwith 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).
BESTLAPon a blue bar on y1. A line below, "%s %s": the best lap's time and its holder (row 0). From y1 + 3h,0x4c52d0draws 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 is0x4ad748: the first row with the holder's name and that time. - Right column, centred at x 420 (300 + 120).
BESTINTon 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 itVIRT2, 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):
BEST3Lon 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
.BRTloaded, the race's crossings inserted). The emulated PC's install has noData\Records(measured on the records screen), so its pages show the race alone;tests/time_attack_pages.rsstarts with noRECORDSfiles for the same reason. tests/time_attack_pages.rschecks 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).0x43f338passes 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 which0x49f358makes 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_gapwith0x4c4094'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.
- 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 (
- 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.
0x43f338then blits three pictures withblit_paletted: the picture table0x4f9b6chas 16-byte slots, so0x4fb2bc,0x4fb2ccand0x4fb2dcare 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.rschecks 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.
- Records. The ghost's record is the one after the racers (
- Not ported: the ghost import's best times (
load_records,0x4b3f58,save_recordsin 1940x4a76e0).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 OK0x42e7b0when0x4fdf24is set), its restore (0x42e4c0when0x4fdefais 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 block0x6a87dcbefore eachload_records, sodefault_recordsfills nothing, and the install has noData\Records("Best times").The palette of the menu fonts: converted at load by the mode-8 texture reader (
FUN_0046a7bc); measured to matchscreen_colour's rounding, not traced.The key bindings: the options screens (11, 13) and the
Ubi.inireader own the player's key map at0x60bbdc(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 at0x60b428, whichpod-aitakes aspod_ai::human::Controls(observed by the controllers session).