Delving Into Game Modifications
There's a specific kind of frustration that only comes from a game you love. Not a broken game — a brilliant one. One that nails the world, the story, the atmosphere, the combat. Everything clicks except one thing.
For me, in Cyberpunk 2077, it's the driving.
Night City is stunning. The missions are layered. The characters stay with you. But every time I get behind the wheel of a Rayfield Caliburn — a hypercar that should feel like controlled violence — it floats. It slides. The suspension feels disconnected. The tyres grip like they're made of soap.
It's not a bug. It's a design choice. But it's the wrong one.
The Mod Scene
Cyberpunk has an incredible modding community. Cyber Engine Tweaks (CET) gives you runtime access to the game's internal database — TweakDB — which stores every value the game uses. Vehicle mass, tyre friction, suspension stiffness, braking torque. All of it, exposed and modifiable.
Mods like True Grip exist and do a solid job of improving the feel. But they're one-size-fits-all. Every vehicle gets the same treatment. You can't tune per-car. You can't experiment. You install, you play, you hope it's better.
I wanted more control than that.
Building the Configurator
So I built one. A full vehicle physics configurator — a desktop tool that lets you pick any vehicle in the game, adjust 23 handling parameters across six categories, and push those changes directly into the running game.
The categories map to what actually matters in vehicle dynamics:
- Mass and dynamics — total mass, centre of mass height, air resistance
- Engine — max torque, engine braking, top speed
- Suspension — spring rate, damping, rebound, anti-roll (front and rear independently)
- Tyres — grip coefficients, lateral and longitudinal multipliers
- Steering — lock angle, speed sensitivity
- Braking — front, rear, and handbrake torque
Each parameter has a slider. Green means you've changed it from stock. Amber means it's at the game's default. You can see exactly what you've touched at a glance.
Presets as Starting Points
Rather than tweaking 23 sliders blindly, the tool ships with preset profiles:
Realistic pulls grip down, adds mass, softens suspension. Cars feel heavier, less arcade. You actually have to brake for corners.
Sport tightens everything up. Stiffer springs, more grip, sharper turn-in. The car does what you tell it.
Drift drops rear grip, loosens the anti-roll bar, increases steering lock. The back end steps out on demand.
Track is the full circuit setup — lightweight, stiff, maximum grip, aggressive braking.
Apply a preset, then fine-tune from there. Each vehicle has its own stock values, so the same "Sport" preset makes a Caliburn feel different from a Quadra Type-66 — because it should.
Live Tuning
The part that makes this genuinely useful: you don't have to restart the game.
The configurator writes a config file directly into the CET mod folder. The in-game Lua script polls that file every two seconds. Change a slider, alt-tab back to the game, and the car you're driving has already updated.
No restart. No save/load. No friction.
The CET overlay shows you exactly what's active — which vehicles are modified, what values are applied, when the last update landed. You know the mod is working because it tells you.
Safety First
This is important: no game files are modified. Ever.
The tool generates a CET Lua script that modifies TweakDB values in memory at runtime. When you close the game, everything reverts to stock. The mod is a single folder inside CET's mod directory. Delete the folder and the game is exactly as it was.
No file replacement. No archive patching. No risk to saves. No risk to game launch. If something feels wrong, apply the Stock preset and push — instant revert.
Fighting the Sandbox
This is where it got interesting — and by interesting, I mean frustrating.
The first plan was clean: the configurator writes a JSON config file, the in-game Lua script polls it every couple of seconds, applies changes on the fly. Tune a slider, alt-tab back, feel the difference immediately. No restart.
CET's Lua sandbox had other ideas.
io.open failed with "invalid argument" regardless of path format — backslashes, forward slashes, absolute, relative. I tried five different path strategies. All of them hit the same wall. CET's sandbox restricts file I/O in ways that aren't documented. Then require("json") returned nil — no JSON parsing available either. So even if I could read a file, I couldn't decode it.
The fallback was embedding config values directly into the Lua script itself. The configurator regenerates init.lua with the values baked in as a native Lua table. No file I/O, no parsing, no external dependencies. You push from the configurator, reload CET mods, and the new values are live. It's less elegant than file polling, but it actually works.
Then came the real puzzle: TweakDB path resolution.
TweakDB stores every game value as a "flat" — a key-value pair addressed by a hashed ID. To change the Caliburn's max speed, you need the flat ID for its drive model's maxSpeed property. The obvious approach is to get the drive model record's ID, append .maxSpeed, and use that as the flat address.
Except CET has at least three different ways to construct a TweakDBID, and they don't all produce the same hash.
The string round-trip — get a record ID, convert it to a debug string with TDBID.ToStringDEBUG(), concatenate the flat name, feed it back into TweakDBID.new() — produces a completely different hash than the original. The native .. concatenation operator on TweakDBIDs? Also wrong. The diagnostic overlay told the story clearly:
<TDBID:6B9883AA33>: nil -> 22.22 (now: nil)
nil before. nil after. The SetFlat call writes to an address that doesn't correspond to anything the game actually reads. The value goes into a void.
The fix turned out to be the two-argument form: TweakDBID.new(baseId, ".flatName"). This derives the child hash from the parent's actual hash rather than from a string representation of it. It's the difference between asking "what's the maxSpeed field of record X" and "what's the record whose name happens to look like X.maxSpeed". Same question, different answer.
But even with the right ID construction, the values still weren't changing. The diagnostic showed [OK] — writes confirmed — but the car drove the same. One more piece of the puzzle: spawned vehicles cache their physics. TweakDB changes update the database, but an already-loaded vehicle keeps its old values. You have to despawn the car and summon a fresh one to pick up the changes. Not obvious. Not documented. Discovered by driving in circles wondering why nothing felt different.
The Property Map Problem
Once the addressing was solved, the next surprise was that half the property names were wrong.
The desktop configurator was built around a property map derived from community documentation and older mods. Properties like maxSpeed, turnMaxAngle, tireFriction, speedTurnMultiplier — all standard names from pre-2.0 Cyberpunk. None of them exist anymore.
CDPR's 2.0 update completely restructured the vehicle physics system. Properties moved, got renamed, or disappeared entirely. maxSpeed as a flat doesn't exist — speed is now emergent from engine torque, RPM, gear curves, and air resistance. Tyre friction moved from the drive model to individual wheel presets. Steering parameters got replaced with turn rate values.
The only way to find out was to write a diagnostic script that probed every possible property name on every record in the chain. Vehicle record → drive model → engine → wheel setup → front preset → rear preset. Try fifty getter names. Report which ones return values.
The results were illuminating. The drive model has total_mass, airResistanceFactor, handbrakeBrakingTorque, wheelTurnMaxAddPerSecond — but not maxSpeed or turnMaxAngle. The engine has engineMaxTorque and resistanceTorque — but not engineBrakingTorque. The wheel presets have frictionMulLateral, springStiffness, swaybarStiffness — but not tireFriction.
And the rear wheel preset? Not accessed via RearPreset() — it's BackPreset(). Because of course it is.
Moving In-Game
The desktop Python configurator worked. But alt-tabbing to tune, then reloading, then resummoning, then alt-tabbing again — the loop was too slow. So the configurator moved where it belongs: into the game itself.
The entire tool now lives in a single CET Lua file. No Python, no external dependencies, no file I/O. It reads stock values directly from TweakDB when you open the overlay, presents sliders for 26 parameters across seven categories, applies presets as multipliers against actual stock values, and writes everything back with a single button press.
The vehicle selector reads the game's own data. The sliders turn green when you've changed something from stock. The presets — Realistic, Sport, Drift, Track, Offroad — are starting points, not final answers. Pick one, adjust, apply, resummon, drive.
It's the workflow I wanted from the start: tune while you play.
What I Learned
Game modding sits in an interesting space. You're working with someone else's engine, someone else's data structures, someone else's assumptions about how things should feel. You can't refactor the physics system. You can only change the numbers it reads.
But those numbers matter enormously. The difference between a friction multiplier of 0.94 and 0.55 is the difference between a car that sticks to the road and one that slides through every corner. The difference between a spring rate of 21 and 14 is the difference between a go-kart and a boat.
What surprised me most wasn't the tuning itself — it was how much the game changed underneath. A mod built on pre-2.0 property names is dead on arrival in 2.0+. The community documentation lags behind the patches. The only reliable reference is the game itself, queried at runtime, one property at a time.
The tools you need to build the tool are half the work.
What's Next
The in-game configurator works. All 26 parameters write correctly, all stock values read from the live database. The next steps are about refinement:
- Tuning profiles — hours of driving to build presets that actually feel right per-vehicle, not just generic multipliers
- More vehicles — the database has twelve cars and bikes, but the game has dozens more worth supporting
- Packaging — making this distributable as a standalone CET mod, no Python dependency, just drop the folder and go
There's also a web interface on the roadmap — serve the configurator UI over the local network so you can tune from a phone or tablet while you drive. No alt-tab, no overlay, just adjust and feel.
The driving in Cyberpunk 2077 was always the weakest link. Now it's the part I'm spending the most time on — not because I have to, but because I finally can.