Derived channels & metrics¶
Every lap stores a full 60 Hz sample series. Some columns are raw packet values; others are computed at capture time. This page lists every stored channel with its exact formula, then the per-lap aggregates, then the channels the frontend derives on the fly.
Stored sample columns¶
One value per column per tick (~60/s):
| Column | Formula | Unit |
|---|---|---|
t |
tick_index × 1/60, from half the boundary gap (why) |
s |
dist |
running Σ speed_mps × 1/60, from half the boundary gap |
m |
speed |
speed_mps × 3.6 |
km/h |
throttle |
raw_byte ÷ 2.55 |
% (0–100) |
brake |
raw_byte ÷ 2.55 |
% |
coast |
1 if throttle < 1 % and brake < 1 % |
flag |
gear |
low nibble of the gear byte (15 = neutral) | — |
suggested_gear |
high nibble of the gear byte, as broadcast: the gear GT7 suggests, 15 = no suggestion. A lap on which GT7 suggested nothing has no such column, and nor do laps recorded before it was kept (below) | — |
rpm |
raw | rpm |
boost |
raw − 1.0 (packet stores bar + 1) |
bar |
tire_slip |
mean of the four wheel slips | ratio |
yaw_rate |
abs(angular_velocity_y) |
rad/s |
pos_x, pos_z |
raw world coordinates | m |
pos_y |
raw world elevation. Laps stored before it existed have no such column; the survey judge then places them on plan alone, which where the road crosses over itself means against both levels at once | m |
body_height |
raw × 1000 |
mm |
fuel |
raw fuel level | L |
slip_fl/fr/rl/rr |
abs(wheel_rad_per_s) × tire_radius ÷ speed_mps |
ratio |
tt_fl/fr/rl/rr |
raw tire temps | °C |
sus_fl/fr/rl/rr |
suspension_travel × 1000 |
mm |
aids |
bitmask: TCS=1, ASM=2, handbrake=4, rev limiter=8 | mask |
surface |
per-wheel surface codes, 4 bits each, FL in the lowest nibble (0 = no data, 1 = tarmac, 2 = kerb, 3 = dirt, 4 = grass, 5 = sand, 6 = snow, 7 = other) — packet C only | mask |
Optional columns¶
These need an extended packet format, and a lap that did not carry one from start to
finish does not have the column at all rather than a column of zeros. A zero-filled
steering trace reads as "the driver never turned"; an absent panel says nothing false.
The rule is enforced once, at lap completion (prune_optional in
app/processing/laps.py), so it also covers a recording whose packet format changed
mid-lap.
| Column | Needs | Formula | Unit |
|---|---|---|---|
steer |
packet B | wheel_rotation as broadcast |
rad |
acc_lat |
packet B | sway, raw |
see below |
acc_long |
packet B | surge, raw |
see below |
acc_vert |
packet B | heave, raw |
see below |
throttle_f |
packet ~ | throttle_filtered ÷ 2.55 |
% |
brake_f |
packet ~ | brake_filtered ÷ 2.55 — the brake actually applied, after ABS; live driving only, a replay does not carry it |
% |
race_pos |
a race (any format) | race_position, only while GT7 reports one — it sends −1 outside races |
position |
body_slip |
a source that sends the car's orientation (any format) | angle between the car's nose and its velocity, below | ° |
wheel_angle |
packet C | (wheel_steering[0] + wheel_steering[1]) ÷ 2, the front road wheels' mean angle, sign as broadcast |
rad |
engine_torque |
packet ~ | engine_torque_norm as broadcast, 0 where its magnitude is under 1e-5 (replays carry noise below that) |
normalized |
Front wheel angle is what the tyres actually did, where steer (packet B) is what
the driver's hands did on the steering wheel. The two differ by the steering ratio, and
they part company where the rack runs out of travel: past full lock steer keeps rising
while wheel_angle stops. Paired with the lap's wheelbase_m (also packet C), it gives
the kinematic yaw rate — speed × wheel_angle ÷ wheelbase_m, the yaw the car would
have with no tyre slip — against which the measured yaw_rate reads as understeer
(less) or oversteer (more) without a learned baseline — see
Kinematic yaw and balance.
Engine torque is the ~ packet's float at 0x154, unnamed in the community
layout until late 2026 (#150). What it is comes from community reverse-engineering
(GTPlanet's telemetry thread, Nenkai/PDTools#14), not from Polyphony: the engine
torque being delivered, normalized. Over real recordings it correlates with boost
(+0.92) and throttle (+0.87–0.90), is negative on the overrun and exactly 0 with the
clutch in or in neutral. It is not bounded to ±1 — a Taycan runs about −2.7 to +9.9 —
so the column is stored as broadcast and charted on its own axis. The two bytes
before the torque vectors are decoded on the live packet too, but not recorded per
tick: powertrain_flags (0x13E, 0 on combustion cars, 4 electric, 9 four-motor
torque vectoring) and energy_recovery_available (0x13F, whether
energy_recovery carries anything for the car). Both are constant for a car.
Filtered pedals are the pedal position after the aids acted on it. The gap to the
raw throttle / brake column is the intervention itself — TCS trimming throttle, ABS
bleeding brake pressure — measured rather than inferred from the aids bitmask, which
only says whether an aid was active at all.
Accelerometer units are calibrated, not assumed¶
GT7 documents neither a unit nor a sign convention for sway/heave/surge, and the
simulator source cannot prove what a real console sends. The channels are therefore
stored raw, and analysis.accel_calibration() fits each axis against physics the same
lap already recorded:
| Axis | Reference | Recovers |
|---|---|---|
| lateral | v × ω, with ω the signed yaw rate taken from the driven path (the stored yaw_rate column is absolute, so it cannot settle the sign) |
unit and sign |
| longitudinal | dv/dt from the speed trace |
unit and sign |
| vertical | — | nothing; no independent reference exists |
Each fit is a least-squares slope forced through the origin (zero acceleration must mean
zero reading), graded by R² about zero rather than a mean-centred correlation — a
long constant-rate braking zone makes both series near-constant, where Pearson r
collapses into numerical noise. A fit needs ≥ 150 qualifying samples and R² ≥ 0.75; below
that the axis reports fitted: false, falls back to assuming m/s², and the g-g panel
says the scale is unverified.
The result is delivered as g_per_unit — multiply the raw channel by it to get g, sign
included — on the accel key of /api/analysis/compare, fitted on the reference lap and
applied to every lap in the comparison so they share one axis.
Body slip angle¶
The angle between where the car points and where it is going: nothing on a straight, a degree or so in a corner taken cleanly, tens of degrees in a slide. It is what understeer and oversteer are, measured and not inferred from steering against yaw rate.
The packet has carried what it takes all along, under names that say something else.
The community layout calls offset 0x1C rotation (pitch, yaw, roll) and 0x28 relative
orientation to north. They are neither three angles nor a heading: together they are
one unit quaternion, x, y, z and then w. Checked against 793 packets of a real
capture (the surface survey's log, which keeps the raw fields), 466 of them at 15 m/s
or more, which are the ones a direction of travel can be taken from:
| Question | Answer from the capture |
|---|---|
| Are the four floats a quaternion? | Their squared sum is 1.00000 in every packet |
| Which way is the car's nose? | Its local −Z: turned by the quaternion, −Z lies along the velocity with a median difference of 0.00° and 90 % of packets inside ±1.2°. +Z is 180° out, ±X 90° |
| Is "orientation to north" a heading? | No. As an angle (2·acos) it is 29° out on average, because it is the quaternion's w and has lost the sign |
| Which side is the car's right? | Its local +X |
So the velocity is turned into the car's own frame by the inverse of the orientation, and the angle is taken between its two horizontal parts there:
v_car = q⁻¹ · v · q
body_slip = atan2(−v_car.x, −v_car.z) # degrees
In the car's frame, and not as a difference of two headings on the map, so that banking and gradient do not read as slip. Positive is the nose to the right of travel — ISO 8855's sign, and the sign of a right-hander everywhere else in the app. In a right-hander that is the nose turned further into the corner than the car is moving; in a left-hander it is the nose pointing out of it. The corner report card turns the sign per corner.
Below 8 m/s (29 km/h) the column holds 0: a car barely moving has no direction of
travel to measure from. Over the capture the angle's median is 0.2–0.5° in every speed
band above that and 1.6°, 4.6° and 10.6° in the three below. The gate is on the car's
velocity, not the broadcast speed: the two differ by up to 11 m/s in the capture.
A packet whose four floats are not a unit quaternion (the squared sum more than 0.05 from 1) carries no orientation, and the lap has no column. Laps recorded before 0.7.0 have none either: a stored lap kept the car's position and not its heading, so the angle cannot be worked out for them after the fact.
Wheel slip is a slip-ratio proxy: wheel surface speed divided by car speed. < 1
under braking means the wheel is locking; > 1 under power means it's spinning. Below
1 m/s car speed the ratio is meaningless, so all four are pinned to 1.0.
Suggested gear¶
The gear byte at 0x90 carries two nibbles: the gear the car is in, and the gear GT7
suggests on its HUD. Every packet format has both, so suggested_gear is recorded on
every tick like gear, and is not an optional column. It is kept as
broadcast, 15 where GT7 suggests nothing: a sample column holds numbers, and neither
a lap file nor the importer has a way to say "no value" for one tick. Both nibbles
count gears from 1; anything that compares them takes a value outside 1–14 as no
gear (0 is reverse, 15 neutral or no suggestion).
A lap on which no tick holds a suggestion would carry a column of 15s, about 36 KB of
a two-minute lap that says nothing, so the column is dropped when the lap completes
(prune_unsuggested in app/processing/laps.py, which runs on import too). Absent
therefore means "GT7 suggested nothing on this lap", or a lap recorded before the
column was kept.
Resampled onto the comparison grid it takes the nearest sample's value, like the
aids and surface bitmasks: interpolated, halfway between a suggested third and no
suggestion would be a suggested ninth. gear itself is still interpolated, so the
frontend rounds it wherever it compares the two.
Per corner (app/processing/corner_metrics.py, on the reference lap's corners and
axis):
| Figure | Is |
|---|---|
| suggested gear | the last suggestion in 1–14 from the start of the corner's approach — 250 m before its entry, never back past the previous corner's apex — up to and including its slowest point. Held rather than read at the slowest point, because when GT7 clears the hint has not been checked against a console |
| gear high | the gear at the slowest point (the sample that gives the minimum speed) is higher than that suggestion. Unknown unless both are known and the gear is in 1–14, so a car in neutral or reverse there is not judged |
Neither is worked out for a corner across the start line. GT7's real behaviour — when it fills the nibble, on which cars and modes, whether it ever suggests an upshift and whether a HUD setting turns it off — has not been checked against a console recording; the simulator's suggestion is a stand-in.
Per-lap aggregates¶
Computed once when the lap is saved (n = number of samples):
| Metric | Formula |
|---|---|
| Fuel consumed | max(0, fuel_at_lap_start − fuel_at_lap_end) L |
| Full throttle % | 100 × count(throttle ≥ 98 %) ÷ n |
| Full brake % | 100 × count(brake ≥ 98 %) ÷ n |
| Coasting % | 100 × count(coast flag set) ÷ n |
| Tire spin % | 100 × count(tire_slip ≥ 1.1) ÷ n |
| Max speed | max(speed) km/h |
| Min body height | min(body_height) mm |
| TCS / ASM active % | 100 × count(aid bit set) ÷ n |
| Off-track count | excursions where ≥ 3 wheels sat on a loose surface for ≥ 6 ticks; -1 = unknown (no surface data) |
| Clean lap | off_track_count == 0; null = unknown — distinct from counts_for_best, which flags partial pit out-laps |
Engine health is tracked as running per-lap extremes rather than per-tick columns (an engine's temperatures move over minutes, not corners):
max_water_temp,max_oil_temp— running maxima (°C)min_oil_pressure— running minimum (bar), sampled only while RPM > 1200 so idling in the pits doesn't record a misleading low;−1means unknown
GT7 appears to hold the two temperatures fixed. Every lap recorded from a console so far — 446 laps in 28 cars, July to September 2026 — reads a maximum water temperature of exactly 85 °C and oil of 110 °C; only the simulator varies them. So nothing charts them across a session (a lap-on-lap trend would be a flat line, #105), and the Tuning panel's figures and the alerts' limits matter only if a GT7 update makes them move. Oil pressure does vary on real laps, but with revs and incidents (a spin pulls it down) rather than with the engine's health.
Gearing is captured once per lap from the lap-boundary packet: the non-zero gear
ratios, GT7's calculated top speed for the car (calculated_max_speed, km/h), and the
redline (rpm_alert_max). Laps recorded before 0.7.0 hold transmission_top_speed in
top_speed instead, a ratio; see Gearing panel math.
Frontend-derived channels¶
These are computed in the browser from the stored columns, never persisted:
| Channel | Formula |
|---|---|
| Slip front / rear avg | (slip_fl + slip_fr) ÷ 2, (slip_rl + slip_rr) ÷ 2 |
| Tire temp front / rear avg | mean of the two front / rear corners |
| Tire temp F−R balance | mean(tt_fl, tt_fr) − mean(tt_rl, tt_rr) °C — positive = fronts running hotter (push/understeer working the fronts) |
| Susp travel front / rear avg | mean of the two front / rear corners |
| TCS cut | max(0, throttle − throttle_f) % — how much throttle traction control took away |
| ABS release | max(0, brake − brake_f) % — how much brake pressure ABS gave back |
| Lateral / longitudinal / vertical g | acc_* ÷ 9.80665 as a trace (assumes m/s²; the g-g panel uses the fitted scale instead) |
Laps recorded before per-corner channels existed simply skip these lines rather than erroring.
Kinematic yaw and balance¶
Packet C's front wheel angle and wheelbase give the yaw rate the car would have if its
tyres went exactly where the front wheels point — the bicycle model with no slip at
all (#155). Against it, the measured yaw rate reads directly as balance. These are
derived on read, server-side, from the full 60 Hz ticks (app/processing/balance.py):
nothing extra is stored, the chart channels are resampled only for drawing, and the
corner figures are taken from the ticks themselves. A lap without both a full
wheel_angle column and a wheelbase_m has none of them — absent, never zero.
| Channel | Formula | Unit |
|---|---|---|
kin_yaw |
abs(speed_mps × wheel_angle ÷ wheelbase_m) |
rad/s |
yaw_balance |
yaw_rate − kin_yaw: below 0 the car rotated less than its wheels asked (understeer), above 0 more (oversteer) |
rad/s |
Both are compared as magnitudes. The stored yaw_rate is absolute, and how GT7
signs the wheel angle against the car's rotation has not been checked against a
console, so kin_yaw is made absolute too. The cost is opposite lock: a slide caught
with the wheels pointing out of the corner can ask for as much rotation as the car
has, and reads nearer neutral than it was. Body slip sees that.
Per corner, from the corner window's ticks where the wheels asked for at least 0.03 rad/s (the turn-in floor) above 29 km/h, and at least five of them:
yaw_ratio = Σ yaw_rate ÷ Σ kin_yaw
A ratio of sums rather than a mean of ratios, so a tick asking for next to nothing
cannot swing the corner. Under 1 is understeer, over 1 oversteer; the Corner report's
Yaw ÷ wheels column and the lap analysis document's balance.yaw_ratio.
The steering lock¶
steer is the input at the steering wheel and wheel_angle what the road wheels did;
between them is the steering ratio, constant while the rack has travel. Past full lock
the input keeps rising and the wheels stop. Per lap:
- Steering ratio = the median of
abs(steer) ÷ abs(wheel_angle)over ticks with the wheels turned at least 0.02 rad and at most 70 % of the lap's largest angle (inside the rack's travel, clear of any lock), from at least 60 ticks. No ratio, no lock verdict: a lap steered in steps has nothing to take it from. - A tick is at the lock when the wheels sit within 3 % of the lap's largest angle
while the input runs ahead of
ratio × abs(wheel_angle)by at least 10 % and at least 0.1 rad (about 6° of steering wheel).
A corner's lock_m is the metres of its window spent at the lock: 0 when it never got
there, absent without both channels. It is the figure a ride-height or steering-angle
change moves, lap against lap. Neither threshold has been calibrated against a real
console yet: a car with a speed-sensitive or variable steering ratio could read as
locked where it is not.
Units: speed is the only channel with a display transform (km/h → mph × 0.621371 when imperial units are selected). Everything else displays in its stored unit.
Gearing panel math¶
Road speed in a gear is engine speed through that gear, the final drive and the tyre:
speed_g(rpm) = K × rpm ÷ ratio_g km/h
K folds the final drive and the tyre's circumference into one figure per car and
setup, the same for every gear, and it is the one thing the stored gearing lacks: GT7
broadcasts the gearbox ratios and the redline, and the tire radius on every tick, but
never the final drive, and a lap keeps no tire radius. The
Gearing panel draws speed_g for each gear from
0 rpm to the redline, and its speed at redline is where each line ends:
at_redline_g = K × rpm_alert ÷ ratio_g
K is fitted from the lap. Over the comparison's series (a sample every 5 m), a sample is clean when:
- its gear is within 0.01 of a whole gear from 1 to the number of ratios. The series interpolate gear like any other channel, so a point between two ticks either side of a shift reads 2.4 and is dropped, never rounded into a gear; 0 (reverse) and 15 (neutral) are out of range;
- rpm is at least 0.35 ×
rpm_alertand speed at least 30 km/h, which leaves out the clutch and the idle at a crawl; tire_slipis between 0.95 and 1.05, when the lap has the channel: the driven wheels turning with the road. Wheelspin and lockups break the link between rpm and speed.
K is the median of speed × ratio_g ÷ rpm over the clean samples. The fit is
accepted only when:
- there are at least 30 clean samples;
- at least two gears have 10 or more each (one, on a car with a single gear), so there is something to check the scale against. A lap driven all the way round in one gear is not fitted;
- those gears agree: the largest of their medians is at most 1.15 × the smallest. A gear with fewer than 10 samples, a short burst of first out of a hairpin, still counts towards the median but has no say in this check.
On recorded laps the gears' medians agree to within 13 %. The simulator's rpm does not follow its ratios, and its gears come out about 1.5 to 1.95 times apart, which is what the check is for.
Fallback: GT7's top speed. When the fit is refused, top gear is taken to reach the
stored top speed at the redline, where ratio_top is the smallest ratio:
K = top_speed × ratio_top ÷ rpm_alert
This applies only when top_speed is a speed: 30 km/h or more. Laps recorded before
0.7.0 (#128) store GT7's transmission_top_speed there, a unitless figure between
about 2 and 6. It is not a speed, and it is not the final drive either: the same car on
two setups stores two different values and fits the same K. On those laps a refused
fit leaves no lines, the table shows "—", and the top speed is not shown.
The dots are the same series' samples in whole gears, at most 1,500 a lap. Over that, the two samples either side of every gear change are always kept, so the sawtooth's tips and drops survive, and evenly spaced samples fill the rest.
Same gearbox. The reference lap is drawn behind the focus lap only when their gearboxes differ: a different number of gears, any ratio more than 0.001 apart, or a different redline.
A note on "tiers"¶
If you browse the code or the commit history you'll see channels grouped into tiers. That's a roadmap grouping from the project's original feature list (what is left of it now lives in the GitHub issues), not a runtime concept: Tier 1 = surface data already in the packet (per-corner slip/temps/suspension, aids, engine health, gearing — all implemented), Tier 2 = derived analytics like sector splits (sectors still future work; the g-g diagram is implemented from the broadcast accelerometer rather than derived from speed deltas), Tier 3+ = larger features (overlay, webhooks, strategy — implemented). There is no brake-temp channel — GT7 doesn't send one and it isn't synthesized. The body slip angle is not sent either, but the velocity and the orientation it is the angle between are.