pro_engineer — rge

Pro Engineer/RGE

RGE — the little language your own screens are written in

Small enough to read in one sitting. Enough to build something with.

Every telemetry program worth the name lets you make a channel the game does not publish. RGE started there and went further: arithmetic over the numbers already coming out of the simulator, then words with the car written into them, things a formula does as well as works out, something kept between frames, and a canvas you can draw a rev counter or a map of the track on. A screen you wrote is a tab of the program.

It is total, and that is the whole design. No loops, no recursion, nothing that allocates while it runs, every jump forward. A formula cannot be written badly enough to cost a frame, however long the document gets — which is what lets it run inside a window you are racing beside.

THE CHANNELS

What a formula can read. Each is one number at one instant, taken from the lap the program already has — there is nothing to import and nothing to name.

THE LAP
distance (0-1)How far round the lap, nought at the line.
time (s)Seconds since the lap started.
x (m)Position across the map.
y (m)Position along the map.
THE CAR
speed (km/h)Road speed.
gearGear. Neutral is nought, reverse is negative.
rpm (rpm)Engine speed.
fuel (L)Fuel in the tank.
brake_bias (%)How much of the braking is at the front.
WHAT YOU ARE DOING
throttle (0-1)Throttle pedal, nought to one.
brake (0-1)Brake pedal, nought to one.
steer (-1..1)Steering, negative to the left.
clutch (0-1)Clutch pedal.
FORCES
lat_g (g)Cornering load. Positive to one side.
lon_g (g)Braking and acceleration. Negative under brakes.
vertical_g (g)Up and down: kerbs, crests, compressions.
TYRES
slipHow much the tyres are sliding, all four averaged.
tyre_temp_fl (°C)Tyre core temperature, front left.
tyre_temp_fr (°C)Tyre core temperature, front right.
tyre_temp_rl (°C)Tyre core temperature, rear left.
tyre_temp_rr (°C)Tyre core temperature, rear right.
tyre_inner_fl (°C)Tread temperature at the inner edge, front left.
tyre_inner_fr (°C)Tread temperature at the inner edge, front right.
tyre_inner_rl (°C)Tread temperature at the inner edge, rear left.
tyre_inner_rr (°C)Tread temperature at the inner edge, rear right.
tyre_middle_fl (°C)Tread temperature across the middle, front left.
tyre_middle_fr (°C)Tread temperature across the middle, front right.
tyre_middle_rl (°C)Tread temperature across the middle, rear left.
tyre_middle_rr (°C)Tread temperature across the middle, rear right.
tyre_outer_fl (°C)Tread temperature at the outer edge, front left.
tyre_outer_fr (°C)Tread temperature at the outer edge, front right.
tyre_outer_rl (°C)Tread temperature at the outer edge, rear left.
tyre_outer_rr (°C)Tread temperature at the outer edge, rear right.
pressure_fl (psi)Tyre pressure, front left.
pressure_fr (psi)Tyre pressure, front right.
pressure_rl (psi)Tyre pressure, rear left.
pressure_rr (psi)Tyre pressure, rear right.
wear_fl (%)How much tread is left, front left. 100 is new.
wear_fr (%)How much tread is left, front right. 100 is new.
wear_rl (%)How much tread is left, rear left. 100 is new.
wear_rr (%)How much tread is left, rear right. 100 is new.
grip_flHow much that tyre is sliding, front left.
grip_frHow much that tyre is sliding, front right.
grip_rlHow much that tyre is sliding, rear left.
grip_rrHow much that tyre is sliding, rear right.
BRAKES
brake_temp_fl (°C)Brake disc temperature, front left.
brake_temp_fr (°C)Brake disc temperature, front right.
brake_temp_rl (°C)Brake disc temperature, rear left.
brake_temp_rr (°C)Brake disc temperature, rear right.
brake_pad_fl (mm)Brake pad left, front left.
brake_pad_fr (mm)Brake pad left, front right.
brake_pad_rl (mm)Brake pad left, rear left.
brake_pad_rr (mm)Brake pad left, rear right.
SUSPENSION
ride_front (mm)Ride height at the front.
ride_rear (mm)Ride height at the rear.
load_fl (N)How hard that corner is being pressed into the road, front left.
load_fr (N)How hard that corner is being pressed into the road, front right.
load_rl (N)How hard that corner is being pressed into the road, rear left.
load_rr (N)How hard that corner is being pressed into the road, rear right.
travel_fl (m)How far the suspension has moved, front left.
travel_fr (m)How far the suspension has moved, front right.
travel_rl (m)How far the suspension has moved, rear left.
travel_rr (m)How far the suspension has moved, rear right.
camber_fl (°)Camber, front left. Negative leans the top of the tyre in.
camber_fr (°)Camber, front right. Negative leans the top of the tyre in.
camber_rl (°)Camber, rear left. Negative leans the top of the tyre in.
camber_rr (°)Camber, rear right. Negative leans the top of the tyre in.
AIDS
tc (0-1)How hard traction control is working.
abs (0-1)How hard the anti-lock is working.
ffb (0-1)Force feedback on the wheel.
CONDITIONS
air_temp (°C)Air temperature.
road_temp (°C)Road temperature.

THE FUNCTIONS

23 take numbers and give a number, trigonometry included — and every angle is in degrees, in and out, because every angle this program shows is. The last three ask the whole lap rather than this instant — speed / highest(speed) is where you are against the best you managed — and they are worked out once when the lap changes, never per sample.

abs(a)Drop the sign.
sqrt(a)Square root.
sin(a)Sine of an angle in degrees.
cos(a)Cosine of an angle in degrees.
tan(a)Tangent of an angle in degrees.
asin(a)The angle, in degrees, whose sine this is.
acos(a)The angle, in degrees, whose cosine this is.
atan(a)The angle, in degrees, whose tangent this is.
atan2(a, b)The direction of (x across, y along), -180 to 180 degrees.
log(a)Natural logarithm.
log10(a)Logarithm base ten.
exp(a)e raised to this.
floor(a)Down to the whole number below.
ceil(a)Up to the whole number above.
round(a)To the nearest whole number.
sign(a)-1, 0 or 1, by which way it points.
min(a, b)The smaller of the two.
max(a, b)The larger of the two.
hypot(a, b)The two as sides of a triangle: combined g.
clamp(a, b, c)Hold a value between a floor and a ceiling.
lerp(a, b, c)Blend from the first to the second, by 0 to 1.
highest(a)The largest it got anywhere on the lap.
lowest(a)The smallest it got anywhere on the lap.
average(a)Its average over the whole lap.
best(a)What it was here on your quickest lap. `speed - best(speed)` is the delta.
last(a)What it was here on the lap you just finished.

THE ACTIONS

The four things a formula can do rather than work out. Each takes a piece of text with the same {…} holes a sentence has, and each is worth 1 if it happened. Only the arm of an if that is taken runs, so if a then print "yes" else print "no" says one of them and not both — and a row that fired warn or alert draws itself in that colour, which is one thought written once.

print "…"Write a line under the row.
warn "…"The same, in the warning colour.
alert "…"The same, in the alarm colour.
title "…"Rename the tab this row is on.

THE GRAMMAR

The whole of a formula. There are no loops, no recursion and no way to call anything you wrote, and every jump goes forward — which is what makes a formula impossible to write badly enough to cost a frame.

+ - * / %The arithmetic, in the usual order.
^Power. Right to left, so 2 ^ 3 ^ 2 is 512.
( )Brackets, up to thirty-two deep.
< <= > >= == !=Comparisons. They give 1 or 0.
and or notLogic, in words rather than symbols.
if … then … else …A choice. Both sides are worked out, so neither can be slow.
? :The same choice, written short: speed > 100 ? 1 : 0.

WRITING A CHANNEL

Install RGE script studio from the MODULES tab. A SCRIPT tab appears. Type a formula into the scratchpad; it is checked as you type, coloured as you type, the number it gives right now is under it, and its shape over your last lap is under that. Every channel on this page is listed there too, with your car’s current value beside it — type three letters and the rest of the name is offered.

When it does what you meant, KEEP IT AS gives it a name and it lands in CHANNELS: plotted, kept between sessions, recomputed on the lap you are driving.

# combined g, which no simulator publishes
hypot(lat_g, lon_g)

# how much of the rev range a lap actually used
rpm / 8000 * 100

# speed, but only while the brake is down — two ways to write it
if brake > 0.05 then speed else 0
brake > 0.05 ? speed : 0

# slip as a percentage, never past 100
clamp(slip * 100, 0, 100)

A formula that cannot produce a number at a sample — a division by a channel that was zero — leaves that sample out rather than plotting a nought. That rule runs through the whole program: a gap is a gap and a zero is a claim about the car.

WRITING A SCREEN

Your own screen is a document rather than a program. One line per thing you want to see, and nothing has to line up — KEEP IT lays it out for you. It draws under the editor in STUDIO → SCREENS as you type; write screen NAME and it becomes a tab of its own as well.

# a name for a piece of maths, used by the rows below
let g = hypot(lat_g, lon_g)

columns 2

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

panel IN WORDS
"Doing {speed:0} km/h in gear {gear:0}."
"Fuel is low — {fuel:1} L left." when fuel < 5
Name (unit) = formulaA number, read at a glance from a metre away.
Name (unit) low..high = formulaA range makes it a bar — a bar is the thing that needs two ends.
~ Name (unit) = formulaA line over the lap.
"Words with {a formula} in them."A sentence. `{fuel:1}` is one decimal place; two is the default.
"…" when formulaOnly shown while that reads as true — which is how a warning is written. Sentences only: a reading that vanished could not be told from one you deleted.
panel HEADINGStarts a box. The rows after it go in it.
paint HEADING 200Starts a canvas, that many points tall. What goes on it is marks.
dot x, y · dot x, y, sizeOn a canvas: a point. Every number is a formula.
line x, y, x, yOn a canvas: a straight piece.
box x, y, width, heightOn a canvas: a rectangle.
ring x, y, radiusOn a canvas: a circle, drawn round.
arc x, y, radius, from, toOn a canvas: part of one. Degrees, 0 straight up, going clockwise.
text x, y "…"On a canvas: words, with the same holes a sentence has.
here across, alongOn a canvas: a point in the space the `path` above it drew — `here x, y` is where the car is on the map.
path across, alongOn a canvas: the whole lap as a shape — `path x, y` is a map of the track you are on. Fitted to the canvas, so the numbers need no scaling.
… in accentAny mark, in a named colour: accent, highlight, good, warning, alarm, text, dim.
… when formulaAny mark, only while that is true — which is how a warning light is drawn.
screen NAMEStarts a tab. A document with no `screen` in it draws under the editor and nowhere else.
instead of DASHBOARDStarts a tab standing where one of the program's own was.
instead of ANALYSIS / CORNERSThe same for one sub-tab. The others stay the program's own, and the strip above stays where it was.
columns 2How many panels sit side by side, 1 to 4.
let name = formulaNames a piece of maths for the rows below.
state name = 0Declares something kept between frames, and the number it starts at.
remember name = formulaWrites to it, once a frame, in the order written.
module "Name" · about "…"What the whole document calls itself. It appears on MODULES beside the program’s own.
# anythingA note to yourself.

It is drawn by the same widgets every other screen uses and costs the same, because the layout is yours and the drawing is still the program’s. A line that does not read costs its own row and names its line number — half a document still draws, because a screen that went blank on a typo is a screen nobody would dare change. KEEP IT saves; BACK returns to the last version you kept.

KEEPING SOMETHING

A formula is an expression and always will be — that is what makes it impossible to write one that costs a frame. What an expression cannot do is remember: a fastest lap, a counter, how long something has been true. state declares a slot and the number it starts at; remember writes to it, once a frame, in the order written, so a later line sees what an earlier one just wrote.

state best = 0
state seen = 0

remember best = max(best, speed)
remember seen = seen + (speed > 5 ? 1 : 0)

panel HOW IT IS GOING
Best (km/h)         = best
Off the best (km/h) = best - speed
"You have been moving for about {seen / 60:0} seconds."

A slot is read anywhere a channel is. It is not saved: it is what is happening in this session, and restoring yesterday’s peak into today’s would be a number nobody measured — which is this program’s worst class of bug. A formula that has no answer for one frame leaves the slot alone rather than wiping it.

Per lap rather than per session needs no keyword of its own. time is seconds since the lap started, so it goes back to nought at the line, and that is the whole of it:

state this_lap = 0
remember this_lap = time < 1 ? speed : max(this_lap, speed)

The lines run in the order they are written, once a frame, wherever you are looking — a fastest lap does not stop counting because you opened another tab.

ANOTHER LAP

best(f) is what f was at this place on the track on your quickest complete lap of the session; last(f) is the same on the lap you just finished. So the thing every telemetry program is for is one line:

let delta = speed - best(speed)

panel HERE, AGAINST YOUR BEST
Now (km/h)           = speed
Your best had (km/h) = best(speed)
Difference (km/h)    = if delta < -0.5 then alert "losing it here" else delta

# and the two traces, plotted against each other
~ This lap (km/h)  = speed
~ Your best (km/h) = best(speed)
~ Difference       = delta

They are a table by distance, not a number — that is what makes speed - best(speed) mean “slower than your best lap here” rather than “than its average”. The table is worked out once when the lap changes, so a formula asking it costs one lookup per sample. Inside best(…) everything is about that lap: best(highest(speed)) is the highest it went round the quickest lap.

Until there is such a lap they give nothing, and a row reading nothing draws a dash. A delta of zero in your first minute would read as “exactly on your best”, which is the one thing it must not say.

DRAWING ONE

paint NAME 220 is a panel you draw on. The space is 0 to 1 across and 0 to 1 down, top-left to bottom-right, and a radius is a fraction of whichever side is shorter — so a ring is round on a canvas of any shape. Angles are degrees, 0 straight up, going clockwise, because that is how a dial is read. Every number is a formula.

let turn = rpm / 8000

paint REVS 220
ring 0.5, 0.55, 0.38 in dim
arc  0.5, 0.55, 0.38, -130, 130 in dim
arc  0.5, 0.55, 0.38, -130, -130 + turn * 260 in accent
arc  0.5, 0.55, 0.38, 110, 130 in alarm
line 0.5, 0.55, 0.5 + sin(-130 + turn * 260) * 0.3, 0.55 - cos(-130 + turn * 260) * 0.3
text 0.5, 0.55 "{rpm:0}"

Nine lines and it is a rev counter with a needle, a red zone and the number in the middle. It costs one rectangle and one loop: a mark is a line segment, and giving each one an id, a response and a layout would cost more than drawing it. Any mark takes the same when a sentence does, so a warning light is one more line.

Two marks are about the whole lap rather than this instant. path x, y is a map of the track you are on — it is fitted to the canvas, at one scale on both axes so the circuit keeps its shape, which is why the numbers need no scaling of yours. here x, y puts a point in that same space.

paint THE TRACK YOU ARE ON 220
path x, y in dim
here x, y, 0.025 in accent

A MODULE YOU WROTE

A document that says module "Fuel Warden" has a name, a line about what it is for, and tabs of its own — every part of what a module is from where a driver sits, minus the part where somebody else compiled it. It appears on the MODULES screen beside the ones that shipped, and it travels as one line like everything else here.

module "Fuel Warden"
about "Tells me when to box, and keeps my best lap."

screen FUEL

SENDING ONE TO A FRIEND

A palette, your channels or a whole screen becomes one line you can paste into a chat window. COPY MINE puts it on the clipboard; the other person pastes it into USE IT on the same screen.

RGE1.channels.3f2a91c4.eJyrVkrKz1eyUkpKLFKqBQAlIQP8

The format carries its own name and version, what kind of thing it is, and eight characters of checksum — so a palette pasted where channels go is answered by name, and a line a chat window broke in half is refused rather than half-applied. It is not encryption and does not claim to be: it is your channels, not your password, and a format people can read is one they can fix by hand.

WHY NOT LUA

The in-game panel is Lua because Custom Shaders Patch is. Here it would mean an interpreter, a garbage collector and a foreign stack between the driver and every frame, for a job whose whole grammar fits on one screen. The cost of RGE is that it is ours to maintain; the cost of the alternative is a collector running during a braking zone.

It is also why RGE is total: there is no way to write a formula that does not finish. The worst a bad one can do is be wrong, and a wrong number is caught by the same rule as everything else here — if it is not a finite number, it is not drawn. It is the same reason a screen is a description and not a program: writing the interface in the language would put an interpreter inside the drawing loop, which is the cost this refused in the first place.