Every release — what changed, and what was wrong before it
The whole history, out of the file the program ships with.
This page is the application's own CHANGELOG.md, rendered. Nothing on
it is written twice: an entry is added when the release is made, and this is where
it appears. Every version is its own address, so one fix can be linked to on its
own.
The terminal front end, and the core underneath both. Its source, its releases and its checksums are public.
v0.4.8
Added
Findings that hold still long enough to be read. A verdict is recomputed on every reading and a threshold has two sides, so a tyre resting on its limit crossed it several times a second: every frame of the advice was honest and the panel flickered. steady::Steady holds a finding until it has been continuously true for two and a half seconds and keeps it for four after it stops — the asymmetry is the point, because equal timings just move the flicker. It changes no verdict, invents none and reorders none; the wording it shows is the latest one, so a temperature that is still climbing keeps counting up while the line holds still. In the core because it is a rule, and three front ends smoothing the same list differently would show different advice about the same lap.
Named palettes, and a green that is not invented three times.Theme had one value, so a front end offering a driver a choice of colours had to invent the alternatives — and three front ends inventing them separately is how a family of programs stops looking like one. Theme::all() is five complete palettes with a name and a line each: Terminal (the default, unchanged), Amber, Ice, Paper — light, for a daytime desk or a stream overlay — and Contrast. Theme::named falls back to the default rather than failing, because a config naming a palette a later release removed still has to open, and Theme::is_light comes with them so a front end knows which way round to shade its own panels.
Theme::good is a field. Every front end needed a colour for "this reading is where it should be" and every one of them picked its own, which is the single thing this type exists to prevent. #[serde(default)], so a config written before today still reads.
Thanks
@1sad, who read the tyre table against the game's own app and would not let it go. The tread mirror in v0.4.6 was found from a frame he sent, and reading each car's own PRESSURE_IDEAL — which is the only thing that works for mods — was his suggestion. Named here with his permission.
Nothing changed in the terminal or the core. The number moves because the two front ends are released together and carry the same version — a bug report that says "0.4.7" has to mean one build of everything, not a guess about which half. The work in this release is all in the window; see its changelog.
A patch for three faults a driver found and reported, two of them in what the engineer was telling people about their tyres.
Fixed
The left of the car had its tread shoulders the wrong way round. Assetto Corsa fills tyreTempI and tyreTempO across the contact patch in the wheel's frame, so the array that is the inner edge on the right of the car is the outer edge on the left. Read as named, every left-hand tyre came out reversed — and because a camber verdict is inner minus outer, the advice for that side of the car was the opposite of the right thing to do. The reporter's own frame is the regression test: their game showed the inner edge hotter on all four, and this program agreed on the right and disagreed on the left.
The tyre pressure a car is judged against is the one the car states. The class table added in v0.4.5 could not help most of Assetto Corsa's grid — GT3, road and unrecognised all carry 27.5 psi — so a single-seater that wants 21 was still told to inflate. Every car's own PRESSURE_IDEAL is read now, per axle, out of data/tyres.ini and out of data.acd for the cars that ship it packed. A figure the car states beats any table; a figure the driver typed still beats both; anything unreadable falls back to the class rather than guessing.
LAN found only one of this machine's addresses. Which interfaces to announce on came from probing three destinations and reading back what the routing table chose — one address per destination, so a machine with two ways onto the same network revealed only one of them, and which one could change between runs. That is the shape of "sharing works sometimes". On Linux and the Steam Deck the addresses are now asked of the system; on Windows the probe covers all four private ranges instead of two, which closes the 172.16/12 and default-route-is-a-VPN holes there.
The release LAN was the point of. Two people, two machines, one network: one drives and the other watches — every screen, not a summary — and it works between the terminal and the window in any combination.
Numbered 0.4.5 rather than 0.4.3: the version this jumps over is the one where the two front ends spoke different protocols, and nothing was released from it.
The frame does not change. Every shm-bridge.exe and every installed panel from v0.4.0 onward keeps working, and nobody has to fetch a bridge.
Added
Watch somebody else's session, on your own screens.LAN is the tenth tab (0): S shares this session, W watches, Enter aims at whoever the cursor is on in the list of machines, O switches everything off keeping the addresses. Nothing else is asked — the port, the rate and being findable are chosen, and the one thing left is who to send to.
What arrives is the whole reading, so the watching machine finds the laps, builds the traces, draws the map's line and runs its own engineer. A spectator gets every tab, in their own units and their own language, rather than four sentences written on somebody else's machine.
The copies find each other. A couple of hundred bytes every two seconds on a multicast group everybody joins, carrying a name, a role, a port, the car and the track — and nothing about the driving. Nobody reads an IP address out loud unless the network refuses multicast, and typing one still works. Announcing is a switch of its own: off does not stop you seeing anybody.
The terminal and the window speak one protocol. Either can watch either. Both machines have to be on 0.4.5: v0.4.2's window put a reading's parts at the top level rather than under one key, so its datagrams cannot be read — the shape changed, not just the name. The name is still recognised, which is what lets the screen say "that copy is older" instead of "something arrived that is not a session". Discovery is compatible: an older copy still shows up in the list.
Settings → SHARING [G] — readings a second, only-while-on-track, the quiet timeout, being findable, and whether a session is accepted from the whole network or only from this machine.
lan_probe — a probe that says which of the four halves of the network is at fault: this machine's address, the port, the group, and whether anything is arriving. docs/lan.md is the walkthrough, with its real output in it.
Fixed
The tyre pressure a car is judged against is its own class's. Reported on Overtake: "this app will always give you false information ... esp when it comes to tyre pressure". Every car was held to one figure — 27.5 psi, which is a GT3's — so a single-seater sitting exactly on its own 21 psi was told to inflate all four, on every lap, and the alert band it was outside was a band it should never have been inside. The class table has carried a hot pressure per class since v0.4.0 and the window's PRESSURES view was already reading it; the advice was the half that was not. The alert band moves with the target, and a number the driver typed still outranks the table.
A car with no wing is no longer told to change one. From the same report: "I'm setting up a road car, and its telling me to adjust front downforce." Road and vintage cars now get the mechanical half of the same fix — softer front springs or anti-roll bar for understeer, softer rear for oversteer — and nothing is invented to fill the gap. Advice about a part the car does not have is worse than no advice: it is the line that makes somebody stop believing the other seven.
The camber advice no longer contradicts itself. Told to add negative camber on a car already at -3.5°, a driver would read the reason first — and it said "the outer shoulder is not being loaded through corners", which is the reason to take camber out. Too little negative camber means the tyre stands up and the outer shoulder takes the load; that is what it says now, in the same words the lap debrief has always used. The message names the edge as well: "outer edge hotter" or "heated too evenly", rather than "contact patch inefficient" beside a number the reader had to interpret.
engineer_probe prints the tread it judged. Inner, middle, outer, the inner-minus-outer that every camber verdict is made of, and the camber the wheel is running. When that advice was questioned there was no way to ask the program what it had read — only what it had concluded.
A lap saved by an older release loads again. Selecting your own reference lap could answer "not a saved lap: missing field rpms". That field was added to a sample and not to the format's rules, so every lap saved before it stopped opening — and the laps most worth keeping are the oldest ones. x, y and rpms default now, and the rule is written where the next field will be added: one that arrives after the format shipped defaults, the ones that were there from the first version do not, so a genuinely wrong file still fails rather than loading as a lap of zeroes. The regression test uses the actual file that failed.
The demo fills the Analysis tab. It built one lap, and that tab is the half of this program that is about comparing: with a single lap the picker had one entry, there was no reference to overlay, the sector table had nothing to subtract and the delta was flat. Five laps now, a second and a half apart and slower in the corners rather than everywhere.
An empty mapping is no longer read as a running game. On Linux the bridge creates the shared-memory pages when it starts and leaves them when the game quits, so a reader gets a Reading of zeros whether or not anything is running. games::Reading::car_is_loaded is the shared answer — the car's name or its rev limit, not the status, because Assetto Corsa reports Status::Off in the menus and in the garage. A watched session obeys the same rule: a friend who has not started their game reads as waiting rather than as a nameless car at 0 km/h.
Changed
The summary receiver is gone from the terminal. Two sockets cannot hold one port, and of the two the session is the one worth having. broadcast::receiver is untouched in the core for relays and for anything outside this project, and broadcast_to still sends the summary alongside.
The settings categories, their captions and the letter that opens each are one list now. The footer's [A/S/D/F/G/H] is built from it, so it cannot go stale the way [A/S/D] did twice.
Sharing and listening each have a switch of their own, so turning either off keeps the address it was using rather than making somebody type it again next time.
A receiver reports the port it actually got. Asking for port zero means "any free one", and a screen has to be able to say which one that was.
The hint check walks the tab strip rather than a list of nine tabs written out by hand — a tenth added to the program and not to the test is exactly the failure that test exists to catch, one level up.
Known
Both machines have to be on 0.4.5 to watch each other. There is no half-working mode with v0.4.2, and the screen says so by name rather than calling the datagram noise.
A whole reading is 1917 bytes, which is two IP fragments rather than one. On a switched network that is nothing; over a mesh VPN it is why the rate is a setting and why the LAN screen counts the readings that never arrived.
Assetto Corsa Competizione's advice thresholds are still Assetto Corsa's. Camber and tread verdicts are withheld there and cannot be wrong; the brake and pad numbers are owed a driven stint. docs/plan-acc.md §10 is the item, and it is why the graphical front end still offers one game.
A patch, and the frame does not change — every shm-bridge.exe and every installed panel from v0.4.0 onward keeps working, and nobody has to fetch a bridge.
Most of what is fixed here looked like something else while it was happening: laps that vanished, a reference lap that made no sense, advice about a race nobody was running. Three of the additions are measurements this core could have made all along and never did.
Fixed
Saved laps no longer depend on which folder the program was started from.saved_laps/ was a path relative to the working directory. The terminal is started from the folder it was unpacked into, a graphical front end from a desktop entry whose working directory is your home — so the two wrote to different places and neither could see the other's laps. There is one store now, beside the settings and the records, and laps from earlier releases are moved into it once, at startup.
A lap is written atomically. Everything else that saves your data here already was; a lap went out through a plain write, which returns before the data is on the disk. A crash or a power cut left a file that failed to parse on the day you wanted it.
Two laps to the same thousandth no longer overwrite each other. The name was the car, the track and the lap time, so driving the same time twice silently replaced the first one. The date is in the name now.
A car or track with :, ?, *, " or | in its name saves. Spaces and slashes were handled; the rest of what Windows refuses was not, and the save failed with an error about the file system rather than about the name.
Changing car or track without restarting kept the old session. The two were read once, when the connection was made, and the shared memory stays mapped when you go back to the menu — so laps in the new car were recorded under the old car's name, your personal best went into the wrong car's record, the analysis compared a GT3 against a lap set in a Miata, and the circuit length measured at Spa was used for laps of Monza. A blank frame, which both games publish at a menu, is not treated as a change.
A lap the game invalidated could become your best lap and your record. The best sectors always skipped invalid laps; the best lap and the saved personal best did not, so on Competizione a cut lap became the reference everything later was compared against — and it outlived the session in records.json. Assetto Corsa reports no validity, so nothing changes there.
A Cup car was judged against GT3 numbers on Assetto Corsa. The car's id says cup and the rule for it existed, but the game's own tags are checked first and Kunos tags the 911 GT3 Cup #GT3 — so the rule was unreachable on the one game that has tags. A Cup car ran against a GT3's tyre window and brake ceilings.
The engineer told you to save fuel in practice. The "you will not reach the finish" check fired in any session with a clock, which is every session. Practice, qualifying and superpole have no finish to be short of. Running the tank dry is still warned about everywhere.
A setup is written atomically. Saving into the game's own setup folder used a plain write, so an interrupted save left a correctly named setup with half a file in it — found in the pits.
Eight security advisories, five of them in the code that downloads software. reqwest 0.11 pinned hyper 0.14, h2 0.3 and rustls-webpki 0.101 — including two bugs in how a certificate's name constraints are checked and a panic reachable while parsing a revocation list. An update channel is a way of running chosen code on your machine, and the part of it that decides who it is talking to has to be right. Also memmap2, which maps the simulator's shared memory, and anyhow.
Added
Where the time went, along the lap.corners::delta_trace gives time gained and lost against a reference lap sampled at the lap's own points — what a racing line coloured by time rather than by speed is drawn from. It agrees with the corner decomposition at the line by construction, so a coloured map and a table of corners cannot tell you two different stories.
Braking, measured. Every corner now carries its braking zone: where it started and ended, how long it lasted, the hardest the pedal was pressed, the strongest deceleration reached, and the trail — timed from leaving peak pressure rather than from reaching it. Against a reference lap: how much harder the car was stopped, how much longer the zone was in metres, and how much longer the driver stayed on the pedal. A corner taken flat has no braking rather than a braking of zeros.
Competizione's circuit measures itself. ACC publishes no track length, so every answer this program gives in metres was withheld on it — no "braking 14 m earlier", no corner distances. The car's own distance travelled between two crossings of the line is the length of the lap between them, and both games already published that and nothing had ever read it. It is a measurement and is marked as one: a published length always wins, nothing is reported until the car has been round, and a lap outside 500 m to 30 km is not believed.
In the terminal
The delta graph was shifted. It resampled both laps onto a grid and compared sample i of one with sample i of the other, which is the same place on the track only if the two traces start at the same distance — a lap from a standing start and a lap joined at speed do not. The line is interpolated at matching distances now, and a stretch the reference never covered is missing from it rather than drawn flat at zero.
Braking, on the worst corner. Beside "braking 14 m later": how much harder the car was stopped, how much longer the braking zone was, and how much longer you stayed on the pedal after peak pressure.
The Analysis screen says when the metres were measured rather than published, which on Competizione they are.
For anyone building on it
ac_core::laps::LapStore is where a saved lap lives — save, list, load, delete, and the migration. A front end that still writes saved_laps/ itself is writing somewhere its user cannot find.
Five fixes, all of them the program reporting something nobody measured. Four were found by driving Competizione for the first time; one of them has been wrong on Assetto Corsa since v0.4.0.
Fixed
One long slide was counted as hundreds of separate incidents. The counters added one per sample, so "Oversteer: 1454x" and understeer at 1105 a lap could arrive for the same lap — one condition, counted twice. An episode is counted once now, when it starts.
A symptom was blamed on the driver even when the driving never varied. The car-versus-driver rule has two halves and only one was checked, so four laps driven to the same time came out as "the driving" — the one thing it cannot be.
The engineer said nothing at all for the first three laps. It still will not say whose a symptom is before it has a stint, but it reports what happened from lap one. A two-lap sprint was silent.
"Bottoming out" was warned on every lap of Assetto Corsa. Ride height is in metres and the threshold was written in millimetres, so an ordinary 62 mm read as under the floor — and printed itself back as "0 mm".
Ride height, tread temperature and wind were drawn as zeros on Competizione, which the game does not publish. Ice-cold tyres and a car on the tarmac are plausible readings, which is what made them worse than an error. All three say "not measured" now, and the launcher lists them.
A telemetry page left by an earlier run could be read as a live session. The Proton helper sized the file it created and never cleared it. Zeroed, it reads as "waiting" — which is what it is.
Changed
The Windows build is now run in testing, not only compiled: ./tools/test-windows.sh puts the whole suite through Wine.
Removed a "car control" score that was computed every lap and shown nowhere.
Linux and Steam Deck
The Proton helper changed. After updating, press [B] on the launcher's overlay card to fetch the matching shm-bridge.exe — it is published with this release.
The point: a second simulator, and an engineer that knows what car it is looking at.
At a glance, everything since v0.3.6
Assetto Corsa Competizione is read. Its three shared-memory pages, pinned to a recorded GT3 session at Spa rather than to a header file. Dashboard, tyres, brakes, fuel, strategy and lap analysis all work on it.
Which simulator is a choice on the launcher, not a guess — GAME: < … >, on every platform, with a list of what that game can and cannot measure.
The wrong parser can no longer attach. Both games publish under the same three names; each reader now refuses pages that declare the other's version.
The engineer judges a car against its class. GT3, GT4, Formula, touring, road — tyre windows and per-axle brake ceilings from published operating ranges instead of one band meant for road cars.
Brake pad and disc wear, on the game that measures it, in place of the tyre wear it does not.
Track limits: a lap the game called invalid is stored as invalid.
The Proton helper no longer blocks the game from starting. It comes up when you press START and goes away when you return to the launcher, and it mirrors pages the game already owns — so the order you start things in stopped mattering.
Corner-by-corner lap analysis, confidence beside every line of advice, car-versus-driver over a stint, and a cause/effect/check chain on every rule.
Licence: MIT → AGPL v3 from this release. v0.3.6 and earlier stay MIT.
The name is Pro Engineer. It read "AC Pro Engineer", which stopped being true the moment it read a second game. Nothing about an install changes: the binary, the panel's folder and the shared-memory names are what the game and Custom Shaders Patch look for, and renaming those would break every existing install for a word.
Everything below is the detail.
🏎️ The engineer knows what car it is
Tyre and brake thresholds come from the class of car. One band — 70–105 °C tyres, an 800 °C brake ceiling — was doing for a Formula car and a Fiat 500 alike, which is why so little was ever said: a GT3's 520 °C fronts never reached it and a Formula car's cold tyre never fell below it. Classes are read off the car's id and, on Assetto Corsa, the game's own tags; the windows come from published operating ranges, with the sources in docs/plan-0.4.0-car-classes.md.
Brake ceilings are per axle. Fronts do the work; one number for four corners is either too low for the fronts or blind to the rears.
An unrecognised car keeps your own settings, and so does any threshold you have set yourself. Only untouched defaults are replaced.
Tyre temperature is judged on whatever the game measures — the tread mean where there is one, the core where there is not — and the verdict says which. Competizione used to get no word about tyre temperature at all.
The point: the Analysis tab drew what happened. It says where now, and how sure it is.
⚖️ Licence: MIT → AGPL v3
The project is licensed under the GNU AGPL v3 from this release onward. Nothing changes for anyone using the application: it is free, it stays free, there is no account and no paid tier. What changes is what may be done with the source.
Using it, and changing it for yourself, is unrestricted. The licence conditions passing a copy on, not what runs on your own machine.
Publishing something built on it now requires that your source is open too, under the same licence, with this project credited. Under MIT it did not.
Keeping your own source closed, or selling a product with this code inside it, needs written permission first — rgoshbbb@gmail.com. There is no price list; terms are per case.
v0.3.6 and every release before it stay MIT, permanently. That grant cannot be withdrawn, and LICENSE-MIT-HISTORICAL keeps its terms. The AGPL applies to everything committed after 9ba92a3.
shm-bridge is untouched. It is a fork of poljar's work and keeps its own MIT licence.
LICENSING.md is the plain-language version — what needs asking, what does not, and what happens to a closed product found shipping this code without asking. CONTRIBUTING.md covers the sign-off patches now need, and why.
🏁 A second simulator: Assetto Corsa Competizione
Telemetry from ACC arrives. Its three shared-memory pages are read, turned into the same neutral reading Assetto Corsa produces, and every screen, the analyser and the engineer work on it unchanged — none of them knows which game it is looking at.
The layout was measured, not transcribed. 337 seconds and 8376 samples of a Huracán GT3 EVO at Spa, recorded off a running game under Proton, with what every four-byte word did over the session. That recording is in the repository and it is what the tests assert against: 4.24 litres a lap that the tank divides by to give the 14.6 laps the next field holds, 520 °C front brakes against 257 °C rear, pad and disc thickness in millimetres.
The wrong parser can no longer attach. Both games publish under the same three names — acpmf_physics and the rest — and on Linux they mirror into the same /dev/shm files, so a reader on the wrong pages gets numbers rather than an error. Each now refuses a page that declares the other's shared-memory version, on connecting and on every tick.
What ACC does not measure, it says so. No tyre wear and no tread temperatures across the tyre, so the wear advice and the camber advice stay silent on this game instead of reporting four unworn tyres as four destroyed ones. The launcher lists what the chosen game reports before you start, so advice going quiet reads as a property of the game.
Tyre temperature falls back to the core where a game measures no tread. Every screen averaged the three tread readings, which on ACC are zero — so four tyres read 0 °C, which is not a gap but a confident wrong answer in the direction of "stone cold". The core temperature is a real reading of the same tyre and is what is drawn now. The advice that rests on the tread stays silent, because inner minus outer has no substitute.
Brake wear advice, on the game that measures it. ACC publishes what is left of the pads and discs in millimetres, per corner, and the engineer reads it: pads low, pads done, disc thin — with the same chain every other rule carries, and a check a driver can take off this screen two laps later. It is the trade the two games make, and neither gets the other's rule.
A lap the game called invalid is stored as invalid. ACC reports track limits; Assetto Corsa never has, so every lap there was treated as clean — which was the absence of a verdict, not one. An invalid lap now sets no best sector.
Track grip stopped being invented. ACC does not publish a grip figure, and the missing zero was read as the greenest track there is: the cold-pressure target quietly gained 0.3 psi on every lap of every session. The Strategy tab says "not measured" now, and the calculator adds nothing.
No setups on ACC yet, and no in-game panel ever. Its setups are JSON whose numbers are clicks rather than units, and the click-to-value mapping differs per car — writing that reader from memory of the format is the mistake the recording exists to prevent, and there is no ACC setup file to pin it against until one is saved in the game. The panel is a Custom Shaders Patch app, and CSP is an Assetto Corsa mod.
The simulator stands in for either game.simulator acc publishes Competizione's pages — the same drive, in that game's layout, with the arrays it does not publish left at zero. engineer_probe takes a game as an argument and reads it through the registry, so the advice can be read against the numbers that produced it on both.
Which simulator is a choice now, on the launcher — GAME: < … >, on every platform. It used to be detected, and detection is the wrong tool for this pair: the Linux bridge has to be running inside one game's Proton prefix before that game starts, so there is no process to detect yet, and both games publish under the same names. A wrong answer costs a bridge in the wrong prefix and an engineer running the other game's thresholds. The row also says what the chosen game can and cannot measure, so advice going quiet reads as a property of the game rather than as a broken feature. Choosing another game restarts the bridge in its prefix.
One thing is still owed, and it is named here rather than left to be found. The engineer's temperature thresholds were chosen against Assetto Corsa's cars and have not been read against a stint on a GT3 — the brake alert sits at 800 °C where ACC's carbon runs at 520, so it is likelier to stay quiet than to cry wolf, and the brake-wear thresholds are millimetres that want the same check. docs/plan-acc.md §10 is the list, and engineer_probe is how it gets done: every line of advice printed next to the numbers that produced it.
✨ Added
A CORNERS sub-tab: where the lap actually went. Corners are found in the trace — a stretch where lateral load stays up long enough to be a corner rather than a kink — so it needs no track data and works on mods. Each is charged the track from its own entry to the next corner's entry, which is where a bad exit is paid for, and the per-corner deltas plus the run to T1 add up to the lap's own delta. The worst corner is pulled apart: braking point in metres, entry, minimum and exit speed, throttle timing after the apex.
F hides everything that cost less than a tenth. That filter is the point — twenty corners with a number beside each is another table to read.
How sure the engineer is, beside every line of advice rather than after it: a count of corroborating observations and their spread, not a number picked while writing the rule. One observation is never confident however extreme, and four that cancel out are Low rather than a balanced car.
The car or the driving, over a stint. A car that understeers does it on the lap you got right and the lap you got wrong; a mistake follows the driving. It refuses to answer below four laps, which is the feature and not a limitation — the wrong answer here sends somebody to change a setup that was never the problem.
What a setup change did, measured against the laps either side of it — and, in the same breath, everything else that moved at the same time. It never claims a cause: the tyres are always older and the track is never the same temperature, and a driver told "the ARB gained you 0.2 s" has been misled by their own tooling.
Advice carries a chain — cause, effect, and what to look at next time to know whether it worked. Every rule with a mechanism now states one: pressures, tyre temperatures, wear, brakes, brake bias, camber, bottoming, rake, force feedback, coasting, understeer, oversteer and over-rotation. confirm is the field the idea rests on — advice nobody can check is not advice — so it names something a driver can actually go and look at, in the units they chose.
The two fuel rules deliberately have none, and that is a finding rather than an omission: "the fuel will not last" is arithmetic on what is in the tank, not a mechanism, and its check is the same number a lap later, already on the screen. A chain there would be three fields of ceremony making the advice look better researched than it is.
A third state is also deliberate. A rule that can explain itself but counts one whole-lap number rather than several agreeing observations carries a chain with empty evidence and keeps its hand-picked confidence — one counter is one observation however large it gets, and feeding it to the evidence model would dress a single number up as corroboration.
Two rules were sharpened on the way. Bottoming now knows which corners grounded rather than only that one did — the loop stopped at the first, which was enough to raise the alert and not enough to say anything about it — and the wear rule says laps left on the worst corner where it used to leave a percentage, because a percentage is not what a driver deciding whether to stop needs to hear.
🔌 For anyone building on it
The other end of the UDP feed. Set overlay.receive_from and this shows another machine's telemetry and their engineer's advice in your own panel. One network only — two houses behind two routers is NAT, which needs a relay that is not built. No discovery and no championship mode: you type an address.
A frame knows when it is not yours. A new flag, which costs no frame version, so a lap counter about a car you are not sitting in cannot be mistaken for your own telemetry gone wrong.
🗑 Removed
Attributing a change to a setup, which was written and then cut before release. It rests on identifying the loaded setup, and Assetto Corsa publishes fuel, brake bias, pressures and camber and nothing else — so two setups differing only in a roll bar are indistinguishable, which is exactly the change the feature existed to measure. Worse, fuel burning off mid-stint could make a different saved setup start matching and invent a change that never happened. A feature that misses the real thing and occasionally reports a false one is worse than no feature. It comes back when the setup can be identified rather than guessed.
🐞 Fixed
The screenshot mock filled distance with metres where AC publishes a normalised 0..1. Every consumer of a trace read that as three thousand laps — the delta graph resampled to a ceiling of 1.0 and drew a single sample.
The POST-STINT column had never been screenshotted, so it went out unreviewed release after release.
Confidence markers drew as a dash. 🟢🟡🔴 are recent enough that the terminal font renders all three identically; filled, half and hollow circles carry it without needing colour.
The point: the panel could tell you what was happening; it had nothing to say about the lap you had just finished. Now it does, with the last three laps to page through.
The frame changed, so Linux needs a matching shm-bridge.exe — [B] on the launcher's overlay card fetches one. Everything else is optional and off until you turn it on.
⚠️ Breaking
Overlay frame version 6, 712 bytes to 2484, so shm-bridge.exe has to be updated on Linux. Everything new is appended — no existing offset moved, and a panel or bridge one version behind misreads nothing, it simply does not see the debrief.
✨ Added
A lap debrief in the game. Its own window: what the engineer made of the lap you have just finished — pressures and temperatures against your own windows, camber per axle, brakes, and how the lap was driven. < and > page through the last three laps. Eight lines per lap rather than four, because a lap can go wrong in more than four ways at once and whatever came fifth used to be dropped silently.
Sector times against the session's best. Four tenths spread across a lap is a shrug; four tenths in sector three is a corner to go and look at, and a lap time on its own cannot tell them apart.
What is left: laps of tyre life in the worst corner, laps of fuel, and how long you have been on this set of tyres. All three existed already and only ever reached the terminal — which is on the other monitor with a helmet in the way.
Compare against your best lap, not just the one before. People race their own best; the lap before is what says whether the last change helped.
Inner / middle / outer tyre temperatures under each corner, coloured by the spread rather than the heat: a tyre can be in its window and still riding on one edge. Off by default.
Settings of its own for all of it — how many lines (zero switches the whole thing off), what to show, whether a finished lap pulls the window back to it, and its own look: backing plate from transparent to solid black, text size, line spacing, a rule between lines, upper case. The window scrolls, which it needs to: eight lines plus a header plus sectors do not fit a window sized for four.
Assigning a wheel button to the paging was in this release and has been taken out again: the binding was written into Assetto Corsa's own controls.ini correctly and the press never arrived back. It returns when it works, rather than shipping as a control that looks bound and does nothing.
🐞 Fixed
The camber verdict in a debrief was backwards half the time. The lap summary took the temperature spread's magnitude and threw its sign away, so a front tyre whose outer edge ran 13 °C hot — a car short of negative camber — was told to take camber out. There is a test for each direction now.
The terminal and the panel gave different advice about the same lap. The post-stint column computed its own verdicts, in three hundred lines where the analysis and the spans that drew it were the same code, and the two had already drifted. One implementation now, 311 lines lighter, and a clean lap says so instead of leaving an empty column that reads as "it did not run".
A lap that published nothing says nothing. Every average reads zero on a session that ended before the analyser had anything to average, and zero pressures are not flat tyres.
Four corners of one problem are one line in the lap summary too, the way the live advice has been since v0.3.5.
Wear:, T: and B: were English in both languages — the only words in the panel that were, sitting under every corner where they are hardest to miss.
A spent GitHub allowance said "403 Forbidden", which reads as a permission problem and sends people looking for a token they do not need.
The LuaJIT harness reported success on a panel that failed to load.
README.txt described an archive that no longer exists — it was written for a Windows-only bundle while the published one holds both builds and the bridge.
🧱 Under it
The core reads the game. The shared-memory reader lived in the terminal, so a user interface owned the connection to the simulator. It is core/src/games/assetto_corsa/ now, behind a Source trait, so a second simulator becomes a folder beside it rather than conditionals through the middle of the engineer. The panel moved to assets/frontends/csp-panel/: it is the Assetto Corsa front end, not "the overlay".
The computed frame goes to a list of sinks rather than to one hard-wired writer, and the producer never waits for any of them — a sink that fails is dropped rather than allowed to stall the loop feeding the driver's overlay.
A raw UDP feed, for anyone who wants to write a front end. Set overlay.broadcast_to and the computed frame goes out as JSON. That is all it is: nothing ships that reads it, and there is no spectating, no LAN mode and no relay — this is a documented hook and an address to point at, off unless you set one. docs/ARCHITECTURE.md says where it is meant to go.
The lap summary is data now, not five hundred lines of ratatui: debrief::debrief(&LapData, &AppConfig) -> Vec<Recommendation>, which is exactly why the panel had never had a word to say after a lap.
The point: the in-game overlay stopped being "the panel that sometimes works". It opens before the race and in the pits, keeps its settings when you close its window, and shows up to eight lines of advice instead of four. There is one overlay now rather than two. In the terminal, every key does what the screen says it does, and every one of them can be rebound.
Run in a real session on Linux (Proton) and on Windows 10.
⚠️ Breaking
The overlay frame grew from 440 to 712 bytes (version 5, eight advice slots). On Linux this means shm-bridge.exe has to be updated: an older bridge maps too few bytes, CSP silently refuses the mapping, and the panel waits forever for an application that is running. Press [B] on the launcher's overlay card to fetch a current one, or [C] for a report on which of the three pieces does not fit.
F10 and F11 are no longer bound to anything, and the --overlay-test-* flags are gone.
✨ Added
The panel works outside a session — in the garage, in the pits, before the green. It now tells waiting for the car apart from waiting for AC Pro Engineer; it only ever said the second, which sent people hunting for a broken bridge that was fine.
Up to eight lines of advice, with a slider for how many to draw and the number the application actually sent printed under it.
Rebind any key — a KEYS category in Settings, stored as text in config.json, working on both keyboard layouts.
A Changed tab in the panel's settings: everything that differs from the defaults, what it was, and a reset beside each line. The setting making the panel look wrong is the one you do not remember touching.
New advice is drawn differently from advice that has been on screen for four laps.
[C] — the whole overlay diagnosis, in the application.Why is the panel blank is the question this program gets asked most; it used to have one answer, and it was a cargo command.
shm-bridge.exe --verify opens the mapping with exactly the call CSP makes and prints what is inside it.
🗑 Removed
The desktop overlay on F10 and its control centre on F11. There were two overlays. That one had no implementation on Linux at all, and on Windows it drew a worse copy of what the CSP panel already draws — it did not survive exclusive fullscreen, never appeared in VR, and was invisible to AC's own screenshots. About 1,500 lines went with it.
🐞 Fixed
The panel now really does install itself. Two separate reasons it did not. Windows detection started from two literal paths under Program Files and read Steam's library list only out of a root found that way, so a machine with Steam anywhere else had no libraries at all and the game was unfindable — it asks the registry first now, then the environment, then every mounted drive, and it will also find a copy that is in no Steam library. And the install was attempted once, while the application was starting, and never again: whatever went wrong that one time meant no panel for the rest of the session, recorded in a log file and nowhere anyone would look. It is retried when Assetto Corsa starts, and a failure now says so on the launcher card in the operating system's own words.
The panel forgot everything. Closing its window unloaded the script, and the settings table was CSP's storage proxy rather than a table — so a ticked box was not merely unsaved, it was lost on the next frame. Settings are also written to a plain file now, which makes "did it save" a question with an answer.
The keys the screens named. Hints promised keys that reached no handler, headings named function keys the tabs were never on, and the launcher listed two of its six keys. Every hint, heading and help page is printed from the bindings now, and a test walks all nine tabs to keep it that way.
[B] fetched the newest published bridge, not the one built for this release — and then reported "nothing to fetch", which is indistinguishable from everything being fine.
Pressing [B] crashed the application, and the Setup Cloud was one keystroke from the same crash: a blocking HTTP client built inside the async runtime.
The engineer said one thing four times. "FL COLD / FR COLD / RL COLD / RR COLD" filled every slot in the frame; it is "All four COLD" now, or "Fronts", or "Left side".
The camber advice fired on every straight, four lines at a time, about a car with nothing wrong with it — a tyre only says something about camber while it is loaded sideways. It is judged over cornering now, and the degrees in it come from what AC reports the car is running rather than from a step index in the setup file that no reading can turn into degrees. The post-stint verdict on it was also backwards half the time.
Wear no longer screams on lap three, and the tyre bars scale to your own threshold instead of a hard-coded 94 %.
Advice comes in the units you chose. The pressures printed raw psi to someone working in bar.
Plus the panel's plate, its clipped captions, its overflowing corner readouts, the console's Again button, a checkout running whichever shm-bridge.exe was nearest rather than the one built for it, and two bugs in the test harness that had been agreeing with a panel that was wrong.
🧱 Under it
The Lua panel is 19 modules instead of one 2,429-line file — settings, language, theme, layout, formatting, frame, blocks, widgets, console, and one file per window.
Every picture in the README is generated by the code that draws it, the terminal's and the panel's alike, and there are now pictures of the panel itself rather than of a window that no longer exists.
The README was rewritten: installation per platform, every screen, the full key table, every command-line flag, and a troubleshooting section for the overlay.
A preview, not a finished feature. It is published so that it can be checked on real machines, which is the one thing that cannot be done while developing it.
It has never once been run on Windows. The tests pass by cross-compilation and clippy is clean against the Windows target, but not a line of it has executed there.
It has not been checked in game by the author of these changes. The panel is driven under LuaJIT and LÖVE and every ui.* call it makes is checked against the installed CSP — that is not the same as a session.
Some console captions and the Wear:, T:, B: prefixes are not translated yet.
The official release comes after real sessions on both systems. Report what breaks: the panel's status window now shows every version at once.
The point: before this release the overlay could not work for anyone on Linux. v0.3.3 was tagged eleven minutes before the commit that taught shm-bridge to map the overlay, so every published bridge created only AC's own pages. Confirmed by scanning the artifact.
⚠️ Breaking
Overlay frame version 4: the application's version was added, so the panel can tell that the game is drawing an older copy of it. The field is last, so no offset moved. The application, the panel and shm-bridge.exe have to come from the same release; the panel installs itself, the bridge comes from [B] on the overlay card.
🐞 Fixed
The panel did not load at all.ui.Icons and ui.Icons.Settings at file level: ui.Icons only has to be truthy to be indexed, and the table argument is built before pcall, so pcall does not protect it. Every window drew the error text instead of the panel.
Both developer-mode switches fell through to nil.applyDemo and DEMO_ADVICE were declared below their callers, which makes them globals to those callers. The fourth instance of that trap here.
Both of the panel's versions lied.manifest.ini showed 1.0 for eleven releases running, and the panel had no version of its own.
Both harnesses reported OK on a broken panel — LuaJIT drew 27 strings instead of 140, and LÖVE did not count a load failure as an error.
The overlay card clipped its own diagnostics at 66 columns.
🚀 Added
The bridge says who it is. It writes /dev/shm/acpe-bridge.info and compiles its version into its own binary, so it can be identified without being run.
The card judges all three pieces, and [B] downloads the published bridge, verifying it before it replaces anything. The old one is kept as .previous.
A bridge update check at startup — it only looks; fetching is a keypress. The application's own version is not touched by this path.
The panel says the game is holding an older copy of it and offers to restart AC. Switched off in Panel → Blocks.
bridge_probe — which bridge is on disk, which is running, and whether the overlay can work at all.
--export-overlay <dir> — write the panel out for a manual install.
proton-setup.sh in the archive — the protontricks commands without which CSP does not load at all. There are no fonts in the archive and there cannot be: the terminal draws with its own font, the panel through CSP's DirectWrite, and fonts are installed into the prefix (corefonts), which is what the script does.
A small follow-up to v0.3.1. Four pieces of functionality that were fully implemented but had no way to reach the user are now wired up, one wrong number in the analysis tab is corrected, and three things that ran far more often than they needed to no longer do.
🚀 New Features
Screenshot the interface with Ctrl+S. A complete SVG renderer for a drawn terminal buffer already existed inside tui_tester, where it generates the images in the README; the application itself had no way to capture what it was showing. Frames are written to <data>/screenshots/<timestamp>.svg and the path is reported in the status line. SVG keeps the text selectable and needs no image encoder.
Tyre pressure targets are on screen.ColdPressureCalculator and TyrePressureOptimizer were both fully implemented in ac_core and called only by the test suite. A third Engineer sub-tab shows what to set the tyres to cold so they reach the configured hot target at the current air temperature and track grip, and what each corner's inner-versus-outer temperature spread says to change.
Frame and tick timing in the footer. The render loop and the background tick thread contend for the same state mutex, so when one stalls it is usually because the other holds the lock — and from the outside both look identical, because the numbers stop moving either way. The footer now shows frames per second and how long ago the tick completed, in red past 500ms.
🛡️ Fixed
A missing sector split no longer zeroes the best sector. The analysis tab computed each best sector as a plain minimum over the raw values, which includes the zeroes left by a lap whose split was never captured and by the unused third slot of a two-sector track. One such lap pinned that sector to 0.000 and made the "Optimal" row a lap time no car could set. The analyzer's own theoretical_best_lap_ms — which filters those out and had no callers outside its unit test — is used instead, and a sector with nothing recorded renders as a dash rather than as a time.
The config is no longer rewritten on every launch. The decision to save compared the file's text against a re-serialisation, so different indentation, a different key order, or a serialisation failure all triggered a write. The comparison is now between values, and formatting stops mattering. Migration and validation still write, which they must.
The mouse is no longer captured. Capture was enabled at startup and no mouse event was ever handled, so the only effect was taking selection and copy away from the terminal — which is how anyone gets a lap time or an error message out of a TUI and into a bug report.
The timing readout stays blank until a frame is measured, rather than reporting a fabricated "0fps" before anything has been drawn.
⚡ Performance
The delta-versus-best series is cached. It was recomputed every frame, and computing it resamples two telemetry traces — cloning and fully sorting up to 7200 points each — to arrive at an answer that cannot change, since both laps are finished.
Setup folders are rescanned on a ten second heartbeat instead of twice a second. The scan walks three directory trees and parses every setup ini in them, for a directory that changes only when the user saves a setup from inside the game.
🧹 Internal
185 tests, up from 171. The SVG renderer moved out of tui_tester into ui::screenshot so the binary and the application share one implementation; the README screenshots regenerate byte-identical from it.
A bug-fix release, and a large one. Three features that the interface has always advertised — the version carousel, saving your settings, and the Setup Cloud browser — did not work at all and now do. Four reachable crashes are gone. Assetto Corsa is finally found on Linux.
47 commits, 171 tests (up from 130), green on Linux and Windows.
⚠️ Read This First
Your cold tyre pressure targets will change. The calculator scales its recommendation by surface_grip, which used to read a constant 0.0 and clamp to a floor of 0.80 — so every recommendation carried the same fixed compensation regardless of track state. With real grip being read, a well-rubbered track (≈0.94) produces roughly a third of the previous adjustment. Numbers will differ from v0.3.0 for the same car and track. This is the fix working, not a regression.
Any settings you saved before this release were never written to disk. The Settings tab did not persist anything, so it comes up with defaults one last time. From now on it saves as you edit.
Lap records saved before this release may be missing. Personal bests were compared against the world record rather than your own history, so records.json only ever gained an entry from someone who had beaten it.
🚀 New Features
Assetto Corsa is found on Linux. The install root was probed as four hardcoded Windows drive letters, so content/cars was never located and every car-spec lookup returned nothing. Setups were looked for in ~/Documents, but under Proton the game is a Windows process writing inside its own prefix. The new ac_paths module walks the real Steam roots (~/.steam/steam, ~/.local/share/Steam, Flatpak and Snap homes, Program Files on Windows), reads Steam's libraryfolders.vdf so a library on any drive is found rather than guessed at, and locates the Proton prefix by app id. ac_install_path and ac_documents_path in the config override both.
The Setup Cloud browser works. The Setup tab handled only Up, Down and B, so pressing B opened a browser onto a permanently empty setup list with no way to install anything — while the tab's own hint line, the help overlay and the README all documented D to download. Arrows navigate, Enter reloads a car, D installs, PgUp/PgDn scroll the details. Fetching runs off the render thread, so the UI no longer freezes on a five-second HTTP call.
Fuel strategy no longer waits on AC. Every fuel figure was gated on gfx.fuel_x_lap, which reads zero for the whole of lap one and sits in the part of the graphics page not yet confirmed against a live capture. Consumption measured across completed laps now fills in, so the strategy tab works from lap two regardless of that field.
Honest connection status. The footer distinguishes LIVE, AC RUNNING - NO DATA and AC NOT RUNNING rather than collapsing three tracked states into ONLINE/OFFLINE. Panels with no telemetry say which it is instead of drawing nothing.
Richer CSV export. RPM, lateral G, longitudinal G and average slip were being dropped even though the trace carries them — the three things an external tool is most often opened for. Files are named after the car, track and lap instead of colliding on lap_3_export.csv, and a failed export now reports itself instead of failing silently.
Terminal-too-small screen. Below 80x20 the app shows its current and required size instead of drawing into an area that cannot hold the layout. The startup resize is now grow-only, so it stops shrinking the window of anyone running maximised.
Ghost delta. The show_ghost_delta toggle now selects the delta source: with it on, the readout compares against your own recorded best lap through calculate_ghost_delta, which was fully implemented and had no caller.
🛡️ Crashes Fixed
Narrow terminals. Four Rect fields in the Setup tab subtracted constants from a u16 width and height. Below 20 columns they wrapped to around 65530 and indexed out of the render buffer.
Mid-download panic. The updater's progress bar built its trailing segment with "░".repeat(20 - filled) on an unclamped percentage, so a response body longer than its Content-Length aborted the app while the user watched it update.
NaN from stale shared memory.Gauge::ratio asserts its input is within 0.0..=1.0 and clamp returns NaN unchanged, so a single garbage float from a zeroed /dev/shm page took the app down. All nine gauge call sites reject non-finite input first.
100% CPU from a config file.AppConfig::validate had no caller outside its own unit test, so update_rate: 0 reached event::poll and thread::sleep and spun two cores. Validation now runs on load, and covers the pressure targets, alert bands, temperature limits and shift point that previously had no bounds at all.
🛡️ Things That Silently Did Nothing
Version carousel arrows.check_for_updates dropped every release older than the running one, so on the newest build the list held a single entry and Left/Right had nowhere to move — while the launcher rendered a "you won't be able to switch back" warning for versions that could never appear.
Update checks after being offline. The check ran once at startup, so a machine behind a captive portal kept an empty carousel for the whole session with no way to retry. Selecting the UPDATE item now re-checks, debounced to once a minute.
Saving settings.handle_input mutated the config and nothing wrote it back; apply_config had no callers, so changes did not take effect until a restart. The auto_save and show_ghost_delta toggles were read by nothing.
Personal bests. Compared against the world record, and the whole block was nested inside a car-specs lookup that always failed on Linux — so no record was created, compared or saved there at all, which also left world_record as None and disabled the off-pace advice.
Setup auto-detection.match_score can only produce 0/20/25/30/45/50/ 55/75 and the threshold was > 60, so only a perfect three-way match ever qualified. One lap of burnt fuel dropped it to 55 and silently blanked the "(NOW: x%)" hints in the brake-bias and camber advice.
Suspension roll-asymmetry warning. It compared avg_ride_height[0] against itself, so the difference was always exactly zero. AC publishes ride height per axle, not per corner, so the check cannot be written against this data and has been removed rather than left looking functional.
Simulator detection on Linux.is_process_running matched only simulator.exe, but the Linux build is called simulator, so the launcher waited forever on the platform the bridge exists for.
🛡️ Wrong Numbers
Driving-style aggression combined the lateral and vertical G axes, so a stationary car scored 40% and braking or acceleration was invisible to it.
Out-laps scored perfect tyre management. With no sample above the speed gate, pressure deviation computed to 0.0 and the score to a perfect 100 — an out-lap rated better than a hot lap, and the advice recommended inflating by 27.5 psi against a 0.0 psi reading.
Mistake counts scaled with Update Rate. Oversteer, understeer, lockup and scrubbing counters were divided by a fixed sample count, so changing the rate in Settings halved every score and made laps recorded at different rates incomparable.
The final sector split raced the lap counter and could land in the following lap. It is derived from the lap time now. AcStatic::sector_count is honoured too, so 2- and 4-sector mod tracks produce a theoretical best.
Fuel targets under-fuelled. A timed race ends when the leader completes the lap the clock ran out on, and the lap already in progress still has to be finished; the target accounted for neither.
Stale fuel warnings.fuel_laps_remaining was never cleared, so BOX BOX BOX could fire after a refuel on a value measured before the stop.
Torn shared-memory reads. The physics page is rewritten at 333 Hz while ~600 bytes are copied out of it. Pages are re-read when AC's packet_id moves mid-copy, so a frame spliced from two game ticks no longer reaches the jerk accumulators and peak-G tracking as a phantom lockup.
Track-map bounds were serialised as f32::MAX/f32::MIN sentinels when a lap had no usable coordinates, so anything computing max - min from a saved lap got -6.8e38.
Units were ignored. Target pressures printed a hardcoded "PSI" and ambient temperatures a hardcoded "C" whatever the Display settings said; alert thresholds printed no unit at all. Tyre temperature spreads were converted as absolute temperatures, adding a 32°F offset that does not belong to a difference. Min Speed was folded from a seed of 999.0, so an empty trace displayed "999.0 km/h" as if it were a measurement.
🛡️ Keys, Text and Alerts
The first-run prompt could not be exited with Ctrl+C, q or Esc — the first screen every new user sees, and Enter was the only way out.
F1 did not close the help modal that says "PRESS ESC, ?, Q, OR F1 TO CLOSE" in nine places.
Esc in the analysis load menu quit the whole session back to the launcher, while the menu's own footer promised "ESC: Close".
Held keys were dropped on Windows, which reports them as Repeat rather than Press.
S in the analysis tab saved the fastest lap rather than the selected one.
Tabs were documented as F1–F9 in nine screen titles, the navigation summary and the README; they are 1–9. The footer advertised "[H: Help]" for a key that is not handled, and F10 was described as a compact UI mode when it toggles the game overlay. Keys documented nowhere — Tab/Shift+Tab, F11, Ctrl+L, E, PgUp/PgDn, the A/S/D category switches — are now listed.
Brake and tyre-temperature alerts pushed a fresh recommendation on every frame the condition held — roughly sixty a second per corner, burying every other message. They now use the same hysteresis as every comparable alert.
Status messages never cleared, so "Exported CSV: ..." stayed pinned to the footer for the session and a stale message looked like a fresh one.
Twelve locale keys existed only in Russian; a test now enforces parity. A malformed locale override produced an empty dictionary in silence, degrading the whole UI to raw key names.
🛡️ Data, Shutdown and Security
Durability. The records file, config and CSV export renamed a temp file into place without flushing it first, so a power loss could publish a correctly-named empty file. Two instances saving at once also shared a temp path, which is the one way that pattern corrupts rather than merely loses.
Records validation. A zero or negative lap time was accepted, written to disk, then dropped by the read path on next load — which reads to the driver as a personal best vanishing between sessions.
Crash reports and logs were written relative to the working directory, unwritable when launched from a shortcut or installed under Program Files. The crash report was then dropped in silence. A logging failure also aborted startup before the TUI was drawn.
Stale /dev/shm mappings. shm-bridge's cleanup returned on the first failure, leaving the remaining pages behind zero-filled — and the app maps those without complaint, reporting a healthy connection to a dead feed.
Quitting could hang forever waiting on a bridge that never acknowledged the exit request. Bounded to five seconds, and errors inside that task are no longer discarded.
A missing protontricks-launch was fatal, so anyone running AC natively, through another launcher, or simply reviewing saved laps offline could not start the app at all.
INI injection. A newline in a downloaded setup's notes field opened a new line in the file AC parses as a car setup, letting a [SECTION] be smuggled past everything the downloader validates.
⚡ Performance
is_process_running reads every process on the system and was called twice per frame from the launcher — roughly 124 full process-table scans a second while sitting in a menu. Cached for one second.
Loading a car's cloud setups no longer blocks the render thread.
🧹 Internal
Shared-memory layout tests parse graphics, physics and static pages captured verbatim from a live AC 1.16.4 session through the same zerocopy call the app uses. Previously every test built an Ac* value in Rust and read it back, so none could detect a mismatch with the game.
The test suite now compiles under the workspace edition and lints. It was pinned to edition 2021 against the workspace's 2024 and omitted [lints] workspace = true, so unwrap_used and panic were silently unenforced across it. Two modules that asserted nothing about this project were removed — one never imported the crate under test, the other spawned sh and checked its exit status.
CI builds with --locked and runs on actions/checkout@v6, matching the release workflow.
Version numbers come from the manifest. The release scripts and the generated screenshots hardcoded v0.2.3, two releases behind.
Screenshots regenerated, including Help_Modal.svg, which was byte-identical to Analysis_Radar.svg because the tester set a field the renderer does not read.
The commit convention is written down in AGENTS.md.
Automated Release Pipeline: Added cargo-dist configuration and a GitHub Actions workflow that builds and publishes Linux and Windows binaries, with shell and PowerShell installers.
Continuous Integration: Added a CI workflow running cargo fmt --check, cargo clippy --workspace --all-targets and cargo test --workspace on Linux and Windows.
🛡️ Bug Fixes & Stability
In-App Updater Platform Selection: The updater looked for a -linux asset suffix that no release has ever published, so on Linux no update was ever offered. Asset selection is now based on the running OS, rejects artifacts that are not the application (shm-bridge, installers, checksums), and refuses to install a build for a foreign platform.
In-App Updater Archive Support: The updater now unpacks the application binary out of release archives (.tar.gz on Linux, .zip on Windows) instead of only handling bare binaries.
Live Micro-Sector & Predictive Lap Time Engine: Real-time sector split analytics (S1, S2, S3) and predictive delta estimation built into ac_core::analyzer and UI tabs.
Crash Diagnostic Logging & Panic Hook: Added custom std::panic::set_hook diagnostic logger that captures unhandled exceptions and exports detailed crash trace dumps (crash_report_<timestamp>.log) to the logs directory.
External JSON Localization System: Moved all UI translations to external data/locales/en.json and data/locales/ru.json files with embedded compile-time fallbacks.
Linux Bash Build Script (build_release.sh): Added executable Linux release packaging script for native Linux TUI binaries and Wine/Proton shm-bridge.exe.
Pixel-Perfect PNG Text Glyph Renderer: Enhanced tui_tester tool with bitmap text glyph rendering for readable English PNG screenshots.
🛡️ Bug Fixes & Stability
Safe Lock Protection: Converted SafeLock mutex primitives to handle poisoned locks without crashing or panicking.
Cross-Platform Compatibility: Gated Win32 file mapping APIs cleanly under Linux target stubs so cargo check --workspace passes without errors on all platforms.
Clippy Cleanliness: Resolved all Clippy warnings and enforced strict workspace linting rules (unwrap_used = "deny", panic = "deny").
The graphical front end. Everything that decides anything is in the shared core, so a fix in the list above reaches this too; what is listed here is the window's own.
v0.4.9this build
Braking, and the half of the engineer that was missing: it now follows up on what it told you. Plus a library of laps other people drove, so nobody's first evening at a circuit is a blank screen.
Added
A BRAKING view, under ANALYSIS. Half a lap time lives in the brake pedal and nothing in this program looked at it. It names which of the five things a driver can do wrong they are doing — soft, early, released, trailed, or slow through the middle on braking that matched — in which corners and what it cost: "you reach 1.50 g where the reference reaches 1.77, in three corners, and it costs you 0.42 s". One fault per corner, because a corner has one thing wrong with it and three symptoms, and pressure is named before the brake point it explains: told to brake later while still pressing gently, a driver arrives too fast with no way out of it.
Braking points on the map: this lap's filled, the reference's as a ring, and a line along the road between them, so "14 m early in T7" is a length on the track rather than a number in a table. Amber where the core found a fault.
SINCE LAST TIME, on ENGINEER → POST-STINT. What is no longer flagged, what is new, what got worse, and the best lap against the best lap — and under a fixed finding, "you were asked for front bump 9.00 → 7.00 clicks". That is the only line in the program that tells a driver their own change did something. It never claims the change caused it: track temperature moves, and a driver on their second evening is a better driver than on their first.
A LIBRARY tab — laps other people shared, and the ones you choose to share. A module, so the base program's promise holds by construction for anybody who does not install it. No accounts: two random numbers made on your machine, a name you claim, and nothing that can ever be sent to you. What leaves the machine is yours to decide per lap, and the setup is off unless you say so. A downloaded lap lands in your ghosts like one you saved yourself.
Names that cannot be faked. A name belongs to one identity, and names that only look different are the same name: Rg0sh, Rgоsh with a Cyrillic о, R g o s h and Rgoosh are all taken the moment somebody has Rgosh.
Fixed
Somebody who restarted their program appeared twice in the LAN list, and three times if they did it again. Peers were keyed by an id that is fresh on every run; they are keyed by where a session is actually sent now, so a restart replaces itself. One row per machine and not per interface, too.
corners::detect and decompose were running on every frame, separately, in three views of ANALYSIS. They walk the whole trace. One walk now, kept until the lap changes.
Modules: the parts of the window you choose to have, a little language to write your own with, and the program's own screens written in it — so a tab is something you edit rather than something you replace.
Added
Modules. A MODULES tab, and seven things in it that are yours to switch on. Each says what it costs before you press anything, what it depends on, how large it is and when it last changed; each that adds a tab says so on that tab, with its name and where to turn it off. Off, a module costs nothing at all — not the code, not the memory, not the frame time — and that is asserted by a test rather than promised. Turning one on takes effect at once: no restart.
The catalogue comes off the server. The list of modules is fetched from proengineer.app at startup, cached, and falls back to the copy built into the program when there is no network and nothing cached — so a new module or a corrected description needs no new release. A module newer than your build says "needs RG Pro Engineer x.y.z" rather than offering a button that would do nothing.
One STUDIO tab, and one thing in it: the screen you are writing. Three modules with a tab each meant three places to write a formula and no view of the three together — work a number out, save it, put it on a screen, and that was three tabs. Then one tab with four sub-tabs, which was the same fault smaller: three of them were a place to try a line, and a line of the document with the preview beside it is trying it. It is one module, one tab and one document now. What was worth keeping from the other three is the channels themselves — the ones you saved and the ones the tyre pack downloads — which are ready-made lines listed beside the editor and put into the document by pressing one. Anybody who had the old ones has this: they all depended on the same module, so nothing had to be migrated and nothing is lost.
The studio is a place to work, not a pile. A rail of tools on the left, the document in the middle and what it draws on the right — all three on the screen at once. Everything that is not the document used to sit under it in the order it happened to be written, so writing a screen meant scrolling past what you had just written to reach the thing you wanted to put in it, and then scrolling back to see where it went. The rail holds the four of them: the ready-made lines, the program's own tabs, your builds and the sharing, each one foldable, and only the lines — the one thing nobody could guess at — open to begin with.
An editor, not a box of grey text. The formula field colours what you type — channels one colour, functions another, keywords a third, and anything the program does not know in red the moment it stops being a name it does know. A mistake is underlined where you wrote it rather than described underneath. The names finish themselves: type three letters and the list appears with what each one is, tab takes the lit one. The document editor has line numbers, because its own messages talk in them, and a row of buttons that write each shape of line, because Name (unit) 0..100 = a formula is four ideas at once and nobody memorises it from a paragraph. It indents itself: Enter starts the next line where the last one did and one step further in under panel or paint, Shift-Tab is the way back out, and a blank line returns to the margin so the indentation cannot creep.
Nine channels ready-made. Tyre and brake channels: grip used, trail braking, both feet down at once, coasting, speed per gear. This one really is a download — data rather than code — verified against the checksum the catalogue publishes and refreshed in the background, which the program never does to itself.
A language you can build something in. RGE started as a way to work a number out and grew, over this release, into something a person can write a small application with. It is still total — no loops, no recursion, nothing that allocates while it runs, every jump forward — which is what lets a document somebody wrote run inside a window a driver is racing beside.
Words. A line in quotes is a sentence, {…} in it is a formula, and {fuel:1} is that formula to one decimal place. Put when after the closing quote and it appears only while that is true, which is how a warning is written. A hole with no answer draws a dash, never a nought.
Things it does.print, warn, alert and title are actions: a formula can say something as well as work something out, and a row that fired warn or alert draws itself in that colour — one thought written once. Only the arm of an if that is taken runs, so if a then print "yes" else print "no" says one of them.
Things it keeps.state best = 0 declares a slot and remember best = max(best, speed) writes to it, once a frame, in the order written. A peak, a counter, how long something has been true: the things an expression alone cannot do. Slots are not saved — a slot is what is happening in this session, and restoring yesterday's peak would be a number nobody measured.
Things it draws.paint NAME 220 is a panel you draw on, and on it dot, line, box, ring, arc and text — every number a formula. The space is 0 to 1 across and down, a radius is a fraction of the shorter side so a ring is round on any shape, and angles are degrees with 0 straight up going clockwise, the way a dial is read. A rev counter with a needle, a red zone and the number in the middle is nine lines. Any mark takes the same when a sentence does, so a warning light is one more.
And two marks that are about the whole lap rather than this instant: path x, y draws a map of the track you are on, fitted to the canvas so the numbers need no scaling, and here x, y puts a point on that map — where the car is. Two lines for a track map you drew yourself.
Another lap.best(f) is what f was at this place on the track on your quickest complete lap of the session, and last(f) the same on the one you just finished — so the thing every telemetry program is for is one line:
let delta = speed - best(speed)
They are a table by distance rather than a number, which is what makes that mean "slower than your best lap here" rather than "than its average". The table is worked out once when the lap changes, so asking it costs one lookup per sample. Inside best(…) everything is about that lap, so best(highest(speed)) is the highest it went round the quickest one. Until there is such a lap they give nothing at all: a delta of zero in your first minute would read as "exactly on your best", which is the one thing it must not say. The best lap is chosen the way the analysis screens choose it — quickest, complete and valid — from one place, because two would drift.
Trigonometry, and ten other functions: sin, cos, tan, asin, acos, atan, atan2, log, log10, exp and lerp. Every angle is in degrees, in and out, because every angle this program shows is — atan2(lat_g, lon_g) is which way the car is being pushed, 0 straight ahead.
A module you wrote.module "Fuel Warden" and about "…" give a document a name and a line about what it is for; it appears on MODULES beside the ones that shipped, saying which tabs it adds and whether every line reads.
And a chapter of the handbook about all of it, in the program: GUIDE → Writing your own screen, which appears while the studio is on and is not there when it is off, because "off costs nothing" has to mean the documentation too. It is this window's own chapter rather than the shared core's, on the same argument the streaming ones are: the terminal has no studio.
Screens you write yourself. A document, not a program, and one line per thing you want to see:
``` let g = hypot(lat_g, lon_g)
panel HOW HARD IT IS WORKING Combined g (g) = g Grip used (%) 0..100 = clamp(g / 1.6 * 100, 0, 100) ~ Speed (km/h) = speed ```
A range makes it a bar, a ~ makes it a line over the lap, and anything else is a number — so there are no shape keywords to remember. instead of DASHBOARD puts your screen where the program's own tab was, and that is the one thing in the format that takes something away: it is a separate word from screen, the editor shows a table of everything the document is taking and a GIVE THEM ALL BACK that keeps your work, and the tab itself says every time it is opened that it is not the program's screen. panel NAME starts a box, screen NAME starts another tab, columns 2 puts two side by side, and let grip = … names a piece of maths the rows below can use. Nothing has to line up.
It is drawn by the same widgets every other screen uses, at the same cost, and it follows the car — including a car arriving over the network. The document and what it draws are side by side, updating from the car as you type, so writing a screen and looking at it is one loop rather than two tabs. KEEP IT saves and BACK returns to the last version you kept; six worked documents are one press away under START FROM. A line that does not read costs its own row and names its line number, because a screen that went blank on a typo is a screen nobody would dare change.
The program's own tabs are yours to edit, not to replace.instead of DASHBOARD always worked, but what it gave you was an empty panel and the job of rebuilding from memory what the tab had been drawing — which is deleting a screen, not editing one, and the rows somebody forgets are the warnings they stop getting. Pressing a tab's name in the studio now puts that tab's own document in the editor: its panels, its rows, its arrangement, as they are drawn now. Taking a reading off it is deleting a line; moving one is moving a line.
It ships that way for the tabs this format can say — DASHBOARD, STRATEGY and both halves of TELEMETRY — and each of them states, in the red panel before anything happens, what does not come with it: the engineer's findings and the damage on DASHBOARD, the four-corner tables on TELEMETRY, the stops themselves on STRATEGY. For the tabs built out of the corner table, the engineer's chains or the setup files there is nothing to hand over, and the panel says that too rather than pretending an empty page is an edit.
A sub-tab can be stood in for on its own. Six of the eleven tabs are not one screen: TELEMETRY is two sub-tabs, ANALYSIS is eight, and ENGINEER, SETUP, RECORDS and SETTINGS have their own. Taking one of those whole took every sub-tab at once and put one document under all their names. instead of ANALYSIS / CORNERS stands in for one of them, drawn under that tab's own strip where that sub-tab was, with the other seven one press away and untouched. Taking such a tab whole is now refused, and says which sub-tabs it has.
The preview is a window of its own. A whole screen judged in a column forty characters wide, beside the code that made it, is the wrong shape for the job. Opening a tab's code now opens the preview as a second window — one the desktop treats like any other, so it goes on the other monitor — and the studio gives its whole width to the editor while that window is up. KEEP IT closes it and puts everything back.
Builds: the same program arranged more than one way. One document was one answer, so a second arrangement lived in a text file somewhere and was pasted back. A build is that document under a name; switching is a press, a new one is copied from the one you have rather than blank, and the one that was always there keeps its own file, so a driver who never makes a second build sees no change.
Seventy-two channels instead of thirteen. Every tyre's core and tread temperatures, its pressure, its wear and its slip; every brake's disc and pad; load, travel and camber at each corner; fuel, brake bias, the aids, force feedback, ride heights and the weather. Filed under nine headings, because seventy names in one list is a list nobody reads, and every one carries what it is in and a line about it.
A formula can ask the whole lap, not only this instant.highest, lowest and average: speed / highest(speed) * 100 is where you are against the best you managed. They are worked out once when the lap changes, never per sample — a formula that walked the lap at every point of it would be ten million evaluations to draw one line.
a ? b : c in RGE, the short way to write if a then b else c. The words are for somebody who has never written a formula; the marks are for somebody writing their fifteenth. They compile to the same three instructions, and a test insists the two spellings never disagree.
Look. Five palettes including a light one for a daytime desk or a stream overlay, four text sizes, three spacings, square or soft corners, three weights of panel border, three heights a panel lifts off the page, bars plain or marked in quarters, how loud a verdict colour is, what sits behind the screens — the graph paper, the lap, both or nothing — proportional prose, and one colour instead of two for anybody who finds the orange loud. PUT IT ALL BACK is one press. The palettes come from the shared core, so the terminal and the in-game panel offer the same five under the same names.
The Circuit in Three Dimensions. Every map in this program is flat, and a circuit is not. The one on ANALYSIS is drawn from where the car went — the right shape, seen from directly above, and half the information. Eau Rouge climbs forty metres in three hundred; the Corkscrew drops six storeys; Bathurst is a mountain. From above, all three are a bend.
The altitude was there the whole time — it comes off the game's own files with the rest of the circuit, and the core's own note beside it said no map used it. This is the map that does: turn it, tip it, zoom it, drag it with the pointer, and colour your lap on it by speed, throttle, brake, gear or height, with the corners carrying the names the track gives them and the car on it where you are. The elevation is at its own size: a circuit as it is, with the slopes the track really has, rather than a hill drawn taller than it is to make a point. And a free camera — a switch, then W, A, S, D to fly, Q and E for up and down, the pointer to look — so it can be walked the way a scene is walked rather than only turned on a turntable. The old turntable is still there and is still what it opens with.
And it answers rather than only showing. The lap is drawn where the car actually was, from the car's own position rather than down the middle of the road — running wide at an exit, clipping an apex and a different line into a corner are the three things a map of a lap is for, and all three used to come out as the same stripe down the centre. The car is drawn as the car, its own outline read from its own model, at its own size, pointing along the road: on a whole lap it is a speck inside the marker, and the moment you zoom into a corner it is what says how much of the road you were using. And pointing at the lap says what was happening there — speed, gear, the pedal, where round the lap, and how far off your best you were at that exact place.
It is not the scenery. The trees, the buildings and the grandstands are meshes inside the game's own model files; reading and drawing those would be a different program, and one that could not run beside the game it was decoding. What is here is every part of the circuit the game will tell us about.
And a great deal on it: the road at its own width, which comes out of the track's files — eleven metres at Spa and half that through the Bus Stop — with its two kerbs, the pit lane, the start line, the sector lines, the corner names, the surface lit by which way it faces, its own shadow on the ground beneath it, and the climb as a profile under the map with the car's place marked on it.
It costs more than the other screens and says so where you can see it. PLAIN is the road and your lap and nothing else, for a machine the game is already using all of; FULL adds the shadow, the kerbs, the pit lane and the start line; EVERYTHING adds the names, the sectors and the profile. The projection is worked out once per camera move rather than once per frame, and a frame hands egui two meshes and a handful of lines.
The Logbook. The one part of setup work this program did not touch. It reads the car, it tells you the front left is over its window, it keeps every lap of every session — and what a driver actually does between stints is write down what they altered and look, next time, at whether the times moved. That was a notebook beside the keyboard, and a notebook cannot say what the best lap was that evening.
It adds no tab. RECORDS already lists every session you have driven, so this is what that list becomes: each evening carries how much its best lap moved from the one before it in the same car — the number a work log exists to produce — a line you write about what you changed, typed straight into the row so it can be done between stints, and under it the run of evenings in the car you are in, as a line. Off, the table is exactly the five columns it has always been.
It is deliberately not a setup diff. The core can compare two setups, but nothing on disk records which setup was on the car during a past session, and inventing that link would be a guess dressed as a measurement.
Call Outs. One line across the top of every screen, carrying the single worst thing the engineer currently has to say. The core works out what is wrong continuously and the window drew it on ENGINEER and on the dashboard — so somebody reading a trace, or writing a screen in the studio, was told nothing at all, and a warning that only reaches you on the page about warnings is a warning that arrives after the stint. Nothing is drawn when there is nothing to say, which on a clean car is most of a stint: a banner that is always there stops being read. How much has to be wrong before it appears is a choice, and so is whether it is one line or the whole finding with the reasoning under it.
Motion has five switches, not one yes. Panels rising, the cascade down the page, a panel answering the pointer, the worst reading breathing, and the backdrop drifting — each on its own, because this is the one module that is entirely a matter of taste and somebody who wants panels to rise but cannot stand a moving backdrop had to have both or neither. And how long is separate from how much: CALM, STEADY and QUICK change what an animation takes rather than how far it travels, which are different tastes. PUT IT ALL BACK is one press, as in Look.
Look grows three settings about reading rather than taste.THE BIG NUMBERS makes the readings you take at a glance larger and leaves everything else exactly as it was — so a window on a second monitor can be read from across the room and still holds as much as it did; scaling everything gives you a screen with half the content on it. CONTRAST is distance from the background rather than brightness, which is what the word has to mean on a light palette as well as a dark one — "sharper" cannot mean "brighter" when the text is black. WARMTH puts a cast over everything for two in the morning, by multiplying and putting the brightness back rather than blending toward amber, so the blue stays blue and the red stays red instead of the whole palette flattening into one colour. All three are baked into the eight colour slots when the choice is made, so they cost nothing per frame.
Motion. For a machine with frames to spare: every animation longer, the window running at your screen's rate instead of waking only when something changed, panels rising into place rather than only fading in, and the lap behind the screens drifting — the one thing here allowed to move on its own, because it carries no reading and a window of unchanging numbers reads as a photograph of a program. Panels arrive in a cascade, each a little after the one above it, so a screen assembles rather than appears; a panel answers the pointer, its border lifting toward the structure colour; and the single worst reading on the screen breathes — the one colour allowed to move, because a screen where everything moves says nothing and one where the worst thing on it breathes says exactly what somebody needs to see. GENTLE is half of all of it, chosen on its own card.
What Motion deliberately does not do is roll a number to its new value. An eased number is a number nobody measured, shown as though somebody had, and that is the one thing this program refuses everywhere else.
Send what you made to somebody, as one line. A palette, your channels or a whole screen becomes a string like RGE1.channels.3f2a91c4.… that survives being pasted into a chat window; COPY MINE puts it on the clipboard and USE IT takes theirs. A file would have been the wrong shape — asking somebody to find a folder, attach a file and explain where it goes is asking them not to bother. Pasting a palette where channels go is answered by name, and a line that a chat window broke in half is refused rather than half-applied.
A module can have settings of its own, drawn on its card in MODULES — beside the thing they are about, on the page somebody is already looking at when they wonder. A tab for one switch would be the "a module per function" fault the catalogue is written against.
Faster
A plot of a lap is rebuilt when the lap changes, not every frame. Every screen in the studio ran each of its formulas over every sample, allocated a fresh vector of points and handed it to egui — sixty times a second. A dozen channels plotted at once was a hundred thousand formula evaluations and about a megabyte of allocation per frame, to draw lines that had not moved, and the window lagged with the studio open — which is exactly the tab somebody sits on while writing. Three things: a lap is decimated to four hundred distance buckets keeping the lowest and the highest of each, so a spike survives and eight hundred points are drawn instead of three thousand; derived work is done when its input changed and a tenth of a second has passed, which is invisible on a two-minute lap and six times less work, with a keystroke bypassing it so the editor still answers at once; and the lap is copied only when there is something new to copy, rather than copied to find out whether it is the same lap. Measured on the demo stint: 73 ms of formula evaluation per second of wall clock became about 8, and 2.16 million plotted points per second became 28.8 thousand.
Fixed
The engineer's advice stops flickering. A verdict is recomputed on every reading and a threshold has two sides, so a tyre resting on its limit crossed it several times a second and the line appeared and vanished with it. A finding is now shown once it has been continuously true for two and a half seconds, and it stays for four after it stops — so it neither flashes up nor blinks off. The number inside it keeps moving; only the line holds still.
--version.rg_pro_engineer --version prints the window's version and the core's. Until now the only way to know what somebody was running was to read the corner of a screenshot, which is how a driver on the first release, reporting a fault fixed two releases earlier, cost an evening to identify.
The player played a two-minute lap in twenty seconds.1x moved the head a fixed fraction per second regardless of how long the lap actually was, so every speed on the bar was wrong by whatever your lap time was not. It is a multiple of real time now, taken from the lap's own duration.
The tab strip fits. It was written when the program had ten tabs; with modules installed the fourteenth was painted underneath the LAN button and could not be clicked.
No module claims to be a download when it is not. Every entry offered DOWNLOAD · 0 KB. One that ships in the program says INCLUDED and offers a switch; only one whose content really comes from the site offers to fetch it.
Motion's panels never arrived. The arrival animation began already finished, so every panel was simply there and not one frame of the effect the module advertises had ever been drawn.
Thanks
@1sad, who found that this window drew the car mirrored — his front-left tyre's numbers were at the front-right corner — and then read the tread table against the game's own app closely enough to find a second, separate fault underneath it. Both are fixed; the second reaches all three front ends because they share one core. Named here with his permission.
The patch that makes it pleasant to use: a lap you can play back, a session that survives the window closing, and the screens tidied up.
Added
Play the lap back. A transport under the analysis screens: play, pause, step a fiftieth of a lap at a time, drag the head anywhere along the bar, and four speeds from a slow walk to fast enough to find the corner you meant. The map's marker, every trace's cursor and every readout follow it, because it writes the same number the pointer writes — walk a corner and watch the line and the pedals together, with your hand off the mouse.
A race, not a stint. Every session is written as it is driven, one lap at a time, and RECORDS → PAST SESSIONS lists them: when, the car, the track, how many laps and the best of them. A lap out of a session in June loads on the analysis screen as the lap to compare against, exactly like one saved by hand — it is the same file in the same shape. A line at the top says whether what you are driving right now is being kept, and nothing from demo is ever written.
How repeatable you are, corner by corner. ANALYSIS → CONSISTENCY. Every telemetry tool measures consistency by lap time, which tells a driver they are inconsistent — something they knew — and nothing about what to practise. This measures every corner across the whole stint: how much its time wanders, what the average is losing to your own best through it, and why it moves — the brake point, the apex speed or the speed you arrive at. It also adds up a lap made of your own best corners, which unlike a simulated optimum is a time you have already proved you can drive, section by section.
What to fix first, on the dashboard. The single most important finding, drawn whole — the line, the action, the mechanism behind it, the measurement, and what to look at next run to know whether the change worked. The engineer's answer used to be three tabs away, so a first hour was spent reading numbers with nothing saying which one mattered. Nothing is drawn when there is nothing to say.
Fixed
The update offer is a card, above the panels. It was a sentence and a small button below both of them — on an 800-pixel window, below the fold. A driver said he could miss it, and he could.
A row of panels is a row. A panel with four lines beside one with twelve ended its border a third of the way down and left the rest of the row as a hole; on STRATEGY the fuel calculator stopped a hundred and thirty pixels above the brake bars next to it. Rows are drawn to one height now.
The analysis overview's CAR (T/B/W) panel is called EACH CORNER.
Three faults reported by one driver, and the name back.
Fixed
The tread and pressure fixes from the core. The left of the car had its inner and outer tread readings the wrong way round in Assetto Corsa — so the camber advice for that side was backwards — and the pressure every car was judged against is now the one the car states in its own files rather than a class average. Both are in the shared core; see its changelog for the detail.
One answer about tyre pressure instead of three. This window had its own copy of the rule: ENGINEER → PRESSURES read the class table directly, the in-game panel's frame was sent the raw configuration, and a lap's pressure deviation used a third figure. All four places ask the core now, so the number on the screen and the number the advice is aiming at cannot disagree.
The name is RG Pro Engineer again. v0.4.5 dropped the "RG" to make one product of two; they are two, with two listings and two downloads, and somebody has to be able to tell which they are holding. The wordmark, the title bar, the splash and the Linux desktop entry all say it. Nothing about an install, a download or an update changes.
--locked builds again. The core gained a dependency and this crate's lock file did not record it, which stopped the release build rather than anything a driver would see.
Watching somebody else drive, and the car drawn the way the driver sits in it.
Added
Somebody else's session, on your screens. Open LAN in the strip: SHARE MY SESSION or WATCH SOMEBODY. The whole reading travels thirty times a second, so the watching copy finds the laps, builds the traces, draws the map's line and runs its own engineer — every screen, in the watcher's own units and language. The copies find each other on the network; typing an address still works. It works with the terminal in either direction.
Where the time went, on the map. The lap coloured green where it was quicker through here and red where it was slower, by what each stretch cost rather than by the running delta. Second in the strip, after speed.
The braking zone, in the corner table. How much harder the car was stopped, how much longer the zone was, and how much longer the driver stayed on the pedal after peak pressure. All three go absent together on a corner either lap took flat.
Settings → SHARING. Readings a second, only-while-on-track, the quiet timeout, being findable, and whether a session is accepted from the whole network or only from this machine.
Fixed
The car was drawn mirrored. Assetto Corsa models a car with its left wheels at positive x and this widget added that to the centreline instead of subtracting it, so the front-left tyre's temperatures were drawn at the front-right corner — and the tread gradient with them. Reported on the release thread as three faults; it was one sign in one line.
Laps stopped disappearing. They were written beside whatever directory the program happened to start in — the home folder, for a desktop entry — while the terminal wrote beside itself, so neither could see the other's. One store now, and older laps are moved into it at startup.
It no longer connects to a game that is not running. On Linux the bridge leaves its shared-memory pages behind when the game quits, and those read as a connected car at 0 km/h on a track with no name.
The page stopped twitching. Every screen shared one scroll position, the scrollbar appeared and vanished under live numbers and reflowed the content beside it, and a finished lap slid the lap picker out from under the pointer.
The update check reads this window's own version, not the core's — the first release where the two differ would otherwise have offered an update for ever.
The car's class comes from the game's own tags in ui_car.json where there are any, rather than from a guess about the car's name.
Nothing is drawn as a zero that the game does not publish. Competizione reports no tread temperature, no ride height and no wind; Assetto Corsa reports no brake pad wear. Four zeros read as ice-cold tyres and 0 mm of ride height reads as a car on the tarmac, which is what v0.4.0 showed.
The first release of the window: ten screens over the same core the terminal reads, with the analysis a character grid cannot have — hover a trace and read the value under the cursor, lay two laps over each other, pull one corner out and zoom into it.