# Kart races with laps, drifts and items (race.kart) ## What it is for `race.kart` runs a kart race on a circuit: a start grid, a countdown, the racers (the first is the player, the others are bots), ordered progress along the track with checkpoints and laps, drift charge with two mini-turbo tiers, boost pads, item crates (boost, shield, pulse), a recover that returns a kart to its last checkpoint for a time penalty, standings and a final result. The karts are vehicles of `vehicle.drive`; the circuit comes from `world.racetrack`. The kit holds the race rules and wires them; the presentation (HUD, start screen, drivers, sounds) is shipped with the kit. ## Turn it on ```text gripforge_gamekit_install { project_id: "gkp_…", id: "race.kart" } gripforge_gamekit_configure { project_id: "gkp_…", id: "race.kart", config: { "racers": 6, "countdown": 3, "difficulty": "normal" } } ``` It requires `world.racetrack`, `vehicle.drive` (with `handling: kart`), `input.actions`, `spawn.entities`, `physics.port` and `game.engine`, and conflicts with `character.third_person` and `character.platformer`. The circuit is data of the project (`race_tracks`, owned by `world.racetrack`: width, laps, points, `boostPads`, `crates`). Default controls: W A S D or the arrows drive, Space drifts, Shift boosts, `E` uses the item (action `race.item`), `R` returns to the track (action `race.reset`); gamepad and touch share the same actions. ## First example The test replays the fixture `race.kart.race`: a circuit `ring` of width 12 with two laps, four racers (`driverNames: ["Ace", "Bo"]`) and a 0.5 s countdown. It calls the services, presses the keys and checks every frame: 1. `race.laps.playerVehicle()` answers the entity of the player's kart; `race.laps.standings()` answers the table. 2. `resetToTrack()` and the key `R` return the kart to its last checkpoint (`race.respawn`, with a penalty). 3. `E` uses the held item (`race.item`), `W` and a steering axis drive, and the standings are read again until the finish (`race.finished`). ## Settings and defaults | Setting | Type | Default | Meaning | |---|---|---|---| | `racers` | integer (1..8) | `6` | number of karts; the first is the player | | `countdown` | number (0..5) | `3` | seconds before the start | | `difficulty` | `easy`, `normal`, `hard` | `normal` | bot target speed times `bots.skill` | | `items` | boolean | `true` | crates and items | | `driverNames` | list of strings | `[]` | names that replace the default ones (You, Nova, Mako, Pip, Tess, Sol, Ash, Rue) | | `camera` | `distance`, `height` | `8.5`, `4` | chase camera, metres | | `grid` | `start`, `spacing`, `lane`, `spawnStart`, `spawnSpacing` | `5`, `5`, `2.1`, `0.01`, `0.008` | first row behind the line, row gap, column offset (m); first spawn in track fractions | | `progress` | see below | | laps, recovery and road edge | | `drift` | see below | | drift charge and mini-turbo | | `boost` | `pad`, `padRadius`, `minTank` | `1.4`, `3`, `30` | pad boost seconds, pad half-length (m), tank floor while boosting | | `itemRules` | see below | | items and crates | | `bots` | see below | | bot driving | - `progress`: `checkpoints` 16 per lap; `recoverPenalty` 2 s; `travelMin` 2, `travelFactor` 3, `travelSlack` 1 (accepted travel per tick, so teleports and infield cuts never award laps); `lateralSlack` 2; `edgeInset` 0.9 and `edgeDamping` 0.94 (karts are held inside the road edge); `offTrack` 15 and `fallY` -3 (beyond these a kart recovers); `stuckSpeed` 1 and `stuckSeconds` 4 (a stuck bot recovers). - `drift`: `minSlip` 0.18, `minSpeed` 8, `resetSpeed` 7, `maxCharge` 2.4, `tier1` 0.65, `tier2` 1.6, `boost1` 1.15, `boost2` 2.1. Releasing with a charge of at least `tier1` gives `boost1` seconds of boost; at least `tier2` gives `boost2`. - `itemRules`: `boost` 2.5 and `shield` 6 (seconds), `pulseTime` 0.5, `pulseRadius` 15, `slowTime` 1.5, `slowDamping` 2, `slowThrottle` 0.1, `crateRadius` 3, `crateRespawn` 3, `crateHeight` 0.9, `crateLanes` 0.29, `botUseEvery` 5. - `bots`: `lookahead` 8, `lookaheadPerSpeed` 0.5, `laneSwing` 2, `laneFrequency` 2.2, `cornerAngle` 0.5, `cornerSpeed` 18, `straightSpeed` 30, `steerGain` 1.65, `brake` 0.25, and `skill` with `easy` 0.65, `normal` 0.84, `hard` 1. The three items are `boost`, `shield` (absorbs one pulse, not a crash) and `pulse` (a short-range flash that slows nearby karts). Every setting is moddable, inside the ranges of the schema. ## Services, events, variables Services `race.laps`, `race.items` and `vehicle.kart_drive` all reach the same race: - `start()`: restarts the countdown with the existing racers. - `restart()`: the race again from the grid (the Restart of the pause screen); answers `true`. - `playerVehicle()`: the entity id of the player's kart. - `standings()`: the table of the racers. - `resetToTrack()`: the player's recover, with its penalty. - `useItem()`: uses the player's item. Events: `race.started`; `race.lap` (`entity`, `lap`, `player`); `race.pickup` (`entity`, `item`, `player`); `race.item` (`entity`, `item`, `player`); `race.boost` (`entity`, `source` `drift` or `pad`, `player`); `race.respawn` (`penalty`, the player's kart); `race.finished` (`seconds`, `position`, the player); `race.refused` (`decision`, `entity`, `player`, `reason`), emitted when a hook cancelled a decision and never without one. Variables: `race.standings`, `race.position`, `race.lap`, `race.laps`, `race.seconds`, `race.countdown`, `race.item`, `race.shield`, `race.drift`, `race.boost`, `race.boosting`, `race.name`, `race.result`. Asset slots: `race_driver` binds the first pilot, `race_driver_2` to `race_driver_6` the other racers (a missing one uses the first); audio slots `race_engine_audio`, `race_skid_audio`, `race_pickup_audio`, `race_boost_audio`, `race_finish_audio`. ## Hooks and modding Each decision is asked once, when the state changes, for the bots and the player alike; `entity` is the kart and `player` says whether it is the player's. | Hook | Payload | A tap may | Cancelled | |---|---|---|---| | `race.start` | `entity`, `racers`, `laps` | cancel | the start is held (karts on the grid, clock stopped) until `start()` | | `race.pickup` | `entity`, `player`, `crate`, `item` | change `item`, cancel | an empty box: the kart gets nothing | | `race.use` | `entity`, `player`, `item`, `duration` | change `duration`, cancel | the item is kept | | `race.hit` | `entity`, `player`, `by`, `item`, `shielded`, `duration` | change `duration`, cancel | immunity: not slowed, shield kept | | `race.boost` | `entity`, `player`, `source`, `duration` | change `duration`, cancel | no boost | | `race.recover` | `entity`, `player`, `reason`, `checkpoint`, `penalty` | change `penalty` | not cancellable | | `race.finish` | `entity`, `player`, `laps`, `penalty`, `position`, `seconds` | change `seconds` | not cancellable | `reason` of `race.recover` is `reset`, `off_track` or `stuck`. A crate's item is drawn before `race.pickup` is asked, so the following crates give the same items with or without a tap. A pulse on a shielded kart only removes the shield. Data taps of the project or of a mod need no code: ```json { "id": "slow_rivals", "hook": "race.hit", "match": { "player": false }, "mul": { "duration": 2 } } { "id": "wait_for_intro", "hook": "race.start", "if": [{ "var": "intro.done", "op": "!=", "value": true }], "cancel": true, "reason": "intro" } ``` On `game.engine` the race belongs to the scene: its track is the scene's world, a scene may override `countdown` and `difficulty`, leaving a scene holds the karts and entering the next one puts them on the grid and starts a new race. ## Engines The race rules are the same Rust module on the web and the native player. The Godot, Unity and Unreal deliveries race the same way (laps, standings, items, drift boosts, rivals, return to track) but do not run the hooks yet. ## When it does not do what you expect - The race never starts: a `race.start` tap cancelled it (see `race.refused`); call `start()` once it is allowed. - A kart gets no lap: it left the road width plus `lateralSlack`, or moved faster than the accepted travel per tick; raise `travelSlack` for very fast karts. - No item from a crate: `items` is `false`, the kart already holds one, or a `race.pickup` tap cancelled. - A bot sits still: it recovers after `stuckSeconds` below `stuckSpeed`. - The result is missing: `race.result` is written when the first racer has finished. ## Limits - Three items only, and the circuits belong to `world.racetrack`, not to this kit. - Up to 8 racers; the player is always the first. - The best time is kept per project and circuit in the browser only. ## Versions Kit version 1.9.0 (services at 1.0.0). 1.7.0 added the hooks and the moddable settings; 1.9.0 added `restart()`.