# gaugeClusters
A car's speedometer, rev counter and gear display in **ten looks**, switched
live with one word. Every style shows the same three things: speed (km/h or
mph), revs with a redline zone, and the gear (`R`, `N`, `1`…). Each one is
drawn with the engine UI and reads like a real instrument: needles, shift
lights, seven-segment digits, pixel fonts and holographic arcs.
| Style | Looks like |
|---|---|
| `analog` | Classic twin dials in chrome bezels, orange needles, red redline band, gear LCD and shift lamp between them |
| `digital` | Modern glass dashboard: slanted segmented rev bar, big speed, glowing gear tile that flashes SHIFT, pedal bars |
| `rally` | Stage-style: 15 shift lights (green → yellow → red, all blue and blinking at the limit), huge gear, speed, rpm |
| `retro80s` | Neon VFD: rising bar graph for revs, seven-segment speed and gear with unlit segments showing, scanlines |
| `scifi` | Holographic arcs: gradient rev sweep, turning dashed outer ring, speed ring, gear in a hexagon |
| `minimal` | Thin rev line with ticks, big light numbers, nothing else |
| `arcade` | Chunky rainbow speedo blocks with ink outlines, star-burst gear bubble that pops on a shift |
| `pixel` | Pixel-art font and blocks, stepped frame with a hard drop shadow |
| `hud` | Transparent heads-up display, centre low: rev and speed arcs either side of the speed |
| `truck` | Big truck cluster: cream dials, tach, speedo with odometer, boost, fuel and temperature gauges, warning lamps |
## First example
Paste this into any world. The dummy car pretends to drive (through the
gears, braking, reverse), so the gauge moves with no real car:
```lua
local dummy = entity.spawn("dummyCar")
dummy.component.add("FakeCar")
local hud = entity.spawn("hud")
hud.component.add("GaugeCluster", { style = "analog", car = dummy })
-- switch the look live, any time:
hud.component.get("GaugeCluster").style = "rally"
```
Enter play to see it move. With a real car (for example the torqueCars README car), leave `car` out: the cluster
follows the local player's own car.
```lua
entity.spawn("hud").component.add("GaugeCluster", { style = "digital", units = "mph" })
```
## GaugeCluster settings
| Setting | Default | What it does |
|---|---|---|
| `style` | `analog` | Which look (the ten above). Switches live. |
| `units` | `kmh` | `kmh` or `mph`. |
| `car` | empty | The car to read. Empty follows the local player's own car: the car announced as `player.vehicle` on their avatar or the main camera (a torqueCars car does this while the player drives it), else their avatar (or the car it sits in) when that is a car, else the entity the main camera follows. |
| `anchor` | `auto` | Where it sits: `auto` (the style's own place), `bottomRight`, `bottomCenter`, `bottomLeft`, `topRight`, `topCenter`, `topLeft`, `hud` (centre low). |
| `size` | `1` | Extra scale on top of the automatic screen fit. |
| `maxSpeed` | `0` | Top of the speed scale; 0 = the style's own (260 km/h / 160 mph, truck 140 / 90). |
| `offsetX`, `offsetY` | `0` | Nudge in points. |
| `visible` | `true` | Draw it. |
Methods: `gauge:setStyle(id)`, `gauge:cycleStyle(dir?)`, `gauge:target()` (the car
id being read), `gauge:reading()` (what it shows right now).
## From code, without a component
```lua
local Gauges = require("gaugeClusters.gauges")
Gauges.show("scifi") -- follows the local player's car
Gauges.show("truck", someCar, { units = "mph", anchor = "bottomLeft" })
Gauges.setStyle("pixel")
Gauges.setCar(otherCar)
Gauges.hide()
Gauges.styles() --> { "analog", "digital", ..., "truck" }
```
The parts are public too: `gaugeClusters.reader` reads any car into one
table (`read`, `resolve`, `gearLabel`), `gaugeClusters.styles` builds each
look as a widget tree (`S.get(id).build(view, scale, state)`), and
`gaugeClusters.draw` holds the canvas helpers (arcs, needles, seven-segment
digits, a 3x5 pixel font).
## What it reads
The driving contract's named values on the car's chassis entity (see
`torqueCars.package/CONTRACT.md`): `car.kmh` (or `car.speed`), `car.rpm`,
`car.gear` (-1 R, 0 N, 1..n), `car.rpmRedline`, `car.rpmMax`, and for
flavour `car.throttle`, `car.brake`, `car.boost`, `car.limiter`. Any car that
writes them works. A plain builtin `VehicleController` car with no values
reads too: speed from `Physics.vehicleState`, 0 rpm, gear `-`.
## Gallery
`gallery` (a scene) shows all ten side by side, driven by a pretend car:
```lua
layers.load(asset.ref("gaugeClusters.gallery", "scene"), { additive = true })
```
Or put `GaugeGallery` on any entity (`focus = "truck"` shows one style large).
## Screens and phones
Each cluster fits itself to the screen: about a third of a desktop screen's
width, nearly the full width of a phone held upright, and never more than
about 36% of the height, so the road stays visible. It lays out inside the
safe area, so notches, cutouts and gesture bars never cover it. The UI is a
personal overlay: each player sees their own car's cluster, and nothing is
synced.
## What is inside
| Part | What it is |
|---|---|
| `GaugeCluster` (component) | One cluster on screen, owned by the component. |
| `GaugeGallery` (component) | All ten styles in a grid with a pretend car. |
| `FakeCar` (component) | Makes its entity write fake contract values (for tests and demos). |
| `gauges` (module) | `show` / `hide` / `setStyle` API and the per-frame pipeline. |
| `styles` (module) | The ten looks. |
| `reader` (module) | Reads a car (contract values or VehicleController) and finds the local player's car. |
| `draw` (module) | Canvas paint helpers. |
| `fake` (module) | The pretend car's simulation. |
| `gallery` (scene) | The gallery, ready to load. |
## Known limits
- **What it needs from a car** — a car that does not write the driving contract's values shows
speed only (from `Physics.vehicleState`), 0 rpm and gear `-`.
- **Finding your own car** — with `car` empty the cluster reads the car announced as
`player.vehicle` on the local player's avatar or the main camera (torqueCars cars announce
themselves while the player drives them), else the avatar or what the camera follows when
that is a car. A car that writes the values but announces nothing needs `car` set.
- **#21241** — `car` is an entity reference and is not synced; the cluster is a personal
overlay, so every player's cluster resolves its own car locally.
- **Cost** — each cluster redraws its UI every frame, about 0.5 ms on a desktop.
- **#20946** — phone layouts were checked at phone sizes in the native engine; the web build
has not been run.
# gaugeClusters.fake
A pretend car's drive simulation for tests and demos. `Fake.new()` makes one;
`sim:step(dt, entityId?)` advances it (full throttle through six gears to the
redline, braking with downshifts, a stop, reverse, neutral, again) and, given
an entity, writes the driving contract's values on it (`car.kmh`, `car.rpm`,
`car.gear`, `car.rpmRedline`, `car.rpmMax`, `car.throttle`, `car.brake`,
`car.boost`, ...). The `FakeCar` component and `GaugeGallery` use it.