Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions dash-apps/contest/prag-designs-monte-carlo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Monte Carlo

**Category:** Rive
**File:** [`monte-carlo.riv`](monte-carlo.riv) (about 930 KB), built from [`src/`](src/)

![Monte Carlo](preview.jpg)

Monte Carlo is a sports-car cluster behind one curved lens. Power runs across the top as a single ribbon from 0 to 10, with the redline marked at 8 to 10: the zone stays dark and textured until the live fill lights it. The speed sits large in the middle inside one crimson glow, with a carbon weave in the glass behind it. Gear on the left, the drive mode on the right (ASSIST or MANUAL), the posted limit beside the speed. Two narrow gauges hug the edges of the glass: battery temperature on the left, charge on the right. A quiet tab along the bottom carries the clock, the odometer and the charge in percent.

When DashPilot starts, the cluster wakes like a car's: the glass lights, the top edge and the gold dip trace out from the centre, the power ribbon sweeps to the redline and back while the scale numbers light as it passes, then everything settles onto the live readings in about two seconds.

## What it shows

| Data | How |
|---|---|
| `egoSpeed` | the big number in the crimson glow. Drive over the limit and the glass heats red |
| `powerLevel` | the power ribbon across the top, 0 to 10 (x10 % power), with a bright head on its leading edge. Negative power is regen: a short teal fill runs left of 0 |
| `fusedSpeedLimit` | the posted limit beside the speed, hidden while unknown |
| `gear` | the gear letter left of the speed |
| `adasOn` | ASSIST in white or MANUAL in grey, right of the speed. The outgoing word hides at once and the new one fades in, so they never overlap |
| `leftBlinker`, `rightBlinker` | an amber arrow above the gauges on that side, lit in step with the lamp |
| `leftBlindSpot`, `rightBlindSpot` | the lens tip on that side glows amber for a car in your blind spot, red when it is urgent |
| `trafficLightColor` | a small lamp under the limit, lit red, yellow or green |
| `packTMax` | the left edge gauge, C to H |
| `nominalEnergyRemaining`, `fullPackEnergy`, `energyBuffer` | the right edge gauge, E to F, with a red dash when the charge runs low, and the percentage in the bottom tab |
| `odometer`, `currentTime` | the bottom tab |
| `renderQuality` | the lens pass skips its bloom at the lowest setting |

The instrument keeps its 2280 x 1080 design and scales to fit the screen (contain), centred. Speed is shown in km/h. There is no mph switch.

## How it is built

The lens, the ribbon, the numbers, the gauges and every data bind on `DashViewModel` are native Rive. A scripted layout (`src/v3_fx.luau`) draws a private copy of the instrument into a texture each frame and finishes it in a WGSL pass (`src/lensfx.wgsl`): the bloom, the head of the power ribbon, the redline heat and the start-up. The script also writes a few numbers of its own into the view model (`intro*`, `displayPower`) that drive the start-up and one smoothed power value both layers share. While it runs it sets `fxActive`, which hides the native copy underneath. If a runtime cannot run GPU Canvas, nothing in that layer draws and the native dash shows on its own, every reading still live. In DashPilot on Android the GPU layer needs a worker that supports GPU Canvas, which is what softwiredtech/dashpilot#267 turns on.

- `src/v3.py` writes `src/scene.rml` from `viewmodel.part`, `converters.part`, `mine.part` and `v3.part`, using the helpers in `src/gen.py`. One `.rml` file on purpose: a project split across several publishes script signatures the web runtime rejects.
- `src/viewmodel.part` is the `DashViewModel` from `rive/dash-demo/`, plus the properties of my own that only the script writes.
- Rebuild: `cd src && python3 v3.py && rive . --publish`. This writes `../monte-carlo.riv` with the scripts signed. `rive . --once` builds without signing.

Tested in DashPilot on Android (OnePlus 12R, 2780×1264 landscape) with the Demo data source: with GPU Canvas on (softwiredtech/dashpilot#267, Settings > Developer) the lens pass renders live, and with it off the native dash shows every reading. Also checked in the Rive web runtime with a simulated drive that sends the same values Demo mode does.

## Credits

- **Saira** font (`src/fonts/saira-italic-sub.ttf`, a subset of Saira Italic) by The Saira Project Authors, [SIL Open Font License 1.1](src/fonts/OFL-Saira.txt). It sets the speed, the gear, the mode and the scale.
- **Exo 2** font (`src/fonts/exo2-*.ttf`) by The Exo 2 Project Authors, [SIL Open Font License 1.1](src/fonts/OFL-Exo2.txt).
- **Inter** font (`src/fonts/inter-400.ttf`, `inter-600.ttf`) by The Inter Project Authors, [SIL Open Font License 1.1](https://github.com/rsms/inter/blob/master/LICENSE.txt). These are the same files `rive/dash-demo/` already ships.
- Everything else (the lens, the ribbon, the gauges, the glow and weave, the icons, the shader and the start-up) is my own work in this folder. No images and no sounds.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions dash-apps/contest/prag-designs-monte-carlo/src/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
build/
__pycache__/
270 changes: 270 additions & 0 deletions dash-apps/contest/prag-designs-monte-carlo/src/converters.part
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
<Rive version="1" kind="fragment">
<FontAsset file="fonts/inter-400.ttf" name="Inter Regular" id="2:900"/>
<FontAsset file="fonts/inter-600.ttf" name="Inter SemiBold" id="2:901"/>

<!-- Formula tokens: operationType 0 + 1 - 2 * 3 / 4 %; functionType 0 min 1 max 4 floor 5 sqrt 6 pow -->

<DataConverterToString decimals="0" round="true" name="Integer" id="2:1"/>
<!-- Booleans: ToString gives "0"/"1", ToNumber turns that into a number that can drive opacity. -->
<DataConverterToString name="Bool To Text" id="2:40"/>
<DataConverterToNumber name="Text To Number" id="2:42"/>
<DataConverterGroup name="Bool As Number" id="2:41">
<DataConverterGroupItem converterId="2:40" id="0:903"/>
<DataConverterGroupItem converterId="2:42" id="0:904"/>
</DataConverterGroup>
<DataConverterGroup name="Not Bool As Number" id="2:43">
<DataConverterGroupItem converterId="2:40" id="0:905"/>
<DataConverterGroupItem converterId="2:42" id="0:906"/>
<DataConverterGroupItem converterId="2:7" id="0:907"/>
</DataConverterGroup>
<DataConverterGroup name="Cruise Marker Opacity" id="2:44">
<DataConverterGroupItem converterId="2:40" id="0:908"/>
<DataConverterGroupItem converterId="2:42" id="0:909"/>
<DataConverterGroupItem converterId="2:45" id="0:910"/>
</DataConverterGroup>
<DataConverterRangeMapper minInput="0" maxInput="1" minOutput="0.35" maxOutput="1"
clampLower="true" clampUpper="true" name="Dim Unless On" id="2:45"/>
<DataConverterStringPad length="2" text="0" padType="start" name="Pad 2" id="2:27"/>

<DataConverterRangeMapper minInput="0" maxInput="1" minOutput="0" maxOutput="1"
clampLower="true" clampUpper="true" name="Clamp 0..1" id="2:5"/>
<DataConverterRangeMapper minInput="0" maxInput="1" minOutput="1" maxOutput="0"
clampLower="true" clampUpper="true" name="1 - Clamp 0..1" id="2:7"/>
<DataConverterRangeMapper minInput="1" maxInput="2" minOutput="0" maxOutput="1"
clampLower="true" clampUpper="true" name="Is 2" id="2:6"/>
<DataConverterRangeMapper minInput="0" maxInput="100" minOutput="100" maxOutput="0"
clampLower="true" clampUpper="true" name="100 - Percent" id="2:18"/>
<DataConverterRangeMapper minInput="0" maxInput="100" minOutput="0" maxOutput="100"
clampLower="true" clampUpper="true" name="Percent" id="2:22"/>

<DataConverterRangeMapper minInput="0" maxInput="160" minOutput="0" maxOutput="0.75"
clampLower="true" clampUpper="true" name="Speed To Arc" id="2:2"/>
<DataConverterRangeMapper minInput="0" maxInput="160" minOutput="3.9269908" maxOutput="8.6393798"
clampLower="true" clampUpper="true" name="Speed To Angle" id="2:3"/>
<DataConverterSystemDegsToRads operationType="2" operationValue="0.017453292"
name="Degrees to Radians" id="2:15"/>

<DataConverterFormula name="Over Limit" id="2:4">
<FormulaTokenFunction functionType="0" id="0:911"/>
<FormulaTokenValue operationValue="1" id="0:912"/>
<FormulaTokenArgumentSeparator id="0:913"/>
<FormulaTokenFunction functionType="1" id="0:914"/>
<FormulaTokenValue operationValue="0" id="0:915"/>
<FormulaTokenArgumentSeparator id="0:916"/>
<FormulaTokenParenthesisOpen id="0:917"/>
<FormulaTokenInput id="0:918"/>
<FormulaTokenOperation operationType="1" id="0:919"/>
<FormulaTokenValue id="0:920">
<DataBindContext sourcePathIds="1:1-1:19" propertyKey="777" id="0:921"/>
</FormulaTokenValue>
<FormulaTokenOperation operationType="1" id="0:922"/>
<FormulaTokenValue operationValue="2" id="0:923"/>
<FormulaTokenParenthesisClose id="0:924"/>
<FormulaTokenOperation operationType="3" id="0:925"/>
<FormulaTokenValue operationValue="3" id="0:926"/>
<FormulaTokenParenthesisClose id="0:927"/>
<FormulaTokenParenthesisClose id="0:928"/>
<FormulaTokenOperation operationType="2" id="0:929"/>
<FormulaTokenFunction functionType="0" id="0:930"/>
<FormulaTokenValue operationValue="1" id="0:931"/>
<FormulaTokenArgumentSeparator id="0:932"/>
<FormulaTokenValue id="0:933">
<DataBindContext sourcePathIds="1:1-1:19" propertyKey="777" id="0:934"/>
</FormulaTokenValue>
<FormulaTokenParenthesisClose id="0:935"/>
</DataConverterFormula>



<!-- 1 - min(1, |input - k|): 1 when input == k -->
<DataConverterFormula name="Equals 1" id="2:11">
<FormulaTokenValue operationValue="1" id="0:936"/>
<FormulaTokenOperation operationType="1" id="0:937"/>
<FormulaTokenFunction functionType="0" id="0:938"/>
<FormulaTokenValue operationValue="1" id="0:939"/>
<FormulaTokenArgumentSeparator id="0:940"/>
<FormulaTokenFunction functionType="5" id="0:941"/>
<FormulaTokenFunction functionType="6" id="0:942"/>
<FormulaTokenInput id="0:943"/>
<FormulaTokenOperation operationType="1" id="0:944"/>
<FormulaTokenValue operationValue="1" id="0:945"/>
<FormulaTokenArgumentSeparator id="0:946"/>
<FormulaTokenValue operationValue="2" id="0:947"/>
<FormulaTokenParenthesisClose id="0:948"/>
<FormulaTokenParenthesisClose id="0:949"/>
<FormulaTokenParenthesisClose id="0:950"/>
</DataConverterFormula>
<DataConverterFormula name="Equals 2" id="2:12">
<FormulaTokenValue operationValue="1" id="0:951"/>
<FormulaTokenOperation operationType="1" id="0:952"/>
<FormulaTokenFunction functionType="0" id="0:953"/>
<FormulaTokenValue operationValue="1" id="0:954"/>
<FormulaTokenArgumentSeparator id="0:955"/>
<FormulaTokenFunction functionType="5" id="0:956"/>
<FormulaTokenFunction functionType="6" id="0:957"/>
<FormulaTokenInput id="0:958"/>
<FormulaTokenOperation operationType="1" id="0:959"/>
<FormulaTokenValue operationValue="2" id="0:960"/>
<FormulaTokenArgumentSeparator id="0:961"/>
<FormulaTokenValue operationValue="2" id="0:962"/>
<FormulaTokenParenthesisClose id="0:963"/>
<FormulaTokenParenthesisClose id="0:964"/>
<FormulaTokenParenthesisClose id="0:965"/>
</DataConverterFormula>
<DataConverterFormula name="Equals 3" id="2:13">
<FormulaTokenValue operationValue="1" id="0:966"/>
<FormulaTokenOperation operationType="1" id="0:967"/>
<FormulaTokenFunction functionType="0" id="0:968"/>
<FormulaTokenValue operationValue="1" id="0:969"/>
<FormulaTokenArgumentSeparator id="0:970"/>
<FormulaTokenFunction functionType="5" id="0:971"/>
<FormulaTokenFunction functionType="6" id="0:972"/>
<FormulaTokenInput id="0:973"/>
<FormulaTokenOperation operationType="1" id="0:974"/>
<FormulaTokenValue operationValue="3" id="0:975"/>
<FormulaTokenArgumentSeparator id="0:976"/>
<FormulaTokenValue operationValue="2" id="0:977"/>
<FormulaTokenParenthesisClose id="0:978"/>
<FormulaTokenParenthesisClose id="0:979"/>
<FormulaTokenParenthesisClose id="0:980"/>
</DataConverterFormula>
<DataConverterFormula name="Equals 4" id="2:14">
<FormulaTokenValue operationValue="1" id="0:981"/>
<FormulaTokenOperation operationType="1" id="0:982"/>
<FormulaTokenFunction functionType="0" id="0:983"/>
<FormulaTokenValue operationValue="1" id="0:984"/>
<FormulaTokenArgumentSeparator id="0:985"/>
<FormulaTokenFunction functionType="5" id="0:986"/>
<FormulaTokenFunction functionType="6" id="0:987"/>
<FormulaTokenInput id="0:988"/>
<FormulaTokenOperation operationType="1" id="0:989"/>
<FormulaTokenValue operationValue="4" id="0:990"/>
<FormulaTokenArgumentSeparator id="0:991"/>
<FormulaTokenValue operationValue="2" id="0:992"/>
<FormulaTokenParenthesisClose id="0:993"/>
<FormulaTokenParenthesisClose id="0:994"/>
<FormulaTokenParenthesisClose id="0:995"/>
</DataConverterFormula>

<DataConverterFormula name="Urgent Left" id="2:33">
<FormulaTokenFunction functionType="0" id="0:996"/>
<FormulaTokenValue operationValue="1" id="0:997"/>
<FormulaTokenArgumentSeparator id="0:998"/>
<FormulaTokenFunction functionType="1" id="0:999"/>
<FormulaTokenValue operationValue="0" id="0:1000"/>
<FormulaTokenArgumentSeparator id="0:1001"/>
<FormulaTokenInput id="0:1002"/>
<FormulaTokenOperation operationType="1" id="0:1003"/>
<FormulaTokenValue operationValue="1" id="0:1004"/>
<FormulaTokenParenthesisClose id="0:1005"/>
<FormulaTokenParenthesisClose id="0:1006"/>
</DataConverterFormula>
<DataConverterFormula name="Urgent Right" id="2:34">
<FormulaTokenFunction functionType="0" id="0:1007"/>
<FormulaTokenValue operationValue="1" id="0:1008"/>
<FormulaTokenArgumentSeparator id="0:1009"/>
<FormulaTokenFunction functionType="1" id="0:1010"/>
<FormulaTokenValue operationValue="0" id="0:1011"/>
<FormulaTokenArgumentSeparator id="0:1012"/>
<FormulaTokenInput id="0:1013"/>
<FormulaTokenOperation operationType="1" id="0:1014"/>
<FormulaTokenValue operationValue="1" id="0:1015"/>
<FormulaTokenParenthesisClose id="0:1016"/>
<FormulaTokenParenthesisClose id="0:1017"/>
</DataConverterFormula>

<!-- state of charge: (remaining - buffer) / (full - buffer) * 100 -->
<DataConverterFormula name="State Of Charge" id="2:16">
<FormulaTokenFunction functionType="1" id="0:1018"/>
<FormulaTokenValue operationValue="0" id="0:1019"/>
<FormulaTokenArgumentSeparator id="0:1020"/>
<FormulaTokenFunction functionType="0" id="0:1021"/>
<FormulaTokenValue operationValue="100" id="0:1022"/>
<FormulaTokenArgumentSeparator id="0:1023"/>
<FormulaTokenParenthesisOpen id="0:1024"/>
<FormulaTokenInput id="0:1025"/>
<FormulaTokenOperation operationType="1" id="0:1026"/>
<FormulaTokenValue id="0:1027">
<DataBindContext sourcePathIds="1:1-1:28" propertyKey="777" id="0:1028"/>
</FormulaTokenValue>
<FormulaTokenParenthesisClose id="0:1029"/>
<FormulaTokenOperation operationType="3" id="0:1030"/>
<FormulaTokenFunction functionType="1" id="0:1031"/>
<FormulaTokenValue operationValue="0.01" id="0:1032"/>
<FormulaTokenArgumentSeparator id="0:1033"/>
<FormulaTokenValue id="0:1034">
<DataBindContext sourcePathIds="1:1-1:27" propertyKey="777" id="0:1035"/>
</FormulaTokenValue>
<FormulaTokenOperation operationType="1" id="0:1036"/>
<FormulaTokenValue id="0:1037">
<DataBindContext sourcePathIds="1:1-1:28" propertyKey="777" id="0:1038"/>
</FormulaTokenValue>
<FormulaTokenParenthesisClose id="0:1039"/>
<FormulaTokenOperation operationType="2" id="0:1040"/>
<FormulaTokenValue operationValue="100" id="0:1041"/>
<FormulaTokenParenthesisClose id="0:1042"/>
<FormulaTokenParenthesisClose id="0:1043"/>
</DataConverterFormula>
<DataConverterGroup name="State Of Charge Text" id="2:17">
<DataConverterGroupItem converterId="2:16" id="0:1044"/>
<DataConverterGroupItem converterId="2:1" id="0:1045"/>
</DataConverterGroup>
<DataConverterGroup name="State Of Charge Rest" id="2:19">
<DataConverterGroupItem converterId="2:16" id="0:1046"/>
<DataConverterGroupItem converterId="2:18" id="0:1047"/>
</DataConverterGroup>

<DataConverterRangeMapper minInput="-100" maxInput="0" minOutput="100" maxOutput="0"
clampLower="true" clampUpper="true" name="Regen" id="2:20"/>
<DataConverterRangeMapper minInput="-100" maxInput="0" minOutput="0" maxOutput="100"
clampLower="true" clampUpper="true" name="Regen Rest" id="2:21"/>

<!-- packCurrent * packVoltage / 1000 -->
<DataConverterFormula name="Kilowatts" id="2:24">
<FormulaTokenInput id="0:1048"/>
<FormulaTokenOperation operationType="2" id="0:1049"/>
<FormulaTokenValue id="0:1050">
<DataBindContext sourcePathIds="1:1-1:30" propertyKey="777" id="0:1051"/>
</FormulaTokenValue>
<FormulaTokenOperation operationType="3" id="0:1052"/>
<FormulaTokenValue operationValue="1000" id="0:1053"/>
</DataConverterFormula>
<DataConverterGroup name="Kilowatts Text" id="2:25">
<DataConverterGroupItem converterId="2:24" id="0:1054"/>
<DataConverterGroupItem converterId="2:1" id="0:1055"/>
</DataConverterGroup>

<DataConverterFormula name="Hours" id="2:26">
<FormulaTokenFunction functionType="4" id="0:1056"/>
<FormulaTokenInput id="0:1057"/>
<FormulaTokenOperation operationType="3" id="0:1058"/>
<FormulaTokenValue operationValue="3600" id="0:1059"/>
<FormulaTokenParenthesisClose id="0:1060"/>
</DataConverterFormula>
<DataConverterGroup name="Hours Text" id="2:28">
<DataConverterGroupItem converterId="2:26" id="0:1061"/>
<DataConverterGroupItem converterId="2:1" id="0:1062"/>
<DataConverterGroupItem converterId="2:27" id="0:1063"/>
</DataConverterGroup>
<DataConverterFormula name="Minutes" id="2:29">
<FormulaTokenFunction functionType="4" id="0:1064"/>
<FormulaTokenParenthesisOpen id="0:1065"/>
<FormulaTokenInput id="0:1066"/>
<FormulaTokenOperation operationType="4" id="0:1067"/>
<FormulaTokenValue operationValue="3600" id="0:1068"/>
<FormulaTokenParenthesisClose id="0:1069"/>
<FormulaTokenOperation operationType="3" id="0:1070"/>
<FormulaTokenValue operationValue="60" id="0:1071"/>
<FormulaTokenParenthesisClose id="0:1072"/>
</DataConverterFormula>
<DataConverterGroup name="Minutes Text" id="2:30">
<DataConverterGroupItem converterId="2:29" id="0:1073"/>
<DataConverterGroupItem converterId="2:1" id="0:1074"/>
<DataConverterGroupItem converterId="2:27" id="0:1075"/>
</DataConverterGroup>

<DataConverterRangeMapper minInput="0" maxInput="100" minOutput="0" maxOutput="34"
clampLower="true" clampUpper="true" name="Phone Level Width" id="2:31"/>
<DataConverterRangeMapper minInput="-1" maxInput="0" minOutput="0" maxOutput="1"
clampLower="true" clampUpper="true" name="Phone Known" id="2:32"/>
</Rive>
Loading
Loading