A steering wheel in a browser tab
We build our browser driving sim, Seat Time, with artificial intelligence (AI) coding agents. None of those agents has touched our steering wheel. On 25 October, our G923 accepted force commands without reporting a fault. We felt nothing. The rim only went slack.
We wanted to plug in the wheel, open Chrome and drive. A recognised wheel should have its steering and pedals mapped without a calibration interview. We had started sending classic Logitech force commands on 10 October. Getting from code to something our hands could judge took much longer.
Previously: Every number gets a source.
Chrome couldn't see our wheel
Our first obstacle arrived before force feedback—the motor's pull on the wheel. Chrome's native gamepad diagnostic showed zero devices. This was our G923 in PlayStation mode, on macOS. The agents built a passive capture tool, then an input decoder over WebHID, a browser interface for devices. We needed to see what the wheel actually sent before deciding what its controls meant.
The Gamepad specification defines a browser interface for game controllers. WebHID offers access to devices using the Human Interface Device protocol. That gave us another route when the browser's gamepad view wasn't enough. Chrome's WebHID guide describes device selection, incoming reports and outgoing commands.
Our decoder had to turn those incoming reports into steering and pedal values. Force feedback travelled the other way, as commands telling the motor how hard to pull. A readable input report didn't prove that output worked. Neither path proved that driving would feel right.
The agents tested the code; only our hands could test the feel
We kept separate status rows: Implemented, Tested with synthetic input, and Verified on our wheel. Synthetic reports let the agents exercise decoding and state changes without a device. They couldn't tell us whether the rim moved. An unchecked hardware row stayed unchecked after the code passed its tests.
Even a familiar default was wrong for this device. The steering deadzone—the range of movement ignored around centre—inherited a gamepad value of 5%. On our 900° wheel, that meant roughly 22° from centre before steering registered. We chose 0.2% instead. The change made sense in code; the drive still belonged to us.
Our reports became the next fix
The pedals initially showed 100% with our feet off them. The game also told us the accelerator wasn't assigned. After clutch detection, menu focus could run away. That was a long way from the zero-setup connection we wanted.
The agents added an admin Debug tab with a Copy report button. Then they moved it into the Settings screen we actually reached. We could drive, copy the report, and give the agents something more useful than “still wrong”. The report connected what we saw to the values the code had received.
Our released pedals read +1, while one axis stayed at a stale zero until we first moved it. That observation explained why a plausible mapping could treat rest as full input. A later report confirmed the throttle and brake mapping on our wheel. We had a usable feedback loop: we supplied the physical observation, and the agents supplied the next change.
“Very loose” turned out to be a steering ratio
Our description of the steering was “very loose”. The game mapped the entire 900° rim travel onto each car's full road-wheel lock, or maximum steering angle. For the kart, our chosen steering geometry called for only 86° lock-to-lock at the rim. We were turning about ten times farther than that geometry required.
The fix gave each car its own rim lock. The kart used ±43°, Formula Hybrid ±145°, Formula Club 1600 ±224°, and GT3 ±357°. These were our simulation's chosen values, not measurements of those vehicles. We hadn't driven the per-car fix on the wheel yet.
Force could arrive and still disappear
Our pulse and probe checks eventually moved the rim on the G923. That established an output path, but ordinary driving still had separate problems. We started tracing the requested force all the way to the command sent out. Counting the final levels made “weak” a more useful report.
We used the classic commands documented in Logitech's force-feedback protocol. The community new-lg4ff driver was another reference. In our output mapping, force magnitude ran to 127 levels in either direction. That is a command scale, not a measurement of torque at our hands.
The first loss came from the order of operations. We added a minimum-force floor, then multiplied it down with the ceiling. A typical load became about ±5 out of 127. The fix mapped the result between the floor and ceiling instead, with a chosen minimum of 0.08. We stopped shrinking the very floor intended to preserve small forces.
The check worked; the game wasn't sending
Another report showed 38 output writes, all from the force check. There was no recorded in-game force being sent. Moving the rim in a diagnostic hadn't connected driving to force output. We had proved the wrong part of the journey.
The agents made force feedback a saved On/Off preference. With it enabled, driving would arm output automatically. Torque would rise over one second rather than arrive all at once. The hardware evidence at that point still covered the output check, not the new in-game behaviour.
We stopped flattening the kart's force
Headless runs—tests without a display or wheel—exposed another loss before the command reached the wheel. The kart's old reference torque clipped almost the entire steering sweep. Once clipped, different steering loads produced the same maximum signal. We replaced that reference with a per-car peak from a simulated skid-pad sweep, a test on a circular track.
Kart clipping fell from 99% to 0% in those runs. We also introduced a soft knee: a curve that eased the force into its limit. At the low ceiling, the largest frame-to-frame step fell from 23–27 levels to 11–12. These were measurements of the simulation and force code, without a wheel connected.
The ceiling left little room for grip cues
The output loop ran at a chosen 250 Hz, but the ceiling still allowed only 0.2 of full range. Our report remained “still very weak”. We chose full range, using the G923's rated 2.3 N·m. We also strengthened the grip-limit emphasis and slide cues. By choosing full rated strength, we skipped the study's staged thermal soaks, or sustained tests of heating.
The combined change expanded the headless grip-limit cue from 2–5 levels to 20–39 on the non-kart cars. The kart moved from 1 to 9. In the steering-ramp test, understeer drops rose from 1–6 to 7–57 levels, excluding the open-wheel car. That car moved from 0 to 1; its simulated rack barely lightened in that test.
Those exceptions matter. A stronger output range couldn't create a drop absent from the simulated steering load. Nor did a larger command prove we could read it through the wheel. The numbers gave us something specific to take back to a drive.
A frame was hiding the steps inside it
Missions had a further mismatch: they sent the last physics step's torque rather than the frame average. The agents first corrected that. Then they replaced the averaged output with playback of each 400 Hz physics step through the output loop. The chosen Smoothing default was 25%, corresponding to 5 ms.
That change aimed to preserve force changes occurring inside a displayed frame. It didn't come with a recorded drive on our G923. Missions, step playback and Smoothing remained on the list of things we still needed to feel. More detailed output was an implementation result, not yet a driving verdict.
What our hands actually confirmed
Our first full-strength drive from a local development build was “ok so far”. We also reported that the force sometimes “escapes”. We suspected the hands-off guard, which reduces force when the rim appears to spin freely. We hadn't confirmed that diagnosis.
The guard's chosen trigger was rim speed above 360°/s in the pull direction for more than 150 ms. It then limited force to 0.3 of full range. We needed a real-wheel trace to distinguish an intended cut from a false trigger. The record contained no such trace.
The mode switch also remained unverified. Our implementation followed the device byte in the community driver's switch code. It differed from the value in Logitech's PlayStation-mode appendix. We hadn't run that switch on hardware, and our confirmed output checks don't establish which mode was active.
We hadn't connected a G29 or a direct-drive base. The supervised checklist still had no recorded results. It calls for starting with the wheel clamped and force feedback off. Our confirmed pedal mapping and moving-rim checks were real progress, but they weren't a blanket support claim. The missing drives stayed visible beside the features we'd built.
Next, getting into the game sooner
We could finally count where force disappeared between the simulated steering rack and the output command. What we wanted next was for the wheel to tell us when the tyres let go. First, another problem needed attention: how long the game took to load.
Open Seat Time; the G923 connection controls are under Settings → Controls → Wheel in Chrome.
Next: Fast for visitors on phones.
Notes and sources
- Measured — recorded hardware observations: zero devices in our macOS Chrome gamepad diagnostic; pedals at 100% when released; released readings of +1 and a stale zero; corrected pedal mapping; “very loose” steering; slack rim despite accepted commands; moving pulse/probe checks; 38 check-only writes; “still very weak”; and the local-build “ok so far” and “escapes” reports. These are individual observations, not a completed hardware checklist.
- Chosen design values: 5% → 0.2% deadzone; 900° input range; per-car locks ±43°, ±145°, ±224° and ±357°; minimum force 0.08; one-second ramp; 250 Hz output; ceiling 0.2 → 1.0; 400 Hz step playback; Smoothing 25% = 5 ms; guard above 360°/s for over 150 ms, limiting force to 0.3. The roughly tenfold kart mismatch comes from 900° divided by 86°.
- Measured — headless: kart clipping 99% → 0%; low-ceiling frame steps 23–27 → 11–12 levels; non-kart grip cue 2–5 → 20–39, kart 1 → 9. Steering-ramp drops were 1–6 → 7–57, with open-wheel 0 → 1 separately. The full-range comparison also changed grip emphasis and slide cues. These are command-level results, not measured wheel torque or proof of feel.
- Sourced fact: Gamepad specification, WebHID Standard and Chrome's WebHID guide describe the browser interfaces. Logitech Force Feedback Protocol and new-lg4ff describe the command references. Logitech's G923 page supplies the 2.3 N·m rating, not a measurement of our unit.
- Our interpretation: our reports became the agents' hardware acceptance loop; the hands-off guard might explain the escaping force. The study's torque conversions assumed a linear 2.2 N·m scale that wasn't hardware-confirmed. We report levels instead and use 2.3 N·m only as the rating.
- Not yet driven on hardware: per-car rim lock, mode switch, missions, step playback and Smoothing. No G29 or direct-drive results, no completed supervised checklist, and no recorded staged thermal-soak results. The strength decision above is our build history, not a safety recommendation.
AI tools helped draft and translate this article. Logitech, G923 and G29 are Logitech trademarks; PlayStation is a Sony trademark. Seat Time is not affiliated with or endorsed by Logitech or Sony.