Skip to content

jengine-rally

Point-to-point rally stages: seeded stages checked drivable, pace notes read off the geometry, damage between services, and ghosts recorded as input that replay bit for bit.

KindProject package: linked into the builds of the projects that list it
Sourcepackages/jengine-rally
Version0.1.0
Entry pointjengine_rally::RallyPackage
Script namespaceRally
Depends onjengine-racing
Listed byprojects/car-classics

List it in the top-level "packages" array of project.jengine, together with every package it depends on: a crate dependency compiles a package in but does not register it, and packages_a_project_lists_cover_their_dependencies fails a project that lists only the outer one:

{
"packages": [
{ "name": "jengine-rally", "source": { "type": "path", "path": "../../packages/jengine-rally" } },
{ "name": "jengine-racing", "source": { "type": "path", "path": "../../packages/jengine-racing" } }
]
}

Then rebuild the editor viewport for the project (jengine viewport-build projects/<name>, or the editor’s Rebuild viewport banner). Native and web builds link it on their own.

A scene that uses its components must be a v2 scene ("version": 2): a v1 scene has no field for a package component and keeps it as inert custom data.

A script can check that the package is linked with if Rally == nil then return end.

Scene keyRust type
rally_stageRallyStagepackage only: needs a v2 scene

Commands run in every runtime and return nothing; a package publishes what scripts read onto components (see the notes below). The flat name also works.

CallFlat name
Rally.start(e, id, scale, countdown)rally_start
Rally.damage(e, engine, brakes, steering, suspension, tyres)rally_damage
Rally.ghost(e, json)rally_ghost
Rally.drive(e, throttle, brake, steer, handbrake, reset)rally_drive
Rally.auto(e, on)rally_auto
Rally.clear(e)rally_clear
  • projects/car-classics/assets/scenes/rally.scene

From packages/jengine-rally/README.md.

Point-to-point rally special stages: a stage generated from a seed and refused unless it can be driven, the co-driver’s pace notes read off its geometry, a car on mixed surfaces that breaks, and ghosts kept as input that replay to the bit - on the player’s machine and on a leaderboard server alike.

Used by Car Classics’ GRAVEL STAGE (projects/car-classics, the rally scene), whose online boards (projects/car-classics/server/games/rally) verify every submitted time by driving its ghost again through this crate. Built on jengine-racing: the car’s state is its Car, its numbers a CarSpec, its input a Drive - so a project lists both packages.

ModuleWhat it is
stageA stage from a StageDef (id, name, theme, seed, length): a road laid as a curvature profile of straights and arcs, smoothed and integrated into points every 4 units; surface sections, crests, jumps, fords, cliffs, snowbanks, trees, rocks, walls, bales, decoration and the crowd. check_road refuses a road that passes near itself or bends tighter than the car can take.
notesThe pace notes, derived from the stage’s own curvature.
simThe car, one fixed 1/120 s step at a time, and a Run of a stage: countdown, clock, splits, recoveries, finish, every input recorded.
pilotThe autopilot: attract mode, the par time, the proof that every stage can be finished, and the keys a test presses.
ghostA finished run as a Ghost, its run-length input code, replay, verify, and a Trace for drawing a ghost beside the player.
catalogueThe championship: six rallies, three stages each, every one a seed.
detArithmetic that comes out the same on every machine.
drawThe stage on screen with ordinary engine components.

A leaderboard needs a server to drive a submitted ghost again and arrive at the same time. Addition, multiplication, division and sqrt are IEEE and give the same bits everywhere Rust runs; sin, cos, atan2 and exp are the platform’s maths library, free to differ in the last bit - and a car at the grip limit turns a last bit into a different line through the next hairpin. So the stage generator and the simulation use only det::sin_cos, det::atan2 and det::exp_small (polynomials of exact operations, checked against the library to 1e-14) and a seeded splitmix generator. That is also why this package steps the car itself rather than calling jengine_racing::car::Car::step, which uses f32::sin_cos (and Car::place, for the same reason): the model is jengine-racing’s - engine and brakes along the nose, quadratic drag to a top speed, steering that turns the nose, one grip limit pulling the velocity back to it - with what a rally adds:

  • surfaces with their own grip and traction (tarmac 430, gravel 330, sand 300, mud 265, snow 255, ice 165 units/s^2 of sideways grip; off the road a share of the theme’s), where the racing model has one scalar; traction caps the engine and brakes by the grip, so ice spins the wheels out of a bend and will not stop for the next;
  • a steering wheel between the input and the tyres, ramping at 4.6/s (7.5/s back to centre), so a keyboard’s on/off arrives as a ramp;
  • flight: crests and jumps launch the car for a time that grows with its speed; no grip or steering in the air; a landing that comes down crooked or from high hurts the suspension;
  • obstacles: two circles of car against circles of tree, rock, wall, post and bale; the impact’s normal speed becomes damage to the parts the hit reached (nose: engine, steering; flank: suspension, steering, tyres; tail: suspension, brakes); bales are soft; snowbanks hold the car in; cliffs are gone over (a recovery and damage);
  • damage to five parts, 0..1, each changing a number: engine (power and top speed), brakes, steering (rate, and a pull to the right), suspension and tyres (grip). Tyres also wear with distance and sliding.

Progress along the road is searched only a little behind and ahead of where the car was, so cutting across land to a later part of the road gains nothing - the car is off course until it comes back, and is recovered (+5 s) after three seconds lost.

A corner is a stretch where the curvature the road was built from is above 1/1250; its grade is its tightest radius against notes::GRADES (hairpin under 100 units, then 1 to 6 up to 1250), its direction the sign. It is long past 100 degrees or 520 units, tightens / opens by its first and last thirds, into when the next corner starts within 110 units, don't cut when a real solid hazard stands on its inside within reach of the line, and caution for a tight bend after a fast straight or one with a cliff on its outside. Crests, jumps, fords and the flying finish are notes of their own. tests/stages.rs::the_pace_notes_never_lie walks every stage: each call’s geometry is in its grade’s band and turns its way, every bend tighter than grade 5 is inside a call, every feature is called.

A Ghost is JSON: { v, stage: StageDef, damage, time, steps, input }, where input is runs of identical steps, count:throttle:brake:steer:flags (pedals in eighths, steering in sixteenths, flags handbrake and reset). A two-minute stage on a keyboard is a few kilobytes. ghost::verify rebuilds the stage from the seed, drives the input from the start damage, and accepts the ghost only if the replay finishes on the claimed step and the same bits of time. catalogue::is_official says whether a ghost’s stage is one of the championship’s - a ghost carries its road, so a server must not take a road of the submitter’s choosing.

rally_stage on the controller, in a "version": 2 scene:

"rally_stage": { "art": "sprites/rally", "camera": true, "zoom": 1.15 }

The package draws the stage itself (road, bank and cliff meshes chunked every 256 units where their textures repeat; hazards, decor and spectators as BulkEntity sprites; a ground pool that follows the camera; skid trails) from textures named under art (road_<surface>.png, ground_<theme>.png, tree_<theme>_<n>.png, rock_<n>.png, …: see draw.rs). It moves the scene’s own entities by name - Car, CarShadow, Ghost, GhostShadow, Headlights, Dust, Spray - and, with camera, the main camera, which it turns with the car. Nothing names those textures until a stage is built, so the scene preloads them (a sprite_library listing them).

CallDoes
Rally.start(e, id, scale, countdown)build championship stage id (its length times scale) and line the car up; countdown false rolls at once
Rally.damage(e, engine, brakes, steering, suspension, tyres)the damage the next start begins with
Rally.ghost(e, json)a ghost to drive beside the next start ("" for none); one that is not of the stage, or does not replay to its time, is not shown
Rally.drive(e, throttle, brake, steer, handbrake, reset)the player’s controls this frame; reset asks to be put back on the road
Rally.auto(e, on)hand the car to the autopilot
Rally.clear(e)no stage
  • RallyCatalog once: the rallies, their themes and blurbs, and their stages’ ids, names, lengths in km and surfaces.
  • RallyInfo on every start: the stage’s id, name, theme, km, splits, par (the autopilot’s own time with a fresh car) and notes - each with kind, s, s_end, dir, grade, long, tightens, opens, into, dont_cut, caution and text.
  • RallyState every frame: serial (counts starts, so a script can tell this stage’s state from the last), phase (countdown, running, finished), countdown, time (penalties in), elapsed, penalty, recoveries, splits, finish_time, par, the car (x, y, heading, speed, kmh, s, lateral, progress, sliding, air, surface, water), damage, ghost (x, y, time, progress, delta - seconds ahead or behind at this point), advice (the autopilot’s keys as letters: U throttle, D brake, L/R steer, H handbrake, X recover) and this frame’s events (beep, go, hit, bank, take_off, land, splash, cliff, recover, split, finish).
  • RallyGhost once a run finishes: { stage, ghost, time }, the ghost as JSON.

tests/stages.rs: all eighteen stages build clear of themselves with nothing solid on the road and are finished by the autopilot flat out and on keys (with a frame of lag); a stage is the same road every time; the notes never lie; a ghost replays to the bit, verifies, and a doctored time, input or road is refused; the input code round-trips; a better driver is faster and a battered car slower; snow stops in a longer distance than tarmac; a tree stops the car and breaks it; cutting across country gains nothing.